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-30 11:54:24 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2022-10-30 11:54:24 +0300
commitea686aa66485196cfee45c87bb209b7f59fd18db (patch)
tree2ecd3f01a2baa98636fda91fe7cc51eb4771e730
parent7d6ae6d8d6c134435cb9a57c6d5bda38499d2b12 (diff)
Pipeline
-rw-r--r--.github/workflows/release.yml6
-rw-r--r--NesTiler/CmdArgs.cs486
-rw-r--r--NesTiler/ColorFinder.cs388
-rw-r--r--NesTiler/Config.cs554
-rw-r--r--NesTiler/NesTiler.csproj3
5 files changed, 720 insertions, 717 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 578c205..01259db 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -59,7 +59,7 @@ jobs:
- name: Build
env:
SC_OPS: ${{ matrix.sc == 'self-contained' && '--self-contained true -p:PublishTrimmed=False' || '--no-self-contained' }}
- run: dotnet publish ${{ env.PROJECT_PATH }} -c ${{ env.CONFIGURATION }} -r ${{ matrix.os }}-${{ matrix.arch }} -p:PublishSingleFile=true ${{ env.SC_OPS }} -o ${{ env.OUTPUT_DIR }}/${{ env.OUTPUT_SUBDIR }}/${{ env.APP_NAME }} -p:IncludeAllContentForSelfExtract=true
+ run: dotnet publish ${{ env.PROJECT_PATH }} -c ${{ env.CONFIGURATION }} -r ${{ matrix.os }}-${{ matrix.arch }} ${{ env.SC_OPS }} -o ${{ env.OUTPUT_DIR }}/${{ env.OUTPUT_SUBDIR }}/${{ env.APP_NAME }}
- name: Archive
working-directory: ${{ env.OUTPUT_DIR }}/${{ env.OUTPUT_SUBDIR }}
env:
@@ -76,9 +76,9 @@ jobs:
TAG_REF_NAME: ${{ github.ref }}
REPOSITORY_NAME: ${{ github.repository }}
run: |
- echo ::set-output name=file_name::${REPOSITORY_NAME##*/}-${TAG_REF_NAME##*/v}
+ echo file_name=${REPOSITORY_NAME##*/}-${TAG_REF_NAME##*/v} >> $GITHUB_OUTPUT
value=`cat release_url/release_url.txt`
- echo ::set-output name=upload_url::$value
+ echo name=upload_url=$value >> $GITHUB_OUTPUT
- name: Upload
uses: actions/upload-release-asset@v1
env:
diff --git a/NesTiler/CmdArgs.cs b/NesTiler/CmdArgs.cs
index fc1d398..98411f9 100644
--- a/NesTiler/CmdArgs.cs
+++ b/NesTiler/CmdArgs.cs
@@ -1,243 +1,243 @@
-namespace com.clusterrr.Famicom.NesTiler
-{
- interface IArg
- {
- public string Short { get; }
- public string Long { get; }
- public string? Params { get; }
- public string Description { get; }
- public bool HasIndex { get; }
-
- public static IArg[] Args = new IArg[]
- {
- new ArgIn(),
- new ArgColors(),
- new ArgMode(),
- new ArgBgColor(),
- new ArgEnablePalettes(),
- new ArgPalette(),
- new ArgPatternOffset(),
- new ArgAttributeTableYOffset(),
- new ArgSharePatternTable(),
- new ArgLossy(),
- new ArgOutPreview(),
- new ArgOutPalette(),
- new ArgOutPatternTable(),
- new ArgOutNameTable(),
- new ArgOutAttributeTable(),
- new ArgOutTilesCsv(),
- new ArgOutPalettesCsv(),
- new ArgOutColorsTable(),
- new ArgQuiet()
- };
- }
-
- class ArgIn : IArg
- {
- public const string S = "i";
- public const string L = "in";
- public string? Params { get; } = "<filename>[:offset[:height]]";
- public string Description { get; } = "input filename number #, optionally cropped vertically";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgColors : IArg
- {
- public const string S = "c";
- public const string L = "colors";
- public string? Params { get; } = "<filename>";
- public string Description { get; } = $"JSON or PAL file with the list of available colors\n(default - {Config.DEFAULT_COLORS_FILE})";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgMode : IArg
- {
- public const string S = "m";
- public const string L = "mode";
- public string? Params { get; } = "bg|sprites8x8|sprites8x16";
- public string Description { get; } = "mode: backgrounds, 8x8 sprites or 8x16 sprites (default - bg)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgBgColor : IArg
- {
- public const string S = "b";
- public const string L = "bg-color";
- public string? Params { get; } = "<color>";
- public string Description { get; } = "background color in HTML color format (default - auto)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgEnablePalettes : IArg
- {
- public const string S = "e";
- public const string L = "enable-palettes";
- public string? Params { get; } = "<palettes>";
- public string Description { get; } = "zero-based comma separated list of palette numbers to use\n(default - 0,1,2,3)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgPalette : IArg
- {
- public const string S = "p";
- public const string L = "palette";
- public string? Params { get; } = "<colors>";
- public string Description { get; } = "comma separated list of colors to use in palette number #\n(default - auto)";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgPatternOffset : IArg
- {
- public const string S = "o";
- public const string L = "pattern-offset";
- public string? Params { get; } = "<tile_index>";
- public string Description { get; } = "first tile index for pattern table for file number # (default - 0)";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgAttributeTableYOffset : IArg
- {
- public const string S = "y";
- public const string L = "attribute-table-y-offset";
- public string? Params { get; } = "<pixels>";
- public string Description { get; } = "vertical offset for attribute table in pixels (default - 0)";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgSharePatternTable : IArg
- {
- public const string S = "s";
- public const string L = "share-pattern-table";
- public string? Params { get; } = null;
- public string Description { get; } = "vertical offset for attribute table in pixels (default - 0)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgLossy : IArg
- {
- public const string S = "l";
- public const string L = "lossy";
- public string? Params { get; } = "<level>";
- public string Description { get; } = "lossy level: 0-3, defines how many color distortion is allowed\nwithout throwing an error (default - 2)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutPreview : IArg
- {
- public const string S = "v";
- public const string L = "out-preview";
- public string? Params { get; } = "<filename.png>";
- public string Description { get; } = "output filename for preview of image number #";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutPalette : IArg
- {
- public const string S = "t";
- public const string L = "out-palette";
- public string? Params { get; } = "<filename>";
- public string Description { get; } = "output filename for palette number #";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutPatternTable : IArg
- {
- public const string S = "n";
- public const string L = "out-pattern-table";
- public string? Params { get; } = "<filename>";
- public string Description { get; } = "output filename for pattern table of image number #";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutNameTable : IArg
- {
- public const string S = "a";
- public const string L = "out-name-table";
- public string? Params { get; } = "<filename>";
- public string Description { get; } = "output filename for nametable of image number #";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutAttributeTable : IArg
- {
- public const string S = "u";
- public const string L = "out-attribute-table";
- public string? Params { get; } = "<filename>";
- public string Description { get; } = "output filename for attribute table of image number #";
- public bool HasIndex { get; } = true;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutTilesCsv : IArg
- {
- public const string S = "z";
- public const string L = "out-tiles-csv";
- public string? Params { get; } = "<filename.csv>";
- public string Description { get; } = "output filename for tiles info in CSV format";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutPalettesCsv : IArg
- {
- public const string S = "x";
- public const string L = "out-palettes-csv";
- public string? Params { get; } = "<filename.csv>";
- public string Description { get; } = "output filename for palettes info in CSV format";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgOutColorsTable : IArg
- {
- public const string S = "g";
- public const string L = "out-colors-table";
- public string? Params { get; } = "<filename.png>";
- public string Description { get; } = "output filename for graphical table of available colors\n(from \"--colors\" option)";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-
- class ArgQuiet : IArg
- {
- public const string S = "q";
- public const string L = "quiet";
- public string? Params { get; } = null;
- public string Description { get; } = "suppress console output";
- public bool HasIndex { get; } = false;
- public string Short { get; } = S;
- public string Long { get; } = L;
- }
-}
+namespace com.clusterrr.Famicom.NesTiler
+{
+ interface IArg
+ {
+ public string Short { get; }
+ public string Long { get; }
+ public string? Params { get; }
+ public string Description { get; }
+ public bool HasIndex { get; }
+
+ public static IArg[] Args = new IArg[]
+ {
+ new ArgIn(),
+ new ArgColors(),
+ new ArgMode(),
+ new ArgBgColor(),
+ new ArgEnablePalettes(),
+ new ArgPalette(),
+ new ArgPatternOffset(),
+ new ArgAttributeTableYOffset(),
+ new ArgSharePatternTable(),
+ new ArgLossy(),
+ new ArgOutPreview(),
+ new ArgOutPalette(),
+ new ArgOutPatternTable(),
+ new ArgOutNameTable(),
+ new ArgOutAttributeTable(),
+ new ArgOutTilesCsv(),
+ new ArgOutPalettesCsv(),
+ new ArgOutColorsTable(),
+ new ArgQuiet()
+ };
+ }
+
+ class ArgIn : IArg
+ {
+ public const string S = "i";
+ public const string L = "in";
+ public string? Params { get; } = "<filename>[:offset[:height]]";
+ public string Description { get; } = "input filename number #, optionally cropped vertically";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgColors : IArg
+ {
+ public const string S = "c";
+ public const string L = "colors";
+ public string? Params { get; } = "<filename>";
+ public string Description { get; } = $"JSON or PAL file with the list of available colors\n(default - {Config.DEFAULT_COLORS_FILE})";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgMode : IArg
+ {
+ public const string S = "m";
+ public const string L = "mode";
+ public string? Params { get; } = "bg|sprites8x8|sprites8x16";
+ public string Description { get; } = "mode: backgrounds, 8x8 sprites or 8x16 sprites (default - bg)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgBgColor : IArg
+ {
+ public const string S = "b";
+ public const string L = "bg-color";
+ public string? Params { get; } = "<color>";
+ public string Description { get; } = "background color in HTML color format (default - auto)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgEnablePalettes : IArg
+ {
+ public const string S = "e";
+ public const string L = "enable-palettes";
+ public string? Params { get; } = "<palettes>";
+ public string Description { get; } = "zero-based comma separated list of palette numbers to use\n(default - 0,1,2,3)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgPalette : IArg
+ {
+ public const string S = "p";
+ public const string L = "palette";
+ public string? Params { get; } = "<colors>";
+ public string Description { get; } = "comma separated list of colors to use in palette number #\n(default - auto)";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgPatternOffset : IArg
+ {
+ public const string S = "o";
+ public const string L = "pattern-offset";
+ public string? Params { get; } = "<tile_index>";
+ public string Description { get; } = "first tile index for pattern table for file number # (default - 0)";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgAttributeTableYOffset : IArg
+ {
+ public const string S = "y";
+ public const string L = "attribute-table-y-offset";
+ public string? Params { get; } = "<pixels>";
+ public string Description { get; } = "vertical offset for attribute table in pixels (default - 0)";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgSharePatternTable : IArg
+ {
+ public const string S = "s";
+ public const string L = "share-pattern-table";
+ public string? Params { get; } = null;
+ public string Description { get; } = "vertical offset for attribute table in pixels (default - 0)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgLossy : IArg
+ {
+ public const string S = "l";
+ public const string L = "lossy";
+ public string? Params { get; } = "<level>";
+ public string Description { get; } = "lossy level: 0-3, defines how many color distortion is allowed\nwithout throwing an error (default - 2)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutPreview : IArg
+ {
+ public const string S = "v";
+ public const string L = "out-preview";
+ public string? Params { get; } = "<filename.png>";
+ public string Description { get; } = "output filename for preview of image number #";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutPalette : IArg
+ {
+ public const string S = "t";
+ public const string L = "out-palette";
+ public string? Params { get; } = "<filename>";
+ public string Description { get; } = "output filename for palette number #";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutPatternTable : IArg
+ {
+ public const string S = "n";
+ public const string L = "out-pattern-table";
+ public string? Params { get; } = "<filename>";
+ public string Description { get; } = "output filename for pattern table of image number #";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutNameTable : IArg
+ {
+ public const string S = "a";
+ public const string L = "out-name-table";
+ public string? Params { get; } = "<filename>";
+ public string Description { get; } = "output filename for nametable of image number #";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutAttributeTable : IArg
+ {
+ public const string S = "u";
+ public const string L = "out-attribute-table";
+ public string? Params { get; } = "<filename>";
+ public string Description { get; } = "output filename for attribute table of image number #";
+ public bool HasIndex { get; } = true;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutTilesCsv : IArg
+ {
+ public const string S = "z";
+ public const string L = "out-tiles-csv";
+ public string? Params { get; } = "<filename.csv>";
+ public string Description { get; } = "output filename for tiles info in CSV format";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutPalettesCsv : IArg
+ {
+ public const string S = "x";
+ public const string L = "out-palettes-csv";
+ public string? Params { get; } = "<filename.csv>";
+ public string Description { get; } = "output filename for palettes info in CSV format";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgOutColorsTable : IArg
+ {
+ public const string S = "g";
+ public const string L = "out-colors-table";
+ public string? Params { get; } = "<filename.png>";
+ public string Description { get; } = "output filename for graphical table of available colors\n(from \"--colors\" option)";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+
+ class ArgQuiet : IArg
+ {
+ public const string S = "q";
+ public const string L = "quiet";
+ public string? Params { get; } = null;
+ public string Description { get; } = "suppress console output";
+ public bool HasIndex { get; } = false;
+ public string Short { get; } = S;
+ public string Long { get; } = L;
+ }
+}
diff --git a/NesTiler/ColorFinder.cs b/NesTiler/ColorFinder.cs
index 257e9b1..0a615cb 100644
--- a/NesTiler/ColorFinder.cs
+++ b/NesTiler/ColorFinder.cs
@@ -1,194 +1,194 @@
-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 = { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F };
-
- 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, 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, 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, SKColor>();
- for (byte c = 0; c < 64; c++)
- {
- var color = new SKColor(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 > 0x3F) 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
- {
- var color = ColorTranslator.FromHtml(kv.Value); ;
- return new SKColor(color.R, color.G, color.B);
- }
- 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;
- }
-
- /// <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(SKColor color)
- {
- if (cache.ContainsKey(color))
- return cache[color];
- byte result = byte.MaxValue;
- double minDelta = double.MaxValue;
- SKColor c = SKColors.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;
- }
-
- /// <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 SKColor FindSimilarColor(IEnumerable<SKColor> colors, SKColor color)
- {
- SKColor result = SKColors.Black;
- double minDelta = double.MaxValue;
- foreach (var c in colors)
- {
- var delta = color.GetDelta(c);
- if (delta < minDelta)
- {
- minDelta = delta;
- result = c;
- }
- }
- return result;
- }
-
- /// <summary>
- /// Find most similar color from NES colors
- /// </summary>
- /// <param name="color">Input colo</param>
- /// <returns>Output color</returns>
- public SKColor FindSimilarColor(SKColor 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++)
- {
- SKColor color;
- SKPaint paint;
- if (Colors.TryGetValue((byte)((y * colorColumns) + x), out color))
- {
- paint = new SKPaint() { Color = color };
- canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint);
-
- color = new SKColor((byte)(0xFF - color.Red), (byte)(0xFF - color.Green), (byte)(0xFF - color.Blue)); // invert color
- paint = new SKPaint()
- {
- Color = color,
- 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());
- }
- }
-}
+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 = { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F };
+
+ 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, 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, 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, SKColor>();
+ for (byte c = 0; c < 64; c++)
+ {
+ var color = new SKColor(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 > 0x3F) 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
+ {
+ var color = ColorTranslator.FromHtml(kv.Value); ;
+ return new SKColor(color.R, color.G, color.B);
+ }
+ 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;
+ }
+
+ /// <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(SKColor color)
+ {
+ if (cache.ContainsKey(color))
+ return cache[color];
+ byte result = byte.MaxValue;
+ double minDelta = double.MaxValue;
+ SKColor c = SKColors.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;
+ }
+
+ /// <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 SKColor FindSimilarColor(IEnumerable<SKColor> colors, SKColor color)
+ {
+ SKColor result = SKColors.Black;
+ double minDelta = double.MaxValue;
+ foreach (var c in colors)
+ {
+ var delta = color.GetDelta(c);
+ if (delta < minDelta)
+ {
+ minDelta = delta;
+ result = c;
+ }
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Find most similar color from NES colors
+ /// </summary>
+ /// <param name="color">Input colo</param>
+ /// <returns>Output color</returns>
+ public SKColor FindSimilarColor(SKColor 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++)
+ {
+ SKColor color;
+ SKPaint paint;
+ if (Colors.TryGetValue((byte)((y * colorColumns) + x), out color))
+ {
+ paint = new SKPaint() { Color = color };
+ canvas.DrawRegion(new SKRegion(new SKRectI(x * colorSize, y * colorSize, (x + 1) * colorSize, (y + 1) * colorSize)), paint);
+
+ color = new SKColor((byte)(0xFF - color.Red), (byte)(0xFF - color.Green), (byte)(0xFF - color.Blue)); // invert color
+ paint = new SKPaint()
+ {
+ Color = color,
+ 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 927bf76..9c1c8f4 100644
--- a/NesTiler/Config.cs
+++ b/NesTiler/Config.cs
@@ -1,277 +1,277 @@
-using SkiaSharp;
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace com.clusterrr.Famicom.NesTiler
-{
- class Config
- {
- public const string DEFAULT_COLORS_FILE = @"nestiler-colors.json";
-
- public enum TilesMode
- {
- Backgrounds,
- Sprites8x8,
- Sprites8x16
- }
-
- public string ColorsFile { get; private set; }
- public Dictionary<int, string> ImageFiles { get; private set; } = new Dictionary<int, string>();
- 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;
- public int TileWidth { get; private set; } = 8;
- public int TileHeight { get; private set; } = 8;
- public int TilePalWidth { get; private set; } = 16;
- public int TilePalHeight { get; private set; } = 16;
- public bool SharePatternTable { get; private set; } = false;
- public int LossyLevel { get; private set; } = 2;
- public int PatternTableStartOffsetShared { get; private set; } = 0;
- public Dictionary<int, int> PatternTableStartOffsets { get; private set; } = new Dictionary<int, int>();
- public Dictionary<int, int> PattributeTableYOffsets { get; private set; } = new Dictionary<int, int>();
- public bool Quiet { get; private set; } = false;
-
- // Filenames
- public Dictionary<int, string> OutPreview { get; private set; } = new Dictionary<int, string>();
- public Dictionary<int, string> OutPalette { get; private set; } = new Dictionary<int, string>();
- public Dictionary<int, string> OutPatternTable { get; private set; } = new Dictionary<int, string>();
- public Dictionary<int, string> OutNameTable { get; private set; } = new Dictionary<int, string>();
- public Dictionary<int, string> OutAttributeTable { get; private set; } = new Dictionary<int, string>();
- public string? OutPatternTableShared { get; private set; } = null;
- public string? OutTilesCsv { get; private set; } = null;
- public string? OutPalettesCsv { get; private set; } = null;
- public string? OutColorsTable { get; private set; } = null;
-
- private Config()
- {
- ColorsFile = Path.Combine(AppContext.BaseDirectory, DEFAULT_COLORS_FILE);
- if (!File.Exists(ColorsFile))
- ColorsFile = Path.Combine(Directory.GetCurrentDirectory(), DEFAULT_COLORS_FILE);
- if (!File.Exists(ColorsFile) && !OperatingSystem.IsWindows())
- ColorsFile = Path.Combine("/etc", DEFAULT_COLORS_FILE);
- }
-
- public static Config Parse(string[] args)
- {
- Config config = new Config();
- var paramRegex = new Regex(@"^--?(?<param>[a-zA-Z-]+?)-?(?<index>[0-9]*)$");
- for (int i = 0; i < args.Length; i++)
- {
- var match = paramRegex.Match(args[i]);
- if (!match.Success)
- throw new ArgumentException($"Invalid argument.", args[i]);
- string param = match.Groups["param"].Value;
- string indexStr = match.Groups["index"].Value;
- int indexNum = 0;
- if (!string.IsNullOrEmpty(indexStr))
- indexNum = int.Parse(indexStr);
- string value = i < args.Length - 1 ? args[i + 1] : "";
- int valueInt;
- switch (param)
- {
- case ArgIn.S:
- case ArgIn.L:
- config.ImageFiles[indexNum] = value;
- i++;
- break;
- case ArgColors.S:
- case ArgColors.L:
- config.ColorsFile = value;
- i++;
- break;
- case ArgMode.S:
- case ArgMode.L:
- switch (value.ToLower())
- {
- case "sprite":
- case "sprites":
- case "sprites8x8":
- config.Mode = TilesMode.Sprites8x8;
- config.TileWidth = 8;
- config.TileHeight = 8;
- config.TilePalWidth = 8;
- config.TilePalHeight = 8;
- break;
- case "sprite8x16":
- case "sprites8x16":
- config.Mode = TilesMode.Sprites8x16;
- config.TileWidth = 8;
- config.TileHeight = 16;
- config.TilePalWidth = 8;
- config.TilePalHeight = 16;
- break;
- case "bg":
- case "background":
- case "backgrounds":
- config.Mode = TilesMode.Backgrounds;
- config.TileWidth = 8;
- config.TileHeight = 8;
- config.TilePalWidth = 16;
- config.TilePalHeight = 16;
- break;
- default:
- throw new ArgumentException($"{value} - invalid mode.", param);
- }
- i++;
- break;
- case ArgBgColor.S:
- case ArgBgColor.L:
- if (value != "auto")
- {
- try
- {
- config.BgColor = ColorTranslator.FromHtml(value).ToSKColor();
- }
- catch (FormatException)
- {
- throw new ArgumentException($"{value} - invalid color.", param);
- }
- }
- i++;
- break;
- case ArgEnablePalettes.S:
- case ArgEnablePalettes.L:
- {
- var paletteNumbersStr = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
- for (int pal = 0; pal < config.PaletteEnabled.Length; pal++)
- config.PaletteEnabled[pal] = false; // disable all palettes
- foreach (var palNumStr in paletteNumbersStr)
- {
- if (!int.TryParse(palNumStr, out valueInt))
- throw new ArgumentException($"\"{palNumStr}\" is not valid integer value.", param);
- if (valueInt < 0 || valueInt > 3)
- throw new ArgumentException($"Palette index must be between 0 and 3.", param);
- config.PaletteEnabled[valueInt] = true;
- }
- if (!config.PaletteEnabled.Where(p => p).Any()) // will never be executed?
- throw new ArgumentException($"You need to enable at least one palette.", param);
- }
- i++;
- break;
- case ArgPalette.S:
- case ArgPalette.L:
- {
- var colors = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c =>
- {
- try
- {
- return ColorTranslator.FromHtml(c).ToSKColor();
- }
- catch (FormatException)
- {
- throw new ArgumentException($"{c} - invalid color.", param);
- }
- });
- config.FixedPalettes[indexNum] = new Palette(colors);
- }
- i++;
- break;
- case ArgPatternOffset.S:
- case ArgPatternOffset.L:
- if (!int.TryParse(value, out valueInt))
- throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
- if (valueInt < 0 || valueInt >= 256)
- throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
- config.PatternTableStartOffsets[indexNum] = valueInt;
- config.PatternTableStartOffsetShared = config.PatternTableStartOffsets[indexNum];
- i++;
- break;
- case ArgAttributeTableYOffset.S:
- case ArgAttributeTableYOffset.L:
- if (!int.TryParse(value, out valueInt))
- throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
- if (valueInt % 8 != 0)
- throw new ArgumentException($"Value ({valueInt}) must be divisible by 8.", param);
- if (valueInt < 0 || valueInt >= 256)
- throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
- config.PattributeTableYOffsets[indexNum] = valueInt;
- i++;
- break;
- case ArgSharePatternTable.S:
- case ArgSharePatternTable.L:
- config.SharePatternTable = true;
- break;
- case ArgLossy.S:
- case ArgLossy.L:
- if (!int.TryParse(value, out valueInt))
- throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
- if (valueInt < 0 || valueInt > 3)
- throw new ArgumentException($"Value ({valueInt}) must be between 0 and 3.", param);
- config.LossyLevel = valueInt;
- i++;
- break;
- case ArgOutPreview.S:
- case ArgOutPreview.L:
- config.OutPreview[indexNum] = value;
- i++;
- break;
- case ArgOutPalette.S:
- case ArgOutPalette.L:
- if (indexNum < 0 || indexNum > 3)
- throw new ArgumentException($"Palette index must be between 0 and 3.", param);
- config.OutPalette[indexNum] = value;
- i++;
- break;
- case ArgOutPatternTable.S:
- case ArgOutPatternTable.L:
- config.OutPatternTable[indexNum] = value;
- config.OutPatternTableShared = value;
- i++;
- break;
- case ArgOutNameTable.S:
- case ArgOutNameTable.L:
- config.OutNameTable[indexNum] = value;
- i++;
- break;
- case ArgOutAttributeTable.S:
- case ArgOutAttributeTable.L:
- config.OutAttributeTable[indexNum] = value;
- i++;
- break;
- case ArgOutTilesCsv.S:
- case ArgOutTilesCsv.L:
- config.OutTilesCsv = value;
- i++;
- break;
- case ArgOutPalettesCsv.S:
- case ArgOutPalettesCsv.L:
- config.OutPalettesCsv = value;
- i++;
- break;
- case ArgOutColorsTable.S:
- case ArgOutColorsTable.L:
- config.OutColorsTable = value;
- i++;
- break;
- case ArgQuiet.S:
- case ArgQuiet.L:
- config.Quiet = true;
- break;
- default:
- throw new ArgumentException($"Unknown argument.", args[i]);
- }
- }
-
- // Some input data checks
- switch (config.Mode)
- {
- case TilesMode.Sprites8x8:
- case TilesMode.Sprites8x16:
- if (!config.BgColor.HasValue) throw new InvalidDataException("You must specify background color for sprites mode.");
- break;
- }
- // Check output files
- foreach (var c in new Dictionary<int, string>[] { config.OutPreview, config.OutPatternTable, config.OutNameTable, config.OutAttributeTable })
- foreach (var f in c)
- if (!config.ImageFiles.ContainsKey(f.Key))
- throw new ArgumentException($"Can't write {f.Value} - there is no input image with index {f.Key}.");
-
- return config;
- }
- }
-}
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace com.clusterrr.Famicom.NesTiler
+{
+ class Config
+ {
+ public const string DEFAULT_COLORS_FILE = @"nestiler-colors.json";
+
+ public enum TilesMode
+ {
+ Backgrounds,
+ Sprites8x8,
+ Sprites8x16
+ }
+
+ public string ColorsFile { get; private set; }
+ public Dictionary<int, string> ImageFiles { get; private set; } = new Dictionary<int, string>();
+ 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;
+ public int TileWidth { get; private set; } = 8;
+ public int TileHeight { get; private set; } = 8;
+ public int TilePalWidth { get; private set; } = 16;
+ public int TilePalHeight { get; private set; } = 16;
+ public bool SharePatternTable { get; private set; } = false;
+ public int LossyLevel { get; private set; } = 2;
+ public int PatternTableStartOffsetShared { get; private set; } = 0;
+ public Dictionary<int, int> PatternTableStartOffsets { get; private set; } = new Dictionary<int, int>();
+ public Dictionary<int, int> PattributeTableYOffsets { get; private set; } = new Dictionary<int, int>();
+ public bool Quiet { get; private set; } = false;
+
+ // Filenames
+ public Dictionary<int, string> OutPreview { get; private set; } = new Dictionary<int, string>();
+ public Dictionary<int, string> OutPalette { get; private set; } = new Dictionary<int, string>();
+ public Dictionary<int, string> OutPatternTable { get; private set; } = new Dictionary<int, string>();
+ public Dictionary<int, string> OutNameTable { get; private set; } = new Dictionary<int, string>();
+ public Dictionary<int, string> OutAttributeTable { get; private set; } = new Dictionary<int, string>();
+ public string? OutPatternTableShared { get; private set; } = null;
+ public string? OutTilesCsv { get; private set; } = null;
+ public string? OutPalettesCsv { get; private set; } = null;
+ public string? OutColorsTable { get; private set; } = null;
+
+ private Config()
+ {
+ ColorsFile = Path.Combine(AppContext.BaseDirectory, DEFAULT_COLORS_FILE);
+ if (!File.Exists(ColorsFile))
+ ColorsFile = Path.Combine(Directory.GetCurrentDirectory(), DEFAULT_COLORS_FILE);
+ if (!File.Exists(ColorsFile) && !OperatingSystem.IsWindows())
+ ColorsFile = Path.Combine("/etc", DEFAULT_COLORS_FILE);
+ }
+
+ public static Config Parse(string[] args)
+ {
+ Config config = new Config();
+ var paramRegex = new Regex(@"^--?(?<param>[a-zA-Z-]+?)-?(?<index>[0-9]*)$");
+ for (int i = 0; i < args.Length; i++)
+ {
+ var match = paramRegex.Match(args[i]);
+ if (!match.Success)
+ throw new ArgumentException($"Invalid argument.", args[i]);
+ string param = match.Groups["param"].Value;
+ string indexStr = match.Groups["index"].Value;
+ int indexNum = 0;
+ if (!string.IsNullOrEmpty(indexStr))
+ indexNum = int.Parse(indexStr);
+ string value = i < args.Length - 1 ? args[i + 1] : "";
+ int valueInt;
+ switch (param)
+ {
+ case ArgIn.S:
+ case ArgIn.L:
+ config.ImageFiles[indexNum] = value;
+ i++;
+ break;
+ case ArgColors.S:
+ case ArgColors.L:
+ config.ColorsFile = value;
+ i++;
+ break;
+ case ArgMode.S:
+ case ArgMode.L:
+ switch (value.ToLower())
+ {
+ case "sprite":
+ case "sprites":
+ case "sprites8x8":
+ config.Mode = TilesMode.Sprites8x8;
+ config.TileWidth = 8;
+ config.TileHeight = 8;
+ config.TilePalWidth = 8;
+ config.TilePalHeight = 8;
+ break;
+ case "sprite8x16":
+ case "sprites8x16":
+ config.Mode = TilesMode.Sprites8x16;
+ config.TileWidth = 8;
+ config.TileHeight = 16;
+ config.TilePalWidth = 8;
+ config.TilePalHeight = 16;
+ break;
+ case "bg":
+ case "background":
+ case "backgrounds":
+ config.Mode = TilesMode.Backgrounds;
+ config.TileWidth = 8;
+ config.TileHeight = 8;
+ config.TilePalWidth = 16;
+ config.TilePalHeight = 16;
+ break;
+ default:
+ throw new ArgumentException($"{value} - invalid mode.", param);
+ }
+ i++;
+ break;
+ case ArgBgColor.S:
+ case ArgBgColor.L:
+ if (value != "auto")
+ {
+ try
+ {
+ config.BgColor = ColorTranslator.FromHtml(value).ToSKColor();
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException($"{value} - invalid color.", param);
+ }
+ }
+ i++;
+ break;
+ case ArgEnablePalettes.S:
+ case ArgEnablePalettes.L:
+ {
+ var paletteNumbersStr = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ for (int pal = 0; pal < config.PaletteEnabled.Length; pal++)
+ config.PaletteEnabled[pal] = false; // disable all palettes
+ foreach (var palNumStr in paletteNumbersStr)
+ {
+ if (!int.TryParse(palNumStr, out valueInt))
+ throw new ArgumentException($"\"{palNumStr}\" is not valid integer value.", param);
+ if (valueInt < 0 || valueInt > 3)
+ throw new ArgumentException($"Palette index must be between 0 and 3.", param);
+ config.PaletteEnabled[valueInt] = true;
+ }
+ if (!config.PaletteEnabled.Where(p => p).Any()) // will never be executed?
+ throw new ArgumentException($"You need to enable at least one palette.", param);
+ }
+ i++;
+ break;
+ case ArgPalette.S:
+ case ArgPalette.L:
+ {
+ var colors = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c =>
+ {
+ try
+ {
+ return ColorTranslator.FromHtml(c).ToSKColor();
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException($"{c} - invalid color.", param);
+ }
+ });
+ config.FixedPalettes[indexNum] = new Palette(colors);
+ }
+ i++;
+ break;
+ case ArgPatternOffset.S:
+ case ArgPatternOffset.L:
+ if (!int.TryParse(value, out valueInt))
+ throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
+ if (valueInt < 0 || valueInt >= 256)
+ throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
+ config.PatternTableStartOffsets[indexNum] = valueInt;
+ config.PatternTableStartOffsetShared = config.PatternTableStartOffsets[indexNum];
+ i++;
+ break;
+ case ArgAttributeTableYOffset.S:
+ case ArgAttributeTableYOffset.L:
+ if (!int.TryParse(value, out valueInt))
+ throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
+ if (valueInt % 8 != 0)
+ throw new ArgumentException($"Value ({valueInt}) must be divisible by 8.", param);
+ if (valueInt < 0 || valueInt >= 256)
+ throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
+ config.PattributeTableYOffsets[indexNum] = valueInt;
+ i++;
+ break;
+ case ArgSharePatternTable.S:
+ case ArgSharePatternTable.L:
+ config.SharePatternTable = true;
+ break;
+ case ArgLossy.S:
+ case ArgLossy.L:
+ if (!int.TryParse(value, out valueInt))
+ throw new ArgumentException($"\"{value}\" is not valid integer value.", param);
+ if (valueInt < 0 || valueInt > 3)
+ throw new ArgumentException($"Value ({valueInt}) must be between 0 and 3.", param);
+ config.LossyLevel = valueInt;
+ i++;
+ break;
+ case ArgOutPreview.S:
+ case ArgOutPreview.L:
+ config.OutPreview[indexNum] = value;
+ i++;
+ break;
+ case ArgOutPalette.S:
+ case ArgOutPalette.L:
+ if (indexNum < 0 || indexNum > 3)
+ throw new ArgumentException($"Palette index must be between 0 and 3.", param);
+ config.OutPalette[indexNum] = value;
+ i++;
+ break;
+ case ArgOutPatternTable.S:
+ case ArgOutPatternTable.L:
+ config.OutPatternTable[indexNum] = value;
+ config.OutPatternTableShared = value;
+ i++;
+ break;
+ case ArgOutNameTable.S:
+ case ArgOutNameTable.L:
+ config.OutNameTable[indexNum] = value;
+ i++;
+ break;
+ case ArgOutAttributeTable.S:
+ case ArgOutAttributeTable.L:
+ config.OutAttributeTable[indexNum] = value;
+ i++;
+ break;
+ case ArgOutTilesCsv.S:
+ case ArgOutTilesCsv.L:
+ config.OutTilesCsv = value;
+ i++;
+ break;
+ case ArgOutPalettesCsv.S:
+ case ArgOutPalettesCsv.L:
+ config.OutPalettesCsv = value;
+ i++;
+ break;
+ case ArgOutColorsTable.S:
+ case ArgOutColorsTable.L:
+ config.OutColorsTable = value;
+ i++;
+ break;
+ case ArgQuiet.S:
+ case ArgQuiet.L:
+ config.Quiet = true;
+ break;
+ default:
+ throw new ArgumentException($"Unknown argument.", args[i]);
+ }
+ }
+
+ // Some input data checks
+ switch (config.Mode)
+ {
+ case TilesMode.Sprites8x8:
+ case TilesMode.Sprites8x16:
+ if (!config.BgColor.HasValue) throw new InvalidDataException("You must specify background color for sprites mode.");
+ break;
+ }
+ // Check output files
+ foreach (var c in new Dictionary<int, string>[] { config.OutPreview, config.OutPatternTable, config.OutNameTable, config.OutAttributeTable })
+ foreach (var f in c)
+ if (!config.ImageFiles.ContainsKey(f.Key))
+ throw new ArgumentException($"Can't write {f.Value} - there is no input image with index {f.Key}.");
+
+ return config;
+ }
+ }
+}
diff --git a/NesTiler/NesTiler.csproj b/NesTiler/NesTiler.csproj
index 4f12212..40a55ba 100644
--- a/NesTiler/NesTiler.csproj
+++ b/NesTiler/NesTiler.csproj
@@ -4,6 +4,9 @@
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>com.clusterrr.Famicom.NesTiler</RootNamespace>
<AssemblyName>nestiler</AssemblyName>
+ <PublishSingleFile>true</PublishSingleFile>
+ <IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
+ <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>