From 824ea431debac0af3be927be45c58799df148073 Mon Sep 17 00:00:00 2001 From: Frederica Bernkastel Date: Fri, 29 Dec 2023 22:55:57 +0600 Subject: [PATCH 1/2] KG encoder --- ArcFormats/Interheart/ImageKG.cs | 86 ++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/ArcFormats/Interheart/ImageKG.cs b/ArcFormats/Interheart/ImageKG.cs index 43677246..65e2a5a3 100644 --- a/ArcFormats/Interheart/ImageKG.cs +++ b/ArcFormats/Interheart/ImageKG.cs @@ -26,7 +26,11 @@ using System; using System.ComponentModel.Composition; using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; using GameRes.Utility; namespace GameRes.Formats.Interheart @@ -94,9 +98,85 @@ namespace GameRes.Formats.Interheart return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); } - public override void Write (Stream file, ImageData image) - { - throw new System.NotImplementedException ("KgFormat.Write not implemented"); + public override void Write (Stream file, ImageData image) { + //var watch = System.Diagnostics.Stopwatch.StartNew(); + + var bitmap = image.Bitmap; + if (bitmap.Format != PixelFormats.Bgra32) { + bitmap = new FormatConvertedBitmap(image.Bitmap, PixelFormats.Bgra32, null, 0); + } + uint[] offset_table = new uint[image.Height]; + var kg_data = new List(); + byte[] row_data = new byte[image.Width * 4]; + for (int y = 0; y < image.Height; y++) + { + offset_table[y] = (uint)kg_data.Count(); + + Int32Rect rect = new Int32Rect(0, y, (int)image.Width, 1); + bitmap.CopyPixels(rect, row_data, (int)image.Width * 4, 0); + + row_data + .Chunk(4) + //.TakeWhile(x => x.Count() > 0) + .Aggregate(new List<(byte alpha, List> data)> { }, (acc, v) => { + void add_chunk (byte alpha, List data) => acc.Add((alpha, data: new List> { data })); + + if (!acc.Any()) { + add_chunk(v.Last(), v.ToList()); + } + else { + var (alpha, data) = acc.Last(); + if (v.Last() != data.Last().Last() || data.Count >= 256 ) { + add_chunk(v.Last(), v.ToList()); + } + else { + data.Add(v.ToList()); + } + } + return acc; + } + ) + .ForEach(chunk => { + kg_data.Add(chunk.alpha); + kg_data.Add((byte)chunk.data.Count()); + if (chunk.alpha > 0) { + var pixels = chunk.data + .SelectMany(pixel => new[] { pixel.ElementAt(2), pixel.ElementAt(1), pixel.ElementAt(0) }) + .ToList(); + kg_data.AddRange(pixels); + } + }); + } + + //watch.Stop(); + //var elapsedMs = watch.ElapsedMilliseconds; + //Console.WriteLine(elapsedMs.ToString() + "ms"); + + var bw = new BinaryWriter(file); + + bw.Write(new byte[] { 0x47, 0x43, 0x47, 0x4B }); + bw.Write((UInt16)image.Width); + bw.Write((UInt16)image.Height); + bw.Write((uint)kg_data.Count); + + { + byte[] buf = new byte[offset_table.Length * sizeof(uint)]; + Buffer.BlockCopy(offset_table, 0, buf, 0, buf.Length); + bw.Write(buf); + } + + bw.Write(kg_data.ToArray()); + } + } + + // Polyfill, remove when tagerting .NET >= 6.0 + static class Extensions { + public static IEnumerable> Chunk(this IEnumerable arr, int size) { + for (var i = 0; i < arr.Count() / size + 1; i++) { + if (i * size < arr.Count()) { + yield return arr.Skip(i * size).Take(size); + } + } } } } From 741f50619e912e5491e42712a60768ed96802a43 Mon Sep 17 00:00:00 2001 From: Frederica Bernkastel Date: Sat, 30 Dec 2023 04:01:40 +0600 Subject: [PATCH 2/2] optimize --- ArcFormats/Interheart/ImageKG.cs | 62 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/ArcFormats/Interheart/ImageKG.cs b/ArcFormats/Interheart/ImageKG.cs index 65e2a5a3..4a95ed52 100644 --- a/ArcFormats/Interheart/ImageKG.cs +++ b/ArcFormats/Interheart/ImageKG.cs @@ -41,6 +41,7 @@ namespace GameRes.Formats.Interheart public override string Tag { get { return "KG"; } } public override string Description { get { return "Interheart image format"; } } public override uint Signature { get { return 0x4B474347; } } // 'GCGK' + public override bool CanWrite { get { return true; } } public override ImageMetaData ReadMetaData (IBinaryStream stream) { @@ -99,58 +100,52 @@ namespace GameRes.Formats.Interheart } public override void Write (Stream file, ImageData image) { - //var watch = System.Diagnostics.Stopwatch.StartNew(); - - var bitmap = image.Bitmap; - if (bitmap.Format != PixelFormats.Bgra32) { - bitmap = new FormatConvertedBitmap(image.Bitmap, PixelFormats.Bgra32, null, 0); + int stride = (int)image.Width * 4; + byte[] bitmap = new byte[stride * image.Height]; + { + var image_bgra = image.Bitmap.Format != PixelFormats.Bgra32 + ? new FormatConvertedBitmap(image.Bitmap, PixelFormats.Bgra32, null, 0) + : image.Bitmap; + image_bgra.CopyPixels(bitmap, stride, 0); } uint[] offset_table = new uint[image.Height]; var kg_data = new List(); - byte[] row_data = new byte[image.Width * 4]; - for (int y = 0; y < image.Height; y++) - { + + for (int y = 0; y < image.Height; y++) { offset_table[y] = (uint)kg_data.Count(); - Int32Rect rect = new Int32Rect(0, y, (int)image.Width, 1); - bitmap.CopyPixels(rect, row_data, (int)image.Width * 4, 0); - - row_data + bitmap + .AsMemory() + .Slice(y * stride, stride) .Chunk(4) - //.TakeWhile(x => x.Count() > 0) - .Aggregate(new List<(byte alpha, List> data)> { }, (acc, v) => { - void add_chunk (byte alpha, List data) => acc.Add((alpha, data: new List> { data })); + .Aggregate(new List<(byte alpha, List data)> { }, (chunks, x) => { + var pixel = x.ToArray(); + void new_chunk () => chunks.Add((alpha: pixel[3], data: new List { pixel })); - if (!acc.Any()) { - add_chunk(v.Last(), v.ToList()); + if (!chunks.Any()) { + new_chunk(); } else { - var (alpha, data) = acc.Last(); - if (v.Last() != data.Last().Last() || data.Count >= 256 ) { - add_chunk(v.Last(), v.ToList()); + var (alpha, data) = chunks.Last(); + if (pixel[3] != data.Last()[3] || data.Count >= 256) { + new_chunk(); } else { - data.Add(v.ToList()); + data.Add(pixel); } } - return acc; + return chunks; } ) .ForEach(chunk => { kg_data.Add(chunk.alpha); kg_data.Add((byte)chunk.data.Count()); if (chunk.alpha > 0) { - var pixels = chunk.data - .SelectMany(pixel => new[] { pixel.ElementAt(2), pixel.ElementAt(1), pixel.ElementAt(0) }) - .ToList(); - kg_data.AddRange(pixels); + kg_data.AddRange(chunk.data.SelectMany(p => new[] { p[2], p[1], p[0] })); } }); } - //watch.Stop(); - //var elapsedMs = watch.ElapsedMilliseconds; - //Console.WriteLine(elapsedMs.ToString() + "ms"); var bw = new BinaryWriter(file); @@ -169,12 +164,11 @@ namespace GameRes.Formats.Interheart } } - // Polyfill, remove when tagerting .NET >= 6.0 static class Extensions { - public static IEnumerable> Chunk(this IEnumerable arr, int size) { - for (var i = 0; i < arr.Count() / size + 1; i++) { - if (i * size < arr.Count()) { - yield return arr.Skip(i * size).Take(size); + public static IEnumerable> Chunk(this Memory arr, int size) { + for (var i = 0; i < arr.Length / size + 1; i++) { + if (i * size < arr.Length) { + yield return arr.Slice(i * size, size); } } }