From 7eef9314dd24ba73bb40e058b50d4f4e7bce1035 Mon Sep 17 00:00:00 2001 From: Alexey 'Cluster' Avdyukhin Date: Thu, 27 Oct 2022 18:30:40 +0400 Subject: Great optimization. --- NesTiler/ColorExtensions.cs | 20 +++++++++++++++----- NesTiler/ColorFinder.cs | 35 +++++++++++++++++------------------ NesTiler/Config.cs | 9 +++++---- NesTiler/FastBitmap.cs | 11 +++++------ NesTiler/Palette.cs | 39 ++++++++++++++++++++------------------- NesTiler/Program.cs | 20 ++++++++++---------- 6 files changed, 72 insertions(+), 62 deletions(-) diff --git a/NesTiler/ColorExtensions.cs b/NesTiler/ColorExtensions.cs index 88f973a..70f8bc0 100644 --- a/NesTiler/ColorExtensions.cs +++ b/NesTiler/ColorExtensions.cs @@ -1,5 +1,6 @@ using ColorMine.ColorSpaces; using ColorMine.ColorSpaces.Comparisons; +using SkiaSharp; using System.Collections.Generic; using System.Drawing; @@ -7,8 +8,8 @@ namespace com.clusterrr.Famicom.NesTiler { public record ColorPair { - public Color Color1; - public Color Color2; + public SKColor Color1; + public SKColor Color2; } static class ColorExtensions @@ -17,7 +18,16 @@ namespace com.clusterrr.Famicom.NesTiler public static void ClearCache() => cache.Clear(); - public static double GetDelta(this Color color1, Color color2) + public static Color ToColor(this SKColor color) + => Color.FromArgb((int)color.ToArgb()); + + public static SKColor ToSKColor(this Color color) + => new SKColor((uint)color.ToArgb()); + + public static uint ToArgb(this SKColor color) + => (uint)((color.Alpha << 24) | (color.Red << 16) | (color.Green << 8) | color.Blue); + + public static double GetDelta(this SKColor color1, SKColor color2) { var pair = new ColorPair() { @@ -26,8 +36,8 @@ namespace com.clusterrr.Famicom.NesTiler }; if (cache.ContainsKey(pair)) return cache[pair]; - var a = new Rgb { R = color1.R, G = color1.G, B = color1.B }; - var b = new Rgb { R = color2.R, G = color2.G, B = color2.B }; + var a = new Rgb { R = color1.Red, G = color1.Green, B = color1.Blue }; + var b = new Rgb { R = color2.Red, G = color2.Green, B = color2.Blue }; var delta = a.Compare(b, new CieDe2000Comparison()); cache[pair] = delta; return delta; diff --git a/NesTiler/ColorFinder.cs b/NesTiler/ColorFinder.cs index a6f55b0..748f844 100644 --- a/NesTiler/ColorFinder.cs +++ b/NesTiler/ColorFinder.cs @@ -13,28 +13,28 @@ namespace com.clusterrr.Famicom.NesTiler { 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 readonly Dictionary Colors; + private readonly Dictionary cache = new(); public ColorFinder(string filename) { this.Colors = LoadColors(filename); } - private static Dictionary LoadColors(string 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; + 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(); + 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]); + var color = new SKColor(data[c * 3], data[(c * 3) + 1], data[(c * 3) + 2]); nesColors[c] = color; } } @@ -63,7 +63,8 @@ namespace com.clusterrr.Famicom.NesTiler { try { - return ColorTranslator.FromHtml(kv.Value); + var color = ColorTranslator.FromHtml(kv.Value); ; + return new SKColor(color.R, color.G, color.B); } catch (FormatException) { @@ -82,13 +83,13 @@ namespace com.clusterrr.Famicom.NesTiler /// /// Input color /// Output color index - public byte FindSimilarColorIndex(Color color) + public byte FindSimilarColorIndex(SKColor color) { if (cache.ContainsKey(color)) return cache[color]; byte result = byte.MaxValue; double minDelta = double.MaxValue; - Color c = Color.Transparent; + SKColor c = SKColors.Transparent; foreach (var index in Colors.Keys) { var delta = color.GetDelta(Colors[index]); @@ -112,9 +113,9 @@ namespace com.clusterrr.Famicom.NesTiler /// Haystack /// Niddle /// Output color - public Color FindSimilarColor(IEnumerable colors, Color color) + public SKColor FindSimilarColor(IEnumerable colors, SKColor color) { - Color result = Color.Black; + SKColor result = SKColors.Black; double minDelta = double.MaxValue; foreach (var c in colors) { @@ -133,7 +134,7 @@ namespace com.clusterrr.Famicom.NesTiler /// /// Input colo /// Output color - public Color FindSimilarColor(Color color) => Colors[FindSimilarColorIndex(color)]; + public SKColor FindSimilarColor(SKColor color) => Colors[FindSimilarColorIndex(color)]; public void WriteColorsTable(string filename) { @@ -150,19 +151,17 @@ namespace com.clusterrr.Famicom.NesTiler { for (int x = 0; x < colorColumns; x++) { - Color color; - SKColor skColor; + SKColor color; 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 }; + paint = new SKPaint() { Color = color }; 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 + color = new SKColor((byte)(0xFF - color.Red), (byte)(0xFF - color.Green), (byte)(0xFF - color.Blue)); // invert color paint = new SKPaint() { - Color = skColor, + Color = color, TextAlign = SKTextAlign.Center, TextSize = textSize, FilterQuality = SKFilterQuality.High, diff --git a/NesTiler/Config.cs b/NesTiler/Config.cs index 9916a3b..3046748 100644 --- a/NesTiler/Config.cs +++ b/NesTiler/Config.cs @@ -1,4 +1,5 @@ -using System; +using SkiaSharp; +using System; using System.Collections.Generic; using System.Drawing; using System.IO; @@ -20,7 +21,7 @@ namespace com.clusterrr.Famicom.NesTiler public string ColorsFile { get; private set; } public Dictionary ImageFiles { get; private set; } = new Dictionary(); - public Color? BgColor { get; private set; } = null; + public SKColor? BgColor { get; private set; } = null; public bool[] PaletteEnabled { get; private set; } = new bool[4] { true, true, true, true }; public Palette?[] FixedPalettes { get; private set; } = new Palette?[4] { null, null, null, null }; public TilesMode Mode { get; private set; } = TilesMode.Backgrounds; @@ -125,7 +126,7 @@ namespace com.clusterrr.Famicom.NesTiler { try { - config.BgColor = ColorTranslator.FromHtml(value); + config.BgColor = ColorTranslator.FromHtml(value).ToSKColor(); } catch (FormatException) { @@ -156,7 +157,7 @@ namespace com.clusterrr.Famicom.NesTiler case ArgPalette.S: case ArgPalette.L: { - var colors = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c => ColorTranslator.FromHtml(c)); + var colors = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c => ColorTranslator.FromHtml(c).ToSKColor()); config.FixedPalettes[indexNum] = new Palette(colors); } i++; diff --git a/NesTiler/FastBitmap.cs b/NesTiler/FastBitmap.cs index 07b33e5..1015354 100644 --- a/NesTiler/FastBitmap.cs +++ b/NesTiler/FastBitmap.cs @@ -11,7 +11,7 @@ namespace com.clusterrr.Famicom.NesTiler public int Width { get; } public int Height { get; } - private readonly Color[] colors; + private readonly SKColor[] colors; private static Dictionary imagesCache = new Dictionary(); private FastBitmap(SKBitmap skBitmap, int verticalOffset = 0, int height = -1) @@ -20,7 +20,7 @@ namespace com.clusterrr.Famicom.NesTiler Height = height <= 0 ? skBitmap.Height - verticalOffset : height; if (skBitmap.Height - verticalOffset - Height < 0 || Height <= 0) throw new InvalidOperationException("Invalid image height."); var pixels = skBitmap.Pixels; - colors = skBitmap.Pixels.Skip(verticalOffset * Width).Take(Width * Height).Select(p => Color.FromArgb(p.Alpha, p.Red, p.Green, p.Blue)).ToArray(); + colors = skBitmap.Pixels.Skip(verticalOffset * Width).Take(Width * Height).ToArray(); } public static FastBitmap? Decode(string filename, int verticalOffset = 0, int height = -1) @@ -40,12 +40,12 @@ namespace com.clusterrr.Famicom.NesTiler } } - public Color GetPixelColor(int x, int y) + public SKColor GetPixelColor(int x, int y) { return colors[(y * Width) + x]; } - public void SetPixelColor(int x, int y, Color color) + public void SetPixelColor(int x, int y, SKColor color) { colors[(y * Width) + x] = color; } @@ -58,8 +58,7 @@ namespace com.clusterrr.Famicom.NesTiler for (int x = 0; x < Width; x++) { var color = colors[(y * Width) + x]; - var skColor = new SKColor(color.R, color.G, color.B); - skImage.SetPixel(x, y, skColor); + skImage.SetPixel(x, y, color); } } return skImage.Encode(format, v).ToArray(); diff --git a/NesTiler/Palette.cs b/NesTiler/Palette.cs index 5b8703f..44cb940 100644 --- a/NesTiler/Palette.cs +++ b/NesTiler/Palette.cs @@ -1,4 +1,5 @@ -using System; +using SkiaSharp; +using System; using System.Collections; using System.Collections.Generic; using System.Drawing; @@ -7,12 +8,12 @@ using Color = System.Drawing.Color; namespace com.clusterrr.Famicom.NesTiler { - class Palette : IEquatable, IEnumerable + class Palette : IEquatable, IEnumerable { - private Color?[] colors = new Color?[3]; - private Dictionary deltaCache = new(); + private SKColor?[] colors = new SKColor?[3]; + private Dictionary deltaCache = new(); - public Color? this[int i] + public SKColor? this[int i] { get { @@ -32,9 +33,9 @@ namespace com.clusterrr.Famicom.NesTiler // Empty palette } - public Palette(FastBitmap image, int leftX, int topY, int width, int height, Color bgColor) + public Palette(FastBitmap image, int leftX, int topY, int width, int height, SKColor bgColor) { - Dictionary colorCounter = new Dictionary(); + Dictionary colorCounter = new(); for (int y = topY; y < topY + height; y++) { if (y < 0) continue; @@ -54,21 +55,21 @@ namespace com.clusterrr.Famicom.NesTiler this[i + 1] = sortedColors[i].Key; } - public void Add(Color color) + public void Add(SKColor color) { if (Count >= 3) throw new IndexOutOfRangeException(); this[Count + 1] = color; deltaCache.Clear(); } - public Palette(IEnumerable colors) + public Palette(IEnumerable colors) { var colorsList = colors.ToList(); for (int i = 0; i < 3; i++) if (colorsList.Count > i) this[i + 1] = colorsList[i]; } - public double GetTileDelta(FastBitmap image, int leftX, int topY, int width, int height, Color bgColor) + public double GetTileDelta(FastBitmap image, int leftX, int topY, int width, int height, SKColor bgColor) { double delta = 0; for (int y = topY; y < topY + height; y++) @@ -83,7 +84,7 @@ namespace com.clusterrr.Famicom.NesTiler return delta; } - private (Color color, double delta) GetMinDelta(Color color, Color bgColor) + private (SKColor color, double delta) GetMinDelta(SKColor color, SKColor bgColor) { var pair = new ColorPair() { @@ -91,7 +92,7 @@ namespace com.clusterrr.Famicom.NesTiler Color2 = bgColor }; if (deltaCache.ContainsKey(pair)) return deltaCache[pair]; - var ac = Enumerable.Concat(colors.Where(c => c.HasValue).Select(c => c!.Value), new Color[] { bgColor }); + var ac = Enumerable.Concat(colors.Where(c => c.HasValue).Select(c => c!.Value), new SKColor[] { bgColor }); var result = ac.OrderBy(c => c.GetDelta(color)).First(); var r = (result, result.GetDelta(color)); deltaCache[pair] = r; @@ -105,7 +106,7 @@ namespace com.clusterrr.Famicom.NesTiler .OrderBy(c => c!.Value.ToArgb()) .Select(c => c!.Value) .ToArray(); - var colors2 = new Color?[] { other[1], other[2], other[3] } + var colors2 = new SKColor?[] { other[1], other[2], other[3] } .Where(c => c.HasValue) .OrderBy(c => c!.Value.ToArgb()) .Select(c => c!.Value) @@ -116,7 +117,7 @@ namespace com.clusterrr.Famicom.NesTiler public bool Contains(Palette other) { var thisColors = colors.Where(c => c.HasValue); - var otherColors = new Color?[] { other[1], other[2], other[3] }.Where(c => c.HasValue).Select(c => c!.Value); + var otherColors = new SKColor?[] { other[1], other[2], other[3] }.Where(c => c.HasValue).Select(c => c!.Value); foreach (var color in otherColors) { @@ -126,7 +127,7 @@ namespace com.clusterrr.Famicom.NesTiler return true; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return colors.Where(c => c.HasValue).Select(c => c!.Value).GetEnumerator(); } @@ -136,13 +137,13 @@ namespace com.clusterrr.Famicom.NesTiler return GetEnumerator(); } - public override string ToString() => string.Join(", ", colors.Where(c => c.HasValue).Select(c => ColorTranslator.ToHtml(c!.Value)).OrderBy(c => c)); + public override string ToString() => string.Join(", ", colors.Where(c => c.HasValue).Select(c => ColorTranslator.ToHtml(c!.Value.ToColor())).OrderBy(c => c)); public override int GetHashCode() { - 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); + return ((this[1]?.Red ?? 0) + (this[2]?.Red ?? 0) + (this[3]?.Red ?? 0)) + | (((this[1]?.Green ?? 0) + (this[2]?.Green ?? 0) + (this[3]?.Green ?? 0)) << 10) + | (((this[1]?.Blue ?? 0) + (this[2]?.Blue ?? 0) + (this[3]?.Blue ?? 0)) << 20); } } } diff --git a/NesTiler/Program.cs b/NesTiler/Program.cs index 92c6d12..fde58db 100644 --- a/NesTiler/Program.cs +++ b/NesTiler/Program.cs @@ -139,7 +139,7 @@ namespace com.clusterrr.Famicom.NesTiler for (int x = 0; x < image.Width; x++) { var color = image.GetPixelColor(x, y); - if (color.A >= 0x80 || c.Mode == Config.TilesMode.Backgrounds) + if (color.Alpha >= 0x80 || c.Mode == Config.TilesMode.Backgrounds) { var similarColor = nesColors.FindSimilarColor(color); image.SetPixelColor(x, y, similarColor); @@ -156,7 +156,7 @@ namespace com.clusterrr.Famicom.NesTiler List calculatedPalettes; var maxCalculatedPaletteCount = Enumerable.Range(0, 4) .Select(i => c.PaletteEnabled[i] && c.FixedPalettes[i] == null).Count(); - Color bgColor; + SKColor bgColor; // Detect background color if (c.BgColor.HasValue) { @@ -174,7 +174,7 @@ namespace com.clusterrr.Famicom.NesTiler { // Autodetect most used color Trace.Write($"Background color autodetect... "); - Dictionary colorPerTileCounter = new Dictionary(); + Dictionary colorPerTileCounter = new(); foreach (var imageNum in images.Keys) { var image = images[imageNum]; @@ -183,7 +183,7 @@ namespace com.clusterrr.Famicom.NesTiler for (int tileX = 0; tileX < image.Width / c.TilePalWidth; tileX++) { // Count each color only once per tile/sprite - var colorsInTile = new List(); + var colorsInTile = new List(); for (int y = 0; y < c.TilePalHeight; y++) { for (int x = 0; x < c.TilePalWidth; x++) @@ -206,7 +206,7 @@ namespace com.clusterrr.Famicom.NesTiler // Most used colors var candidates = colorPerTileCounter.OrderByDescending(kv => kv.Value).Select(kv => kv.Key).ToArray(); // Try to calculate palettes for every background color - var calcResults = new Dictionary(); + var calcResults = new Dictionary(); for (int i = 0; i < Math.Min(candidates.Length, MAX_BG_COLOR_AUTODETECT_ITERATIONS); i++) { calcResults[candidates[i]] = CalculatePalettes(images, @@ -220,7 +220,7 @@ namespace com.clusterrr.Famicom.NesTiler // Select background color which uses minimum palettes var kv = calcResults.OrderBy(kv => kv.Value.Length).First(); (bgColor, calculatedPalettes) = (kv.Key, kv.Value.ToList()); - Trace.WriteLine(ColorTranslator.ToHtml(bgColor)); + Trace.WriteLine(ColorTranslator.ToHtml(bgColor.ToColor())); } if (calculatedPalettes.Count > maxCalculatedPaletteCount && !c.Lossy) @@ -254,9 +254,9 @@ namespace com.clusterrr.Famicom.NesTiler if (palettes[i] != null) { - Trace.WriteLine($"Palette #{i}: {ColorTranslator.ToHtml(bgColor)}(BG) {string.Join(" ", palettes[i]!.Select(p => ColorTranslator.ToHtml(p)))}"); + Trace.WriteLine($"Palette #{i}: {ColorTranslator.ToHtml(bgColor.ToColor())}(BG) {string.Join(" ", palettes[i]!.Select(p => ColorTranslator.ToHtml(p.ToColor())))}"); // Write CSV if required - outPalettesCsvLines?.Add($"{i},{ColorTranslator.ToHtml(bgColor)},{string.Join(",", Enumerable.Range(1, 3).Select(c => (palettes[i]![c] != null ? ColorTranslator.ToHtml(palettes[i]![c]!.Value) : "")))}"); + outPalettesCsvLines?.Add($"{i},{ColorTranslator.ToHtml(bgColor.ToColor())},{string.Join(",", Enumerable.Range(1, 3).Select(c => (palettes[i]![c] != null ? ColorTranslator.ToHtml(palettes[i]![c]!.Value.ToColor()) : "")))}"); } } } @@ -324,7 +324,7 @@ namespace com.clusterrr.Famicom.NesTiler var color = image.GetPixelColor((tilePalX * c.TilePalWidth) + x, cy); var similarColor = nesColors.FindSimilarColor(Enumerable.Concat( bestPalette, - new Color[] { bgColor } + new SKColor[] { bgColor } ), color); image.SetPixelColor( (tilePalX * c.TilePalWidth) + x, @@ -539,7 +539,7 @@ namespace com.clusterrr.Famicom.NesTiler } } - static Palette[] CalculatePalettes(Dictionary images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary attributeTableOffsets, int tilePalWidth, int tilePalHeight, Color bgColor) + static Palette[] CalculatePalettes(Dictionary images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary attributeTableOffsets, int tilePalWidth, int tilePalHeight, SKColor bgColor) { var required = Enumerable.Range(0, 4).Select(i => paletteEnabled[i] && fixedPalettes[i] == null); // Creating and counting the palettes -- cgit v1.2.3