diff options
author | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2022-10-30 11:54:24 +0300 |
---|---|---|
committer | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2022-10-30 11:54:24 +0300 |
commit | ea686aa66485196cfee45c87bb209b7f59fd18db (patch) | |
tree | 2ecd3f01a2baa98636fda91fe7cc51eb4771e730 | |
parent | 7d6ae6d8d6c134435cb9a57c6d5bda38499d2b12 (diff) |
Pipeline
-rw-r--r-- | .github/workflows/release.yml | 6 | ||||
-rw-r--r-- | NesTiler/CmdArgs.cs | 486 | ||||
-rw-r--r-- | NesTiler/ColorFinder.cs | 388 | ||||
-rw-r--r-- | NesTiler/Config.cs | 554 | ||||
-rw-r--r-- | NesTiler/NesTiler.csproj | 3 |
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>
|