diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index aad4891..5415b7d 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -9,6 +9,7 @@ + diff --git a/AssetStudio/Classes/Texture2D.cs b/AssetStudio/Classes/Texture2D.cs index 65d57ad..cac7ca7 100644 --- a/AssetStudio/Classes/Texture2D.cs +++ b/AssetStudio/Classes/Texture2D.cs @@ -19,6 +19,8 @@ namespace AssetStudio public ResourceReader image_data; public StreamingInfo m_StreamData; public StreamingInfo m_DataStreamData; //Tuanjie + [JsonIgnore] + private ulong dataHash; //we need to store hashes because of sprites public Texture2D() { } @@ -179,6 +181,28 @@ namespace AssetStudio : new ResourceReader(reader, reader.BaseStream.Position, image_data_size); } + public ulong GetImageDataHash() + { + if (dataHash != 0) + return dataHash; + + if (image_data.Size == 0) + return 0; + + var buff = BigArrayPool.Shared.Rent(image_data.Size); + try + { + _ = image_data.GetData(buff); + dataHash = System.IO.Hashing.XxHash3.HashToUInt64(buff.AsSpan(0, image_data.Size)); + } + finally + { + BigArrayPool.Shared.Return(buff); + } + + return dataHash; + } + // https://docs.unity3d.com/2023.3/Documentation/Manual/class-TextureImporterOverride.html private int GetImageDataSize(TextureFormat textureFormat) { diff --git a/AssetStudioCLI/Components/AssetItem.cs b/AssetStudioCLI/Components/AssetItem.cs index b167c3a..4be7549 100644 --- a/AssetStudioCLI/Components/AssetItem.cs +++ b/AssetStudioCLI/Components/AssetItem.cs @@ -13,6 +13,7 @@ namespace AssetStudioCLI public ClassIDType Type; public string Text; public string UniqueID; + public ulong Hash; public GameObjectNode Node; public AssetItem(Object asset) @@ -23,6 +24,7 @@ namespace AssetStudioCLI TypeString = Type.ToString(); m_PathID = asset.m_PathID; FullSize = asset.byteSize; + Hash = 0; } } } diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 597fbce..235234b 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -46,6 +46,7 @@ namespace AssetStudioCLI.Options AssetName, AssetName_PathID, PathID, + AssetName_Hash, } internal enum ExportListType @@ -245,10 +246,11 @@ namespace AssetStudioCLI.Options optionDefaultValue: FilenameFormat.AssetName, optionName: "-f, --filename-format ", optionDescription: "Specify the file name format for exported assets\n" + - "\n" + + "\n" + "AssetName - Asset file names will look like \"assetName.extension\"\n" + "AssetName_pathID - Asset file names will look like \"assetName @pathID.extension\"\n" + - "PathID - Asset file names will look like \"pathID.extension\"\n", + "PathID - Asset file names will look like \"pathID.extension\"\n" + + "AssetName_hash - (images only) image names will look like \"assetName @hash.extension\"\n", optionExample: "Example: \"-f assetName_pathID\"\n", optionHelpGroup: HelpGroups.General ); @@ -931,6 +933,9 @@ namespace AssetStudioCLI.Options case "pathid": o_filenameFormat.Value = FilenameFormat.PathID; break; + case "assetname_hash": + o_filenameFormat.Value = FilenameFormat.AssetName_Hash; + break; default: Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported file name format: [{value.Color(brightRed)}].\n"); ShowOptionDescription(o_filenameFormat); diff --git a/AssetStudioCLI/ParallelExporter.cs b/AssetStudioCLI/ParallelExporter.cs index 11e465f..f09c95d 100644 --- a/AssetStudioCLI/ParallelExporter.cs +++ b/AssetStudioCLI/ParallelExporter.cs @@ -19,7 +19,10 @@ namespace AssetStudioCLI if (CLIOptions.convertTexture) { var type = CLIOptions.o_imageFormat.Value; - if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + var converter = new Texture2DConverter(m_Texture2D); + if (CLIOptions.o_filenameFormat.Value == FilenameFormat.AssetName_Hash) + item.Hash = converter.GetTextureHash(); + if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) return false; if (CLIOptions.o_logLevel.Value <= LoggerEvent.Debug) @@ -45,7 +48,7 @@ namespace AssetStudioCLI debugLog += sb.ToString(); } - if (!m_Texture2D.TryConvertToImage(flip: true, out var image)) + if (!converter.TryConvertToBgra32Image(flip: true, out var image)) { Logger.Error($"{debugLog}Export error. Failed to convert texture \"{m_Texture2D.m_Name}\" into image"); return false; @@ -74,11 +77,13 @@ namespace AssetStudioCLI { debugLog = ""; var type = CLIOptions.o_imageFormat.Value; - var alphaMask = SpriteMaskMode.On; - if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + var converter = new SpriteConverter((Sprite)item.Asset); + if (CLIOptions.o_filenameFormat.Value == FilenameFormat.AssetName_Hash) + item.Hash = converter.GetSpriteHash(); + if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) return false; - var image = ((Sprite)item.Asset).GetImage(alphaMask); - if (image != null) + + if (converter.TryConvertToImage(out var image, maskMode: SpriteMaskMode.On)) { using (image) { @@ -192,8 +197,12 @@ namespace AssetStudioCLI case FilenameFormat.PathID: fileName = item.m_PathID.ToString(); break; + case FilenameFormat.AssetName_Hash when item.Hash > 0: + fileName = $"{fileName} @{item.Hash:x}"; + break; } - fullPath = Path.Combine(dir, fileName + extension); + extension = extension.ToLower(); + fullPath = Path.Combine(dir, $"{fileName}{extension}"); if (ExportPathDict.TryAdd(fullPath, true)) { if (CanWrite(fullPath, dir, canOverwrite)) @@ -201,9 +210,9 @@ namespace AssetStudioCLI return true; } } - else if (filenameFormat == FilenameFormat.AssetName) + else if (filenameFormat == FilenameFormat.AssetName || (filenameFormat == FilenameFormat.AssetName_Hash && item.Hash == 0)) { - fullPath = Path.Combine(dir, fileName + item.UniqueID + extension); + fullPath = Path.Combine(dir, $"{fileName}{item.UniqueID}{extension}"); if (CanWrite(fullPath, dir, canOverwrite)) { return true; diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 342a263..452a294 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -53,7 +53,7 @@ namespace AssetStudioGUI #endregion #region SpriteControl - private SpriteMaskMode spriteMaskVisibleMode = SpriteMaskMode.On; + private SpriteMaskMode spriteMaskVisibleMode = SpriteMaskMode.Preview; #endregion #region TexControl @@ -441,16 +441,16 @@ namespace AssetStudioGUI break; } } - else if (lastSelectedItem?.Type == ClassIDType.Sprite && !((Sprite)lastSelectedItem.Asset).m_RD.alphaTexture.IsNull) + else if (lastSelectedItem?.Type == ClassIDType.Sprite && new SpriteConverter((Sprite)lastSelectedItem.Asset).m_AlphaTexture2D != null) { switch (e.KeyCode) { case Keys.A: - spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.On ? SpriteMaskMode.Off : SpriteMaskMode.On; + spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.Preview ? SpriteMaskMode.Off : SpriteMaskMode.Preview; need = true; break; case Keys.M: - spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.MaskOnly ? SpriteMaskMode.On : SpriteMaskMode.MaskOnly; + spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.MaskOnly ? SpriteMaskMode.Preview : SpriteMaskMode.MaskOnly; need = true; break; } @@ -976,15 +976,14 @@ namespace AssetStudioGUI private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D) { - if (!m_Texture2D.TryConvertToImage(true, out var image)) + var bitmap = new DirectBitmap(m_Texture2D, flip: true); + if (bitmap.Bitmap == null) { StatusStripUpdate("Unsupported image for preview"); + bitmap.Dispose(); return; } - var bitmap = new DirectBitmap(image); - image.Dispose(); - assetItem.InfoText = $"Width: {m_Texture2D.m_Width}" + $"\nHeight: {m_Texture2D.m_Height}" + @@ -1488,15 +1487,15 @@ namespace AssetStudioGUI private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite) { - var image = m_Sprite.GetImage(spriteMaskMode: spriteMaskVisibleMode); - if (image != null) + var converter = new SpriteConverter(m_Sprite); + if (converter.TryConvertToImage(out var image, maskMode: spriteMaskVisibleMode)) { var bitmap = new DirectBitmap(image); image.Dispose(); assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n"; PreviewTexture(bitmap); - if (!m_Sprite.m_RD.alphaTexture.IsNull) + if (converter.m_AlphaTexture2D != null) { assetItem.InfoText += $"Alpha Mask: {spriteMaskVisibleMode}\n"; StatusStripUpdate("'Ctrl'+'A' - Enable/Disable alpha mask usage. 'Ctrl'+'M' - Show alpha mask only."); @@ -1510,9 +1509,11 @@ namespace AssetStudioGUI private void PreviewTexture(DirectBitmap bitmap) { + previewPanel.Image.Dispose(); imageTexture?.Dispose(); imageTexture = bitmap; previewPanel.Image = imageTexture.Bitmap; + if (imageTexture.Width > previewPanel.Width || imageTexture.Height > previewPanel.Height) previewPanel.SizeMode = PictureBoxSizeMode.Zoom; else diff --git a/AssetStudioGUI/Components/AssetItem.cs b/AssetStudioGUI/Components/AssetItem.cs index 05f4ff9..0bc34c8 100644 --- a/AssetStudioGUI/Components/AssetItem.cs +++ b/AssetStudioGUI/Components/AssetItem.cs @@ -14,6 +14,7 @@ namespace AssetStudioGUI public ClassIDType Type; public string InfoText; public string UniqueID; + public ulong Hash; public GameObjectTreeNode TreeNode; public AssetItem(Object asset) @@ -24,6 +25,7 @@ namespace AssetStudioGUI TypeString = Type.ToString(); m_PathID = asset.m_PathID; FullSize = asset.byteSize; + Hash = 0; } public void SetSubItems() diff --git a/AssetStudioGUI/DirectBitmap.cs b/AssetStudioGUI/DirectBitmap.cs index baaa483..2d88782 100644 --- a/AssetStudioGUI/DirectBitmap.cs +++ b/AssetStudioGUI/DirectBitmap.cs @@ -20,12 +20,28 @@ namespace AssetStudioGUI m_bitmap = new Bitmap(Width, Height, Stride, PixelFormat.Format32bppArgb, m_handle.AddrOfPinnedObject()); } + public DirectBitmap(Texture2D texture2D, bool flip) + { + Width = texture2D.m_Width; + Height = texture2D.m_Height; + var converter = new Texture2DConverter(texture2D); + Bits = BigArrayPool.Shared.Rent(converter.OutputDataSize); + if (converter.GetBgra32RawImageData(Bits, flip)) + { + m_handle = GCHandle.Alloc(Bits, GCHandleType.Pinned); + m_bitmap = new Bitmap(Width, Height, Stride, PixelFormat.Format32bppArgb, m_handle.AddrOfPinnedObject()); + } + } + private void Dispose(bool disposing) { if (disposing) { - m_bitmap.Dispose(); - m_handle.Free(); + if (m_bitmap != null) + { + m_bitmap.Dispose(); + m_handle.Free(); + } BigArrayPool.Shared.Return(Bits); } m_bitmap = null; diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index 7c243d9..efc15cb 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -201,14 +201,16 @@ // filenameFormatComboBox // this.filenameFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.filenameFormatComboBox.DropDownWidth = 198; this.filenameFormatComboBox.FormattingEnabled = true; this.filenameFormatComboBox.Items.AddRange(new object[] { "asset name", "asset name@pathID", - "pathID"}); + "pathID", + "asset name@hash (images only)"}); this.filenameFormatComboBox.Location = new System.Drawing.Point(210, 25); this.filenameFormatComboBox.Name = "filenameFormatComboBox"; - this.filenameFormatComboBox.Size = new System.Drawing.Size(139, 23); + this.filenameFormatComboBox.Size = new System.Drawing.Size(149, 23); this.filenameFormatComboBox.TabIndex = 3; // // exportSpriteWithAlphaMask diff --git a/AssetStudioGUI/ParallelExport.cs b/AssetStudioGUI/ParallelExport.cs index ab3c993..c97b88b 100644 --- a/AssetStudioGUI/ParallelExport.cs +++ b/AssetStudioGUI/ParallelExport.cs @@ -18,7 +18,10 @@ namespace AssetStudioGUI if (Properties.Settings.Default.convertTexture) { var type = Properties.Settings.Default.convertType; - if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + var converter = new Texture2DConverter(m_Texture2D); + if (Properties.Settings.Default.filenameFormat == 3) //asset name@hash + item.Hash = converter.GetTextureHash(); + if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) return false; if (GUILogger.ShowDebugMessage) @@ -44,9 +47,9 @@ namespace AssetStudioGUI debugLog += sb.ToString(); } - if (!m_Texture2D.TryConvertToImage(flip: true, out var image)) + if (!converter.TryConvertToBgra32Image(flip: true, out var image)) { - Logger.Warning($"Failed to convert texture \"{m_Texture2D.m_Name}\" into image"); + Logger.Warning($"{debugLog}Failed to convert texture \"{m_Texture2D.m_Name}\" into image"); return false; } using (image) @@ -73,11 +76,14 @@ namespace AssetStudioGUI { debugLog = ""; var type = Properties.Settings.Default.convertType; - var spriteMaskMode = Properties.Settings.Default.exportSpriteWithMask ? SpriteMaskMode.Export : SpriteMaskMode.Off; - if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) + var converter = new SpriteConverter((Sprite)item.Asset); + if (Properties.Settings.Default.filenameFormat == 3) //asset name@hash + item.Hash = converter.GetSpriteHash(); + if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) return false; - var image = ((Sprite)item.Asset).GetImage(spriteMaskMode: spriteMaskMode); - if (image != null) + + var spriteMaskMode = Properties.Settings.Default.exportSpriteWithMask ? SpriteMaskMode.On : SpriteMaskMode.Off; + if (converter.TryConvertToImage(out var image, maskMode: spriteMaskMode)) { using (image) { @@ -191,8 +197,12 @@ namespace AssetStudioGUI case 2: //pathID fileName = item.m_PathID.ToString(); break; + case 3 when item.Hash > 0: //asset name@hash + fileName = $"{fileName} @{item.Hash:x}"; + break; } - fullPath = Path.Combine(dir, fileName + extension); + extension = extension.ToLower(); + fullPath = Path.Combine(dir, $"{fileName}{extension}"); if (ExportPathDict.TryAdd(fullPath, true)) { if (CanWrite(fullPath, dir, canOverwrite)) @@ -200,9 +210,9 @@ namespace AssetStudioGUI return true; } } - else if (filenameFormatIndex == 0) //assetName + else if (filenameFormatIndex == 0 || (filenameFormatIndex == 3 && item.Hash == 0)) //assetName, asset name@hash { - fullPath = Path.Combine(dir, fileName + item.UniqueID + extension); + fullPath = Path.Combine(dir, $"{fileName}{item.UniqueID}{extension}"); if (CanWrite(fullPath, dir, canOverwrite)) { return true; diff --git a/AssetStudioUtility/ImageExtensions.cs b/AssetStudioUtility/ImageExtensions.cs index 0807d7f..d274993 100644 --- a/AssetStudioUtility/ImageExtensions.cs +++ b/AssetStudioUtility/ImageExtensions.cs @@ -2,6 +2,8 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using System.IO; namespace AssetStudio @@ -48,5 +50,31 @@ namespace AssetStudio image.WriteToStream(stream, imageFormat); return stream; } + + public static void ApplyRgbMask(this Image srcTex, Image texMask, bool fastResize = false) + { + using (texMask) + { + if (srcTex.Width != texMask.Width || srcTex.Height != texMask.Height) + { + var resampler = fastResize ? KnownResamplers.NearestNeighbor : KnownResamplers.Bicubic; + texMask.Mutate(x => x.Resize(srcTex.Width, srcTex.Height, resampler)); + } + + srcTex.ProcessPixelRows(texMask, (sourceTex, targetTexMask) => + { + for (var y = 0; y < texMask.Height; y++) + { + var texRow = sourceTex.GetRowSpan(y); + var maskRow = targetTexMask.GetRowSpan(y); + for (var x = 0; x < maskRow.Length; x++) + { + var grayscale = (byte)((maskRow[x].R + maskRow[x].G + maskRow[x].B) / 3); + texRow[x].A = grayscale; + } + } + }); + } + } } } diff --git a/AssetStudioUtility/SpriteHelper.cs b/AssetStudioUtility/SpriteConverter.cs similarity index 55% rename from AssetStudioUtility/SpriteHelper.cs rename to AssetStudioUtility/SpriteConverter.cs index 7e2ea3b..0c7f3ce 100644 --- a/AssetStudioUtility/SpriteHelper.cs +++ b/AssetStudioUtility/SpriteConverter.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -15,92 +16,133 @@ namespace AssetStudio { Off, On, + Preview, MaskOnly, - Export } - public static class SpriteHelper + public class SpriteConverter { - public static Image GetImage(this Sprite m_Sprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On) + private Sprite sprite; + private Rectf textureRect; + private Vector2 textureRectOffset; + private float downscaleMultiplier; + private SpriteSettings settingsRaw; + private bool cutImageWithCanvas; + private int resultWidth; + private int resultHeight; + + public Texture2D m_Texture2D { get; } + public Texture2D m_AlphaTexture2D { get; } + + public SpriteConverter(Sprite m_Sprite, bool cutWithCanvas = true) { + sprite = m_Sprite; + cutImageWithCanvas = cutWithCanvas; + if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas)) { - if (m_SpriteAtlas.m_RenderDataMap.TryGetValue(m_Sprite.m_RenderDataKey, out var spriteAtlasData) && spriteAtlasData.texture.TryGet(out var m_Texture2D)) + if (m_SpriteAtlas.m_RenderDataMap.TryGetValue(m_Sprite.m_RenderDataKey, out var spriteAtlasData) && spriteAtlasData.texture.TryGet(out var texture2D)) { - return CutImage(m_Sprite, m_Texture2D, spriteAtlasData.textureRect, spriteAtlasData.textureRectOffset, spriteAtlasData.downscaleMultiplier, spriteAtlasData.settingsRaw); + m_Texture2D = texture2D; + if (spriteAtlasData.alphaTexture.TryGet(out var alphaTexture2D)) + m_AlphaTexture2D = alphaTexture2D; + textureRect = spriteAtlasData.textureRect; + textureRectOffset = spriteAtlasData.textureRectOffset; + downscaleMultiplier = spriteAtlasData.downscaleMultiplier; + settingsRaw = spriteAtlasData.settingsRaw; } } - else + else if (m_Sprite.m_RD.texture.TryGet(out var texture2D)) { - 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 = null; - if (spriteMaskMode != SpriteMaskMode.MaskOnly) - { - tex = CutImageWithCanvas(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); - } - var alphaTex = CutImageWithCanvas(m_Sprite, m_AlphaTexture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); - - switch (spriteMaskMode) - { - case SpriteMaskMode.On: - tex.ApplyRGBMask(alphaTex, isPreview: true); - return tex; - case SpriteMaskMode.Export: - tex.ApplyRGBMask(alphaTex); - return tex; - case SpriteMaskMode.MaskOnly: - return alphaTex; - } - } - else if (m_Sprite.m_RD.texture.TryGet(out m_Texture2D)) - { - return CutImageWithCanvas(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); - } + m_Texture2D = texture2D; + if (m_Sprite.m_RD.alphaTexture.TryGet(out var alphaTexture2D)) + m_AlphaTexture2D = alphaTexture2D; + textureRect = m_Sprite.m_RD.textureRect; + textureRectOffset = m_Sprite.m_RD.textureRectOffset; + downscaleMultiplier = m_Sprite.m_RD.downscaleMultiplier; + settingsRaw = m_Sprite.m_RD.settingsRaw; } - return null; + + if (textureRect == null) + return; + + resultWidth = cutImageWithCanvas + ? (int)sprite.m_Rect.width + : (int)textureRect.width; + resultHeight = cutImageWithCanvas + ? (int)sprite.m_Rect.height + : (int)textureRect.height; } - private static void ApplyRGBMask(this Image tex, Image texMask, bool isPreview = false) + public bool TryConvertToImage(out Image spriteImage, SpriteMaskMode maskMode) { - using (texMask) + spriteImage = null; + if (m_Texture2D == null) + return false; + + if (m_AlphaTexture2D != null) { - if (tex.Width != texMask.Width || tex.Height != texMask.Height) + var alphaTex = CutSprite(m_AlphaTexture2D); + if (maskMode != SpriteMaskMode.MaskOnly) { - var resampler = isPreview ? KnownResamplers.NearestNeighbor : KnownResamplers.Bicubic; - texMask.Mutate(x => x.Resize(tex.Width, tex.Height, resampler)); + spriteImage = CutSprite(m_Texture2D); } - tex.ProcessPixelRows(texMask, (sourceTex, targetTexMask) => + switch (maskMode) { - 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 = (byte)((maskRow[x].R + maskRow[x].G + maskRow[x].B) / 3); - texRow[x].A = grayscale; - } - } - }); + case SpriteMaskMode.MaskOnly: + spriteImage = alphaTex; + break; + case SpriteMaskMode.Preview when alphaTex != null: + spriteImage.ApplyRgbMask(alphaTex, fastResize: true); + break; + case SpriteMaskMode.On when alphaTex != null: + spriteImage.ApplyRgbMask(alphaTex); + break; + } + return spriteImage != null; + } + + spriteImage = CutSprite(m_Texture2D); + return spriteImage != null; + } + + private Image CutSprite(Texture2D texture2D) + { + var croppedImage = CutImage(texture2D); + if (croppedImage == null) + return null; + + if (!cutImageWithCanvas) + return croppedImage; + + using (croppedImage) + { + var canvas = new Image(resultWidth, resultHeight); + canvas.Mutate(ctx => ctx.DrawImage( + croppedImage, + new Point((int)textureRectOffset.X, (int)(resultHeight - textureRectOffset.Y - croppedImage.Height)), + 1f) + ); + + return canvas; } } - private static Image CutImage(Sprite m_Sprite, Texture2D m_Texture2D, Rectf textureRect, Vector2 textureRectOffset, float downscaleMultiplier, SpriteSettings settingsRaw) + private Image CutImage(Texture2D texture2D) { - if (!m_Texture2D.TryConvertToImage(false, out var textureImage)) + if (!texture2D.TryConvertToImage(false, out var textureImage)) return null; if (downscaleMultiplier > 0f && downscaleMultiplier != 1f) { - var width = (int)(m_Texture2D.m_Width / downscaleMultiplier); - var height = (int)(m_Texture2D.m_Height / downscaleMultiplier); + var width = (int)(texture2D.m_Width / downscaleMultiplier); + var height = (int)(texture2D.m_Height / downscaleMultiplier); textureImage.Mutate(x => x.Resize(width, height)); } - var rectX = (int)MathF.Floor(textureRect.x); - var rectY = (int)MathF.Floor(textureRect.y); + var rectX = (int)textureRect.x; + var rectY = (int)textureRect.y; var rectRight = (int)MathF.Ceiling(textureRect.x + textureRect.width); var rectBottom = (int)MathF.Ceiling(textureRect.y + textureRect.height); rectRight = Math.Min(rectRight, textureImage.Width); @@ -133,9 +175,9 @@ namespace AssetStudio { try { - var matrix = Matrix3x2.CreateScale(m_Sprite.m_PixelsToUnits); - matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y); - var triangles = GetTriangles(m_Sprite.m_RD); + var matrix = Matrix3x2.CreateScale(sprite.m_PixelsToUnits); + matrix *= Matrix3x2.CreateTranslation(sprite.m_Rect.width * sprite.m_Pivot.X - textureRectOffset.X, sprite.m_Rect.height * sprite.m_Pivot.Y - textureRectOffset.Y); + var triangles = GetTriangles(sprite.m_RD); var points = triangles.Select(x => x.Select(y => new PointF(y.X, y.Y))); var pathBuilder = new PathBuilder(matrix); foreach (var p in points) @@ -177,7 +219,7 @@ namespace AssetStudio } catch (Exception e) { - Logger.Warning($"{m_Sprite.m_Name} Unable to render the packed sprite correctly.\n{e}"); + Logger.Warning($"{sprite.m_Name} Unable to render the packed sprite correctly.\n{e}"); } } @@ -186,19 +228,6 @@ namespace AssetStudio return spriteImage; } - private static Image CutImageWithCanvas(Sprite m_Sprite, Texture2D m_Texture2D, Rectf textureRect, Vector2 textureRectOffset, float downscaleMultiplier, SpriteSettings settingsRaw) - { - var cropped = CutImage(m_Sprite, m_Texture2D, textureRect, textureRectOffset, downscaleMultiplier, settingsRaw); - if (cropped == null) - return null; - - var canvas = new Image((int)MathF.Floor(m_Sprite.m_Rect.width), (int)MathF.Floor(m_Sprite.m_Rect.height)); - canvas.Mutate(ctx => ctx.DrawImage(cropped, new Point((int)MathF.Floor(textureRectOffset.X), (int)MathF.Floor(m_Sprite.m_Rect.height - textureRectOffset.Y - cropped.Height)), 1f)); - - cropped.Dispose(); - return canvas; - } - private static Vector2[][] GetTriangles(SpriteRenderData m_RD) { if (m_RD.vertices != null) //5.6 down @@ -254,5 +283,40 @@ namespace AssetStudio return triangles.ToArray(); } } + + public ulong GetSpriteHash() + { + ulong spriteHash = 0; + if (m_Texture2D == null) + return spriteHash; + + spriteHash = m_Texture2D.GetImageDataHash(); + + if (m_AlphaTexture2D != null) + { + spriteHash ^= m_AlphaTexture2D.GetImageDataHash(); + } + + return (m_Texture2D.m_Width, m_Texture2D.m_Height) == (resultWidth, resultHeight) + ? spriteHash + : spriteHash ^ GetRectHash(); + } + + private ulong GetRectHash() + { + Span buff = stackalloc byte[40]; + BinaryPrimitives.WriteInt32LittleEndian(buff, (int)textureRect.x); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(4), (int)textureRect.y); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(8), (int)textureRect.width); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(12), (int)textureRect.height); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(16), (int)textureRectOffset.X); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(20), (int)textureRectOffset.Y); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(24), (int)sprite.m_Rect.x); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(28), (int)sprite.m_Rect.y); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(32), (int)sprite.m_Rect.width); + BinaryPrimitives.WriteInt32LittleEndian(buff.Slice(36), (int)sprite.m_Rect.height); + + return System.IO.Hashing.XxHash3.HashToUInt64(buff); + } } } diff --git a/AssetStudioUtility/SpriteExtensions.cs b/AssetStudioUtility/SpriteExtensions.cs new file mode 100644 index 0000000..67bc392 --- /dev/null +++ b/AssetStudioUtility/SpriteExtensions.cs @@ -0,0 +1,13 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace AssetStudio +{ + public static class SpriteExtensions + { + public static bool TryGetImage(this Sprite m_Sprite, out Image spriteImage, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On) + { + return new SpriteConverter(m_Sprite).TryConvertToImage(out spriteImage, spriteMaskMode); + } + } +} diff --git a/AssetStudioUtility/Texture2DConverter.cs b/AssetStudioUtility/Texture2DConverter.cs index d93b12b..da9d70e 100644 --- a/AssetStudioUtility/Texture2DConverter.cs +++ b/AssetStudioUtility/Texture2DConverter.cs @@ -1,4 +1,7 @@ -using System; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using Texture2DDecoder; @@ -7,39 +10,41 @@ namespace AssetStudio { public class Texture2DConverter { - private ResourceReader reader; + private Texture2D m_Texture2D; private int m_Width; private int m_Height; private TextureFormat m_TextureFormat; - private byte[] m_PlatformBlob; - private UnityVersion version; - private BuildTarget platform; - private int outPutDataSize; + private int texDataSize; + private int outputDataSize; + private bool canConvert; private bool switchSwizzled; private int gobsPerBlock; - private SixLabors.ImageSharp.Size blockSize; + private Size blockSize; - public int OutputDataSize => outPutDataSize; + public int OutputDataSize => outputDataSize; public Texture2DConverter(Texture2D m_Texture2D) { - reader = m_Texture2D.image_data; + this.m_Texture2D = m_Texture2D; m_Width = m_Texture2D.m_Width; m_Height = m_Texture2D.m_Height; + texDataSize = m_Texture2D.image_data.Size; + if (texDataSize == 0 || m_Width == 0 || m_Height == 0) + return; + m_TextureFormat = m_Texture2D.m_TextureFormat; - m_PlatformBlob = m_Texture2D.m_PlatformBlob; - version = m_Texture2D.version; - platform = m_Texture2D.platform; // not guaranteed, you can have a swizzled texture without m_PlatformBlob // but officially, I don't think this can happen. maybe check which engine // version this started happening in... - switchSwizzled = platform == BuildTarget.Switch && m_PlatformBlob.Length != 0; + switchSwizzled = m_Texture2D.platform == BuildTarget.Switch && m_Texture2D.m_PlatformBlob.Length != 0; if (switchSwizzled) { SetupSwitchSwizzle(); } - outPutDataSize = m_Width * m_Height * 4; + outputDataSize = m_Width * m_Height * 4; + + canConvert = true; } private void SetupSwitchSwizzle() @@ -47,7 +52,7 @@ namespace AssetStudio //apparently there is another value to worry about, but seeing as it's //always 0 and I have nothing else to test against, this will probably //work fine for now - gobsPerBlock = 1 << BitConverter.ToInt32(m_PlatformBlob, 8); + gobsPerBlock = 1 << BitConverter.ToInt32(m_Texture2D.m_PlatformBlob, 8); switch (m_TextureFormat) { @@ -65,197 +70,255 @@ namespace AssetStudio blockSize = Texture2DSwitchDeswizzler.GetTextureFormatBlockSize(m_TextureFormat); } - public bool DecodeTexture2D(byte[] bytes) + public ulong GetTextureHash() => canConvert + ? m_Texture2D.GetImageDataHash() + : 0; + + public bool GetBgra32RawImageData(byte[] destArray, bool flip) { - if (reader.Size == 0 || m_Width == 0 || m_Height == 0) - { + if (!canConvert) return false; + + var dataSpan = destArray.AsSpan(0, outputDataSize); + if (!DecodeTexture2D(dataSpan)) + return false; + + if (flip) + { + var stride = m_Width * 4; + Span tmp = new byte[stride]; + for (var i = 0; i < m_Height / 2; i++) //flip + { + var j = m_Height - i - 1; + var srcOffset = i * stride; + var dstOffset = j * stride; + dataSpan.Slice(dstOffset, stride).CopyTo(tmp); + dataSpan.Slice(srcOffset, stride).CopyTo(dataSpan.Slice(dstOffset)); + tmp.CopyTo(dataSpan.Slice(srcOffset)); + } } - var flag = false; - var buff = BigArrayPool.Shared.Rent(reader.Size); + + return true; + } + + public bool TryConvertToBgra32Image(bool flip, out Image image) + { + image = null; + if (!canConvert) + return false; + + var imageBuff = BigArrayPool.Shared.Rent(outputDataSize); + var imageBuffSpan = imageBuff.AsSpan(0, outputDataSize); try { - _ = reader.GetData(buff); + if (!DecodeTexture2D(imageBuffSpan)) + return false; + + image = Image.LoadPixelData(imageBuffSpan, m_Width, m_Height); + + if (flip) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + return true; + } + finally + { + BigArrayPool.Shared.Return(imageBuff, clearArray: true); + } + } + + private bool DecodeTexture2D(Span destSpan) + { + if (!canConvert) + return false; + + var flag = false; + var buff = BigArrayPool.Shared.Rent(texDataSize); + try + { + _ = m_Texture2D.image_data.GetData(buff); + if (switchSwizzled) { - var spanBuff = buff.AsSpan(0, reader.Size); - var textureSize = new SixLabors.ImageSharp.Size(m_Width, m_Height); + var spanBuff = buff.AsSpan(0, texDataSize); + var textureSize = new Size(m_Width, m_Height); Texture2DSwitchDeswizzler.Unswizzle(ref spanBuff, textureSize, blockSize, gobsPerBlock); } switch (m_TextureFormat) { case TextureFormat.Alpha8: //test pass - flag = DecodeAlpha8(buff, bytes); + flag = DecodeAlpha8(buff, destSpan); break; case TextureFormat.ARGB4444: //test pass SwapBytesForXbox(buff); - flag = DecodeARGB4444(buff, bytes); + flag = DecodeARGB4444(buff, destSpan); break; case TextureFormat.RGB24: //test pass - flag = DecodeRGB24(buff, bytes); + flag = DecodeRGB24(buff, destSpan); break; case TextureFormat.RGBA32: //test pass - flag = DecodeRGBA32(buff, bytes); + flag = DecodeRGBA32(buff, destSpan); break; case TextureFormat.ARGB32: //test pass - flag = DecodeARGB32(buff, bytes); + flag = DecodeARGB32(buff, destSpan); break; case TextureFormat.RGB565: //test pass SwapBytesForXbox(buff); - flag = DecodeRGB565(buff, bytes); + flag = DecodeRGB565(buff, destSpan); break; case TextureFormat.R16: //test pass - flag = DecodeR16(buff, bytes); + flag = DecodeR16(buff, destSpan); break; case TextureFormat.DXT1: //test pass SwapBytesForXbox(buff); - flag = DecodeDXT1(buff, bytes); + flag = DecodeDXT1(buff, destSpan); break; case TextureFormat.DXT3: break; case TextureFormat.DXT5: //test pass SwapBytesForXbox(buff); - flag = DecodeDXT5(buff, bytes); + flag = DecodeDXT5(buff, destSpan); break; case TextureFormat.RGBA4444: //test pass - flag = DecodeRGBA4444(buff, bytes); + flag = DecodeRGBA4444(buff, destSpan); break; case TextureFormat.BGRA32: //test pass - flag = DecodeBGRA32(buff, bytes); + flag = DecodeBGRA32(buff, destSpan); break; case TextureFormat.RHalf: - flag = DecodeRHalf(buff, bytes); + flag = DecodeRHalf(buff, destSpan); break; case TextureFormat.RGHalf: - flag = DecodeRGHalf(buff, bytes); + flag = DecodeRGHalf(buff, destSpan); break; case TextureFormat.RGBAHalf: //test pass - flag = DecodeRGBAHalf(buff, bytes); + flag = DecodeRGBAHalf(buff, destSpan); break; case TextureFormat.RFloat: - flag = DecodeRFloat(buff, bytes); + flag = DecodeRFloat(buff, destSpan); break; case TextureFormat.RGFloat: - flag = DecodeRGFloat(buff, bytes); + flag = DecodeRGFloat(buff, destSpan); break; case TextureFormat.RGBAFloat: - flag = DecodeRGBAFloat(buff, bytes); + flag = DecodeRGBAFloat(buff, destSpan); break; case TextureFormat.YUY2: //test pass - flag = DecodeYUY2(buff, bytes); + flag = DecodeYUY2(buff, destSpan); break; case TextureFormat.RGB9e5Float: //test pass - flag = DecodeRGB9e5Float(buff, bytes); + flag = DecodeRGB9e5Float(buff, destSpan); break; case TextureFormat.BC6H: //test pass - flag = DecodeBC6H(buff, bytes); + flag = DecodeBC6H(buff, destSpan); break; case TextureFormat.BC7: //test pass - flag = DecodeBC7(buff, bytes); + flag = DecodeBC7(buff, destSpan); break; case TextureFormat.BC4: //test pass - flag = DecodeBC4(buff, bytes); + flag = DecodeBC4(buff, destSpan); break; case TextureFormat.BC5: //test pass - flag = DecodeBC5(buff, bytes); + flag = DecodeBC5(buff, destSpan); break; case TextureFormat.DXT1Crunched: //test pass - flag = DecodeDXT1Crunched(buff, bytes); + flag = DecodeDXT1Crunched(buff, destSpan); break; case TextureFormat.DXT5Crunched: //test pass - flag = DecodeDXT5Crunched(buff, bytes); + flag = DecodeDXT5Crunched(buff, destSpan); break; case TextureFormat.PVRTC_RGB2: //test pass case TextureFormat.PVRTC_RGBA2: //test pass - flag = DecodePVRTC(buff, bytes, true); + flag = DecodePVRTC(buff, destSpan, true); break; case TextureFormat.PVRTC_RGB4: //test pass case TextureFormat.PVRTC_RGBA4: //test pass - flag = DecodePVRTC(buff, bytes, false); + flag = DecodePVRTC(buff, destSpan, false); break; case TextureFormat.ETC_RGB4: //test pass case TextureFormat.ETC_RGB4_3DS: - flag = DecodeETC1(buff, bytes); + flag = DecodeETC1(buff, destSpan); break; case TextureFormat.ATC_RGB4: //test pass - flag = DecodeATCRGB4(buff, bytes); + flag = DecodeATCRGB4(buff, destSpan); break; case TextureFormat.ATC_RGBA8: //test pass - flag = DecodeATCRGBA8(buff, bytes); + flag = DecodeATCRGBA8(buff, destSpan); break; case TextureFormat.EAC_R: //test pass - flag = DecodeEACR(buff, bytes); + flag = DecodeEACR(buff, destSpan); break; case TextureFormat.EAC_R_SIGNED: - flag = DecodeEACRSigned(buff, bytes); + flag = DecodeEACRSigned(buff, destSpan); break; case TextureFormat.EAC_RG: //test pass - flag = DecodeEACRG(buff, bytes); + flag = DecodeEACRG(buff, destSpan); break; case TextureFormat.EAC_RG_SIGNED: - flag = DecodeEACRGSigned(buff, bytes); + flag = DecodeEACRGSigned(buff, destSpan); break; case TextureFormat.ETC2_RGB: //test pass - flag = DecodeETC2(buff, bytes); + flag = DecodeETC2(buff, destSpan); break; case TextureFormat.ETC2_RGBA1: //test pass - flag = DecodeETC2A1(buff, bytes); + flag = DecodeETC2A1(buff, destSpan); break; case TextureFormat.ETC2_RGBA8: //test pass case TextureFormat.ETC_RGBA8_3DS: - flag = DecodeETC2A8(buff, bytes); + flag = DecodeETC2A8(buff, destSpan); break; case TextureFormat.ASTC_RGB_4x4: //test pass case TextureFormat.ASTC_RGBA_4x4: //test pass case TextureFormat.ASTC_HDR_4x4: //test pass - flag = DecodeASTC(buff, bytes, 4); + flag = DecodeASTC(buff, destSpan, 4); break; case TextureFormat.ASTC_RGB_5x5: //test pass case TextureFormat.ASTC_RGBA_5x5: //test pass case TextureFormat.ASTC_HDR_5x5: //test pass - flag = DecodeASTC(buff, bytes, 5); + flag = DecodeASTC(buff, destSpan, 5); break; case TextureFormat.ASTC_RGB_6x6: //test pass case TextureFormat.ASTC_RGBA_6x6: //test pass case TextureFormat.ASTC_HDR_6x6: //test pass - flag = DecodeASTC(buff, bytes, 6); + flag = DecodeASTC(buff, destSpan, 6); break; case TextureFormat.ASTC_RGB_8x8: //test pass case TextureFormat.ASTC_RGBA_8x8: //test pass case TextureFormat.ASTC_HDR_8x8: //test pass - flag = DecodeASTC(buff, bytes, 8); + flag = DecodeASTC(buff, destSpan, 8); break; case TextureFormat.ASTC_RGB_10x10: //test pass case TextureFormat.ASTC_RGBA_10x10: //test pass case TextureFormat.ASTC_HDR_10x10: //test pass - flag = DecodeASTC(buff, bytes, 10); + flag = DecodeASTC(buff, destSpan, 10); break; case TextureFormat.ASTC_RGB_12x12: //test pass case TextureFormat.ASTC_RGBA_12x12: //test pass case TextureFormat.ASTC_HDR_12x12: //test pass - flag = DecodeASTC(buff, bytes, 12); + flag = DecodeASTC(buff, destSpan, 12); break; case TextureFormat.RG16: //test pass - flag = DecodeRG16(buff, bytes); + flag = DecodeRG16(buff, destSpan); break; case TextureFormat.R8: //test pass - flag = DecodeR8(buff, bytes); + flag = DecodeR8(buff, destSpan); break; case TextureFormat.ETC_RGB4Crunched: //test pass - flag = DecodeETC1Crunched(buff, bytes); + flag = DecodeETC1Crunched(buff, destSpan); break; case TextureFormat.ETC2_RGBA8Crunched: //test pass - flag = DecodeETC2A8Crunched(buff, bytes); + flag = DecodeETC2A8Crunched(buff, destSpan); break; case TextureFormat.RG32: //test pass - flag = DecodeRG32(buff, bytes); + flag = DecodeRG32(buff, destSpan); break; case TextureFormat.RGB48: //test pass - flag = DecodeRGB48(buff, bytes); + flag = DecodeRGB48(buff, destSpan); break; case TextureFormat.RGBA64: //test pass - flag = DecodeRGBA64(buff, bytes); + flag = DecodeRGBA64(buff, destSpan); break; } } @@ -263,221 +326,216 @@ namespace AssetStudio { BigArrayPool.Shared.Return(buff, clearArray: true); } + return flag; } - private void SwapBytesForXbox(Span image_data) + private void SwapBytesForXbox(Span buff) { - if (platform == BuildTarget.XBOX360) + if (m_Texture2D.platform == BuildTarget.XBOX360) { - for (var i = 0; i < reader.Size / 2; i++) + for (var i = 0; i < texDataSize / 2; i++) { - image_data.Slice(i * 2, 2).Reverse(); + buff.Slice(i * 2, 2).Reverse(); } } } - private bool DecodeAlpha8(ReadOnlySpan image_data, Span buff) + private bool DecodeAlpha8(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; - buff.Fill(0xFF); + destSpan.Fill(0xFF); for (var i = 0; i < size; i++) { - buff[i * 4 + 3] = image_data[i]; + destSpan[i * 4 + 3] = buff[i]; //A } return true; } - private bool DecodeARGB4444(ReadOnlySpan image_data, Span buff) + private bool DecodeARGB4444(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; var pixelNew = new byte[4].AsSpan(); for (var i = 0; i < size; i++) { - var pixelOldShort = BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2)); + var pixelOldShort = BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2)); pixelNew[0] = (byte)(pixelOldShort & 0x000f); pixelNew[1] = (byte)((pixelOldShort & 0x00f0) >> 4); pixelNew[2] = (byte)((pixelOldShort & 0x0f00) >> 8); pixelNew[3] = (byte)((pixelOldShort & 0xf000) >> 12); for (var j = 0; j < 4; j++) pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]); - pixelNew.CopyTo(buff.Slice(i * 4)); + pixelNew.CopyTo(destSpan.Slice(i * 4)); } return true; } - private bool DecodeRGB24(ReadOnlySpan image_data, Span buff) + private bool DecodeRGB24(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - buff[i * 4] = image_data[i * 3 + 2]; - buff[i * 4 + 1] = image_data[i * 3 + 1]; - buff[i * 4 + 2] = image_data[i * 3 + 0]; - buff[i * 4 + 3] = 255; + destSpan[i * 4] = buff[i * 3 + 2]; + destSpan[i * 4 + 1] = buff[i * 3 + 1]; + destSpan[i * 4 + 2] = buff[i * 3 + 0]; + destSpan[i * 4 + 3] = 255; } return true; } - private bool DecodeRGBA32(ReadOnlySpan image_data, Span buff) + private bool DecodeRGBA32(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = image_data[i + 2]; - buff[i + 1] = image_data[i + 1]; - buff[i + 2] = image_data[i + 0]; - buff[i + 3] = image_data[i + 3]; + destSpan[i] = buff[i + 2]; + destSpan[i + 1] = buff[i + 1]; + destSpan[i + 2] = buff[i + 0]; + destSpan[i + 3] = buff[i + 3]; } return true; } - private bool DecodeARGB32(ReadOnlySpan image_data, Span buff) + private bool DecodeARGB32(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = image_data[i + 3]; - buff[i + 1] = image_data[i + 2]; - buff[i + 2] = image_data[i + 1]; - buff[i + 3] = image_data[i + 0]; + destSpan[i] = buff[i + 3]; + destSpan[i + 1] = buff[i + 2]; + destSpan[i + 2] = buff[i + 1]; + destSpan[i + 3] = buff[i + 0]; } return true; } - private bool DecodeRGB565(ReadOnlySpan image_data, Span buff) + private bool DecodeRGB565(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - var p = BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2)); - buff[i * 4] = (byte)((p << 3) | (p >> 2 & 7)); - buff[i * 4 + 1] = (byte)((p >> 3 & 0xfc) | (p >> 9 & 3)); - buff[i * 4 + 2] = (byte)((p >> 8 & 0xf8) | (p >> 13)); - buff[i * 4 + 3] = 255; + var p = BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2)); + destSpan[i * 4] = (byte)((p << 3) | (p >> 2 & 7)); + destSpan[i * 4 + 1] = (byte)((p >> 3 & 0xfc) | (p >> 9 & 3)); + destSpan[i * 4 + 2] = (byte)((p >> 8 & 0xf8) | (p >> 13)); + destSpan[i * 4 + 3] = 255; } return true; } - private bool DecodeR16(ReadOnlySpan image_data, Span buff) + private bool DecodeR16(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - buff[i * 4] = 0; //b - buff[i * 4 + 1] = 0; //g - buff[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2))); //r - buff[i * 4 + 3] = 255; //a + destSpan[i * 4] = 0; //B + destSpan[i * 4 + 1] = 0; //G + destSpan[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2))); //R + destSpan[i * 4 + 3] = 255; //A } return true; } - private bool DecodeDXT1(ReadOnlySpan image_data, Span buff) + private bool DecodeDXT1(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeDXT1(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeDXT1(buff, m_Width, m_Height, destSpan); } - private bool DecodeDXT5(ReadOnlySpan image_data, Span buff) + private bool DecodeDXT5(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeDXT5(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeDXT5(buff, m_Width, m_Height, destSpan); } - private bool DecodeRGBA4444(ReadOnlySpan image_data, Span buff) + private bool DecodeRGBA4444(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; var pixelNew = new byte[4].AsSpan(); for (var i = 0; i < size; i++) { - var pixelOldShort = BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2)); + var pixelOldShort = BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2)); pixelNew[0] = (byte)((pixelOldShort & 0x00f0) >> 4); pixelNew[1] = (byte)((pixelOldShort & 0x0f00) >> 8); pixelNew[2] = (byte)((pixelOldShort & 0xf000) >> 12); pixelNew[3] = (byte)(pixelOldShort & 0x000f); for (var j = 0; j < 4; j++) pixelNew[j] = (byte)((pixelNew[j] << 4) | pixelNew[j]); - pixelNew.CopyTo(buff.Slice(i * 4)); + pixelNew.CopyTo(destSpan.Slice(i * 4)); } return true; } - private bool DecodeBGRA32(ReadOnlySpan image_data, Span buff) + private bool DecodeBGRA32(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + buff.Slice(0, outputDataSize).CopyTo(destSpan); + return true; + } + + private bool DecodeRHalf(ReadOnlySpan buff, Span destSpan) + { + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = image_data[i]; - buff[i + 1] = image_data[i + 1]; - buff[i + 2] = image_data[i + 2]; - buff[i + 3] = image_data[i + 3]; + destSpan[i] = 0; + destSpan[i + 1] = 0; + destSpan[i + 2] = (byte)MathF.Round((float)buff.ReadHalf(i / 2) * 255f); + destSpan[i + 3] = 255; } return true; } - private bool DecodeRHalf(ReadOnlySpan image_data, Span buff) + private bool DecodeRGHalf(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = 0; - buff[i + 1] = 0; - buff[i + 2] = (byte)MathF.Round((float)image_data.ReadHalf(i / 2) * 255f); - buff[i + 3] = 255; + destSpan[i] = 0; + destSpan[i + 1] = (byte)MathF.Round((float)buff.ReadHalf(i + 2) * 255f); + destSpan[i + 2] = (byte)MathF.Round((float)buff.ReadHalf(i) * 255f); + destSpan[i + 3] = 255; } return true; } - private bool DecodeRGHalf(ReadOnlySpan image_data, Span buff) + private bool DecodeRGBAHalf(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = 0; - buff[i + 1] = (byte)MathF.Round((float)image_data.ReadHalf(i + 2) * 255f); - buff[i + 2] = (byte)MathF.Round((float)image_data.ReadHalf(i) * 255f); - buff[i + 3] = 255; + destSpan[i] = (byte)MathF.Round((float)buff.ReadHalf(i * 2 + 4) * 255f); + destSpan[i + 1] = (byte)MathF.Round((float)buff.ReadHalf(i * 2 + 2) * 255f); + destSpan[i + 2] = (byte)MathF.Round((float)buff.ReadHalf(i * 2) * 255f); + destSpan[i + 3] = (byte)MathF.Round((float)buff.ReadHalf(i * 2 + 6) * 255f); } return true; } - private bool DecodeRGBAHalf(ReadOnlySpan image_data, Span buff) + private bool DecodeRFloat(byte[] buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = (byte)MathF.Round((float)image_data.ReadHalf(i * 2 + 4) * 255f); - buff[i + 1] = (byte)MathF.Round((float)image_data.ReadHalf(i * 2 + 2) * 255f); - buff[i + 2] = (byte)MathF.Round((float)image_data.ReadHalf(i * 2) * 255f); - buff[i + 3] = (byte)MathF.Round((float)image_data.ReadHalf(i * 2 + 6) * 255f); + destSpan[i] = 0; + destSpan[i + 1] = 0; + destSpan[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(buff, i) * 255f); + destSpan[i + 3] = 255; } return true; } - private bool DecodeRFloat(byte[] image_data, Span buff) + private bool DecodeRGFloat(byte[] buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = 0; - buff[i + 1] = 0; - buff[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i) * 255f); - buff[i + 3] = 255; + destSpan[i] = 0; + destSpan[i + 1] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 2 + 4) * 255f); + destSpan[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 2) * 255f); + destSpan[i + 3] = 255; } return true; } - private bool DecodeRGFloat(byte[] image_data, Span buff) + private bool DecodeRGBAFloat(byte[] buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = 0; - buff[i + 1] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 2 + 4) * 255f); - buff[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 2) * 255f); - buff[i + 3] = 255; - } - return true; - } - - private bool DecodeRGBAFloat(byte[] image_data, Span buff) - { - for (var i = 0; i < outPutDataSize; i += 4) - { - buff[i] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 4 + 8) * 255f); - buff[i + 1] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 4 + 4) * 255f); - buff[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 4) * 255f); - buff[i + 3] = (byte)MathF.Round(BitConverter.ToSingle(image_data, i * 4 + 12) * 255f); + destSpan[i] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 4 + 8) * 255f); + destSpan[i + 1] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 4 + 4) * 255f); + destSpan[i + 2] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 4) * 255f); + destSpan[i + 3] = (byte)MathF.Round(BitConverter.ToSingle(buff, i * 4 + 12) * 255f); } return true; } @@ -488,7 +546,7 @@ namespace AssetStudio return (byte)(byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? x : byte.MinValue)); } - private bool DecodeYUY2(ReadOnlySpan image_data, Span buff) + private bool DecodeYUY2(ReadOnlySpan buff, Span destSpan) { int p = 0; int o = 0; @@ -497,195 +555,183 @@ namespace AssetStudio { for (int i = 0; i < halfWidth; ++i) { - int y0 = image_data[p++]; - int u0 = image_data[p++]; - int y1 = image_data[p++]; - int v0 = image_data[p++]; + int y0 = buff[p++]; + int u0 = buff[p++]; + int y1 = buff[p++]; + int v0 = buff[p++]; int c = y0 - 16; int d = u0 - 128; int e = v0 - 128; - buff[o++] = ClampByte((298 * c + 516 * d + 128) >> 8); // b - buff[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); // g - buff[o++] = ClampByte((298 * c + 409 * e + 128) >> 8); // r - buff[o++] = 255; + destSpan[o++] = ClampByte((298 * c + 516 * d + 128) >> 8); //B + destSpan[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); //G + destSpan[o++] = ClampByte((298 * c + 409 * e + 128) >> 8); //R + destSpan[o++] = 255; //A c = y1 - 16; - buff[o++] = ClampByte((298 * c + 516 * d + 128) >> 8); // b - buff[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); // g - buff[o++] = ClampByte((298 * c + 409 * e + 128) >> 8); // r - buff[o++] = 255; + destSpan[o++] = ClampByte((298 * c + 516 * d + 128) >> 8); //B + destSpan[o++] = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); //G + destSpan[o++] = ClampByte((298 * c + 409 * e + 128) >> 8); //R + destSpan[o++] = 255; //A } } return true; } - private bool DecodeRGB9e5Float(ReadOnlySpan image_data, Span buff) + private bool DecodeRGB9e5Float(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - var n = BinaryPrimitives.ReadInt32LittleEndian(image_data.Slice(i)); + var n = BinaryPrimitives.ReadInt32LittleEndian(buff.Slice(i)); var scale = n >> 27 & 0x1f; var scalef = MathF.Pow(2, scale - 24); var b = n >> 18 & 0x1ff; var g = n >> 9 & 0x1ff; var r = n & 0x1ff; - buff[i] = (byte)MathF.Round(b * scalef * 255f); - buff[i + 1] = (byte)MathF.Round(g * scalef * 255f); - buff[i + 2] = (byte)MathF.Round(r * scalef * 255f); - buff[i + 3] = 255; + destSpan[i] = (byte)MathF.Round(b * scalef * 255f); + destSpan[i + 1] = (byte)MathF.Round(g * scalef * 255f); + destSpan[i + 2] = (byte)MathF.Round(r * scalef * 255f); + destSpan[i + 3] = 255; } return true; } - private bool DecodeBC4(ReadOnlySpan image_data, Span buff) + private bool DecodeBC4(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeBC4(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeBC4(buff, m_Width, m_Height, destSpan); } - private bool DecodeBC5(ReadOnlySpan image_data, Span buff) + private bool DecodeBC5(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeBC5(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeBC5(buff, m_Width, m_Height, destSpan); } - private bool DecodeBC6H(ReadOnlySpan image_data, Span buff) + private bool DecodeBC6H(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeBC6(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeBC6(buff, m_Width, m_Height, destSpan); } - private bool DecodeBC7(ReadOnlySpan image_data, Span buff) + private bool DecodeBC7(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeBC7(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeBC7(buff, m_Width, m_Height, destSpan); } - private bool DecodeDXT1Crunched(ReadOnlySpan image_data, Span buff) + private bool DecodeDXT1Crunched(ReadOnlySpan buff, Span destSpan) { - if (UnpackCrunch(image_data, out var result)) + if (TryUnpackCrunch(buff, out var result)) { - if (DecodeDXT1(result, buff)) - { - return true; - } + return DecodeDXT1(result, destSpan); } return false; } - private bool DecodeDXT5Crunched(ReadOnlySpan image_data, Span buff) + private bool DecodeDXT5Crunched(ReadOnlySpan buff, Span destSpan) { - if (UnpackCrunch(image_data, out var result)) + if (TryUnpackCrunch(buff, out var result)) { - if (DecodeDXT5(result, buff)) - { - return true; - } + return DecodeDXT5(result, destSpan); } return false; } - private bool DecodePVRTC(ReadOnlySpan image_data, Span buff, bool is2bpp) + private bool DecodePVRTC(ReadOnlySpan buff, Span destSpan, bool is2bpp) { - return TextureDecoder.DecodePVRTC(image_data, m_Width, m_Height, buff, is2bpp); + return TextureDecoder.DecodePVRTC(buff, m_Width, m_Height, destSpan, is2bpp); } - private bool DecodeETC1(ReadOnlySpan image_data, Span buff) + private bool DecodeETC1(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeETC1(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeETC1(buff, m_Width, m_Height, destSpan); } - private bool DecodeATCRGB4(ReadOnlySpan image_data, Span buff) + private bool DecodeATCRGB4(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeATCRGB4(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeATCRGB4(buff, m_Width, m_Height, destSpan); } - private bool DecodeATCRGBA8(ReadOnlySpan image_data, Span buff) + private bool DecodeATCRGBA8(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeATCRGBA8(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeATCRGBA8(buff, m_Width, m_Height, destSpan); } - private bool DecodeEACR(ReadOnlySpan image_data, Span buff) + private bool DecodeEACR(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeEACR(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeEACR(buff, m_Width, m_Height, destSpan); } - private bool DecodeEACRSigned(ReadOnlySpan image_data, Span buff) + private bool DecodeEACRSigned(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeEACRSigned(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeEACRSigned(buff, m_Width, m_Height, destSpan); } - private bool DecodeEACRG(ReadOnlySpan image_data, Span buff) + private bool DecodeEACRG(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeEACRG(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeEACRG(buff, m_Width, m_Height, destSpan); } - private bool DecodeEACRGSigned(ReadOnlySpan image_data, Span buff) + private bool DecodeEACRGSigned(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeEACRGSigned(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeEACRGSigned(buff, m_Width, m_Height, destSpan); } - private bool DecodeETC2(ReadOnlySpan image_data, Span buff) + private bool DecodeETC2(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeETC2(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeETC2(buff, m_Width, m_Height, destSpan); } - private bool DecodeETC2A1(ReadOnlySpan image_data, Span buff) + private bool DecodeETC2A1(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeETC2A1(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeETC2A1(buff, m_Width, m_Height, destSpan); } - private bool DecodeETC2A8(ReadOnlySpan image_data, Span buff) + private bool DecodeETC2A8(ReadOnlySpan buff, Span destSpan) { - return TextureDecoder.DecodeETC2A8(image_data, m_Width, m_Height, buff); + return TextureDecoder.DecodeETC2A8(buff, m_Width, m_Height, destSpan); } - private bool DecodeASTC(ReadOnlySpan image_data, Span buff, int blocksize) + private bool DecodeASTC(ReadOnlySpan buff, Span destSpan, int blocksize) { - return TextureDecoder.DecodeASTC(image_data, m_Width, m_Height, blocksize, blocksize, buff); + return TextureDecoder.DecodeASTC(buff, m_Width, m_Height, blocksize, blocksize, destSpan); } - private bool DecodeRG16(ReadOnlySpan image_data, Span buff) + private bool DecodeRG16(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - buff[i * 4] = 0; //B - buff[i * 4 + 1] = image_data[i * 2 + 1];//G - buff[i * 4 + 2] = image_data[i * 2];//R - buff[i * 4 + 3] = 255;//A + destSpan[i * 4] = 0; //B + destSpan[i * 4 + 1] = buff[i * 2 + 1]; //G + destSpan[i * 4 + 2] = buff[i * 2]; //R + destSpan[i * 4 + 3] = 255; //A } return true; } - private bool DecodeR8(ReadOnlySpan image_data, Span buff) + private bool DecodeR8(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - buff[i * 4] = 0; //B - buff[i * 4 + 1] = 0; //G - buff[i * 4 + 2] = image_data[i];//R - buff[i * 4 + 3] = 255;//A + destSpan[i * 4] = 0; //B + destSpan[i * 4 + 1] = 0; //G + destSpan[i * 4 + 2] = buff[i]; //R + destSpan[i * 4 + 3] = 255; //A } return true; } - private bool DecodeETC1Crunched(ReadOnlySpan image_data, Span buff) + private bool DecodeETC1Crunched(ReadOnlySpan buff, Span destSpan) { - if (UnpackCrunch(image_data, out var result)) + if (TryUnpackCrunch(buff, out var result)) { - if (DecodeETC1(result, buff)) - { - return true; - } + return DecodeETC1(result, destSpan); } return false; } - private bool DecodeETC2A8Crunched(ReadOnlySpan image_data, Span buff) + private bool DecodeETC2A8Crunched(ReadOnlySpan buff, Span destSpan) { - if (UnpackCrunch(image_data, out var result)) + if (TryUnpackCrunch(buff, out var result)) { - if (DecodeETC2A8(result, buff)) - { - return true; - } + return DecodeETC2A8(result, destSpan); } return false; } @@ -696,54 +742,54 @@ namespace AssetStudio return (byte)(((component * 255) + 32895) >> 16); } - private bool DecodeRG32(ReadOnlySpan image_data, Span buff) + private bool DecodeRG32(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = 0; //b - buff[i + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i + 2))); //g - buff[i + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i))); //r - buff[i + 3] = byte.MaxValue; //a + destSpan[i] = 0; //B + destSpan[i + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i + 2))); //G + destSpan[i + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i))); //R + destSpan[i + 3] = byte.MaxValue; //A } return true; } - private bool DecodeRGB48(ReadOnlySpan image_data, Span buff) + private bool DecodeRGB48(ReadOnlySpan buff, Span destSpan) { var size = m_Width * m_Height; for (var i = 0; i < size; i++) { - buff[i * 4] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 6 + 4))); //b - buff[i * 4 + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 6 + 2))); //g - buff[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 6))); //r - buff[i * 4 + 3] = byte.MaxValue; //a + destSpan[i * 4] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 6 + 4))); //B + destSpan[i * 4 + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 6 + 2))); //G + destSpan[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 6))); //R + destSpan[i * 4 + 3] = byte.MaxValue; //A } return true; } - private bool DecodeRGBA64(ReadOnlySpan image_data, Span buff) + private bool DecodeRGBA64(ReadOnlySpan buff, Span destSpan) { - for (var i = 0; i < outPutDataSize; i += 4) + for (var i = 0; i < outputDataSize; i += 4) { - buff[i] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2 + 4))); //b - buff[i + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2 + 2))); //g - buff[i + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2))); //r - buff[i + 3] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(image_data.Slice(i * 2 + 6))); //a + destSpan[i] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2 + 4))); //B + destSpan[i + 1] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2 + 2))); //G + destSpan[i + 2] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2))); //R + destSpan[i + 3] = DownScaleFrom16BitTo8Bit(BinaryPrimitives.ReadUInt16LittleEndian(buff.Slice(i * 2 + 6))); //A } return true; } - private bool UnpackCrunch(ReadOnlySpan image_data, out ReadOnlySpan result) + private bool TryUnpackCrunch(ReadOnlySpan buff, out ReadOnlySpan result) { - if (version >= (2017, 3) //2017.3 and up + if (m_Texture2D.version >= (2017, 3) //2017.3 and up || m_TextureFormat == TextureFormat.ETC_RGB4Crunched || m_TextureFormat == TextureFormat.ETC2_RGBA8Crunched) { - result = TextureDecoder.UnpackUnityCrunch(image_data); + result = TextureDecoder.UnpackUnityCrunch(buff); } else { - result = TextureDecoder.UnpackCrunch(image_data); + result = TextureDecoder.UnpackCrunch(buff); } return !result.IsEmpty; } diff --git a/AssetStudioUtility/Texture2DExtensions.cs b/AssetStudioUtility/Texture2DExtensions.cs index e854295..1ba05a6 100644 --- a/AssetStudioUtility/Texture2DExtensions.cs +++ b/AssetStudioUtility/Texture2DExtensions.cs @@ -1,8 +1,6 @@ -using System; -using System.IO; -using SixLabors.ImageSharp; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; +using System.IO; namespace AssetStudio { @@ -10,32 +8,13 @@ namespace AssetStudio { public static bool TryConvertToImage(this Texture2D m_Texture2D, bool flip, out Image image) { - image = null; - var converter = new Texture2DConverter(m_Texture2D); - var buff = BigArrayPool.Shared.Rent(converter.OutputDataSize); - var spanBuff = buff.AsSpan(0, converter.OutputDataSize); - try - { - if (!converter.DecodeTexture2D(buff)) - return false; - - image = Image.LoadPixelData(spanBuff, m_Texture2D.m_Width, m_Texture2D.m_Height); - - if (flip) - { - image.Mutate(x => x.Flip(FlipMode.Vertical)); - } - return true; - } - finally - { - BigArrayPool.Shared.Return(buff, clearArray: true); - } + return new Texture2DConverter(m_Texture2D).TryConvertToBgra32Image(flip, out image); } public static MemoryStream ConvertToStream(this Texture2D m_Texture2D, ImageFormat imageFormat, bool flip) { - if (!TryConvertToImage(m_Texture2D, flip, out var image)) + var converter = new Texture2DConverter(m_Texture2D); + if (!converter.TryConvertToBgra32Image(flip, out var image)) return null; using (image) diff --git a/Texture2DDecoderNative/Texture2DDecoderNative.vcxproj b/Texture2DDecoderNative/Texture2DDecoderNative.vcxproj index 8ae9dad..d88dc6b 100644 --- a/Texture2DDecoderNative/Texture2DDecoderNative.vcxproj +++ b/Texture2DDecoderNative/Texture2DDecoderNative.vcxproj @@ -136,6 +136,7 @@ true NotUsing stdafx.h + MultiThreadedDebug Windows @@ -172,6 +173,7 @@ true NotUsing stdafx.h + MultiThreadedDebug Windows