diff options
author | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2022-10-27 17:30:40 +0300 |
---|---|---|
committer | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2022-10-27 17:30:40 +0300 |
commit | 7eef9314dd24ba73bb40e058b50d4f4e7bce1035 (patch) | |
tree | 766f59cf1e86613fc1c01f097f8055a0a9f020aa | |
parent | dfd7e9a652514fe6abf5d84e2f1736036afdcc1c (diff) |
Great optimization.
-rw-r--r-- | NesTiler/ColorExtensions.cs | 20 | ||||
-rw-r--r-- | NesTiler/ColorFinder.cs | 35 | ||||
-rw-r--r-- | NesTiler/Config.cs | 9 | ||||
-rw-r--r-- | NesTiler/FastBitmap.cs | 11 | ||||
-rw-r--r-- | NesTiler/Palette.cs | 39 | ||||
-rw-r--r-- | 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<byte, Color> Colors; - private readonly Dictionary<Color, byte> cache = new(); + public readonly Dictionary<byte, SKColor> Colors; + private readonly Dictionary<SKColor, byte> cache = new(); public ColorFinder(string filename) { this.Colors = LoadColors(filename); } - private static Dictionary<byte, Color> LoadColors(string filename) + private static Dictionary<byte, SKColor> 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<byte, Color> nesColors; + Dictionary<byte, SKColor> 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<byte, Color>(); + nesColors = new Dictionary<byte, SKColor>(); 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 /// </summary> /// <param name="color">Input color</param> /// <returns>Output color index</returns> - 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 /// <param name="colors">Haystack</param> /// <param name="color">Niddle</param> /// <returns>Output color</returns> - public Color FindSimilarColor(IEnumerable<Color> colors, Color color) + public SKColor FindSimilarColor(IEnumerable<SKColor> 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 /// </summary> /// <param name="color">Input colo</param> /// <returns>Output color</returns> - 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<int, string> ImageFiles { get; private set; } = new Dictionary<int, string>(); - 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<string, SKBitmap> imagesCache = new Dictionary<string, SKBitmap>();
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<Palette>, IEnumerable<Color>
+ class Palette : IEquatable<Palette>, IEnumerable<SKColor>
{
- private Color?[] colors = new Color?[3];
- private Dictionary<ColorPair, (Color color, double delta)> deltaCache = new();
+ private SKColor?[] colors = new SKColor?[3];
+ private Dictionary<ColorPair, (SKColor color, double delta)> 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<Color, int> colorCounter = new Dictionary<Color, int>();
+ Dictionary<SKColor, int> 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<Color> colors)
+ public Palette(IEnumerable<SKColor> 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<Color> GetEnumerator()
+ public IEnumerator<SKColor> 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<Palette> 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<Color, int> colorPerTileCounter = new Dictionary<Color, int>();
+ Dictionary<SKColor, int> 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<Color>();
+ var colorsInTile = new List<SKColor>();
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<Color, Palette[]>();
+ var calcResults = new Dictionary<SKColor, Palette[]>();
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<int, FastBitmap> images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary<int, int> attributeTableOffsets, int tilePalWidth, int tilePalHeight, Color bgColor)
+ static Palette[] CalculatePalettes(Dictionary<int, FastBitmap> images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary<int, int> 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
|