using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; namespace agbidx.ImageLib { public static class BitmapProcessor { public static async Task ProcessPokemonImage(string outDirectory, string inPath, bool verbose, bool scanDirectories, bool recursive) { //Create the original bitmap if(Directory.Exists(inPath)) { if(scanDirectories) { List subTasks = new List(); foreach(string s in Directory.GetFiles(inPath)) { subTasks.Add(ProcessPokemonImage(outDirectory, s, verbose, recursive, recursive)); } foreach(string s in Directory.GetDirectories(inPath)) { subTasks.Add(ProcessPokemonImage(outDirectory, s, verbose, recursive, recursive)); } await Task.WhenAll(subTasks); } } else { await Task.Run(() => { Bitmap input = new Bitmap(inPath); ProcessPokemonImageFront(input, outDirectory, Path.GetFileNameWithoutExtension(inPath), verbose); }); } } private static Color[] Quantize4bppColors(Bitmap bmp) { List colors = new List(16); BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; int palCount = 0; unsafe { byte* ptrFirstPixel = (byte*)bitmapData.Scan0; for (int y = 0; y < heightInPixels; y++) { byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride); for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int blue = currentLine[x]; int green = currentLine[x + 1]; int red = currentLine[x + 2]; if (!colors.Any(c => c.R == red && c.G == green && c.B == blue)) { colors.Add(Color.FromArgb(red,green,blue)); palCount++; if(palCount > 16) throw new ArgumentException("The image has too many colors", "bmp"); } } } } bmp.UnlockBits(bitmapData); return colors.ToArray(); } private static Bitmap RenderBitmapFromPalette(Bitmap original, Color[] palette) { Bitmap output = new Bitmap(original.Width,original.Height, PixelFormat.Format4bppIndexed); BitmapData targetData = output.LockBits(new Rectangle(0, 0, original.Width,original.Height), ImageLockMode.ReadWrite, PixelFormat.Format4bppIndexed); BitmapData sourceData = original.LockBits(new Rectangle(0, 0, original.Width,original.Height), ImageLockMode.ReadOnly, original.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(original.PixelFormat) / 8; int heightInPixels = sourceData.Height; int widthInBytes = sourceData.Width * bytesPerPixel; unsafe { byte* ptrFirstPixel = (byte*)sourceData.Scan0; for (int y = 0; y < heightInPixels; y++) { byte* currentLine = ptrFirstPixel + (y * sourceData.Stride); byte* pTarget = (byte*) (targetData.Scan0 + y*targetData.Stride); byte prevValue = 0; for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int blue = currentLine[x]; int green = currentLine[x + 1]; int red = currentLine[x + 2]; if (!palette.Any(c => c.R == red && c.G == green && c.B == blue)) throw new ArgumentException($"The color palette does not contain the required entry {red}:{green}:{blue}.","palette"); else { byte colorIndex = (byte)Array.IndexOf(palette, Color.FromArgb(red,green,blue)); if((x/bytesPerPixel) % 2 == 0) prevValue = colorIndex; else { *pTarget = (byte)((prevValue<<4) | (colorIndex)); pTarget++; } } } } } output.UnlockBits(targetData); original.UnlockBits(sourceData); ColorPalette pal = output.Palette; for(int i = 0; i < ((palette.Length > 16) ? 16 : palette.Length); ++i) { pal.Entries[i] = palette[i]; } output.Palette = pal; return output; } private static void ProcessPokemonImageFront(Bitmap inFile, string outDirectory, string name, bool verbose) { Bitmap normal = new Bitmap(128,64, PixelFormat.Format32bppRgb); Bitmap shiny = new Bitmap(128,64, PixelFormat.Format32bppRgb); using(Graphics g = Graphics.FromImage(normal)) { g.DrawImage(inFile,new Rectangle(0,0,64,64),new Rectangle(0,0,64,64), GraphicsUnit.Pixel); g.DrawImage(inFile,new Rectangle(64,0,64,64),new Rectangle(2*64,0,64,64), GraphicsUnit.Pixel); } using(Graphics g = Graphics.FromImage(shiny)) { g.DrawImage(inFile ,new Rectangle(0,0,64,64),new Rectangle(1*64,0,64,64), GraphicsUnit.Pixel); g.DrawImage(inFile ,new Rectangle(64,0,64,64),new Rectangle(3*64,0,64,64), GraphicsUnit.Pixel); } Color[] normalPalette = Quantize4bppColors(normal); Color[] shinyPalette = Quantize4bppColors(shiny); Bitmap normalIndexed = RenderBitmapFromPalette(normal, normalPalette); Bitmap shinyIndexed = RenderBitmapFromPalette(shiny, shinyPalette); Bitmap frontNormalIndexed = normalIndexed.Clone(new Rectangle(0,0,64,64), PixelFormat.Format4bppIndexed); Bitmap backShinyIndexed = shinyIndexed.Clone(new Rectangle(64,0,64,64), PixelFormat.Format4bppIndexed); frontNormalIndexed.Save(Path.Combine(outDirectory, name+"_frontnormal.png"),ImageFormat.Png); backShinyIndexed.Save(Path.Combine(outDirectory,name+"_backshiny.png"),ImageFormat.Png); if(verbose) { Console.WriteLine("Processed " + Path.Combine(outDirectory, name+"_frontnormal.png")); Console.WriteLine("Processed " + Path.Combine(outDirectory,name+"_backshiny.png")); } } } }