From 2b9753ec8d5e0157ea2a6fdbfcb35b558ca131db Mon Sep 17 00:00:00 2001 From: Alexey 'Cluster' Avdyukhin Date: Thu, 27 Oct 2022 11:33:44 +0400 Subject: Clean up. --- NesTiler/CmdArgs.cs | 8 +- NesTiler/ColorExtensions.cs | 7 -- NesTiler/ColorFinder.cs | 195 +++++++++++++++++++++++++++++++++++++++++++ NesTiler/ColorsFinder.cs | 197 -------------------------------------------- NesTiler/Config.cs | 2 - NesTiler/FastBitmap.cs | 10 +-- NesTiler/Palette.cs | 5 +- NesTiler/Program.cs | 3 +- NesTiler/Tile.cs | 4 +- 9 files changed, 205 insertions(+), 226 deletions(-) create mode 100644 NesTiler/ColorFinder.cs delete mode 100644 NesTiler/ColorsFinder.cs diff --git a/NesTiler/CmdArgs.cs b/NesTiler/CmdArgs.cs index f4b5ee7..7eeec5c 100644 --- a/NesTiler/CmdArgs.cs +++ b/NesTiler/CmdArgs.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace com.clusterrr.Famicom.NesTiler +namespace com.clusterrr.Famicom.NesTiler { interface IArg { diff --git a/NesTiler/ColorExtensions.cs b/NesTiler/ColorExtensions.cs index 3a5105b..88f973a 100644 --- a/NesTiler/ColorExtensions.cs +++ b/NesTiler/ColorExtensions.cs @@ -1,14 +1,7 @@ using ColorMine.ColorSpaces; using ColorMine.ColorSpaces.Comparisons; -using SkiaSharp; -using System; using System.Collections.Generic; -using System.Data.Common; using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Net.Mime.MediaTypeNames; namespace com.clusterrr.Famicom.NesTiler { diff --git a/NesTiler/ColorFinder.cs b/NesTiler/ColorFinder.cs new file mode 100644 index 0000000..d3bea55 --- /dev/null +++ b/NesTiler/ColorFinder.cs @@ -0,0 +1,195 @@ +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace com.clusterrr.Famicom.NesTiler +{ + class ColorFinder + { + static byte[] FORBIDDEN_COLORS = new byte[] { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F }; + + public readonly Dictionary Colors; + private readonly Dictionary cache = new(); + + public ColorFinder(string filename) + { + this.Colors = LoadColors(filename); + } + + private static Dictionary LoadColors(string filename) + { + Trace.WriteLine($"Loading colors from {filename}..."); + if (!File.Exists(filename)) throw new FileNotFoundException($"Could not find file '{filename}'.", filename); + var data = File.ReadAllBytes(filename); + Dictionary nesColors; + // Detect file type + if ((Path.GetExtension(filename) == ".pal") || ((data.Length == 192 || data.Length == 1536) && data.Where(b => b >= 128).Any())) + { + // Binary file + nesColors = new Dictionary(); + for (byte c = 0; c < 64; c++) + { + var color = Color.FromArgb(data[c * 3], data[(c * 3) + 1], data[(c * 3) + 2]); + nesColors[c] = color; + } + } + else + { + var paletteJson = File.ReadAllText(filename); + var nesColorsStr = JsonSerializer.Deserialize>(paletteJson); + if (nesColorsStr == null) throw new InvalidDataException($"Can't parse {filename}"); + nesColors = nesColorsStr.ToDictionary( + kv => + { + try + { + var index = kv.Key.ToLower().StartsWith("0x") ? Convert.ToByte(kv.Key.Substring(2), 16) : byte.Parse(kv.Key); + if (FORBIDDEN_COLORS.Contains(index)) + Trace.WriteLine($"WARNING! color #{kv.Key} is forbidden color, it will be ignored."); + if (index > 0x3D) throw new ArgumentException($"{kv.Key} - invalid color index.", filename); + return index; + } + catch (Exception ex) when (ex is FormatException || ex is OverflowException) + { + throw new ArgumentException($"{kv.Key} - invalid color index.", filename); + } + }, + kv => + { + try + { + return ColorTranslator.FromHtml(kv.Value); + } + catch (FormatException) + { + throw new ArgumentException($"{kv.Value} - invalid color.", filename); + } + } + ); + } + // filter out invalid colors; + nesColors = nesColors.Where(kv => !FORBIDDEN_COLORS.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); + return nesColors; + } + + /// + /// Find index of most similar color from NES colors + /// + /// Input color + /// Output color index + public byte FindSimilarColorIndex(Color color) + { + if (cache.ContainsKey(color)) + return cache[color]; + byte result = byte.MaxValue; + double minDelta = double.MaxValue; + Color c = Color.Transparent; + foreach (var index in Colors.Keys) + { + var delta = color.GetDelta(Colors[index]); + if (delta < minDelta) + { + minDelta = delta; + result = index; + c = Colors[index]; + } + } + if (result == byte.MaxValue) + throw new KeyNotFoundException($"Invalid color: {color}."); + if (cache != null) + cache[color] = result; + return result; + } + + /// + /// Find most similar color from list of colors + /// + /// Haystack + /// Niddle + /// Output color + public Color FindSimilarColor(IEnumerable colors, Color color) + { + Color result = Color.Black; + double minDelta = double.MaxValue; + foreach (var c in colors) + { + var delta = color.GetDelta(c); + if (delta < minDelta) + { + minDelta = delta; + result = c; + } + } + return result; + } + + /// + /// Find most similar color from NES colors + /// + /// Input colo + /// Output color + public Color FindSimilarColor(Color color) => Colors[FindSimilarColorIndex(color)]; + + public void WriteColorsTable(string filename) + { + // Export colors to nice table image + const int colorSize = 64; + const int colorColumns = 16; + const int colorRows = 4; + const int strokeWidth = 5; + float textSize = 20; + float textYOffset = 39; + using var image = new SKBitmap(colorSize * colorColumns, colorSize * colorRows); + using var canvas = new SKCanvas(image); + for (int y = 0; y < colorRows; y++) + { + for (int x = 0; x < colorColumns; x++) + { + Color color; + SKColor skColor; + SKPaint paint; + if (Colors.TryGetValue((byte)((y * colorColumns) + x), out color)) + { + skColor = new SKColor(color.R, color.G, color.B); + paint = new SKPaint() { Color = skColor }; + canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint); + + skColor = new SKColor((byte)(0xFF - color.R), (byte)(0xFF - color.G), (byte)(0xFF - color.B)); // invert color + paint = new SKPaint() + { + Color = skColor, + TextAlign = SKTextAlign.Center, + TextSize = textSize, + FilterQuality = SKFilterQuality.High, + IsAntialias = true + }; + canvas.DrawText($"{(y * colorColumns) + x:X02}", (x * colorSize) + (colorSize / 2), (y * colorSize) + textYOffset, paint); + } + else + { + paint = new SKPaint() { Color = SKColors.Black }; + SKPath path = new SKPath(); + canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint); + paint = new SKPaint() + { + Color = SKColors.Red, + Style = SKPaintStyle.Stroke, + StrokeCap = SKStrokeCap.Round, + StrokeWidth = strokeWidth, + FilterQuality = SKFilterQuality.High, + IsAntialias = true + }; + canvas.DrawLine((x * colorSize) + strokeWidth, (y * colorSize) + strokeWidth, ((x + 1) * colorSize) - strokeWidth, ((y + 1) * colorSize) - strokeWidth, paint); + canvas.DrawLine(((x + 1) * colorSize) - strokeWidth, (y * colorSize) + strokeWidth, (x * colorSize) + strokeWidth, ((y + 1) * colorSize) - strokeWidth, paint); + } + }; + } + File.WriteAllBytes(filename, image.Encode(SKEncodedImageFormat.Png, 0).ToArray()); + } + } +} diff --git a/NesTiler/ColorsFinder.cs b/NesTiler/ColorsFinder.cs deleted file mode 100644 index 77ba1f0..0000000 --- a/NesTiler/ColorsFinder.cs +++ /dev/null @@ -1,197 +0,0 @@ -using SkiaSharp; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace com.clusterrr.Famicom.NesTiler -{ - class ColorsFinder - { - static byte[] FORBIDDEN_COLORS = new byte[] { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F }; - - public readonly Dictionary Colors; - private readonly Dictionary cache = new(); - - public ColorsFinder(string filename) - { - this.Colors = LoadColors(filename); - } - - private static Dictionary LoadColors(string filename) - { - Trace.WriteLine($"Loading colors from {filename}..."); - if (!File.Exists(filename)) throw new FileNotFoundException($"Could not find file '{filename}'.", filename); - var data = File.ReadAllBytes(filename); - Dictionary nesColors; - // Detect file type - if ((Path.GetExtension(filename) == ".pal") || ((data.Length == 192 || data.Length == 1536) && data.Where(b => b >= 128).Any())) - { - // Binary file - nesColors = new Dictionary(); - for (byte c = 0; c < 64; c++) - { - var color = Color.FromArgb(data[c * 3], data[(c * 3) + 1], data[(c * 3) + 2]); - nesColors[c] = color; - } - } - else - { - var paletteJson = File.ReadAllText(filename); - var nesColorsStr = JsonSerializer.Deserialize>(paletteJson); - if (nesColorsStr == null) throw new InvalidDataException($"Can't parse {filename}"); - nesColors = nesColorsStr.ToDictionary( - kv => - { - try - { - var index = kv.Key.ToLower().StartsWith("0x") ? Convert.ToByte(kv.Key.Substring(2), 16) : byte.Parse(kv.Key); - if (FORBIDDEN_COLORS.Contains(index)) - Trace.WriteLine($"WARNING! color #{kv.Key} is forbidden color, it will be ignored."); - if (index > 0x3D) throw new ArgumentException($"{kv.Key} - invalid color index.", filename); - return index; - } - catch (Exception ex) when (ex is FormatException || ex is OverflowException) - { - throw new ArgumentException($"{kv.Key} - invalid color index.", filename); - } - }, - kv => - { - try - { - return ColorTranslator.FromHtml(kv.Value); - } - catch (FormatException) - { - throw new ArgumentException($"{kv.Value} - invalid color.", filename); - } - } - ); - } - // filter out invalid colors; - nesColors = nesColors.Where(kv => !FORBIDDEN_COLORS.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); - return nesColors; - } - - /// - /// Find index of most similar color from NES colors - /// - /// Input color - /// Output color index - public byte FindSimilarColorIndex(Color color) - { - if (cache.ContainsKey(color)) - return cache[color]; - byte result = byte.MaxValue; - double minDelta = double.MaxValue; - Color c = Color.Transparent; - foreach (var index in Colors.Keys) - { - var delta = color.GetDelta(Colors[index]); - if (delta < minDelta) - { - minDelta = delta; - result = index; - c = Colors[index]; - } - } - if (result == byte.MaxValue) - throw new KeyNotFoundException($"Invalid color: {color}."); - if (cache != null) - cache[color] = result; - return result; - } - - /// - /// Find most similar color from list of colors - /// - /// Haystack - /// Niddle - /// Output color - public Color FindSimilarColor(IEnumerable colors, Color color) - { - Color result = Color.Black; - double minDelta = double.MaxValue; - foreach (var c in colors) - { - var delta = color.GetDelta(c); - if (delta < minDelta) - { - minDelta = delta; - result = c; - } - } - return result; - } - - /// - /// Find most similar color from NES colors - /// - /// Input colo - /// Output color - public Color FindSimilarColor(Color color) => Colors[FindSimilarColorIndex(color)]; - - public void WriteColorsTable(string filename) - { - // Export colors to nice table image - const int colorSize = 64; - const int colorColumns = 16; - const int colorRows = 4; - const int strokeWidth = 5; - float textSize = 20; - float textYOffset = 39; - using var image = new SKBitmap(colorSize * colorColumns, colorSize * colorRows); - using var canvas = new SKCanvas(image); - for (int y = 0; y < colorRows; y++) - { - for (int x = 0; x < colorColumns; x++) - { - Color color; - SKColor skColor; - SKPaint paint; - if (Colors.TryGetValue((byte)((y * colorColumns) + x), out color)) - { - skColor = new SKColor(color.R, color.G, color.B); - paint = new SKPaint() { Color = skColor }; - canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint); - - skColor = new SKColor((byte)(0xFF - color.R), (byte)(0xFF - color.G), (byte)(0xFF - color.B)); // invert color - paint = new SKPaint() - { - Color = skColor, - TextAlign = SKTextAlign.Center, - TextSize = textSize, - FilterQuality = SKFilterQuality.High, - IsAntialias = true - }; - canvas.DrawText($"{(y * colorColumns) + x:X02}", (x * colorSize) + (colorSize / 2), (y * colorSize) + textYOffset, paint); - } - else - { - paint = new SKPaint() { Color = SKColors.Black }; - SKPath path = new SKPath(); - canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint); - paint = new SKPaint() - { - Color = SKColors.Red, - Style = SKPaintStyle.Stroke, - StrokeCap = SKStrokeCap.Round, - StrokeWidth = strokeWidth, - FilterQuality = SKFilterQuality.High, - IsAntialias = true - }; - canvas.DrawLine((x * colorSize) + strokeWidth, (y * colorSize) + strokeWidth, ((x + 1) * colorSize) - strokeWidth, ((y + 1) * colorSize) - strokeWidth, paint); - canvas.DrawLine(((x + 1) * colorSize) - strokeWidth, (y * colorSize) + strokeWidth, (x * colorSize) + strokeWidth, ((y + 1) * colorSize) - strokeWidth, paint); - } - }; - } - File.WriteAllBytes(filename, image.Encode(SKEncodedImageFormat.Png, 0).ToArray()); - } - } -} diff --git a/NesTiler/Config.cs b/NesTiler/Config.cs index 60ba891..9916a3b 100644 --- a/NesTiler/Config.cs +++ b/NesTiler/Config.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace com.clusterrr.Famicom.NesTiler { diff --git a/NesTiler/FastBitmap.cs b/NesTiler/FastBitmap.cs index 8ed84e9..fb92468 100644 --- a/NesTiler/FastBitmap.cs +++ b/NesTiler/FastBitmap.cs @@ -1,11 +1,7 @@ using SkiaSharp; -using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace com.clusterrr.Famicom.NesTiler { @@ -35,12 +31,12 @@ namespace com.clusterrr.Famicom.NesTiler public Color GetPixelColor(int x, int y) { - return colors[y * Width + x]; + return colors[(y * Width) + x]; } public void SetPixelColor(int x, int y, Color color) { - colors[y * Width + x] = color; + colors[(y * Width) + x] = color; } public byte[] Encode(SKEncodedImageFormat format, int v) @@ -50,7 +46,7 @@ namespace com.clusterrr.Famicom.NesTiler { for (int x = 0; x < Width; x++) { - var color = colors[y * Width + x]; + var color = colors[(y * Width) + x]; var skColor = new SKColor(color.R, color.G, color.B); skImage.SetPixel(x, y, skColor); } diff --git a/NesTiler/Palette.cs b/NesTiler/Palette.cs index ff4c3c5..5b8703f 100644 --- a/NesTiler/Palette.cs +++ b/NesTiler/Palette.cs @@ -1,5 +1,4 @@ -using SkiaSharp; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Drawing; @@ -141,7 +140,7 @@ namespace com.clusterrr.Famicom.NesTiler public override int GetHashCode() { - return (this[1]?.R ?? 0) + (this[2]?.R ?? 0) + (this[3]?.R ?? 0) + return ((this[1]?.R ?? 0) + (this[2]?.R ?? 0) + (this[3]?.R ?? 0)) | (((this[1]?.G ?? 0) + (this[2]?.G ?? 0) + (this[3]?.G ?? 0)) << 10) | (((this[1]?.B ?? 0) + (this[2]?.B ?? 0) + (this[3]?.B ?? 0)) << 20); } diff --git a/NesTiler/Program.cs b/NesTiler/Program.cs index 7a11d92..09e6519 100644 --- a/NesTiler/Program.cs +++ b/NesTiler/Program.cs @@ -77,7 +77,7 @@ namespace com.clusterrr.Famicom.NesTiler int tileID = 0; // Loading and parsing palette JSON - var nesColors = new ColorsFinder(c.ColorsFile); + var nesColors = new ColorFinder(c.ColorsFile); // CSV output var outTilesCsvLines = !string.IsNullOrEmpty(c.OutTilesCsv) ? new List() : null; @@ -625,6 +625,7 @@ namespace com.clusterrr.Famicom.NesTiler } } } + if (!grouped) break; // Nothing changed, stop iterations } diff --git a/NesTiler/Tile.cs b/NesTiler/Tile.cs index a33f00f..57aca75 100644 --- a/NesTiler/Tile.cs +++ b/NesTiler/Tile.cs @@ -31,9 +31,9 @@ namespace com.clusterrr.Famicom.NesTiler { // for each pixel if ((Pixels[(y * Width) + x] & 1) != 0) // check bit 0 - data[y / 8 * 16 + (y % 8)] |= (byte)(1 << bit); + data[(y / 8 * 16) + (y % 8)] |= (byte)(1 << bit); if ((Pixels[(y * Width) + x] & 2) != 0) // check bit 1 - data[y / 8 * 16 + (y % 8) + 8] |= (byte)(1 << bit); + data[(y / 8 * 16) + (y % 8) + 8] |= (byte)(1 << bit); pixel++; bit = (byte)((byte)(bit - 1) % 8); // decrease bit number, wrap around if need } -- cgit v1.2.3