From 79d192ab70916f73e457005a5bbf2aaa0d049481 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sat, 14 Mar 2026 00:16:08 +0300 Subject: [PATCH] Add option to keep original sprite sizes. Close #107 - Enabled by default - [GUI] Added "Keep original sprite sizes" option to the Export options - [CLI] Added "--sprite-cropping" option to the Advanced section --- AssetStudioCLI/Options/CLIOptions.cs | 34 ++++++++++ AssetStudioCLI/ParallelExporter.cs | 3 +- AssetStudioGUI/AssetStudioGUIForm.cs | 65 ++++++++++++++++--- AssetStudioGUI/ExportOptions.Designer.cs | 21 +++++- AssetStudioGUI/ExportOptions.cs | 2 + AssetStudioGUI/ParallelExport.cs | 2 +- .../Properties/Settings.Designer.cs | 12 ++++ AssetStudioGUI/Properties/Settings.settings | 3 + AssetStudioUtility/SpriteConverter.cs | 20 +++--- AssetStudioUtility/SpriteExtensions.cs | 4 +- 10 files changed, 142 insertions(+), 24 deletions(-) diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 235234b..6d71d7f 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -78,6 +78,12 @@ namespace AssetStudioCLI.Options All, } + internal enum SpriteCroppingMode + { + SpriteRect, + TextureRect, + } + internal static class CLIOptions { public static bool isParsed; @@ -132,6 +138,7 @@ namespace AssetStudioCLI.Options public static Option o_exportAssetList; public static Option o_assemblyPath; public static Option o_stripPathPrefix; + public static Option o_spriteCroppingMode; public static Option f_notRestoreExtensionName; public static Option f_avoidLoadingViaTypetree; public static Option f_rawByteArrayFromMono; @@ -573,6 +580,17 @@ namespace AssetStudioCLI.Options optionExample: "Example: \"--strip-path-prefix assets/models/char/\"\n", optionHelpGroup: HelpGroups.Advanced ); + o_spriteCroppingMode = new GroupedOption + ( + optionDefaultValue: SpriteCroppingMode.SpriteRect, + optionName: "--sprite-cropping", + optionDescription: "Specify the sprite cropping mode\n" + + "\n" + + "SpriteRect - Crop sprites keeping their own size and alignment\n" + + "TextureRect - Crop sprites to their actual image size (aka trim)\n", + optionExample: "Example: \"--sprite-cropping textureRect\"\n", + optionHelpGroup: HelpGroups.Advanced + ); f_notRestoreExtensionName = new GroupedOption ( optionDefaultValue: false, @@ -1277,6 +1295,21 @@ namespace AssetStudioCLI.Options o_stripPathPrefix.Value += Path.DirectorySeparatorChar; } break; + case "--sprite-cropping": + switch (value.ToLower()) + { + case "spriterect": + o_spriteCroppingMode.Value = SpriteCroppingMode.SpriteRect; + break; + case "texturerect": + o_spriteCroppingMode.Value = SpriteCroppingMode.TextureRect; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported sprite cropping mode: [{value.Color(brightRed)}].\n"); + ShowOptionDescription(o_spriteCroppingMode); + return; + } + break; case "--unity-version": try { @@ -1521,6 +1554,7 @@ namespace AssetStudioCLI.Options { sb.AppendLine($"# Export Image Format: {o_imageFormat}"); sb.AppendLine($"# Export Audio Format: {o_audioFormat}"); + sb.AppendLine($"# Sprite Cropping Mode: {o_spriteCroppingMode}"); sb.AppendLine($"# Restore TextAsset Extension: {!f_notRestoreExtensionName.Value}"); sb.AppendLine($"# Max Parallel Export Tasks: {o_maxParallelExportTasks}"); } diff --git a/AssetStudioCLI/ParallelExporter.cs b/AssetStudioCLI/ParallelExporter.cs index f09c95d..c8eb62f 100644 --- a/AssetStudioCLI/ParallelExporter.cs +++ b/AssetStudioCLI/ParallelExporter.cs @@ -77,7 +77,8 @@ namespace AssetStudioCLI { debugLog = ""; var type = CLIOptions.o_imageFormat.Value; - var converter = new SpriteConverter((Sprite)item.Asset); + var keepSpriteSize = CLIOptions.o_spriteCroppingMode.Value == SpriteCroppingMode.SpriteRect; + var converter = new SpriteConverter((Sprite)item.Asset, keepSpriteSize); if (CLIOptions.o_filenameFormat.Value == FilenameFormat.AssetName_Hash) item.Hash = converter.GetSpriteHash(); if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 452a294..642452b 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -54,6 +54,8 @@ namespace AssetStudioGUI #region SpriteControl private SpriteMaskMode spriteMaskVisibleMode = SpriteMaskMode.Preview; + private bool spriteRenderWithOrigSize = true; + private bool spriteRednerWithBorder; #endregion #region TexControl @@ -441,15 +443,24 @@ namespace AssetStudioGUI break; } } - else if (lastSelectedItem?.Type == ClassIDType.Sprite && new SpriteConverter((Sprite)lastSelectedItem.Asset).m_AlphaTexture2D != null) + else if (lastSelectedItem?.Asset is Sprite m_Sprite) { + var spriteConverter = new SpriteConverter(m_Sprite); switch (e.KeyCode) { - case Keys.A: + case Keys.R: + spriteRenderWithOrigSize = !spriteRenderWithOrigSize; + need = true; + break; + case Keys.B: + spriteRednerWithBorder = !spriteRednerWithBorder; + need = true; + break; + case Keys.A when spriteConverter.m_AlphaTexture2D != null: spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.Preview ? SpriteMaskMode.Off : SpriteMaskMode.Preview; need = true; break; - case Keys.M: + case Keys.M when spriteConverter.m_AlphaTexture2D != null: spriteMaskVisibleMode = spriteMaskVisibleMode == SpriteMaskMode.MaskOnly ? SpriteMaskMode.Preview : SpriteMaskMode.MaskOnly; need = true; break; @@ -1487,19 +1498,29 @@ namespace AssetStudioGUI private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite) { - var converter = new SpriteConverter(m_Sprite); + var converter = new SpriteConverter(m_Sprite, spriteRenderWithOrigSize); 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); + assetItem.InfoText = + $"Width: {bitmap.Width}\n" + + $"Height: {bitmap.Height}\n" + + $"Keep original size: {spriteRenderWithOrigSize}\n" + + $"Show border: {spriteRednerWithBorder}\n"; + + if (spriteRednerWithBorder) + PreviewTextureWithBorder(bitmap); + else + PreviewTexture(bitmap); + var statusStrip = "'Ctrl'+'R' - Switch cropping method | 'Ctrl'+'B' - Show sprite border"; 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."); + assetItem.InfoText += $"Alpha Mask: {(spriteMaskVisibleMode == SpriteMaskMode.Preview ? "On" : spriteMaskVisibleMode.ToString())}\n"; + statusStrip += "\n'Ctrl'+'A' - Enable/Disable alpha mask usage | 'Ctrl'+'M' - Show alpha mask only"; } + StatusStripUpdate(statusStrip); } else { @@ -1520,6 +1541,34 @@ namespace AssetStudioGUI previewPanel.SizeMode = PictureBoxSizeMode.CenterImage; } + private void PreviewTextureWithBorder(DirectBitmap bitmap) + { + previewPanel.Image.Dispose(); + imageTexture?.Dispose(); + imageTexture = null; + + var borderSize = 1; + var newWidth = bitmap.Width + borderSize * 2; + var newHeight = bitmap.Height + borderSize * 2; + + var newImage = new Bitmap(newWidth, newHeight); + using (var gfx = Graphics.FromImage(newImage)) + { + using (var border = new Pen(System.Drawing.Color.Black, borderSize)) + { + gfx.DrawRectangle(border, 0, 0, newWidth - borderSize, newHeight - borderSize); + } + gfx.DrawImage(bitmap.Bitmap, new Point(borderSize, borderSize)); + bitmap.Dispose(); + previewPanel.Image = newImage; + } + + if (newWidth > previewPanel.Width || newHeight > previewPanel.Height) + previewPanel.SizeMode = PictureBoxSizeMode.Zoom; + else + previewPanel.SizeMode = PictureBoxSizeMode.CenterImage; + } + private void PreviewText(string text) { textPreviewBox.Text = text; diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index efc15cb..df92417 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -84,6 +84,7 @@ this.generalTabPage = new System.Windows.Forms.TabPage(); this.panel2 = new System.Windows.Forms.Panel(); this.imagesTabPage = new System.Windows.Forms.TabPage(); + this.keepSpriteSizeCheckBox = new System.Windows.Forms.CheckBox(); this.fbxTabPage = new System.Windows.Forms.TabPage(); this.live2DTabPage = new System.Windows.Forms.TabPage(); this.Cancel = new System.Windows.Forms.Button(); @@ -222,9 +223,9 @@ this.exportSpriteWithAlphaMask.CheckState = System.Windows.Forms.CheckState.Checked; this.exportSpriteWithAlphaMask.Location = new System.Drawing.Point(7, 70); this.exportSpriteWithAlphaMask.Name = "exportSpriteWithAlphaMask"; - this.exportSpriteWithAlphaMask.Size = new System.Drawing.Size(228, 19); + this.exportSpriteWithAlphaMask.Size = new System.Drawing.Size(278, 19); this.exportSpriteWithAlphaMask.TabIndex = 2; - this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied"; + this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied (if exists)"; this.exportSpriteWithAlphaMask.UseVisualStyleBackColor = true; // // openAfterExport @@ -809,6 +810,7 @@ // imagesTabPage // this.imagesTabPage.BackColor = System.Drawing.SystemColors.Menu; + this.imagesTabPage.Controls.Add(this.keepSpriteSizeCheckBox); this.imagesTabPage.Controls.Add(this.panel1); this.imagesTabPage.Controls.Add(this.converttexture); this.imagesTabPage.Controls.Add(this.exportSpriteWithAlphaMask); @@ -818,6 +820,20 @@ this.imagesTabPage.TabIndex = 3; this.imagesTabPage.Text = "Images"; // + // keepSpriteSizeCheckBox + // + this.keepSpriteSizeCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.keepSpriteSizeCheckBox.AutoSize = true; + this.keepSpriteSizeCheckBox.Checked = true; + this.keepSpriteSizeCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.keepSpriteSizeCheckBox.Location = new System.Drawing.Point(7, 95); + this.keepSpriteSizeCheckBox.Name = "keepSpriteSizeCheckBox"; + this.keepSpriteSizeCheckBox.Size = new System.Drawing.Size(154, 19); + this.keepSpriteSizeCheckBox.TabIndex = 3; + this.keepSpriteSizeCheckBox.Text = "Keep original sprite sizes"; + this.keepSpriteSizeCheckBox.UseVisualStyleBackColor = true; + // // fbxTabPage // this.fbxTabPage.BackColor = System.Drawing.SystemColors.Menu; @@ -981,5 +997,6 @@ private System.Windows.Forms.TabPage imagesTabPage; private System.Windows.Forms.Button Cancel; private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.CheckBox keepSpriteSizeCheckBox; } } \ No newline at end of file diff --git a/AssetStudioGUI/ExportOptions.cs b/AssetStudioGUI/ExportOptions.cs index 303c26e..883e2af 100644 --- a/AssetStudioGUI/ExportOptions.cs +++ b/AssetStudioGUI/ExportOptions.cs @@ -19,6 +19,7 @@ namespace AssetStudioGUI restoreExtensionName.Checked = Properties.Settings.Default.restoreExtensionName; converttexture.Checked = Properties.Settings.Default.convertTexture; exportSpriteWithAlphaMask.Checked = Properties.Settings.Default.exportSpriteWithMask; + keepSpriteSizeCheckBox.Checked = Properties.Settings.Default.keepSpriteSize; convertAudio.Checked = Properties.Settings.Default.convertAudio; var defaultImageType = Properties.Settings.Default.convertType.ToString(); ((RadioButton)panel1.Controls.Cast().First(x => x.Text == defaultImageType)).Checked = true; @@ -48,6 +49,7 @@ namespace AssetStudioGUI Properties.Settings.Default.restoreExtensionName = restoreExtensionName.Checked; Properties.Settings.Default.convertTexture = converttexture.Checked; Properties.Settings.Default.exportSpriteWithMask = exportSpriteWithAlphaMask.Checked; + Properties.Settings.Default.keepSpriteSize = keepSpriteSizeCheckBox.Checked; Properties.Settings.Default.convertAudio = convertAudio.Checked; var checkedImageType = (RadioButton)panel1.Controls.Cast().First(x => ((RadioButton)x).Checked); Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), checkedImageType.Text); diff --git a/AssetStudioGUI/ParallelExport.cs b/AssetStudioGUI/ParallelExport.cs index c97b88b..0461622 100644 --- a/AssetStudioGUI/ParallelExport.cs +++ b/AssetStudioGUI/ParallelExport.cs @@ -76,7 +76,7 @@ namespace AssetStudioGUI { debugLog = ""; var type = Properties.Settings.Default.convertType; - var converter = new SpriteConverter((Sprite)item.Asset); + var converter = new SpriteConverter((Sprite)item.Asset, Properties.Settings.Default.keepSpriteSize); if (Properties.Settings.Default.filenameFormat == 3) //asset name@hash item.Hash = converter.GetSpriteHash(); if (!TryExportFile(exportPath, item, $".{type}", out var exportFullPath)) diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 173e4fc..1dbb31d 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -382,5 +382,17 @@ namespace AssetStudioGUI.Properties { this["cachePath"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool keepSpriteSize { + get { + return ((bool)(this["keepSpriteSize"])); + } + set { + this["keepSpriteSize"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index c632f8f..3d89255 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -92,5 +92,8 @@ + + True + \ No newline at end of file diff --git a/AssetStudioUtility/SpriteConverter.cs b/AssetStudioUtility/SpriteConverter.cs index 0c7f3ce..a229752 100644 --- a/AssetStudioUtility/SpriteConverter.cs +++ b/AssetStudioUtility/SpriteConverter.cs @@ -27,17 +27,17 @@ namespace AssetStudio private Vector2 textureRectOffset; private float downscaleMultiplier; private SpriteSettings settingsRaw; - private bool cutImageWithCanvas; + private bool keepSpriteSize; private int resultWidth; private int resultHeight; public Texture2D m_Texture2D { get; } public Texture2D m_AlphaTexture2D { get; } - public SpriteConverter(Sprite m_Sprite, bool cutWithCanvas = true) + public SpriteConverter(Sprite m_Sprite, bool keepSize = true) { sprite = m_Sprite; - cutImageWithCanvas = cutWithCanvas; + keepSpriteSize = keepSize; if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas)) { @@ -66,12 +66,12 @@ namespace AssetStudio if (textureRect == null) return; - resultWidth = cutImageWithCanvas - ? (int)sprite.m_Rect.width - : (int)textureRect.width; - resultHeight = cutImageWithCanvas - ? (int)sprite.m_Rect.height - : (int)textureRect.height; + resultWidth = keepSpriteSize + ? (int)MathF.Round(sprite.m_Rect.width) + : (int)MathF.Round(textureRect.width); + resultHeight = keepSpriteSize + ? (int)MathF.Round(sprite.m_Rect.height) + : (int)MathF.Round(textureRect.height); } public bool TryConvertToImage(out Image spriteImage, SpriteMaskMode maskMode) @@ -113,7 +113,7 @@ namespace AssetStudio if (croppedImage == null) return null; - if (!cutImageWithCanvas) + if (!keepSpriteSize) return croppedImage; using (croppedImage) diff --git a/AssetStudioUtility/SpriteExtensions.cs b/AssetStudioUtility/SpriteExtensions.cs index 67bc392..1c0f478 100644 --- a/AssetStudioUtility/SpriteExtensions.cs +++ b/AssetStudioUtility/SpriteExtensions.cs @@ -5,9 +5,9 @@ namespace AssetStudio { public static class SpriteExtensions { - public static bool TryGetImage(this Sprite m_Sprite, out Image spriteImage, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On) + public static bool TryGetImage(this Sprite m_Sprite, out Image spriteImage, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On, bool keepSpriteSize = true) { - return new SpriteConverter(m_Sprite).TryConvertToImage(out spriteImage, spriteMaskMode); + return new SpriteConverter(m_Sprite, keepSpriteSize).TryConvertToImage(out spriteImage, spriteMaskMode); } } }