From 19827e5eeb7879d4c91b7ca0643485f04245daee Mon Sep 17 00:00:00 2001 From: scientificworld Date: Thu, 16 Apr 2026 22:47:43 +0800 Subject: [PATCH] feat: more image formats --- Experimental/CatSystem/ImageATX.cs | 176 +++++++++++++++++++++++++++++ Experimental/Experimental.csproj | 7 ++ Experimental/packages.config | 2 + Legacy/Legacy.csproj | 1 + Legacy/PenguinWorks/ImagePXL.cs | 4 +- Legacy/Ruri/ImageKBM.cs | 58 ++++++++++ 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 Experimental/CatSystem/ImageATX.cs create mode 100644 Legacy/Ruri/ImageKBM.cs diff --git a/Experimental/CatSystem/ImageATX.cs b/Experimental/CatSystem/ImageATX.cs new file mode 100644 index 00000000..a7adf3d6 --- /dev/null +++ b/Experimental/CatSystem/ImageATX.cs @@ -0,0 +1,176 @@ +//! \file ImageATX.cs +//! \date 2026-04-04 +//! \brief CatSystem for Unity image format. +// +// Copyright (C) 2026 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Newtonsoft.Json; + +using SharpZip = ICSharpCode.SharpZipLib.Zip; + +namespace GameRes.Formats.CatSystem { + [Serializable] + public class LayoutInfo { + public LayoutInfo.CanvasInfo Canvas; + public List Block; + + [Serializable] + public class CanvasInfo { + public uint Width; + public uint Height; + } + + [Serializable] + public class MeshInfo { + public int texNo; + public float offsetX; + public float offsetY; + public float srcOffsetX; + public float srcOffsetY; + public float texU1; + public float texV1; + public float texU2; + public float texV2; + public float viewX; + public float viewY; + public float width; + public float height; + } + + [Serializable] + public class AttributeInfo { + public int id; + public int x; + public int y; + public int width; + public int height; + public int color; + } + + [Serializable] + public class BlockInfo { + public string filename; + public string filenameOld; + public string blend; + public int id; + public float anchorX; + public float anchorY; + public float width; + public float height; + public float offsetX; + public float offsetY; + public int priority; + public List Mesh; + public List Attribute; + } + } + + internal class AtlasMetaData : ImageMetaData { + public List Blocks; + } + + [Export(typeof(ImageFormat))] + public class AtxFormat : ImageFormat { + public override string Tag { get { return "ATX"; } } + public override string Description { get { return "CatSystem for Unity image format"; } } + public override uint Signature { get { return 0; } } + + public AtxFormat() { + Extensions = new string[] { "atx" }; + } + + public override ImageMetaData ReadMetaData(IBinaryStream stream) { + if (0x04034b50 != stream.Signature) // 'PK\3\4' + return null; + + var zf = new SharpZip.ZipFile(stream.AsStream); + SharpZip.ZipEntry info_entry; + + if ((info_entry = zf.GetEntry("atlas.json")) == null) + return null; // protobuf decoding is not supported yet. + using (var sr = new StreamReader(zf.GetInputStream(info_entry))) { + var info = JsonConvert.DeserializeObject(sr.ReadToEnd()); + return new AtlasMetaData { + Height = info.Canvas.Height, + Width = info.Canvas.Width, + Blocks = info.Block, + BPP = 32 + }; + } + } + + public override ImageData Read(IBinaryStream stream, ImageMetaData info) { + var amd = (AtlasMetaData)info; + var zf = new SharpZip.ZipFile(stream.AsStream); + var textures = new Dictionary(); + var bitmap = new WriteableBitmap(amd.iWidth, amd.iHeight, + ImageData.DefaultDpiX, ImageData.DefaultDpiY, PixelFormats.Bgra32, null); + foreach (var mesh in amd.Blocks[0].Mesh) { // only process the first image + if (!textures.ContainsKey(mesh.texNo)) { + SharpZip.ZipEntry tex_entry = null; + // https://storage.googleapis.com/downloads.webmproject.org/releases/webp/WebpCodecSetup.exe + // install this for webp codec support + var availableFormats = new[] { "png", "jpg", "webp" }; + foreach (var format in availableFormats) { + if ((tex_entry = zf.GetEntry($"tex{mesh.texNo}.{format}")) != null) + break; + } + if (tex_entry == null) + return null; + using (var input = zf.GetInputStream(tex_entry)) + using (var mem = new MemoryStream()) { + input.CopyTo(mem); + textures[mesh.texNo] = mem.ToArray(); + } + } + using (var mem = new MemoryStream(textures[mesh.texNo])) { + var decoder = BitmapDecoder.Create(mem, + BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource frame = decoder.Frames[0]; + if (frame.Format.BitsPerPixel != 32) + frame = new FormatConvertedBitmap(frame, PixelFormats.Bgra32, null, 0); + int width = (int)mesh.width, height = (int)mesh.height; + var rect = new Int32Rect((int)mesh.viewX, (int)mesh.viewY, width, height); + int stride = width * 4; + var pixels = new byte[stride * height]; + frame.CopyPixels(rect, pixels, stride, 0); + rect = new Int32Rect(0, 0, width, height); + bitmap.WritePixels(rect, pixels, stride, (int)mesh.srcOffsetX, (int)mesh.srcOffsetY); + } + } + bitmap.Freeze(); + return new ImageData(bitmap, info); + } + + public override void Write(Stream file, ImageData image) { + throw new NotImplementedException("AtxFormat.Write not implemented"); + } + } +} diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index b6643c32..4b5690a3 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -64,6 +64,12 @@ ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + + ..\packages\SharpZipLib.1.3.3\lib\net45\ICSharpCode.SharpZipLib.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + @@ -150,6 +156,7 @@ + diff --git a/Experimental/packages.config b/Experimental/packages.config index a6bfb595..2a604b3b 100644 --- a/Experimental/packages.config +++ b/Experimental/packages.config @@ -6,6 +6,8 @@ + + diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 07c82c51..e360bb32 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -206,6 +206,7 @@ + diff --git a/Legacy/PenguinWorks/ImagePXL.cs b/Legacy/PenguinWorks/ImagePXL.cs index 0755db6f..675cb574 100644 --- a/Legacy/PenguinWorks/ImagePXL.cs +++ b/Legacy/PenguinWorks/ImagePXL.cs @@ -37,6 +37,8 @@ namespace GameRes.Formats.PenguinWorks { public override ImageMetaData ReadMetaData(IBinaryStream file) { uint width = file.ReadUInt32(); uint height = file.ReadUInt32(); + if (file.Length != 8 + 3 * width * height) + return null; return new ImageMetaData { Width = width, Height = height, @@ -53,7 +55,7 @@ namespace GameRes.Formats.PenguinWorks { } public override void Write(Stream file, ImageData image) { - throw new System.NotImplementedException ("PxlFormat.Write not implemented"); + throw new System.NotImplementedException("PxlFormat.Write not implemented"); } } } diff --git a/Legacy/Ruri/ImageKBM.cs b/Legacy/Ruri/ImageKBM.cs new file mode 100644 index 00000000..a99841de --- /dev/null +++ b/Legacy/Ruri/ImageKBM.cs @@ -0,0 +1,58 @@ +//! \file ImageKBM.cs +//! \date 2026-04-16 +//! \brief Ruri System image format. +// +// Copyright (C) 2026 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Ruri { + [Export(typeof(ImageFormat))] + public class KbmFormat : ImageFormat { + public override string Tag { get { return "KBM"; } } + public override string Description { get { return "Ruri System image format"; } } + public override uint Signature { get { return 0; } } + public override bool CanWrite { get { return false; } } + + public KbmFormat() { + Extensions = new string[] { "Kbm" }; + } + + public override ImageMetaData ReadMetaData(IBinaryStream stream) { + if ((stream.Signature & 0xFFFFFF) != 0x4D424B) + return null; + stream = BinaryStream.FromStream(new StreamRegion(stream.AsStream, 0x100), stream.Name); + return Bmp.ReadMetaData(stream); + } + + public override ImageData Read(IBinaryStream stream, ImageMetaData info) { + stream = BinaryStream.FromStream(new StreamRegion(stream.AsStream, 0x100), stream.Name); + return Bmp.Read(stream, info); + } + + public override void Write(Stream file, ImageData image) { + throw new NotImplementedException("KbmFormat.Write not implemented"); + } + } +}