using AssetStudio; using AssetStudioGUI; using AssetStudioGUI.Properties; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using System; using System.IO; namespace Arknights { internal static class AkSpriteHelper { public static Texture2D TryFindAlphaTex(AssetItem assetItem, AvgSprite avgSprite, bool isAvgSprite) { Sprite m_Sprite = (Sprite)assetItem.Asset; var imgType = "arts/characters"; if (m_Sprite.m_RD.alphaTexture.m_PathID == 0) { if (isAvgSprite) { if (avgSprite?.FullAlphaTexture != null) return avgSprite.FullAlphaTexture; imgType = "avg/characters"; //since the avg hub was not found for some reason, let's try to find alpha tex by name } var spriteFullName = Path.GetFileNameWithoutExtension(assetItem.Container); foreach (var item in Studio.exportableAssets) { if (item.Type == ClassIDType.Texture2D) { if (item.Container.Contains(imgType) && item.Container.Contains($"illust_{m_Sprite.m_Name}_material") && item.Text.Contains("[alpha]")) return (Texture2D)item.Asset; else if (item.Container.Contains(imgType) && item.Container.Contains(spriteFullName) && item.Text == $"{m_Sprite.m_Name}[alpha]") return (Texture2D)item.Asset; } } } return null; } public static Image AkGetImage(this Sprite m_Sprite, AvgSprite avgSprite = null, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On) { if (m_Sprite.m_RD.texture.TryGet(out var m_Texture2D) && m_Sprite.m_RD.alphaTexture.TryGet(out var m_AlphaTexture2D) && spriteMaskMode != SpriteMaskMode.Off) { Image tex; Image alphaTex; if (avgSprite != null && avgSprite.IsHubParsed) { alphaTex = m_AlphaTexture2D.ConvertToImage(true); if (avgSprite.IsFaceSprite) { var faceImage = m_Texture2D.ConvertToImage(true); var faceAlpha = avgSprite.FaceSpriteAlphaTexture.ConvertToImage(true); if (faceImage.Size() != avgSprite.FaceSize) { faceImage.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); faceAlpha.Mutate(x => x.Resize(new ResizeOptions { Size = avgSprite.FaceSize, Sampler = KnownResamplers.Lanczos3, Mode = ResizeMode.Stretch })); } tex = avgSprite.FullTexture.ConvertToImage(true); tex.Mutate(x => x.DrawImage(faceImage, location: avgSprite.FacePos, opacity: 1f)); alphaTex.Mutate(x => x.DrawImage(faceAlpha, location: avgSprite.FacePos, opacity: 1f)); } else { tex = m_Texture2D.ConvertToImage(true); } } else { tex = CutImage(m_Texture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier); alphaTex = CutImage(m_AlphaTexture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier); } switch (spriteMaskMode) { case SpriteMaskMode.On: tex.ApplyRGBMask(alphaTex, isPreview: true); return tex; case SpriteMaskMode.Export: tex.ApplyRGBMask(alphaTex); return tex; case SpriteMaskMode.MaskOnly: tex.Dispose(); return alphaTex; } } else if (m_Sprite.m_RD.texture.TryGet(out m_Texture2D)) { return CutImage(m_Texture2D.ConvertToImage(false), m_Sprite.m_RD.textureRect, m_Sprite.m_RD.downscaleMultiplier); } return null; } private static IResampler GetResampler(bool isPreview) { IResampler resampler; if (isPreview) { resampler = KnownResamplers.NearestNeighbor; } else { switch (Settings.Default.resamplerIndex) { case 0: resampler = KnownResamplers.NearestNeighbor; break; case 1: //Bilinear resampler = KnownResamplers.Triangle; break; case 2: resampler = KnownResamplers.Bicubic; break; case 3: resampler = KnownResamplers.MitchellNetravali; break; case 4: resampler = KnownResamplers.Spline; break; case 5: resampler = KnownResamplers.Welch; break; default: resampler = KnownResamplers.MitchellNetravali; break; } } return resampler; } private static void ApplyRGBMask(this Image tex, Image texMask, bool isPreview = false) { using (texMask) { bool resized = false; if (tex.Width != texMask.Width || tex.Height != texMask.Height) { texMask.Mutate(x => x.Resize(tex.Width, tex.Height, GetResampler(isPreview))); resized = true; } var invGamma = 1.0 / (1.0 + Settings.Default.alphaMaskGamma / 10.0); if (Settings.Default.resizedOnly && !resized) { invGamma = 1.0; } tex.ProcessPixelRows(texMask, (sourceTex, targetTexMask) => { for (int y = 0; y < texMask.Height; y++) { var texRow = sourceTex.GetRowSpan(y); var maskRow = targetTexMask.GetRowSpan(y); for (int x = 0; x < maskRow.Length; x++) { var grayscale = (maskRow[x].R + maskRow[x].G + maskRow[x].B) / 3.0; if (invGamma != 1) { grayscale = 255 - Math.Pow((255 - grayscale) / 255, invGamma) * 255; } texRow[x].A = (byte)grayscale; } } }); } } private static Image CutImage(Image originalImage, Rectf textureRect, float downscaleMultiplier) { if (originalImage != null) { using (originalImage) { if (downscaleMultiplier > 0f && downscaleMultiplier != 1f) { var newSize = (Size)(originalImage.Size() / downscaleMultiplier); originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true)); } var rectX = (int)Math.Floor(textureRect.x); var rectY = (int)Math.Floor(textureRect.y); var rectRight = (int)Math.Ceiling(textureRect.x + textureRect.width); var rectBottom = (int)Math.Ceiling(textureRect.y + textureRect.height); rectRight = Math.Min(rectRight, originalImage.Width); rectBottom = Math.Min(rectBottom, originalImage.Height); var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY); var spriteImage = originalImage.Clone(x => x.Crop(rect)); spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); return spriteImage; } } return null; } } }