diff --git a/FreeMote.Psb/SprPainter.cs b/FreeMote.Psb/SprPainter.cs index c06cdeb..633d160 100644 --- a/FreeMote.Psb/SprPainter.cs +++ b/FreeMote.Psb/SprPainter.cs @@ -4,6 +4,7 @@ using System.IO; using FreeMote.Psb.Types; using FastBitmapLib; +using System.Drawing.Imaging; namespace FreeMote.Psb { @@ -21,11 +22,15 @@ public class SprPainter private readonly Dictionary _textures = new Dictionary(); //palette: each single line is a palette + public Bitmap Palette { get; set; } - public SprPainter(PSB sprData, string basePath) + public string PaletteName { get; set; } + + public SprPainter(PSB sprData, string basePath, string paletteName = "1ppalette") { SprData = sprData; BasePath = basePath; + PaletteName = paletteName; } private void Collect() @@ -34,6 +39,7 @@ private void Collect() { //read images from BasePath\__ddd.psb.m\*.png var dirs = Directory.GetDirectories(BasePath, "*.psb.m", SearchOption.TopDirectoryOnly); + string prefix = string.Empty; foreach (var dir in dirs) { var png = Path.Combine(dir, "0.png"); @@ -42,9 +48,14 @@ private void Collect() var dirName = Path.GetFileName(dir); try { - if(int.TryParse(dirName.Substring(dirName.IndexOf('.') - IdWidth, IdWidth), out int number)) + if (int.TryParse(dirName.Substring(dirName.IndexOf('.') - IdWidth, IdWidth), out int number)) { - _textures.Add(number, new Bitmap(png)); + if (string.IsNullOrEmpty(prefix)) + { + prefix = dirName.Substring(0, dirName.IndexOf('.') - IdWidth); + } + + _textures.Add(number, BitmapHelper.LoadBitmap(File.ReadAllBytes(png))); } } catch (Exception ex) @@ -53,6 +64,25 @@ private void Collect() } } } + + var paletteDir = Path.Combine(BasePath, PaletteName + ".psb.m"); + if (Directory.Exists(paletteDir)) + { + Palette = new Bitmap(Path.Combine(paletteDir, "0.png")); + } + else if (!string.IsNullOrEmpty(prefix)) + { + paletteDir = Path.Combine(BasePath, prefix + PaletteName + ".psb.m"); + if (Directory.Exists(paletteDir) && File.Exists(Path.Combine(paletteDir, "0.png"))) + { + Palette = new Bitmap(Path.Combine(paletteDir, "0.png")); + } + } + else + { + Logger.LogError($"Cannot find palette {PaletteName} in {BasePath}. Will not apply palette."); + Palette = null; + } } else { @@ -100,7 +130,22 @@ public Dictionary Draw() var org = sprImage.Children("org") as PsbList; var width = org[0].GetInt(); var height = org[1].GetInt(); - var bitmap = new Bitmap(width, height); + Bitmap bitmap; + try + { + bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + var palette = sprImage.Children("palette").GetInt(); + ColorPalette pal = bitmap.Palette; + for (int c = 0; c < 256; c++) + pal.Entries[c] = Palette == null? Color.FromArgb(c, c, c, 0xFF) : Palette.GetPixel(c, palette); + bitmap.Palette = pal; + } + catch (Exception ex) + { + Logger.LogError($"Error creating palette indexed bitmap: {ex.Message}"); + return result; + } + var tiles = new List(); PsbList data = sprImage.Children("data") as PsbList; foreach (var tileData in data) @@ -116,40 +161,76 @@ public Dictionary Draw() tiles.Add(tile); } - using (var f = bitmap.FastLock()) + foreach (var tile in tiles) { - foreach (var tile in tiles) + var texId = tile.TexId; + if (!_textures.ContainsKey(texId)) { - var texId = tile.TexId; - if (!_textures.ContainsKey(texId)) - { - Logger.LogError($"Tex missing: {texId} required by: {tile}"); - continue; - } - var tex = _textures[texId]; - - var texWidth = tex.Width; - var texHeight = tex.Height; //useless, the ID is only related to width - var tilePerLine = texWidth / TileWidth; - - // 0 1 2 3 ... 31 - // ... - // 31*32 31*32+1 31*32+2 ... 31*32+31 - //calculate source rect from tile.Id - var sourceRect = new Rectangle( - (tile.Id % tilePerLine) * TileWidth, - (tile.Id / tilePerLine) * TileHeight, - TileWidth, - TileHeight - ); - var tileX = tile.X; - var tileY = tile.Y; - - f.CopyRegion(tex, sourceRect, new Rectangle(tileX * TileWidth, tileY * TileHeight, TileWidth, TileHeight)); + Logger.LogError($"Tex missing: {texId} required by: {tile}"); + continue; } - - result.Add(i.ToString(), bitmap); + var tex = _textures[texId]; + tex.Palette = bitmap.Palette; + + var texWidth = tex.Width; + var texHeight = tex.Height; //useless, the ID is only related to width + var tilePerLine = texWidth / TileWidth; + + // 0 1 2 3 ... 31 + // ... + // 31*32 31*32+1 31*32+2 ... 31*32+31 + //calculate source rect from tile.Id + var sourceRect = new Rectangle( + (tile.Id % tilePerLine) * TileWidth, + (tile.Id / tilePerLine) * TileHeight, + TileWidth, + TileHeight + ); + var tileX = tile.X; + var tileY = tile.Y; + + bitmap.CopyRegion(tex, sourceRect, new Rectangle(tileX * TileWidth, tileY * TileHeight, TileWidth, TileHeight)); } + result.Add(i.ToString(), bitmap); + + + // RGBA8 + // { + // using (var f = bitmap.FastLock()) + // { + // foreach (var tile in tiles) + // { + // var texId = tile.TexId; + // if (!_textures.ContainsKey(texId)) + // { + // Logger.LogError($"Tex missing: {texId} required by: {tile}"); + // continue; + // } + // var tex = _textures[texId]; + + // var texWidth = tex.Width; + // var texHeight = tex.Height; //useless, the ID is only related to width + // var tilePerLine = texWidth / TileWidth; + + // // 0 1 2 3 ... 31 + // // ... + // // 31*32 31*32+1 31*32+2 ... 31*32+31 + // //calculate source rect from tile.Id + // var sourceRect = new Rectangle( + // (tile.Id % tilePerLine) * TileWidth, + // (tile.Id / tilePerLine) * TileHeight, + // TileWidth, + // TileHeight + // ); + // var tileX = tile.X; + // var tileY = tile.Y; + + // f.CopyRegion(tex, sourceRect, new Rectangle(tileX * TileWidth, tileY * TileHeight, TileWidth, TileHeight)); + // } + // } + // result.Add(i.ToString(), bitmap); + // } + } return result; diff --git a/FreeMote.Psb/Types/M2Types.cs b/FreeMote.Psb/Types/M2Types.cs index cfe838b..ae8b7f9 100644 --- a/FreeMote.Psb/Types/M2Types.cs +++ b/FreeMote.Psb/Types/M2Types.cs @@ -59,10 +59,20 @@ ImageMetadata GenerateMetadata(string name, PsbDictionary dic, PsbResource res) var dataLen = res.Data.Length; var depth = dataLen / (width * height); PsbPixelFormat format = PsbPixelFormat.A8; + byte[] palette = null; switch (depth) { case 1: - format = PsbPixelFormat.A8; + format = PsbPixelFormat.CI8; + palette = new byte[256 * 4]; + // fill with 256 degrees of gray + for (int i = 0; i < 256; i++) + { + palette[i * 4] = (byte)i; + palette[i * 4 + 1] = (byte)i; + palette[i * 4 + 2] = (byte)i; + palette[i * 4 + 3] = 0xFF; + } break; case 4: format = PsbPixelFormat.LeRGBA8; @@ -80,7 +90,8 @@ ImageMetadata GenerateMetadata(string name, PsbDictionary dic, PsbResource res) Width = width, Height = height, Spec = PsbSpec.none, - TypeString = format.ToStringForPsb().ToPsbString() + TypeString = format.ToStringForPsb().ToPsbString(), + Palette = new PsbResource(){Data = palette}, }; return md; diff --git a/FreeMote/BitmapExtension.cs b/FreeMote/BitmapExtension.cs index 9440ec5..70e50eb 100644 --- a/FreeMote/BitmapExtension.cs +++ b/FreeMote/BitmapExtension.cs @@ -17,7 +17,43 @@ public static class BitmapExtension /// 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: + if (srcRect.Width != destRect.Width || srcRect.Height != destRect.Height) + { + throw new ArgumentException("Source and destination rectangles must have the same dimensions."); + } + + var srcData = source.LockBits(srcRect, ImageLockMode.ReadOnly, source.PixelFormat); + var destData = target.LockBits(destRect, ImageLockMode.WriteOnly, target.PixelFormat); + + try + { + unsafe + { + byte* srcPtr = (byte*)srcData.Scan0; + byte* destPtr = (byte*)destData.Scan0; + + int bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) / 8; + int stride = srcData.Stride; + int height = srcRect.Height; + int widthInBytes = srcRect.Width * bytesPerPixel; + + for (int y = 0; y < height; y++) + { + byte* srcRow = srcPtr + (y * stride); + byte* destRow = destPtr + (y * destData.Stride); + + for (int x = 0; x < widthInBytes; x++) + { + destRow[x] = srcRow[x]; + } + } + } + } + finally + { + source.UnlockBits(srcData); + target.UnlockBits(destData); + } } ///