changeset 2493:ec036e88a0c2

More improvements to palette remapping etc.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 28 Apr 2020 20:28:35 +0300
parents 1bd7387984ed
children fcaf2db0cd05
files tools/gfxconv.c
diffstat 1 files changed, 176 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/tools/gfxconv.c	Tue Apr 28 19:26:04 2020 +0300
+++ b/tools/gfxconv.c	Tue Apr 28 20:28:35 2020 +0300
@@ -174,6 +174,7 @@
 static const DMOptArg optList[] =
 {
     {  0, '?', "help"            , "Show this help", OPT_NONE },
+    {  3,   0, "longhelp"        , "Show a longer help", OPT_NONE },
     {  1,   0, "license"         , "Print out this program's license agreement", OPT_NONE },
     {  2, 'v', "verbose"         , "Be more verbose", OPT_NONE },
 
@@ -183,18 +184,18 @@
     { 16, 'f', "format"          , "Set output format (see --formats)", OPT_ARGREQ },
     { 18, 'F', "formats"         , "List supported input/output formats", OPT_NONE },
     { 20, 'q', "sequential"      , "Output sequential files (image output only)", OPT_NONE },
-    { 22, 'm', "colormap"        , "Set color index mapping (see below for information)", OPT_ARGREQ },
+    { 22, 'm', "colormap"        , "Set color index mapping (see '-m help')", OPT_ARGREQ },
     { 24, 'n', "numitems"        , "How many 'items' to output (default: all)", OPT_ARGREQ },
     { 26, 'w', "width"           , "Item width (number of items per row, min 1)", OPT_ARGREQ },
-    { 28, 'S', "scale"           , "Scale output image by specified value(s) (see below)", OPT_ARGREQ },
+    { 28, 'S', "scale"           , "Scale output image by specified value(s) (see '-S help')", OPT_ARGREQ },
     { 30, 'P', "paletted"        , "Use indexed/paletted output IF possible.", OPT_NONE },
     { 32, 'N', "nplanes"         , "# of bitplanes (some output formats)", OPT_ARGREQ },
     { 34, 'B', "bpp"             , "Bits per plane (some output formats)", OPT_ARGREQ },
     { 36, 'I', "interleave"      , "Interleaved/planar output (some output formats)", OPT_NONE },
     { 38, 'C', "compress"        , "Use compression -C <0-9>, 0 = disable, default is 9", OPT_ARGREQ },
-    { 42, 'R', "remap"           , "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>][+remove] | -R auto[+remove] | -R @map.txt[+remove])", OPT_ARGREQ },
+    { 42, 'R', "remap"           , "Remap output image colors (see '-R help')", OPT_ARGREQ },
     { 44,   0, "char-rom"        , "Set character ROM file to be used.", OPT_ARGREQ },
-    { 46, 'p', "palette"         , "Set palette to be used (see list with -p help). "
+    { 46, 'p', "palette"         , "Set palette to be used (see '-p help'). "
                                    "For paletted image file input, this will replace the "
                                    "image's original palette. Color remapping will not be "
                                    "done unless -R option is also specified.", OPT_ARGREQ },
@@ -232,60 +233,110 @@
 }
 
 
-void argShowHelp()
+void argShowHelp(void)
 {
     dmPrintBanner(stdout, dmProgName, "[options] [<input file>]");
     dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2);
 
     fprintf(stdout,
     "\n"
-    "Output image scaling (-S)\n"
-    "-------------------------\n"
-    "Scaling option '-S <n>', '-S <x>:<y>', '-S <x>:<y>*<n>' can be used to set\n"
-    "the direct or relative scale integer factor(s). '-S <n>' sets both height\n"
-    "and width scale factor to <n>. '-S <x>:<y>*<n>' scales width by X*n and\n"
-    "height Y*n. Certain input formats set their default aspect/scale factors.\n"
-    "By prepending -S parameters with asterisk ('*') you can scale relative to\n"
-    "those values. (e.g. '-S *2' for example.) NOTE! Only integer scale factors\n"
-    "can be used at the moment.\n"
-    "\n"
-    "Palette remapping (-R)\n"
-    "----------------------\n"
-    "Indexed palette color remapping can be performed via the -R option, either\n"
-    "specifying single colors or filename of file containing remap definitions.\n"
-    "Colors to be remapped can be specified either by their palette index or by\n"
-    "their RGB values as a hex triplet (#rrggbb). Example of a remap definition:\n"
-    "-R #000000:0,#ffffff:1 would remap black and white to indices 0 and 1.\n"
-    "\n"
-    "Remap file can be specified as \"-R @filename\", and it is a text file with\n"
-    "one remap definition per line in same format as above. All empty lines and\n"
-    "lines starting with a semicolor (;) will be ignored as comments. Any extra\n"
-    "whitespace separating items will be ignored as well.\n"
-    "\n"
-    "Optional +remove can be specified (-R <...>+remove), which will remove all\n"
-    "unused colors from the palette. This is not always desirable, for example\n"
-    "when converting multiple images to same palette. You can also specify the\n"
-    "+remove option by itself to remove all unused colors: -R +remove\n"
-    "\n"
-    "Color index mapping (-m)\n"
-    "------------------------\n"
-    "Color index map definitions are used for sprite/char data input (and ANSI\n"
-    "output), to set what colors of the C64 palette are used for each single\n"
-    "color/multi color bit-combination.\n"
-    "For example, if the input is multi color sprite or char, you can define\n"
-    "colors like: -m 0,8,3,15 .. for hires/single color: -m 0,1\n"
-    "The numbers are palette indexes, and the order is for bit(pair)-values\n"
-    "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
-    "special transparency color index; -m 255,2 would use transparency for\n"
-    "'0' bits and and C64 color 2 for '1' bits.\n"
-    "\n"
-    "Default character ROM file for this build is:\n"
-    "%s\n",
+    "Default C64 character ROM file for this build is:\n"
+    "%s\n"
+    "\n",
     DM_DEF_CHARGEN
     );
 }
 
 
+const char * argGetHelpTopic(const int opt)
+{
+    switch (opt)
+    {
+        case 28:
+            return
+            "Output image scaling (-S)\n"
+            "-------------------------\n"
+            "Scaling option '-S <n>', '-S <x>:<y>', '-S <x>:<y>*<n>' can be used to set\n"
+            "the direct or relative scale integer factor(s). '-S <n>' sets both height\n"
+            "and width scale factor to <n>. '-S <x>:<y>*<n>' scales width by X*n and\n"
+            "height Y*n. Certain input formats set their default aspect/scale factors.\n"
+            "By prepending -S parameters with asterisk ('*') you can scale relative to\n"
+            "those values. (e.g. '-S *2' for example.) NOTE! Only integer scale factors\n"
+            "can be used at the moment.\n";
+
+        case 22:
+            return
+            "Color index mapping (-m)\n"
+            "------------------------\n"
+            "Color index map definitions are used for sprite/char data input (and ANSI\n"
+            "output), to set what colors of the C64 palette are used for each single\n"
+            "color/multi color bit-combination.\n"
+            "For example, if the input is multi color sprite or char, you can define\n"
+            "colors like: -m 0,8,3,15 .. for hires/single color: -m 0,1\n"
+            "The numbers are palette indexes, and the order is for bit(pair)-values\n"
+            "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
+            "special transparency color index; -m 255,2 would use transparency for\n"
+            "'0' bits and and C64 color 2 for '1' bits.\n";
+
+        case 42:
+            return
+            "Palette remapping (-R)\n"
+            "----------------------\n"
+            "Indexed palette color remapping can be performed via the '-R' option in\n"
+            "several different ways:\n"
+            "\n"
+            " 1) '-R auto'\n"
+            "    will remap input image to a destination palette specified with the\n"
+            "    '-p' option (which may be a supported palette file, another paletted\n"
+            "    image file, or one of the internal gfxconv palettes, see '-p help')\n"
+            "    Modifiers: +alpha, +remove, +max=<n.n>, +nomatch=<n>\n"
+            "\n"
+            "    Example: '-R auto+remove -p pepto' would remap the input image to\n"
+            "    the internal Pepto's C64 palette.\n"
+            "\n"
+            " 2) '-R <(#RRGGBBaa|index):index>[,<#RRGGBBaa|index>:index]'\n"
+            "    can be used to specify single RGB(A) color quadruplets/triplets or\n"
+            "    palette indices to be remapped to destination palette indices. Any\n"
+            "    Unspecified indices will be automatically mapped. Specifying alpha\n"
+            "    channel is optional, and will require +alpha flag to be enabled for\n"
+            "    comparisions.\n"
+            "    Modifiers: +alpha, +remove, +max=<n.n>, +nomatch=<n>\n"
+            "\n"
+            "    Example: '-R #000000:0,#ffffff:1' would map black and white to indices 0 and 1.\n"
+            "\n"
+            " 3) '-R @<filename>'\n"
+            "    can be used to specify a mapping file, which is a text file with one\n"
+            "    remap definition per line in same format as above. All empty lines and\n"
+            "    lines starting with a semicolor (;) will be ignored as comments. Also\n"
+            "    any extra whitespace separating items will be ignored as well.\n"
+            "    Modifiers: +remove\n"
+            "\n"
+            "Optional modifier flags can be specified as well:\n"
+            "\n"
+            "  +remove     Remove all unused colors from the resulting palette.\n"
+            "\n"
+            "  +alpha      Enable alpha value matching in color comparisions.\n"
+            "              NOTE! This may result in unexpected behaviour.\n"
+            "\n"
+            "  +max=<n.n>  Set the maximum color distance/delta acceptable for\n"
+            "              matching colors. Default is -1, meaning closest possible\n"
+            "              that can be found even if the match is poor. Any value \n"
+            "              above 0 will be considered strict limit, see 'nomatch'\n"
+            "              modifier below.\n"
+            "\n"
+            "  +nomatch=<n>\n"
+            "              If no acceptable match is found (see +max modifier)\n"
+            "              then use this (<n>) color index. This may have unexpected\n"
+            "              results with +remove modifier. The default value of 'n'\n"
+            "              is -1, which will result in error if no match is found.\n"
+            "";
+
+        default:
+            return NULL;
+    }
+}
+
+
 //
 // Replace filename extension based on format pattern.
 // Usage: res = dm_strdup_fext(orig_filename, "foo_%s.cmp");
@@ -698,12 +749,6 @@
 }
 
 
-BOOL dmParseValTok(const int ch)
-{
-    return ch != '+';
-}
-
-
 BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
 {
     unsigned int tmpUInt;
@@ -716,6 +761,20 @@
             exit(0);
             break;
 
+        case 3:
+            argShowHelp();
+            argShowFormats();
+            argShowC64PaletteHelp(stdout);
+
+            for (int n = 0; n < optListN; n++)
+            {
+                const char *str = argGetHelpTopic(optList[n].id);
+                if (str != NULL)
+                    fprintf(stdout, "\n%s\n", str);
+            }
+            exit(0);
+            break;
+
         case 1:
             dmPrintLicense(stdout);
             exit(0);
@@ -1199,17 +1258,6 @@
 }
 
 
-BOOL dmExactCompareColor(const DMColor *c1, const DMColor *c2, const BOOL alpha)
-{
-    if (c1->r == c2->r &&
-        c1->g == c2->g &&
-        c1->b == c2->b)
-        return alpha ? (c1->a == c2->a) : TRUE;
-    else
-        return FALSE;
-}
-
-
 // XXX TODO: we need to evaluate the color vector itself, not just the distance
 float dmGetColorDist(const DMColor *c1, const DMColor *c2, const BOOL alpha)
 {
@@ -1313,9 +1361,9 @@
 
 
 int dmRemapImageColors(DMImage **pdst, const DMImage *src,
-    const DMPalette *dpal, const BOOL alpha,
+    const DMPalette *dpal,
     const float maxDist, const int noMatchColor,
-    const BOOL removeUnused)
+    const BOOL alpha, const BOOL removeUnused)
 {
     DMPalette *tpal = NULL;
     const DMPalette *ppal;
@@ -1510,12 +1558,14 @@
 
 int dmMapImageColors(DMImage **pdst, const DMImage *src,
     const DMMapValue *mapTable, const int nmapTable,
-    const BOOL removeUnused)
+    const float maxDist, const int noMatchColor,
+    const BOOL alpha, const BOOL removeUnused)
 {
     DMPalette *tpal = NULL;
     BOOL *mapped = NULL, *used = NULL;
     int *mapping = NULL;
     int nused, res = DMERR_OK;
+    BOOL fail = FALSE;
 
     if (pdst == NULL || src == NULL || mapTable == NULL)
         return DMERR_NULLPTR;
@@ -1549,8 +1599,14 @@
         goto out;
     }
 
-    dmMsg(1, "Remapping %d input image of %d colors.\n",
-            optNRemapTable, src->pal->ncolors);
+    dmMsg(1, "Remapping %d input image of %d colors, %s, ",
+        optNRemapTable, src->pal->ncolors,
+        alpha ? "match alpha" : "ignore alpha");
+
+    if (noMatchColor < 0)
+        dmPrint(1, "fail on 'no match'\n");
+    else
+        dmPrint(1, "use color #%d if no match\n", noMatchColor);
 
     // Match and mark mapped colors
     for (int index = 0; index < nmapTable; index++)
@@ -1558,27 +1614,58 @@
         const DMMapValue *map = &mapTable[index];
         if (map->triplet)
         {
-            BOOL found = FALSE;
+            float closestDist = 1000000, dist = 0;
+            int closestDC = -1;
+
             for (int n = 0; n < src->pal->ncolors; n++)
             {
-                if (dmExactCompareColor(&src->pal->colors[n], &map->color, map->alpha))
+                dist = dmGetColorDist(&src->pal->colors[n], &map->color, map->alpha && alpha);
+                if (dist < closestDist)
                 {
-                    dmMsg(3, "RGBA match #%02x%02x%02x%02x: %d -> %d\n",
-                        map->color.r, map->color.g, map->color.b, map->color.a,
-                        n,
-                        map->to);
-
-                    mapping[n] = map->to;
-                    mapped[map->to] = TRUE;
-                    found = TRUE;
+                    closestDist = dist;
+                    closestDC = n;
                 }
             }
 
-            if (!found)
+            // Did we find a close-enough match?
+            if (maxDist >= 0 && closestDist > maxDist)
             {
-                dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x\n",
-                    index,
-                    map->color.r, map->color.g, map->color.b, map->color.a);
+                // No, either error out or use noMatchColor color index
+                if (noMatchColor < 0)
+                {
+                    DMColor *dcol = &src->pal->colors[closestDC];
+
+                    dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x. Closest: #%d (#%02x%02x%02x%02x) [dist=%1.3f > %1.3f]\n",
+                        index, map->color.r, map->color.g, map->color.b, map->color.a,
+                        closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist, maxDist);
+
+                    fail = TRUE;
+                }
+                else
+                {
+                    DMColor *dcol = &src->pal->colors[noMatchColor];
+                    closestDC = noMatchColor;
+
+                    dmMsg(3, "RGBA noMatch #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n",
+                        map->color.r, map->color.g, map->color.b, map->color.a,
+                        map->to,
+                        closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist);
+
+                    mapping[closestDC] = map->to;
+                    mapped[map->to] = TRUE;
+                }
+            }
+            else
+            {
+                DMColor *dcol = &src->pal->colors[closestDC];
+
+                dmMsg(3, "RGBA match #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n",
+                    map->color.r, map->color.g, map->color.b, map->color.a,
+                    map->to,
+                    closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist);
+
+                mapping[closestDC] = map->to;
+                mapped[map->to] = TRUE;
             }
         }
         else
@@ -1591,6 +1678,12 @@
         }
     }
 
+    if (fail)
+    {
+        res = DMERR_INVALID_DATA;
+        goto out;
+    }
+
     // Fill the unmapped colors
     if (removeUnused)
     {