diff --git a/MaiChartManager.CLI/Commands/MakeAbCommand.cs b/MaiChartManager.CLI/Commands/MakeAbCommand.cs index 1a156475..e44414d3 100644 --- a/MaiChartManager.CLI/Commands/MakeAbCommand.cs +++ b/MaiChartManager.CLI/Commands/MakeAbCommand.cs @@ -92,7 +92,8 @@ await Task.Run(() => Path.Combine(jacketDir, $"ui_jacket_{id}.ab"), $"UI_Jacket_{id}", $"assets/assetbundle/jacket/ui_jacket_{id}.png", - $"jacket/ui_jacket_{id}.ab"); + $"jacket/ui_jacket_{id}.ab", + maxDimension: AssetBundleCreator.MaxJacketDimension); AssetBundleCreator.CreateTextureAssetBundle( path!, diff --git a/MaiChartManager/Controllers/Music/MusicTransferController.cs b/MaiChartManager/Controllers/Music/MusicTransferController.cs index bd57fab4..b755da7c 100644 --- a/MaiChartManager/Controllers/Music/MusicTransferController.cs +++ b/MaiChartManager/Controllers/Music/MusicTransferController.cs @@ -526,7 +526,8 @@ private void MoveJacketSoundVideo(MusicXmlWithABJacket music, int newId, string abJacketTarget, $"UI_Jacket_{idPad}", $"assets/assetbundle/jacket/ui_jacket_{idPad}.png", - $"jacket/ui_jacket_{idPad}.ab"); + $"jacket/ui_jacket_{idPad}.ab", + maxDimension: AssetBundleCreator.MaxJacketDimension); // 对小jacket:如果存在,重打包;如果不存在,则从png重新缩放,重新CreateTextureAssetBundle if (oldSmallAb is not null && System.IO.File.Exists(oldSmallAb)) diff --git a/MaiChartManager/Controllers/Tools/ImageToAbToolController.cs b/MaiChartManager/Controllers/Tools/ImageToAbToolController.cs index 0fa5eade..2d1fa90b 100644 --- a/MaiChartManager/Controllers/Tools/ImageToAbToolController.cs +++ b/MaiChartManager/Controllers/Tools/ImageToAbToolController.cs @@ -108,7 +108,8 @@ await WriteEvent( fullAbPath, $"UI_Jacket_{id}", $"assets/assetbundle/jacket/ui_jacket_{id}.png", - $"jacket/ui_jacket_{id}.ab"); + $"jacket/ui_jacket_{id}.ab", + maxDimension: AssetBundleCreator.MaxJacketDimension); var smallAbPath = Path.Combine(jacketSmallDir, $"ui_jacket_{id}_s.ab"); AssetBundleCreator.CreateTextureAssetBundle( diff --git a/MaiChartManager/Utils/AssetBundleCreator.cs b/MaiChartManager/Utils/AssetBundleCreator.cs index b36bbd8d..57c46366 100644 --- a/MaiChartManager/Utils/AssetBundleCreator.cs +++ b/MaiChartManager/Utils/AssetBundleCreator.cs @@ -12,6 +12,14 @@ public static class AssetBundleCreator { private const int TextureFormatRgba32 = 4; + /// + /// 大封面(jacket)允许的最大边长。游戏内官方封面是 400×400 的 BC7, + /// 显示区域也用不到更大尺寸;而自制封面如果按原图尺寸写进 AB, + /// 3000×3000 的 RGBA32 一张就是 34 MB 显存、18 MB 文件, + /// 选歌/结算时同步加载会造成明显掉帧,所以在打包时钳制到这个尺寸。 + /// + public const int MaxJacketDimension = 400; + private static string TemplateAbPath => Path.Combine(StaticSettings.StreamingAssets, "A000", "AssetBundleImages", "jacket", "ui_jacket_000000.ab"); @@ -22,13 +30,24 @@ public static void CreateTextureAssetBundle( string containerPath, string abName, int? resizeWidth = null, - int? resizeHeight = null) + int? resizeHeight = null, + int? maxDimension = null) { if (!File.Exists(imagePath)) throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath); - CreateTextureAssetBundle(File.ReadAllBytes(imagePath), outputAbPath, assetName, containerPath, abName, resizeWidth, resizeHeight); + CreateTextureAssetBundle(File.ReadAllBytes(imagePath), outputAbPath, assetName, containerPath, abName, resizeWidth, resizeHeight, + maxDimension); } + /// + /// 把图片写成 Texture2D 的 AssetBundle。 + /// + /// 显式目标宽度,需与 成对给出;给出时会强制拉伸到该尺寸(可能改变宽高比) + /// 显式目标高度 + /// + /// 长边上限,超出时等比缩小(只缩不放、不裁剪)。 + /// 无论是否给了显式尺寸都会生效:显式尺寸先应用,之后仍按上限钳制。 + /// public static void CreateTextureAssetBundle( ReadOnlySpan pngImageData, string outputAbPath, @@ -36,11 +55,29 @@ public static void CreateTextureAssetBundle( string containerPath, string abName, int? resizeWidth = null, - int? resizeHeight = null) + int? resizeHeight = null, + int? maxDimension = null) { using var image = SixLabors.ImageSharp.Image.Load(pngImageData); - if (resizeWidth.HasValue && resizeHeight.HasValue) - image.Mutate(x => x.Resize(resizeWidth.Value, resizeHeight.Value)); + + if (resizeWidth is > 0 && resizeHeight is > 0) + // 显式尺寸按拉伸处理,和原来的 x.Resize(w, h) 一致;ResizeOptions 默认 Mode 是 Crop,必须显式写 Stretch + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(resizeWidth.Value, resizeHeight.Value), + Mode = ResizeMode.Stretch, + Sampler = KnownResamplers.Bicubic, + })); + + // 上限独立判断,不是 else if:任何调用组合都不该写出超过上限的纹理 + if (maxDimension is > 0 && (image.Width > maxDimension.Value || image.Height > maxDimension.Value)) + // 等比缩到长边不超过上限,只缩不放,避免改变原本的宽高比 + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(maxDimension.Value, maxDimension.Value), + Mode = ResizeMode.Max, + Sampler = KnownResamplers.Lanczos3, + })); image.Mutate(x => x.Flip(FlipMode.Vertical)); @@ -170,7 +207,8 @@ public static string CreateMusicJacketAssetBundles(ReadOnlySpan pngImageDa Path.Combine(abiDir, $"{key.ToLowerInvariant()}.ab"), key, $"assets/assetbundle/jacket/{key.ToLowerInvariant()}.png", - $"jacket/{key.ToLowerInvariant()}.ab"); + $"jacket/{key.ToLowerInvariant()}.ab", + maxDimension: MaxJacketDimension); CreateTextureAssetBundle( pngImageData, @@ -193,7 +231,8 @@ public static void RepackTextureAssetBundle( string outputAbPath, string assetName, string containerPath, - string abName) + string abName, + int? maxDimension = null) { if (!File.Exists(inputAbPath)) throw new FileNotFoundException($"Input AB not found: {inputAbPath}", inputAbPath); @@ -201,7 +240,7 @@ public static void RepackTextureAssetBundle( var pngBytes = ImageConvert.GetTextureAsPngData(inputAbPath); if (pngBytes is null || pngBytes.Length == 0) throw new InvalidOperationException($"Could not decode jacket texture to PNG from: {inputAbPath}"); - CreateTextureAssetBundle(pngBytes, outputAbPath, assetName, containerPath, abName); + CreateTextureAssetBundle(pngBytes, outputAbPath, assetName, containerPath, abName, maxDimension: maxDimension); } private static void WriteUncompressedBundle(byte[] assetsData, string cabName, Stream output)