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:
-rw-r--r--Examples/mmc3_split2_animation/Makefile6
-rw-r--r--Examples/mmc3_split4/Makefile6
-rw-r--r--Examples/nrom_group_scroll/Makefile6
-rw-r--r--Examples/nrom_simple/Makefile6
-rw-r--r--Examples/nrom_simple_offset/Makefile6
-rw-r--r--Examples/nrom_split/Makefile6
-rw-r--r--Examples/nrom_split_lossy/Makefile6
-rw-r--r--Examples/sprites8x16/Makefile6
-rw-r--r--Examples/sprites8x8/Makefile6
-rw-r--r--NesTiler/FastBitmap.cs2
-rw-r--r--NesTiler/NesTiler.csproj14
-rw-r--r--NesTiler/Palette.cs21
-rw-r--r--NesTiler/Program.cs263
-rw-r--r--NesTiler/Tile.cs5
14 files changed, 211 insertions, 148 deletions
diff --git a/Examples/mmc3_split2_animation/Makefile b/Examples/mmc3_split2_animation/Makefile
index 4b75587..65c5474 100644
--- a/Examples/mmc3_split2_animation/Makefile
+++ b/Examples/mmc3_split2_animation/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=mmc3_split2_animation.nes
diff --git a/Examples/mmc3_split4/Makefile b/Examples/mmc3_split4/Makefile
index 1933e05..82bf3bf 100644
--- a/Examples/mmc3_split4/Makefile
+++ b/Examples/mmc3_split4/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=mmc3_split4.nes
diff --git a/Examples/nrom_group_scroll/Makefile b/Examples/nrom_group_scroll/Makefile
index 40c988c..7035090 100644
--- a/Examples/nrom_group_scroll/Makefile
+++ b/Examples/nrom_group_scroll/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=nrom_group_scroll.nes
diff --git a/Examples/nrom_simple/Makefile b/Examples/nrom_simple/Makefile
index ad04963..988128a 100644
--- a/Examples/nrom_simple/Makefile
+++ b/Examples/nrom_simple/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=nrom_simple.nes
diff --git a/Examples/nrom_simple_offset/Makefile b/Examples/nrom_simple_offset/Makefile
index 84b2178..39ce9e7 100644
--- a/Examples/nrom_simple_offset/Makefile
+++ b/Examples/nrom_simple_offset/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=nrom_simple.nes
diff --git a/Examples/nrom_split/Makefile b/Examples/nrom_split/Makefile
index 0f90517..865d6f7 100644
--- a/Examples/nrom_split/Makefile
+++ b/Examples/nrom_split/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=nrom_split.nes
diff --git a/Examples/nrom_split_lossy/Makefile b/Examples/nrom_split_lossy/Makefile
index fa1032c..3ead29a 100644
--- a/Examples/nrom_split_lossy/Makefile
+++ b/Examples/nrom_split_lossy/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=nrom_split_lossy.nes
HEIGHT1=128
diff --git a/Examples/sprites8x16/Makefile b/Examples/sprites8x16/Makefile
index d64cec7..da7cbb1 100644
--- a/Examples/sprites8x16/Makefile
+++ b/Examples/sprites8x16/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=sprites8x16.nes
diff --git a/Examples/sprites8x8/Makefile b/Examples/sprites8x8/Makefile
index 80dce3b..de10d49 100644
--- a/Examples/sprites8x8/Makefile
+++ b/Examples/sprites8x8/Makefile
@@ -1,6 +1,6 @@
-NESASM=nesasm
-EMU=fceux64
-TILER=nestiler
+NESASM?=nesasm
+EMU?=fceux64
+TILER?=nestiler
SOURCE=main.asm
EXECUTABLE=sprites8x8.nes
diff --git a/NesTiler/FastBitmap.cs b/NesTiler/FastBitmap.cs
index 70a3382..8ed84e9 100644
--- a/NesTiler/FastBitmap.cs
+++ b/NesTiler/FastBitmap.cs
@@ -25,7 +25,7 @@ namespace com.clusterrr.Famicom.NesTiler
colors = skBitmap.Pixels.Skip(verticalOffset * Width).Take(Width * Height).Select(p => Color.FromArgb(p.Alpha, p.Red, p.Green, p.Blue)).ToArray();
}
- public static FastBitmap Decode(string filename, int verticalOffset = 0, int height = 0)
+ public static FastBitmap? Decode(string filename, int verticalOffset = 0, int height = 0)
{
using var image = SKBitmap.Decode(filename);
if (image == null) return null;
diff --git a/NesTiler/NesTiler.csproj b/NesTiler/NesTiler.csproj
index b50eec4..4f12212 100644
--- a/NesTiler/NesTiler.csproj
+++ b/NesTiler/NesTiler.csproj
@@ -6,12 +6,15 @@
<AssemblyName>nestiler</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <AllowUnsafeBlocks>False</AllowUnsafeBlocks>
+ <DebugType>full</DebugType>
+ <WarningLevel>7</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
+ <WarningLevel>7</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Remove=".git" />
@@ -37,7 +40,7 @@
</ItemGroup>
<ItemGroup>
<None Update="nestiler-colors.json">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
<None Include="..\LICENSE">
@@ -53,15 +56,16 @@
</ItemGroup>
<PropertyGroup>
<DebugType>embedded</DebugType>
- <AssemblyVersion>0.1.0.0</AssemblyVersion>
- <FileVersion>0.1.0.0</FileVersion>
- <Version>0.1.0</Version>
+ <AssemblyVersion>1.0.0.0</AssemblyVersion>
+ <FileVersion>1.0.0.0</FileVersion>
+ <Version>1.0.0</Version>
<RepositoryUrl>https://github.com/ClusterM/nestiler</RepositoryUrl>
<Description>Tool for converting pictures into NES format: generating pattern tables, palettes, name tables</Description>
<Company>Alexey "Cluster" Avdyukhin</Company>
<Authors>Alexey "Cluster" Avdyukhin</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Product>NesTiler</Product>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="make commit buildtime&#xD;&#xA;" />
diff --git a/NesTiler/Palette.cs b/NesTiler/Palette.cs
index 0dc31ff..ff4c3c5 100644
--- a/NesTiler/Palette.cs
+++ b/NesTiler/Palette.cs
@@ -92,31 +92,32 @@ namespace com.clusterrr.Famicom.NesTiler
Color2 = bgColor
};
if (deltaCache.ContainsKey(pair)) return deltaCache[pair];
- var ac = Enumerable.Concat(colors.Where(c => c.HasValue).Select(c => c.Value), new Color[] { bgColor });
+ var ac = Enumerable.Concat(colors.Where(c => c.HasValue).Select(c => c!.Value), new Color[] { bgColor });
var result = ac.OrderBy(c => c.GetDelta(color)).First();
var r = (result, result.GetDelta(color));
deltaCache[pair] = r;
return r;
}
- public bool Equals(Palette other)
+ public bool Equals(Palette? other)
{
+ if (other == null) return false;
var colors1 = colors.Where(c => c.HasValue)
- .OrderBy(c => c.Value.ToArgb())
- .Select(c => c.Value)
+ .OrderBy(c => c!.Value.ToArgb())
+ .Select(c => c!.Value)
.ToArray();
var colors2 = new Color?[] { other[1], other[2], other[3] }
.Where(c => c.HasValue)
- .OrderBy(c => c.Value.ToArgb())
- .Select(c => c.Value)
+ .OrderBy(c => c!.Value.ToArgb())
+ .Select(c => c!.Value)
.ToArray();
- return Enumerable.SequenceEqual<Color>(colors1, colors2);
+ return Enumerable.SequenceEqual(colors1, colors2);
}
public bool Contains(Palette other)
{
var thisColors = colors.Where(c => c.HasValue);
- var otherColors = new Color?[] { other[1], other[2], other[3] }.Where(c => c.HasValue).Select(c => c.Value);
+ var otherColors = new Color?[] { other[1], other[2], other[3] }.Where(c => c.HasValue).Select(c => c!.Value);
foreach (var color in otherColors)
{
@@ -128,7 +129,7 @@ namespace com.clusterrr.Famicom.NesTiler
public IEnumerator<Color> GetEnumerator()
{
- return colors.Where(c => c.HasValue).Select(c => c.Value).GetEnumerator();
+ return colors.Where(c => c.HasValue).Select(c => c!.Value).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
@@ -136,7 +137,7 @@ namespace com.clusterrr.Famicom.NesTiler
return GetEnumerator();
}
- public override string ToString() => string.Join(", ", colors.Where(c => c.HasValue).Select(c => ColorTranslator.ToHtml(c.Value)).OrderBy(c => c));
+ public override string ToString() => string.Join(", ", colors.Where(c => c.HasValue).Select(c => ColorTranslator.ToHtml(c!.Value)).OrderBy(c => c));
public override int GetHashCode()
{
diff --git a/NesTiler/Program.cs b/NesTiler/Program.cs
index 3b0a8ee..b434e5f 100644
--- a/NesTiler/Program.cs
+++ b/NesTiler/Program.cs
@@ -1,7 +1,9 @@
using SkiaSharp;
using System;
using System.Collections.Generic;
+using System.Data;
using System.Diagnostics;
+using System.Diagnostics.Contracts;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -19,7 +21,7 @@ namespace com.clusterrr.Famicom.NesTiler
const int MAX_BG_COLOR_AUTODETECT_ITERATIONS = 5;
static byte[] FORBIDDEN_COLORS = new byte[] { 0x0D, 0x0E, 0x0F, 0x1E, 0x1F, 0x2E, 0x2F, 0x3E, 0x3F };
- public enum TilesMode
+ enum TilesMode
{
Backgrounds,
Sprites8x8,
@@ -28,7 +30,7 @@ namespace com.clusterrr.Famicom.NesTiler
static void PrintAppInfo()
{
- Console.WriteLine($"NesTiler v{Assembly.GetExecutingAssembly().GetName().Version.Major}.{Assembly.GetExecutingAssembly().GetName().Version.Minor}");
+ Console.WriteLine($"NesTiler v{Assembly.GetExecutingAssembly()?.GetName()?.Version?.Major}.{Assembly.GetExecutingAssembly()?.GetName()?.Version?.Minor}");
Console.WriteLine($" Commit {Properties.Resources.gitCommit} @ {REPO_PATH}");
#if DEBUG
Console.WriteLine($" Debug version, build time: {BUILD_TIME.ToLocalTime()}");
@@ -39,29 +41,30 @@ namespace com.clusterrr.Famicom.NesTiler
static void PrintHelp()
{
- Console.WriteLine($"Usage: {Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName)} <options>");
+ Console.WriteLine($"Usage: {Path.GetFileName(Process.GetCurrentProcess()?.MainModule?.FileName)} <options>");
Console.WriteLine();
Console.WriteLine("Available options:");
- Console.WriteLine(" {0,-40}{1}", "--in-<#> <file>[:offset[:height]]", "input file number #, optionally cropped vertically");
- Console.WriteLine(" {0,-40}{1}", "--colors <file>", $"JSON or PAL file with the list of available colors (default - {DEFAULT_COLORS_FILE})");
- Console.WriteLine(" {0,-40}{1}", "--mode bg|sprite8x8|sprite8x16", "mode: backgrounds, 8x8 sprites or 8x16 sprites (default - bg)");
- Console.WriteLine(" {0,-40}{1}", "--bg-color <color>", "background color in HTML color format (default - autodetected)");
- Console.WriteLine(" {0,-40}{1}", "--enable-palettes <palettes>", "zero-based comma separated list of palette numbers to use (default - 0,1,2,3)");
- Console.WriteLine(" {0,-40}{1}", "--palette-<#>", "comma separated list of colors to use in palette number # (default - auto)");
- Console.WriteLine(" {0,-40}{1}", "--pattern-offset-<#>", "first tile ID for pattern table for file number # (default - 0)");
- Console.WriteLine(" {0,-40}{1}", "--attribute-table-y-offset-#", "vertical offset for attribute table in pixels");
- Console.WriteLine(" {0,-40}{1}", "--share-pattern-table", "use one pattern table for all images");
- Console.WriteLine(" {0,-40}{1}", "--ignore-tiles-range", "option to disable tile ID overflow check");
- Console.WriteLine(" {0,-40}{1}", "--lossy", "option to ignore palettes loss, produces distorted image if there are too many colors");
- Console.WriteLine(" {0,-40}{1}", "--out-preview-<#> <file.png>", "output filename for preview of image number #");
- Console.WriteLine(" {0,-40}{1}", "--out-palette-<#> <file>", "output filename for palette number #");
- Console.WriteLine(" {0,-40}{1}", "--out-pattern-table-<#> <file>", "output filename for pattern table of image number #");
- Console.WriteLine(" {0,-40}{1}", "--out-name-table-<#> <file>", "output filename for nametable of image number #");
- Console.WriteLine(" {0,-40}{1}", "--out-attribute-table-<#> <file>", "output filename for attribute table of image number #");
- Console.WriteLine(" {0,-40}{1}", "--out-tiles-csv <file.csv>", "output filename for tiles info in CSV format");
- Console.WriteLine(" {0,-40}{1}", "--out-palettes-csv <file.csv>", "output filename for palettes info in CSV format");
- Console.WriteLine(" {0,-40}{1}", "--out-colors-table <file.png>", "output filename for graphical table of available colors (from \"--colors\" option)");
- Console.WriteLine(" {0,-40}{1}", "--quiet", "suppress console output");
+ // TODO: move options to constants
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-i#,", "--in-<#> <file>[:offset[:height]]", "input file number #, optionally cropped vertically");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-c,", "--colors <file>", $"JSON or PAL file with the list of available colors (default - {DEFAULT_COLORS_FILE})");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-m,", "--mode bg|sprites8x8|sprites8x16", "mode: backgrounds, 8x8 sprites or 8x16 sprites (default - bg)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-b,", "--bg-color <color>", "background color in HTML color format (default - autodetected)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-e,", "--enable-palettes <palettes>", "zero-based comma separated list of palette numbers to use (default - 0,1,2,3)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-p#,", "--palette-<#>", "comma separated list of colors to use in palette number # (default - auto)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-o#,", "--pattern-offset-<#>", "first tile ID for pattern table for file number # (default - 0)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-y#,", "--attribute-table-y-offset-#", "vertical offset for attribute table in pixels");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-s,", "--share-pattern-table", "use one pattern table for all images");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-r,", "--ignore-tiles-range", "option to disable tile ID overflow check");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-l,", "--lossy", "option to ignore palettes loss, produces distorted image if there are too many colors");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-v#,", "--out-preview-<#> <file.png>", "output filename for preview of image number #");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-t#,", "--out-palette-<#> <file>", "output filename for palette number #");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-n#,", "--out-pattern-table-<#> <file>", "output filename for pattern table of image number #");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-a#,", "--out-name-table-<#> <file>", "output filename for nametable of image number #");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-u#,", "--out-attribute-table-<#> <file>", "output filename for attribute table of image number #");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-z,", "--out-tiles-csv <file.csv>", "output filename for tiles info in CSV format");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-x,", "--out-palettes-csv <file.csv>", "output filename for palettes info in CSV format");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-g,", "--out-colors-table <file.png>", "output filename for graphical table of available colors (from \"--colors\" option)");
+ Console.WriteLine("{0,-4} {1,-40}{2}", "-q,", "--quiet", "suppress console output");
}
public static int Main(string[] args)
@@ -82,8 +85,8 @@ namespace com.clusterrr.Famicom.NesTiler
colorsFile = Path.Combine("/etc", DEFAULT_COLORS_FILE);
var imageFiles = new Dictionary<int, string>();
Color? bgColor = null;
- var paletteEnabled = new bool[4] { true, true, true, true };
- var fixedPalettes = new Palette[4] { null, null, null, null };
+ bool[] paletteEnabled = new bool[4] { true, true, true, true };
+ Palette?[] fixedPalettes = new Palette?[4] { null, null, null, null };
var mode = TilesMode.Backgrounds;
int tileWidth = 8;
int tileHeight = 8;
@@ -101,12 +104,12 @@ namespace com.clusterrr.Famicom.NesTiler
var outPreview = new Dictionary<int, string>();
var outPalette = new Dictionary<int, string>();
var outPatternTable = new Dictionary<int, string>();
- string outPatternTableShared = null;
+ string? outPatternTableShared = null;
var outNameTable = new Dictionary<int, string>();
var outAttributeTable = new Dictionary<int, string>();
- string outTilesCsv = null;
- string outPalettesCsv = null;
- string outColorsTable = null;
+ string? outTilesCsv = null;
+ string? outPalettesCsv = null;
+ string? outColorsTable = null;
var console = (string text) => { if (!quiet) Console.WriteLine(text); };
// Data
@@ -125,7 +128,7 @@ namespace com.clusterrr.Famicom.NesTiler
{
var match = paramRegex.Match(args[i]);
if (!match.Success)
- throw new ArgumentException($"invalid argument", args[i]);
+ throw new ArgumentException($"Invalid argument.", args[i]);
string param = match.Groups["param"].Value;
string indexStr = match.Groups["index"].Value;
int indexNum = 0;
@@ -142,10 +145,12 @@ namespace com.clusterrr.Famicom.NesTiler
i++;
nothingToDo = false;
break;
+ case "c":
case "colors":
colorsFile = value;
i++;
break;
+ case "m":
case "mode":
switch (value.ToLower())
{
@@ -176,10 +181,11 @@ namespace com.clusterrr.Famicom.NesTiler
tilePalHeight = 16;
break;
default:
- throw new ArgumentException($"{value} - invalid mode", param);
+ throw new ArgumentException($"{value} - invalid mode.", param);
}
i++;
break;
+ case "b":
case "bgcolor":
case "bg-color":
case "background-color":
@@ -188,13 +194,15 @@ namespace com.clusterrr.Famicom.NesTiler
try
{
bgColor = ColorTranslator.FromHtml(value);
- } catch (FormatException)
+ }
+ catch (FormatException)
{
- throw new ArgumentException($"{value} - invalid color", param);
+ throw new ArgumentException($"{value} - invalid color.", param);
}
}
i++;
break;
+ case "e":
case "enable-palettes":
{
var paletteNumbersStr = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
@@ -203,16 +211,17 @@ namespace com.clusterrr.Famicom.NesTiler
foreach (var palNumStr in paletteNumbersStr)
{
if (!int.TryParse(palNumStr, out valueInt))
- throw new ArgumentException($"\"{palNumStr}\" is not valid integer value", param);
+ 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);
+ throw new ArgumentException($"Palette index must be between 0 and 3.", param);
paletteEnabled[valueInt] = true;
}
if (!paletteEnabled.Where(p => p).Any()) // will never be executed?
- throw new ArgumentException($"you need to enable at least one palette", param);
+ throw new ArgumentException($"You need to enable at least one palette.", param);
}
i++;
break;
+ case "p":
case "palette":
{
var colors = value.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(c => ColorTranslator.FromHtml(c));
@@ -220,44 +229,62 @@ namespace com.clusterrr.Famicom.NesTiler
}
i++;
break;
+ case "o":
case "pattern-offset":
if (!int.TryParse(value, out valueInt))
- throw new ArgumentException($"\"{valueInt}\" is not valid integer value", param);
+ throw new ArgumentException($"\"{valueInt}\" is not valid integer value.", param);
if (valueInt < 0 || valueInt >= 256)
- throw new ArgumentException($"value ({valueInt}) must between 0 and 255", param);
+ throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
patternTableStartOffsets[indexNum] = valueInt;
patternTableStartOffsetShared = patternTableStartOffsets[indexNum];
i++;
break;
+ case "y":
case "attribute-table-y-offset":
if (!int.TryParse(value, out valueInt))
- throw new ArgumentException($"\"{valueInt}\" is not valid integer value", param);
+ throw new ArgumentException($"\"{valueInt}\" is not valid integer value.", param);
if (valueInt % 8 != 0)
- throw new ArgumentException($"value ({valueInt}) must be divisible by 8", param);
+ throw new ArgumentException($"Value ({valueInt}) must be divisible by 8.", param);
if (valueInt < 0 || valueInt >= 256)
- throw new ArgumentException($"value ({valueInt}) must between 0 and 255", param);
+ throw new ArgumentException($"Value ({valueInt}) must be between 0 and 255.", param);
attributeTableYOffsets[indexNum] = valueInt;
i++;
break;
+ case "s":
case "share-pattern-table":
sharePatternTable = true;
break;
+ case "r":
+ case "ignoretilesrange":
+ case "ignore-tiles-range":
+ ignoreTilesRange = true;
+ break;
+ case "l":
+ case "lossy":
+ lossy = true;
+ break;
+ case "v":
case "out-preview":
case "output-preview":
outPreview[indexNum] = value;
i++;
break;
+ case "t":
case "out-palette":
case "output-palette":
+ if (indexNum < 0 || indexNum > 3)
+ throw new ArgumentException($"Palette index must be between 0 and 3.", param);
outPalette[indexNum] = value;
i++;
break;
+ case "n":
case "out-pattern-table":
case "output-pattern-table":
outPatternTable[indexNum] = value;
outPatternTableShared = value;
i++;
break;
+ case "a":
case "out-name-table":
case "output-name-table":
case "out-nametable":
@@ -265,37 +292,34 @@ namespace com.clusterrr.Famicom.NesTiler
outNameTable[indexNum] = value;
i++;
break;
+ case "u":
case "out-attribute-table":
case "output-attribute-table":
outAttributeTable[indexNum] = value;
i++;
break;
- case "ignoretilesrange":
- case "ignore-tiles-range":
- ignoreTilesRange = true;
- break;
- case "lossy":
- lossy = true;
- break;
+ case "z":
case "out-tiles-csv":
outTilesCsv = value;
i++;
break;
+ case "x":
case "out-palettes-csv":
outPalettesCsv = value;
i++;
break;
+ case "g":
case "out-colors-table":
outColorsTable = value;
i++;
nothingToDo = false;
break;
- case "quiet":
case "q":
+ case "quiet":
quiet = true;
break;
default:
- throw new ArgumentException($"unknown argument", args[i]);
+ throw new ArgumentException($"Unknown argument.", args[i]);
}
}
@@ -308,16 +332,23 @@ namespace com.clusterrr.Famicom.NesTiler
return 1;
}
- if (!quiet) PrintAppInfo();
+ if (!quiet)
+ {
+ PrintAppInfo();
+ Trace.Listeners.Clear();
+ Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
+ Trace.AutoFlush = true;
+ }
// Some input data checks
switch (mode)
{
case TilesMode.Sprites8x8:
case TilesMode.Sprites8x16:
- if (!bgColor.HasValue) throw new InvalidDataException("you must specify background color for sprites");
+ if (!bgColor.HasValue) throw new InvalidDataException("You must specify background color for sprites.");
break;
}
+ // TODO: more input checks
// Loading and parsing palette JSON
var nesColors = LoadColors(colorsFile);
@@ -326,7 +357,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (outColorsTable != null)
{
- console($"Writing color tables to {outColorsTable}...");
+ Trace.WriteLine($"Writing color tables to {outColorsTable}...");
WriteColorsTable(nesColors, outColorsTable);
}
@@ -337,7 +368,7 @@ namespace com.clusterrr.Famicom.NesTiler
for (int i = 0; i < fixedPalettes.Length; i++)
{
if (fixedPalettes[i] == null) continue;
- var colorsInPalette = fixedPalettes[i].ToArray();
+ var colorsInPalette = fixedPalettes[i]!.ToArray();
for (int j = 0; j < colorsInPalette.Length; j++)
colorsInPalette[j] = nesColors[FindSimilarColor(nesColors, colorsInPalette[j], nesColorsCache)];
fixedPalettes[i] = new Palette(colorsInPalette);
@@ -346,7 +377,7 @@ namespace com.clusterrr.Famicom.NesTiler
// Loading images
foreach (var imageFile in imageFiles)
{
- console($"Loading file #{imageFile.Key} - {Path.GetFileName(imageFile.Value)}...");
+ Trace.WriteLine($"Loading image #{imageFile.Key} - {Path.GetFileName(imageFile.Value)}...");
var offsetRegex = new Regex(@"^(?<filename>.*?)(:(?<offset>[0-9]+)(:(?<height>[0-9]+))?)?$");
var match = offsetRegex.Match(imageFile.Value);
var filename = match.Groups["filename"].Value;
@@ -360,20 +391,20 @@ namespace com.clusterrr.Famicom.NesTiler
offset = int.Parse(offsetS);
if (!string.IsNullOrEmpty(heightS)) height = int.Parse(heightS);
}
- if (!File.Exists(filename)) throw new FileNotFoundException($"file not found", filename);
+ if (!File.Exists(filename)) throw new FileNotFoundException($"Could not find file '{filename}'.", filename);
var image = FastBitmap.Decode(filename, offset, height);
- if (image == null) throw new InvalidDataException($"can't load {filename}");
+ if (image == null) throw new InvalidDataException($"Can't load {filename}.");
images[imageFile.Key] = image;
- if (mode == TilesMode.Backgrounds && image.Width != 256) throw new ArgumentException("image width must be 256 for backgrounds mode", filename);
- if (image.Width % tileWidth != 0) throw new ArgumentException($"image width must be divisible by {tileWidth}", filename);
- if (image.Height % tileHeight != 0) throw new ArgumentException($"image height must be divisible by {tileHeight}", filename);
+ if (mode == TilesMode.Backgrounds && image.Width != 256) throw new ArgumentException("Image width must be 256 for backgrounds mode.", filename);
+ if (image.Width % tileWidth != 0) throw new ArgumentException($"Image width must be divisible by {tileWidth}.", filename);
+ if (image.Height % tileHeight != 0) throw new ArgumentException($"Image height must be divisible by {tileHeight}.", filename);
}
// Change all colors in the images to colors from the NES palette
foreach (var imageNum in images.Keys)
{
- console($"Adjusting colors for file #{imageNum} - {imageFiles[imageNum]}...");
+ Trace.WriteLine($"Adjusting colors for image #{imageNum}...");
var image = images[imageNum];
for (int y = 0; y < image.Height; y++)
{
@@ -387,7 +418,7 @@ namespace com.clusterrr.Famicom.NesTiler
}
else
{
- if (!bgColor.HasValue) throw new InvalidDataException("you must specify background color for images with transparency");
+ if (!bgColor.HasValue) throw new InvalidDataException("You must specify background color for images with transparency.");
image.SetPixelColor(x, y, bgColor.Value);
}
}
@@ -406,7 +437,7 @@ namespace com.clusterrr.Famicom.NesTiler
else
{
// Autodetect most used color
- console($"Background color autodetect... ");
+ Trace.Write($"Background color autodetect... ");
Dictionary<Color, int> colorPerTileCounter = new Dictionary<Color, int>();
foreach (var imageNum in images.Keys)
{
@@ -447,16 +478,16 @@ namespace com.clusterrr.Famicom.NesTiler
// Select background color which uses minimum palettes
var kv = calcResults.OrderBy(kv => kv.Value.Length).First();
(bgColor, calculatedPalettes) = (kv.Key, kv.Value.ToList());
- console(ColorTranslator.ToHtml(bgColor.Value));
+ Trace.WriteLine(ColorTranslator.ToHtml(bgColor.Value));
}
if (calculatedPalettes.Count > maxCalculatedPaletteCount && !lossy)
{
- throw new ArgumentOutOfRangeException($"can't fit {calculatedPalettes.Count} palettes - {maxCalculatedPaletteCount} is maximum");
+ throw new ArgumentOutOfRangeException($"Can't fit {calculatedPalettes.Count} palettes - {maxCalculatedPaletteCount} is maximum.");
}
// Select palettes
- var palettes = new Palette[4] { null, null, null, null };
+ var palettes = new Palette?[4] { null, null, null, null };
outPalettesCsvLines?.Add("palette_id,color0,color1,color2,color3");
for (var i = 0; i < palettes.Length; i++)
{
@@ -481,9 +512,9 @@ namespace com.clusterrr.Famicom.NesTiler
if (palettes[i] != null)
{
- console($"Palette #{i}: {ColorTranslator.ToHtml(bgColor.Value)}(BG) {string.Join(" ", palettes[i].Select(p => ColorTranslator.ToHtml(p)))}");
+ Trace.WriteLine($"Palette #{i}: {ColorTranslator.ToHtml(bgColor.Value)}(BG) {string.Join(" ", palettes[i]!.Select(p => ColorTranslator.ToHtml(p)))}");
// Write CSV if required
- outPalettesCsvLines?.Add($"{i},{ColorTranslator.ToHtml(bgColor.Value)},{string.Join(",", Enumerable.Range(1, 3).Select(c => (palettes[i][c] != null ? ColorTranslator.ToHtml(palettes[i][c].Value) : "")))}");
+ outPalettesCsvLines?.Add($"{i},{ColorTranslator.ToHtml(bgColor.Value)},{string.Join(",", Enumerable.Range(1, 3).Select(c => (palettes[i]![c] != null ? ColorTranslator.ToHtml(palettes[i]![c]!.Value) : "")))}");
}
}
}
@@ -500,18 +531,18 @@ namespace com.clusterrr.Famicom.NesTiler
{
if (palettes[p] == null)
paletteRaw[c] = 0;
- else if (palettes[p][c].HasValue)
- paletteRaw[c] = FindSimilarColor(nesColors, palettes[p][c].Value, nesColorsCache);
+ else if (palettes[p]![c].HasValue)
+ paletteRaw[c] = FindSimilarColor(nesColors, palettes[p]![c]!.Value, nesColorsCache);
}
File.WriteAllBytes(outPalette[p], paletteRaw);
- console($"Palette #{p} saved to {outPalette[p]}");
+ Trace.WriteLine($"Palette #{p} saved to {outPalette[p]}");
}
}
// Select palette for each tile/sprite and recolorize using them
foreach (var imageNum in images.Keys)
{
- console($"Mapping palettes for file #{imageNum} - {imageFiles[imageNum]}...");
+ Trace.WriteLine($"Mapping palettes for image #{imageNum}...");
var image = images[imageNum];
int attributeTableOffset;
attributeTableYOffsets.TryGetValue(imageNum, out attributeTableOffset);
@@ -527,7 +558,7 @@ namespace com.clusterrr.Famicom.NesTiler
for (byte paletteIndex = 0; paletteIndex < palettes.Length; paletteIndex++)
{
if (palettes[paletteIndex] == null) continue;
- double delta = palettes[paletteIndex].GetTileDelta(
+ double delta = palettes[paletteIndex]!.GetTileDelta(
image, tilePalX * tilePalWidth, (tilePalY * tilePalHeight) - attributeTableOffset,
tilePalWidth, tilePalHeight, bgColor.Value);
// Find palette with most similar colors
@@ -537,7 +568,7 @@ namespace com.clusterrr.Famicom.NesTiler
bestPaletteIndex = paletteIndex;
}
}
- Palette bestPalette = palettes[bestPaletteIndex];
+ Palette bestPalette = palettes[bestPaletteIndex]!; // at least one palette enabled, so can't be null here
// Remember palette index
paletteIndexes[imageNum][tilePalX, tilePalY] = bestPaletteIndex;
@@ -566,7 +597,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (outPreview.ContainsKey(imageNum))
{
File.WriteAllBytes(outPreview[imageNum], image.Encode(SKEncodedImageFormat.Png, 0));
- console($"Preview #{imageNum} saved to {outPreview[imageNum]}");
+ Trace.WriteLine($"Preview #{imageNum} saved to {outPreview[imageNum]}");
}
}
@@ -574,8 +605,8 @@ namespace com.clusterrr.Famicom.NesTiler
foreach (var imageNum in outAttributeTable.Keys)
{
if (mode != TilesMode.Backgrounds)
- throw new InvalidOperationException("attribute table generation available for backgrounds mode only");
- console($"Creating attribute table for file #{imageNum} - {Path.GetFileName(imageFiles[imageNum])}...");
+ throw new InvalidOperationException("Attribute table generation available for backgrounds mode only.");
+ Trace.WriteLine($"Creating attribute table for image #{imageNum}...");
var image = images[imageNum];
int attributeTableOffset;
attributeTableYOffsets.TryGetValue(imageNum, out attributeTableOffset);
@@ -616,7 +647,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (outAttributeTable.ContainsKey(imageNum))
{
File.WriteAllBytes(outAttributeTable[imageNum], attributeTableRaw.ToArray());
- console($"Attribute table #{imageNum} saved to {outAttributeTable[imageNum]}");
+ Trace.WriteLine($"Attribute table #{imageNum} saved to {outAttributeTable[imageNum]}");
}
}
@@ -624,7 +655,7 @@ namespace com.clusterrr.Famicom.NesTiler
outTilesCsvLines?.Add("image_id,image_file,line,column,tile_x,tile_y,tile_width,tile_height,tile_id,palette_id");
foreach (var imageNum in images.Keys)
{
- console($"Creating pattern table for file #{imageNum} - {Path.GetFileName(imageFiles[imageNum])}...");
+ Trace.WriteLine($"Creating pattern table for image #{imageNum}...");
var image = images[imageNum];
int attributeTableOffset;
attributeTableYOffsets.TryGetValue(imageNum, out attributeTableOffset);
@@ -660,7 +691,7 @@ namespace com.clusterrr.Famicom.NesTiler
if (color != bgColor)
{
colorIndex = 1;
- while (palette[colorIndex] != color) colorIndex++;
+ while (palette![colorIndex] != color) colorIndex++;
}
tileData[(y * tileWidth) + x] = colorIndex;
}
@@ -685,13 +716,13 @@ namespace com.clusterrr.Famicom.NesTiler
}
}
if (sharePatternTable && tileID > patternTableStartOffsetShared)
- console($"#{imageNum} tiles range: {patternTableStartOffsetShared}-{tileID - 1}");
+ Trace.WriteLine($"#{imageNum} tiles range: {patternTableStartOffsetShared}-{tileID - 1}");
else if (tileID > patternTableStartOffsets[imageNum])
- console($"#{imageNum} tiles range: {patternTableStartOffsets[imageNum]}-{tileID - 1}");
+ Trace.WriteLine($"#{imageNum} tiles range: {patternTableStartOffsets[imageNum]}-{tileID - 1}");
else
- console($"Pattern table is empty");
+ Trace.WriteLine($"Pattern table is empty.");
if (tileID > 256 && !ignoreTilesRange)
- throw new ArgumentOutOfRangeException("Tiles out of range");
+ throw new ArgumentOutOfRangeException("Tiles out of range.");
// Save pattern table to file
if (outPatternTable.ContainsKey(imageNum) && !sharePatternTable)
@@ -704,21 +735,21 @@ namespace com.clusterrr.Famicom.NesTiler
patternTableRaw.AddRange(raw);
}
File.WriteAllBytes(outPatternTable[imageNum], patternTableRaw.ToArray());
- console($"Pattern table #{imageNum} saved to {outPatternTable[imageNum]}");
+ Trace.WriteLine($"Pattern table #{imageNum} saved to {outPatternTable[imageNum]}");
}
// Save nametable to file
if (outNameTable.ContainsKey(imageNum))
{
if (mode != TilesMode.Backgrounds)
- throw new InvalidOperationException("Nametable table generation available for backgrounds mode only");
+ throw new InvalidOperationException("Nametable table generation available for backgrounds mode only.");
File.WriteAllBytes(outNameTable[imageNum], nameTable.Select(i => (byte)i).ToArray());
- console($"Nametable #{imageNum} saved to {outNameTable[imageNum]}");
+ Trace.WriteLine($"Nametable #{imageNum} saved to {outNameTable[imageNum]}");
}
}
// Save shared pattern table to file
- if (sharePatternTable)
+ if (sharePatternTable && outPatternTableShared != null)
{
var patternTableReversed = patternTables[0].ToDictionary(kv => kv.Value, kv => kv.Key);
var patternTableRaw = new List<byte>();
@@ -728,16 +759,16 @@ namespace com.clusterrr.Famicom.NesTiler
patternTableRaw.AddRange(raw);
}
File.WriteAllBytes(outPatternTableShared, patternTableRaw.ToArray());
- console($"Pattern table saved to {outPatternTableShared}");
+ Trace.WriteLine($"Pattern table saved to {outPatternTableShared}");
}
// Save CSV tiles report
- if (outTilesCsvLines != null)
+ if (outTilesCsv != null && outTilesCsvLines != null)
{
File.WriteAllLines(outTilesCsv, outTilesCsvLines);
}
// Save CSV palettes report
- if (outPalettesCsv != null)
+ if (outPalettesCsv != null && outPalettesCsvLines != null)
{
File.WriteAllLines(outPalettesCsv, outPalettesCsvLines);
}
@@ -746,17 +777,17 @@ namespace com.clusterrr.Famicom.NesTiler
}
catch (ArgumentException ex)
{
- Console.Error.WriteLine($"{ex.ParamName}: {ex.Message}");
+ Console.Error.WriteLine($"Error. {ex.Message}");
return 1;
}
- catch (FileNotFoundException ex)
+ catch (JsonException ex)
{
- Console.Error.WriteLine($"{ex.FileName}: {ex.Message}");
+ Console.Error.WriteLine($"Can't parse JSON: {ex.Message}");
return 1;
}
- catch (Exception ex) when (ex is InvalidDataException || ex is InvalidOperationException || ex is ArgumentOutOfRangeException)
+ catch (Exception ex) when (ex is InvalidDataException || ex is InvalidOperationException || ex is ArgumentOutOfRangeException || ex is FileNotFoundException)
{
- Console.Error.WriteLine($"Error: {ex.Message}");
+ Console.Error.WriteLine($"Error. {ex.Message}");
return 1;
}
catch (Exception ex)
@@ -768,6 +799,7 @@ namespace com.clusterrr.Famicom.NesTiler
private static Dictionary<byte, Color> LoadColors(string filename)
{
+ if (!File.Exists(filename)) throw new FileNotFoundException($"Could not find file '{filename}'.", filename);
var data = File.ReadAllBytes(filename);
Dictionary<byte, Color> nesColors;
// Detect file type
@@ -785,10 +817,35 @@ namespace com.clusterrr.Famicom.NesTiler
{
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 => kv.Key.ToLower().StartsWith("0x") ? (byte)Convert.ToInt32(kv.Key.Substring(2), 16) : byte.Parse(kv.Key),
- kv => ColorTranslator.FromHtml(kv.Value)
- );
+ kv =>
+ {
+ try
+ {
+ var index = kv.Key.ToLower().StartsWith("0x") ? Convert.ToByte(kv.Key.Substring(2), 16) : byte.Parse(kv.Key);
+ // TODO: show some warning?
+ // if (FORBIDDEN_COLORS.Contains(index))
+ if (index > 0x3D) throw new ArgumentException($"{kv.Key} - invalid color index.", filename);
+ return index;
+ }
+ catch (Exception ex) when (ex is FormatException || ex is OverflowException)
+ {
+ throw new ArgumentException($"{kv.Key} - invalid color index.", filename);
+ }
+ },
+ kv =>
+ {
+ try
+ {
+ return ColorTranslator.FromHtml(kv.Value);
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException($"{kv.Value} - invalid color.", filename);
+ }
+ }
+ );
}
// filter out invalid colors;
nesColors = nesColors.Where(kv => !FORBIDDEN_COLORS.Contains(kv.Key)).ToDictionary(kv => kv.Key, kv => kv.Value);
@@ -797,6 +854,7 @@ namespace com.clusterrr.Famicom.NesTiler
static void WriteColorsTable(Dictionary<byte, Color> nesColors, string filename)
{
+ // Export colors to nice table image
const int colorSize = 64;
const int colorColumns = 16;
const int colorRows = 4;
@@ -851,14 +909,13 @@ namespace com.clusterrr.Famicom.NesTiler
File.WriteAllBytes(filename, image.Encode(SKEncodedImageFormat.Png, 0).ToArray());
}
- static Palette[] CalculatePalettes(Dictionary<int, FastBitmap> images, bool[] paletteEnabled, Palette[] fixedPalettes, Dictionary<int, int> attributeTableOffsets, int tilePalWidth, int tilePalHeight, Color bgColor)
+ static Palette[] CalculatePalettes(Dictionary<int, FastBitmap> images, bool[] paletteEnabled, Palette?[] fixedPalettes, Dictionary<int, int> attributeTableOffsets, int tilePalWidth, int tilePalHeight, Color bgColor)
{
var required = Enumerable.Range(0, 4).Select(i => paletteEnabled[i] && fixedPalettes[i] == null);
// Creating and counting the palettes
var paletteCounter = new Dictionary<Palette, int>();
foreach (var imageNum in images.Keys)
{
- // write($"Calculating palettes for file #{imageNum} - {imageFiles[imageNum]}...");
var image = images[imageNum];
int attributeTableOffset;
attributeTableOffsets.TryGetValue(imageNum, out attributeTableOffset);
@@ -949,7 +1006,7 @@ namespace com.clusterrr.Famicom.NesTiler
return result;
}
- static byte FindSimilarColor(Dictionary<byte, Color> colors, Color color, Dictionary<Color, byte> cache = null)
+ static byte FindSimilarColor(Dictionary<byte, Color> colors, Color color, Dictionary<Color, byte>? cache = null)
{
if (cache != null)
{
@@ -970,7 +1027,7 @@ namespace com.clusterrr.Famicom.NesTiler
}
}
if (result == byte.MaxValue)
- throw new KeyNotFoundException("Invalid color: " + color.ToString());
+ throw new KeyNotFoundException($"Invalid color: {color}.");
if (cache != null)
cache[color] = result;
return result;
diff --git a/NesTiler/Tile.cs b/NesTiler/Tile.cs
index f77352a..a33f00f 100644
--- a/NesTiler/Tile.cs
+++ b/NesTiler/Tile.cs
@@ -10,7 +10,7 @@ namespace com.clusterrr.Famicom.NesTiler
public const int Width = 8;
public readonly int Height;
private int? hash;
- private byte[] data = null;
+ private byte[]? data = null;
public Tile(byte[] data, int height)
{
@@ -42,8 +42,9 @@ namespace com.clusterrr.Famicom.NesTiler
return data;
}
- public bool Equals(Tile other)
+ public bool Equals(Tile? other)
{
+ if (other == null) return false;
var data1 = GetAsPatternData();
var data2 = other.GetAsPatternData();
return Enumerable.SequenceEqual(data1, data2);