From 53d739365d933792dfaa7e91b7a6c5b577c51187 Mon Sep 17 00:00:00 2001 From: Ulysses Wu Date: Tue, 5 Apr 2022 00:08:16 +0800 Subject: [PATCH] Handle "resolution" between win and krkr conversion, Fix #75 --- .editorconfig | 53 ++++++++++++++- .../Converters/Common2KrkrConverter.cs | 39 +++++++++++ .../Converters/Krkr2CommonConverter.cs | 67 +++++++++++++++++-- FreeMote.PsBuild/PsbSpecConverter.cs | 14 ++-- FreeMote.Psb/Textures/TextureSpliter.cs | 13 ++++ FreeMote.Tools.PsBuild/Program.cs | 13 ++-- FreeMote.sln.DotSettings | 1 + FreeMote/BitmapExtension.cs | 56 ++++++++++++++++ 8 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 FreeMote/BitmapExtension.cs diff --git a/.editorconfig b/.editorconfig index 2cbf89f..66369db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,53 @@ -[*.cs] +[*.cs] # CS1591: 缺少对公共可见类型或成员的 XML 注释 -dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.cs1591.severity = none + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion +csharp_space_after_cast = true +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_csharp_max_line_length = 140 +resharper_space_within_single_line_array_initializer_braces = false + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint + +[*.{appxmanifest,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,csproj,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,jsproj,lsproj,mpp,mq4,mq5,mqh,njsproj,nuspec,proj,props,proto,resw,resx,StyleCop,targets,tasks,tpp,usf,ush,vbproj,xml,xsd}] +indent_style = tab +indent_size = tab +tab_width = 4 + +[*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*.{json,resjson}] +indent_style = space +indent_size = 2 +tab_width = 2 diff --git a/FreeMote.PsBuild/Converters/Common2KrkrConverter.cs b/FreeMote.PsBuild/Converters/Common2KrkrConverter.cs index cc6847f..0b3ac28 100644 --- a/FreeMote.PsBuild/Converters/Common2KrkrConverter.cs +++ b/FreeMote.PsBuild/Converters/Common2KrkrConverter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using FreeMote.Psb; using FreeMote.Psb.Textures; +// ReSharper disable CompareOfFloatsByEqualityOperator namespace FreeMote.PsBuild.Converters { @@ -81,6 +82,44 @@ private Dictionary> TranslateResources(PSB psb) { iconList.Add(iconPair.Key); var icon = (PsbDictionary) iconPair.Value; + //handle resolution from win + if (icon.ContainsKey("resolution") && icon["resolution"].GetFloat() != 1.0f) + { + //Converting from win to krkr. Win has scaled image, but krkr wants a full size one. + //Scale it up will cause bad quality image most likely. But there seems no other choice. + var resolution = icon["resolution"].GetFloat(); + var bmp = bmps[iconPair.Key]; + var resizedBmp = bmp.ResizeImage(icon["width"].GetInt(), icon["height"].GetInt()); + bmps[iconPair.Key] = resizedBmp; + bmp.Dispose(); + + //Attempt to remove resolution, won't work + //icon.Remove("resolution"); //you won't be able to convert it back + //if (icon.ContainsKey("width")) + //{ + // icon["width"] = new PsbNumber(Math.Ceiling(icon["width"].GetFloat() * resolution)); + //} + //if (icon.ContainsKey("height")) + //{ + // icon["height"] = new PsbNumber(Math.Ceiling(icon["height"].GetFloat() * resolution)); + //} + + //if (icon.ContainsKey("originX")) + //{ + // icon["originX"] = new PsbNumber(Math.Floor(icon["originX"].GetFloat() * resolution)); + //} + + //if (icon.ContainsKey("originY")) + //{ + // icon["originY"] = new PsbNumber(Math.Floor(icon["originY"].GetFloat() * resolution)); + //} + } + else if (icon.ContainsKey("resolution_hint")) //recover resolution + { + icon["resolution"] = icon["resolution_hint"]; + icon.Remove("resolution_hint"); + } + var data = UseRL ? RL.CompressImage(bmps[iconPair.Key], TargetPixelFormat) : RL.GetPixelBytesFromImage(bmps[iconPair.Key], TargetPixelFormat); diff --git a/FreeMote.PsBuild/Converters/Krkr2CommonConverter.cs b/FreeMote.PsBuild/Converters/Krkr2CommonConverter.cs index d1b46c0..47e6dc2 100644 --- a/FreeMote.PsBuild/Converters/Krkr2CommonConverter.cs +++ b/FreeMote.PsBuild/Converters/Krkr2CommonConverter.cs @@ -3,8 +3,10 @@ using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Text.RegularExpressions; using FreeMote.Psb; using FreeMote.Psb.Textures; +// ReSharper disable CompareOfFloatsByEqualityOperator namespace FreeMote.PsBuild.Converters { @@ -33,8 +35,16 @@ public Krkr2CommonConverter(bool toWin = false) public int TexturePadding { get; set; } = 5; public BestFitHeuristic FitHeuristic { get; set; } = BestFitHeuristic.MaxOneAxis; + /// + /// Use name gained from krkr PSB like "vr足l" + /// public bool UseMeaningfulName { get; set; } = true; /// + /// If enable, scale down the image to match the target resolution, maybe causing bad quality. + /// If not enable, ignore and remove "resolution" in icon (reset resolution to 1, making the image clear) + /// + public bool EnableResolution { get; set; } = false; + /// /// Expand texture edge /// public TextureEdgeProcess EdgeProcess { get; set; } = TextureEdgeProcess.Expand1Px; @@ -150,6 +160,7 @@ void LayerTravel(PsbList collection, List indexList) var source = (PsbDictionary) psb.Objects["source"]; int maxSideLength = 2048; long area = 0; + var texRegex = new Regex($"^tex#.+?{Delimiter}"); //Collect textures foreach (var tex in source) @@ -159,6 +170,11 @@ void LayerTravel(PsbList collection, List indexList) foreach (var icon in icons) { var iconName = icon.Key; + var match = texRegex.Match(iconName); + if (match.Success) + { + iconName = iconName.Substring(match.Length); + } var info = (PsbDictionary) icon.Value; var width = (int) (PsbNumber) info["width"]; var height = (int) (PsbNumber) info["height"]; @@ -171,8 +187,20 @@ void LayerTravel(PsbList collection, List indexList) var bmp = info["compress"]?.ToString().ToUpperInvariant() == "RL" ? RL.DecompressToImage(res.Data, height, width, psb.Platform.DefaultPixelFormat()) : RL.ConvertToImage(res.Data, height, width, psb.Platform.DefaultPixelFormat()); + if (info.ContainsKey("resolution") && info["resolution"].GetFloat() != 1.0f && EnableResolution) + { + //scale down image, not recommended + var resolution = info["resolution"].GetFloat(); + var newWidth = (int) Math.Ceiling(width * resolution); + var newHeight = (int) Math.Ceiling(height * resolution); + var resizedBmp = bmp.ResizeImage(newWidth, newHeight); + bmp.Dispose(); + bmp = resizedBmp; + width = newWidth; + height = newHeight; + } bmp.Tag = iconName; - textures.Add($"{texName}{Delimiter}{icon.Key}", bmp); + textures.Add($"{texName}{Delimiter}{iconName}", bmp); //estimate area and side length area += width * height; if (width >= maxSideLength || height >= maxSideLength) @@ -189,7 +217,7 @@ void LayerTravel(PsbList collection, List indexList) size = 4096; } - int padding = TexturePadding >= 0 && TexturePadding <= 100 ? TexturePadding : 1; + int padding = TexturePadding is >= 0 and <= 100 ? TexturePadding : 1; TexturePacker packer = new TexturePacker { @@ -222,15 +250,44 @@ void LayerTravel(PsbList collection, List indexList) continue; } - var paths = node.Texture.Source.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries); - var icon = (PsbDictionary) source[paths[0]].Children("icon").Children(paths[1]); + var delimiterPos = node.Texture.Source.IndexOf(Delimiter, StringComparison.Ordinal); + if (delimiterPos < 0) + { + throw new FormatException($"cannot parse icon path: {node.Texture.Source}"); + } + var texPath = node.Texture.Source.Substring(0, delimiterPos); + var iconPath = node.Texture.Source.Substring(delimiterPos + 1); + //var paths = node.Texture.Source.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries); + var icon = (PsbDictionary) source[texPath].Children("icon").Children(iconPath); icon.Remove("compress"); icon.Remove(Consts.ResourceKey); icon["attr"] = PsbNumber.Zero; icon["left"] = new PsbNumber(node.Bounds.Left); icon["top"] = new PsbNumber(node.Bounds.Top); + if (icon.ContainsKey("resolution") && icon["resolution"].GetFloat() != 1.0f && !EnableResolution) + { + //Converting from krkr to win. Krkr has the full size image. We just keep resolution = 1. + //Maybe implement scale down later. + var resolution = icon["resolution"].GetFloat(); + icon["resolution_hint"] = new PsbNumber(resolution); //leave a hint here + icon.Remove("resolution"); + } icon.Parent = icons; - var iconName = UseMeaningfulName ? node.Texture.Source : id.ToString(); + var iconName = id.ToString(); + if (UseMeaningfulName) + { + var meaningfulName = node.Texture.Source; + var match = texRegex.Match(meaningfulName); + if (match.Success) + { + meaningfulName = meaningfulName.Substring(match.Length); + } + if (!string.IsNullOrWhiteSpace(meaningfulName) && !icons.ContainsKey(meaningfulName)) + { + iconName = meaningfulName; + } + } + //var iconName = UseMeaningfulName ? node.Texture.Source : id.ToString(); icons.Add(iconName, icon); iconInfos.Add(node.Texture.Source, (texName, iconName)); id++; diff --git a/FreeMote.PsBuild/PsbSpecConverter.cs b/FreeMote.PsBuild/PsbSpecConverter.cs index ea6f25e..2eab66e 100644 --- a/FreeMote.PsBuild/PsbSpecConverter.cs +++ b/FreeMote.PsBuild/PsbSpecConverter.cs @@ -1,11 +1,15 @@ using FreeMote.Psb; -using FreeMote.Psb.Textures; using FreeMote.PsBuild.Converters; namespace FreeMote.PsBuild { public static class PsbSpecConverter { + /// + /// Enable resolution support (may scaling images, quality is not guaranteed) + /// + public static bool EnableResolution = false; + /// /// Try to switch Spec /// @@ -15,7 +19,7 @@ public static class PsbSpecConverter public static void SwitchSpec(this PSB psb, PsbSpec targetSpec, PsbPixelFormat pixelFormat = PsbPixelFormat.None) { - if (targetSpec == PsbSpec.other || targetSpec == PsbSpec.none) + if (targetSpec is PsbSpec.other or PsbSpec.none) { return; } @@ -66,7 +70,7 @@ public static void SwitchSpec(this PSB psb, PsbSpec targetSpec, switch (original) { case PsbSpec.krkr: - Krkr2CommonConverter krkr2Win = new Krkr2CommonConverter(true); + Krkr2CommonConverter krkr2Win = new Krkr2CommonConverter(true) {EnableResolution = EnableResolution}; krkr2Win.Convert(psb); break; case PsbSpec.common: @@ -79,12 +83,12 @@ public static void SwitchSpec(this PSB psb, PsbSpec targetSpec, } } - else if (targetSpec == PsbSpec.common || targetSpec == PsbSpec.ems) + else if (targetSpec is PsbSpec.common or PsbSpec.ems) { switch (original) { case PsbSpec.krkr: - Krkr2CommonConverter krkr2Common = new Krkr2CommonConverter(); + Krkr2CommonConverter krkr2Common = new Krkr2CommonConverter() {EnableResolution = EnableResolution}; krkr2Common.Convert(psb); break; case PsbSpec.win: diff --git a/FreeMote.Psb/Textures/TextureSpliter.cs b/FreeMote.Psb/Textures/TextureSpliter.cs index 1196f64..ca3929b 100644 --- a/FreeMote.Psb/Textures/TextureSpliter.cs +++ b/FreeMote.Psb/Textures/TextureSpliter.cs @@ -1,4 +1,5 @@ #define USE_FASTBITMAP +using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; @@ -6,6 +7,8 @@ #if USE_FASTBITMAP using FastBitmapLib; +// ReSharper disable CompareOfFloatsByEqualityOperator + #else using System.Drawing.Drawing2D; #endif @@ -76,6 +79,16 @@ public static Dictionary SplitTexture(PsbDictionary tex, PsbSpec var height = (int) (PsbNumber) info["height"]; var top = (int) (PsbNumber) info["top"]; var left = (int) (PsbNumber) info["left"]; + if (info.ContainsKey("resolution")) // the actual width and height need * resolution + { + var resolution = (float) (PsbNumber) info["resolution"]; + if (resolution != 1.0f) + { + width = (int) Math.Ceiling(width * resolution); + height = (int) Math.Ceiling(height * resolution); + } + } + Bitmap b = new Bitmap(width, height, PixelFormat.Format32bppArgb); #if USE_FASTBITMAP using (FastBitmap f = b.FastLock()) diff --git a/FreeMote.Tools.PsBuild/Program.cs b/FreeMote.Tools.PsBuild/Program.cs index ed4132f..f616d25 100644 --- a/FreeMote.Tools.PsBuild/Program.cs +++ b/FreeMote.Tools.PsBuild/Program.cs @@ -108,6 +108,8 @@ PsBuild port -p win sample.psb var optPortSpec = portCmd.Option("-p|--spec ", "Target PSB platform (krkr/common/win/ems)", CommandOptionType.SingleValue).IsRequired(); + var optEnableResolution = portCmd.Option("-r|--resolution", + "Enable resolution support (may scaling images, quality is not guaranteed)", CommandOptionType.NoValue); //args var argPsbPath = portCmd.Argument("PSB", "PSB Path", multipleValues: true).IsRequired(); @@ -115,11 +117,12 @@ PsBuild port -p win sample.psb { var portSpec = optPortSpec.ParsedValue; var psbPaths = argPsbPath.Values; + var enableResolution = optEnableResolution.HasValue(); foreach (var s in psbPaths) { if (File.Exists(s)) { - Port(s, portSpec); + Port(s, portSpec, enableResolution); } } }); @@ -255,7 +258,7 @@ PsBuild replace sample.psb sample.json Console.WriteLine("Done."); } - private static void Port(string s, PsbSpec portSpec) + private static void Port(string s, PsbSpec portSpec, bool resolution = false) { var name = Path.GetFileNameWithoutExtension(s); var ext = Path.GetExtension(s); @@ -267,10 +270,12 @@ private static void Port(string s, PsbSpec portSpec) } else { + PsbSpecConverter.EnableResolution = resolution; psb.SwitchSpec(portSpec); psb.Merge(); - File.WriteAllBytes(Path.ChangeExtension(s, $".{portSpec}{psb.Type.DefaultExtension()}"), psb.Build()); - Console.WriteLine($"Convert {name} done."); + var savePath = Path.ChangeExtension(s, $".{portSpec}{psb.Type.DefaultExtension()}"); + File.WriteAllBytes(savePath, psb.Build()); + Console.WriteLine($"Convert output: {savePath}"); } } diff --git a/FreeMote.sln.DotSettings b/FreeMote.sln.DotSettings index 5431a8d..c939f48 100644 --- a/FreeMote.sln.DotSettings +++ b/FreeMote.sln.DotSettings @@ -14,6 +14,7 @@ True True True + True True True True diff --git a/FreeMote/BitmapExtension.cs b/FreeMote/BitmapExtension.cs new file mode 100644 index 0000000..9440ec5 --- /dev/null +++ b/FreeMote/BitmapExtension.cs @@ -0,0 +1,56 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace FreeMote +{ + public static class BitmapExtension + { + /// + /// Copies a region of the source bitmap into this fast bitmap + /// + /// The image which applies to + /// The source image to copy + /// The region on the source bitmap that will be copied over + /// The region on this fast bitmap that will be changed + /// The provided source bitmap is the same bitmap locked in this FastBitmap + public static void CopyRegion(this Bitmap target, Bitmap source, Rectangle srcRect, Rectangle destRect) + { + //TODO: + } + + /// + /// Resize the image to the specified width and height. + /// + /// The image to resize. + /// The width to resize to. + /// The height to resize to. + /// The resized image. + /// https://stackoverflow.com/a/24199315/4374462 + public static Bitmap ResizeImage(this Image image, int width, int height) + { + var destRect = new Rectangle(0, 0, width, height); + var destImage = new Bitmap(width, height); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (var graphics = Graphics.FromImage(destImage)) + { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; // = SmoothingMode.AntiAlias + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + using (var wrapMode = new ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); + } + } + + return destImage; + } + } +}