Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/NesTiler.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2022-10-27 10:04:03 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2022-10-27 10:04:03 +0300
commitdcc1340b4154579155d660943f2652a623a29778 (patch)
treeb7891948a95c622ec04cfbb31f7c41d8f2c7fbeb
parent7393388358b1b2ce6b614f99e146569e94853c76 (diff)
Moved color methods to separate class.
-rw-r--r--NesTiler/ColorsFinder.cs153
-rw-r--r--NesTiler/Program.cs131
2 files changed, 153 insertions, 131 deletions
diff --git a/NesTiler/ColorsFinder.cs b/NesTiler/ColorsFinder.cs
index 2772f69..77ba1f0 100644
--- a/NesTiler/ColorsFinder.cs
+++ b/NesTiler/ColorsFinder.cs
@@ -1,37 +1,104 @@
-using System;
+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
{
- private readonly Dictionary<byte, Color> colors;
+ 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 ColorsFinder(Dictionary<byte, Color> colors)
+ public ColorsFinder(string filename)
{
- this.colors = colors;
+ this.Colors = LoadColors(filename);
+ }
+
+ private static Dictionary<byte, Color> 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;
+ // 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>();
+ 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<Dictionary<string, string>>(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;
}
- public byte FindSimilarColor(Color color)
+ /// <summary>
+ /// Find index of most similar color from NES colors
+ /// </summary>
+ /// <param name="color">Input color</param>
+ /// <returns>Output color index</returns>
+ 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)
+ foreach (var index in Colors.Keys)
{
- var delta = color.GetDelta(colors[index]);
+ var delta = color.GetDelta(Colors[index]);
if (delta < minDelta)
{
minDelta = delta;
result = index;
- c = colors[index];
+ c = Colors[index];
}
}
if (result == byte.MaxValue)
@@ -41,6 +108,12 @@ namespace com.clusterrr.Famicom.NesTiler
return result;
}
+ /// <summary>
+ /// Find most similar color from list of colors
+ /// </summary>
+ /// <param name="colors">Haystack</param>
+ /// <param name="color">Niddle</param>
+ /// <returns>Output color</returns>
public Color FindSimilarColor(IEnumerable<Color> colors, Color color)
{
Color result = Color.Black;
@@ -56,5 +129,69 @@ namespace com.clusterrr.Famicom.NesTiler
}
return result;
}
+
+ /// <summary>
+ /// Find most similar color from NES colors
+ /// </summary>
+ /// <param name="color">Input colo</param>
+ /// <returns>Output color</returns>
+ 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/Program.cs b/NesTiler/Program.cs
index c0cd90d..f607adf 100644
--- a/NesTiler/Program.cs
+++ b/NesTiler/Program.cs
@@ -17,7 +17,6 @@ namespace com.clusterrr.Famicom.NesTiler
public const string REPO_PATH = "https://github.com/ClusterM/nestiler";
public static DateTime BUILD_TIME = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(long.Parse(Properties.Resources.buildtime.Trim()));
public const int MAX_BG_COLOR_AUTODETECT_ITERATIONS = 5;
- static byte[] FORBIDDEN_COLORS = new byte[] { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F };
static void PrintAppInfo()
{
@@ -78,8 +77,7 @@ namespace com.clusterrr.Famicom.NesTiler
int tileID = 0;
// Loading and parsing palette JSON
- var nesColors = LoadColors(c.ColorsFile);
- var colorsFinder = new ColorsFinder(nesColors);
+ var nesColors = new ColorsFinder(c.ColorsFile);
// CSV output
var outTilesCsvLines = !string.IsNullOrEmpty(c.OutTilesCsv) ? new List<string>() : null;
@@ -88,7 +86,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (c.OutColorsTable != null)
{
Trace.WriteLine($"Writing color tables to {c.OutColorsTable}...");
- WriteColorsTable(nesColors, c.OutColorsTable);
+ nesColors.WriteColorsTable(c.OutColorsTable);
}
// Stop if there are no images
@@ -100,7 +98,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (c.FixedPalettes[i] == null) continue;
var colorsInPalette = c.FixedPalettes[i]!.ToArray();
for (int j = 0; j < colorsInPalette.Length; j++)
- colorsInPalette[j] = nesColors[colorsFinder.FindSimilarColor(colorsInPalette[j])];
+ colorsInPalette[j] = nesColors.FindSimilarColor(colorsInPalette[j]);
c.FixedPalettes[i] = new Palette(colorsInPalette);
}
@@ -143,7 +141,7 @@ namespace com.clusterrr.Famicom.NesTiler
var color = image.GetPixelColor(x, y);
if (color.A >= 0x80 || c.Mode == Config.TilesMode.Backgrounds)
{
- var similarColor = nesColors[colorsFinder.FindSimilarColor(color)];
+ var similarColor = nesColors.FindSimilarColor(color);
image.SetPixelColor(x, y, similarColor);
}
else
@@ -163,7 +161,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (c.BgColor.HasValue)
{
// Manually
- bgColor = nesColors[colorsFinder.FindSimilarColor(c.BgColor.Value)];
+ bgColor = nesColors.FindSimilarColor(c.BgColor.Value);
calculatedPalettes = CalculatePalettes(images,
c.PaletteEnabled,
c.FixedPalettes,
@@ -264,7 +262,7 @@ namespace com.clusterrr.Famicom.NesTiler
}
// Calculate palette as color indices and save them to files
- var bgColorIndex = colorsFinder.FindSimilarColor(bgColor);
+ var bgColorIndex = nesColors.FindSimilarColorIndex(bgColor);
for (int p = 0; p < palettes.Length; p++)
{
if (c.PaletteEnabled[p] && c.OutPalette.ContainsKey(p))
@@ -276,7 +274,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (palettes[p] == null)
paletteRaw[colorIndex] = 0;
else if (palettes[p]![colorIndex].HasValue)
- paletteRaw[colorIndex] = colorsFinder.FindSimilarColor(palettes[p]![colorIndex]!.Value);
+ paletteRaw[colorIndex] = nesColors.FindSimilarColorIndex(palettes[p]![colorIndex]!.Value);
}
File.WriteAllBytes(c.OutPalette[p], paletteRaw);
Trace.WriteLine($"Palette #{p} saved to {c.OutPalette[p]}");
@@ -324,7 +322,7 @@ namespace com.clusterrr.Famicom.NesTiler
var cy = (tilePalY * c.TilePalHeight) + y - attributeTableOffset;
if (cy < 0) continue;
var color = image.GetPixelColor((tilePalX * c.TilePalWidth) + x, cy);
- var similarColor = colorsFinder.FindSimilarColor(Enumerable.Concat(
+ var similarColor = nesColors.FindSimilarColor(Enumerable.Concat(
bestPalette,
new Color[] { bgColor }
), color);
@@ -541,119 +539,6 @@ namespace com.clusterrr.Famicom.NesTiler
}
}
- private static Dictionary<byte, Color> 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;
- // 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>();
- 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<Dictionary<string, string>>(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;
- }
-
- static void WriteColorsTable(Dictionary<byte, Color> nesColors, 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 (nesColors.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());
- }
-
static Palette[] CalculatePalettes(Dictionary<int, FastBitmap> images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary<int, int> attributeTableOffsets, int tilePalWidth, int tilePalHeight, Color bgColor)
{
var required = Enumerable.Range(0, 4).Select(i => paletteEnabled[i] && fixedPalettes[i] == null);