comparison tools/gfxconv.c @ 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
comparison
equal deleted inserted replaced
2492:1bd7387984ed 2493:ec036e88a0c2
172 172
173 173
174 static const DMOptArg optList[] = 174 static const DMOptArg optList[] =
175 { 175 {
176 { 0, '?', "help" , "Show this help", OPT_NONE }, 176 { 0, '?', "help" , "Show this help", OPT_NONE },
177 { 3, 0, "longhelp" , "Show a longer help", OPT_NONE },
177 { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE }, 178 { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE },
178 { 2, 'v', "verbose" , "Be more verbose", OPT_NONE }, 179 { 2, 'v', "verbose" , "Be more verbose", OPT_NONE },
179 180
180 { 10, 'o', "output" , "Output filename", OPT_ARGREQ }, 181 { 10, 'o', "output" , "Output filename", OPT_ARGREQ },
181 { 12, 's', "skip" , "Skip N bytes in input from start (negative value will be offset from input end)", OPT_ARGREQ }, 182 { 12, 's', "skip" , "Skip N bytes in input from start (negative value will be offset from input end)", OPT_ARGREQ },
182 { 14, 'i', "informat" , "Set input format (see --formats)", OPT_ARGREQ }, 183 { 14, 'i', "informat" , "Set input format (see --formats)", OPT_ARGREQ },
183 { 16, 'f', "format" , "Set output format (see --formats)", OPT_ARGREQ }, 184 { 16, 'f', "format" , "Set output format (see --formats)", OPT_ARGREQ },
184 { 18, 'F', "formats" , "List supported input/output formats", OPT_NONE }, 185 { 18, 'F', "formats" , "List supported input/output formats", OPT_NONE },
185 { 20, 'q', "sequential" , "Output sequential files (image output only)", OPT_NONE }, 186 { 20, 'q', "sequential" , "Output sequential files (image output only)", OPT_NONE },
186 { 22, 'm', "colormap" , "Set color index mapping (see below for information)", OPT_ARGREQ }, 187 { 22, 'm', "colormap" , "Set color index mapping (see '-m help')", OPT_ARGREQ },
187 { 24, 'n', "numitems" , "How many 'items' to output (default: all)", OPT_ARGREQ }, 188 { 24, 'n', "numitems" , "How many 'items' to output (default: all)", OPT_ARGREQ },
188 { 26, 'w', "width" , "Item width (number of items per row, min 1)", OPT_ARGREQ }, 189 { 26, 'w', "width" , "Item width (number of items per row, min 1)", OPT_ARGREQ },
189 { 28, 'S', "scale" , "Scale output image by specified value(s) (see below)", OPT_ARGREQ }, 190 { 28, 'S', "scale" , "Scale output image by specified value(s) (see '-S help')", OPT_ARGREQ },
190 { 30, 'P', "paletted" , "Use indexed/paletted output IF possible.", OPT_NONE }, 191 { 30, 'P', "paletted" , "Use indexed/paletted output IF possible.", OPT_NONE },
191 { 32, 'N', "nplanes" , "# of bitplanes (some output formats)", OPT_ARGREQ }, 192 { 32, 'N', "nplanes" , "# of bitplanes (some output formats)", OPT_ARGREQ },
192 { 34, 'B', "bpp" , "Bits per plane (some output formats)", OPT_ARGREQ }, 193 { 34, 'B', "bpp" , "Bits per plane (some output formats)", OPT_ARGREQ },
193 { 36, 'I', "interleave" , "Interleaved/planar output (some output formats)", OPT_NONE }, 194 { 36, 'I', "interleave" , "Interleaved/planar output (some output formats)", OPT_NONE },
194 { 38, 'C', "compress" , "Use compression -C <0-9>, 0 = disable, default is 9", OPT_ARGREQ }, 195 { 38, 'C', "compress" , "Use compression -C <0-9>, 0 = disable, default is 9", OPT_ARGREQ },
195 { 42, 'R', "remap" , "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>][+remove] | -R auto[+remove] | -R @map.txt[+remove])", OPT_ARGREQ }, 196 { 42, 'R', "remap" , "Remap output image colors (see '-R help')", OPT_ARGREQ },
196 { 44, 0, "char-rom" , "Set character ROM file to be used.", OPT_ARGREQ }, 197 { 44, 0, "char-rom" , "Set character ROM file to be used.", OPT_ARGREQ },
197 { 46, 'p', "palette" , "Set palette to be used (see list with -p help). " 198 { 46, 'p', "palette" , "Set palette to be used (see '-p help'). "
198 "For paletted image file input, this will replace the " 199 "For paletted image file input, this will replace the "
199 "image's original palette. Color remapping will not be " 200 "image's original palette. Color remapping will not be "
200 "done unless -R option is also specified.", OPT_ARGREQ }, 201 "done unless -R option is also specified.", OPT_ARGREQ },
201 }; 202 };
202 203
230 231
231 argShowC64Formats(stdout, TRUE); 232 argShowC64Formats(stdout, TRUE);
232 } 233 }
233 234
234 235
235 void argShowHelp() 236 void argShowHelp(void)
236 { 237 {
237 dmPrintBanner(stdout, dmProgName, "[options] [<input file>]"); 238 dmPrintBanner(stdout, dmProgName, "[options] [<input file>]");
238 dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2); 239 dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2);
239 240
240 fprintf(stdout, 241 fprintf(stdout,
241 "\n" 242 "\n"
242 "Output image scaling (-S)\n" 243 "Default C64 character ROM file for this build is:\n"
243 "-------------------------\n" 244 "%s\n"
244 "Scaling option '-S <n>', '-S <x>:<y>', '-S <x>:<y>*<n>' can be used to set\n" 245 "\n",
245 "the direct or relative scale integer factor(s). '-S <n>' sets both height\n"
246 "and width scale factor to <n>. '-S <x>:<y>*<n>' scales width by X*n and\n"
247 "height Y*n. Certain input formats set their default aspect/scale factors.\n"
248 "By prepending -S parameters with asterisk ('*') you can scale relative to\n"
249 "those values. (e.g. '-S *2' for example.) NOTE! Only integer scale factors\n"
250 "can be used at the moment.\n"
251 "\n"
252 "Palette remapping (-R)\n"
253 "----------------------\n"
254 "Indexed palette color remapping can be performed via the -R option, either\n"
255 "specifying single colors or filename of file containing remap definitions.\n"
256 "Colors to be remapped can be specified either by their palette index or by\n"
257 "their RGB values as a hex triplet (#rrggbb). Example of a remap definition:\n"
258 "-R #000000:0,#ffffff:1 would remap black and white to indices 0 and 1.\n"
259 "\n"
260 "Remap file can be specified as \"-R @filename\", and it is a text file with\n"
261 "one remap definition per line in same format as above. All empty lines and\n"
262 "lines starting with a semicolor (;) will be ignored as comments. Any extra\n"
263 "whitespace separating items will be ignored as well.\n"
264 "\n"
265 "Optional +remove can be specified (-R <...>+remove), which will remove all\n"
266 "unused colors from the palette. This is not always desirable, for example\n"
267 "when converting multiple images to same palette. You can also specify the\n"
268 "+remove option by itself to remove all unused colors: -R +remove\n"
269 "\n"
270 "Color index mapping (-m)\n"
271 "------------------------\n"
272 "Color index map definitions are used for sprite/char data input (and ANSI\n"
273 "output), to set what colors of the C64 palette are used for each single\n"
274 "color/multi color bit-combination.\n"
275 "For example, if the input is multi color sprite or char, you can define\n"
276 "colors like: -m 0,8,3,15 .. for hires/single color: -m 0,1\n"
277 "The numbers are palette indexes, and the order is for bit(pair)-values\n"
278 "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
279 "special transparency color index; -m 255,2 would use transparency for\n"
280 "'0' bits and and C64 color 2 for '1' bits.\n"
281 "\n"
282 "Default character ROM file for this build is:\n"
283 "%s\n",
284 DM_DEF_CHARGEN 246 DM_DEF_CHARGEN
285 ); 247 );
248 }
249
250
251 const char * argGetHelpTopic(const int opt)
252 {
253 switch (opt)
254 {
255 case 28:
256 return
257 "Output image scaling (-S)\n"
258 "-------------------------\n"
259 "Scaling option '-S <n>', '-S <x>:<y>', '-S <x>:<y>*<n>' can be used to set\n"
260 "the direct or relative scale integer factor(s). '-S <n>' sets both height\n"
261 "and width scale factor to <n>. '-S <x>:<y>*<n>' scales width by X*n and\n"
262 "height Y*n. Certain input formats set their default aspect/scale factors.\n"
263 "By prepending -S parameters with asterisk ('*') you can scale relative to\n"
264 "those values. (e.g. '-S *2' for example.) NOTE! Only integer scale factors\n"
265 "can be used at the moment.\n";
266
267 case 22:
268 return
269 "Color index mapping (-m)\n"
270 "------------------------\n"
271 "Color index map definitions are used for sprite/char data input (and ANSI\n"
272 "output), to set what colors of the C64 palette are used for each single\n"
273 "color/multi color bit-combination.\n"
274 "For example, if the input is multi color sprite or char, you can define\n"
275 "colors like: -m 0,8,3,15 .. for hires/single color: -m 0,1\n"
276 "The numbers are palette indexes, and the order is for bit(pair)-values\n"
277 "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
278 "special transparency color index; -m 255,2 would use transparency for\n"
279 "'0' bits and and C64 color 2 for '1' bits.\n";
280
281 case 42:
282 return
283 "Palette remapping (-R)\n"
284 "----------------------\n"
285 "Indexed palette color remapping can be performed via the '-R' option in\n"
286 "several different ways:\n"
287 "\n"
288 " 1) '-R auto'\n"
289 " will remap input image to a destination palette specified with the\n"
290 " '-p' option (which may be a supported palette file, another paletted\n"
291 " image file, or one of the internal gfxconv palettes, see '-p help')\n"
292 " Modifiers: +alpha, +remove, +max=<n.n>, +nomatch=<n>\n"
293 "\n"
294 " Example: '-R auto+remove -p pepto' would remap the input image to\n"
295 " the internal Pepto's C64 palette.\n"
296 "\n"
297 " 2) '-R <(#RRGGBBaa|index):index>[,<#RRGGBBaa|index>:index]'\n"
298 " can be used to specify single RGB(A) color quadruplets/triplets or\n"
299 " palette indices to be remapped to destination palette indices. Any\n"
300 " Unspecified indices will be automatically mapped. Specifying alpha\n"
301 " channel is optional, and will require +alpha flag to be enabled for\n"
302 " comparisions.\n"
303 " Modifiers: +alpha, +remove, +max=<n.n>, +nomatch=<n>\n"
304 "\n"
305 " Example: '-R #000000:0,#ffffff:1' would map black and white to indices 0 and 1.\n"
306 "\n"
307 " 3) '-R @<filename>'\n"
308 " can be used to specify a mapping file, which is a text file with one\n"
309 " remap definition per line in same format as above. All empty lines and\n"
310 " lines starting with a semicolor (;) will be ignored as comments. Also\n"
311 " any extra whitespace separating items will be ignored as well.\n"
312 " Modifiers: +remove\n"
313 "\n"
314 "Optional modifier flags can be specified as well:\n"
315 "\n"
316 " +remove Remove all unused colors from the resulting palette.\n"
317 "\n"
318 " +alpha Enable alpha value matching in color comparisions.\n"
319 " NOTE! This may result in unexpected behaviour.\n"
320 "\n"
321 " +max=<n.n> Set the maximum color distance/delta acceptable for\n"
322 " matching colors. Default is -1, meaning closest possible\n"
323 " that can be found even if the match is poor. Any value \n"
324 " above 0 will be considered strict limit, see 'nomatch'\n"
325 " modifier below.\n"
326 "\n"
327 " +nomatch=<n>\n"
328 " If no acceptable match is found (see +max modifier)\n"
329 " then use this (<n>) color index. This may have unexpected\n"
330 " results with +remove modifier. The default value of 'n'\n"
331 " is -1, which will result in error if no match is found.\n"
332 "";
333
334 default:
335 return NULL;
336 }
286 } 337 }
287 338
288 339
289 // 340 //
290 // Replace filename extension based on format pattern. 341 // Replace filename extension based on format pattern.
696 { 747 {
697 return dmGetIntVal(dmParseValWithSep(arg, last, dmParseIntValTok, sep), value, NULL); 748 return dmGetIntVal(dmParseValWithSep(arg, last, dmParseIntValTok, sep), value, NULL);
698 } 749 }
699 750
700 751
701 BOOL dmParseValTok(const int ch)
702 {
703 return ch != '+';
704 }
705
706
707 BOOL argHandleOpt(const int optN, char *optArg, char *currArg) 752 BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
708 { 753 {
709 unsigned int tmpUInt; 754 unsigned int tmpUInt;
710 char *tmpStr; 755 char *tmpStr;
711 756
712 switch (optN) 757 switch (optN)
713 { 758 {
714 case 0: 759 case 0:
715 argShowHelp(); 760 argShowHelp();
761 exit(0);
762 break;
763
764 case 3:
765 argShowHelp();
766 argShowFormats();
767 argShowC64PaletteHelp(stdout);
768
769 for (int n = 0; n < optListN; n++)
770 {
771 const char *str = argGetHelpTopic(optList[n].id);
772 if (str != NULL)
773 fprintf(stdout, "\n%s\n", str);
774 }
716 exit(0); 775 exit(0);
717 break; 776 break;
718 777
719 case 1: 778 case 1:
720 dmPrintLicense(stdout); 779 dmPrintLicense(stdout);
1197 fprintf(outFile, "\n"); 1256 fprintf(outFile, "\n");
1198 } 1257 }
1199 } 1258 }
1200 1259
1201 1260
1202 BOOL dmExactCompareColor(const DMColor *c1, const DMColor *c2, const BOOL alpha)
1203 {
1204 if (c1->r == c2->r &&
1205 c1->g == c2->g &&
1206 c1->b == c2->b)
1207 return alpha ? (c1->a == c2->a) : TRUE;
1208 else
1209 return FALSE;
1210 }
1211
1212
1213 // XXX TODO: we need to evaluate the color vector itself, not just the distance 1261 // XXX TODO: we need to evaluate the color vector itself, not just the distance
1214 float dmGetColorDist(const DMColor *c1, const DMColor *c2, const BOOL alpha) 1262 float dmGetColorDist(const DMColor *c1, const DMColor *c2, const BOOL alpha)
1215 { 1263 {
1216 const float 1264 const float
1217 dr = (c1->r - c2->r) / 255.0, 1265 dr = (c1->r - c2->r) / 255.0,
1311 return res; 1359 return res;
1312 } 1360 }
1313 1361
1314 1362
1315 int dmRemapImageColors(DMImage **pdst, const DMImage *src, 1363 int dmRemapImageColors(DMImage **pdst, const DMImage *src,
1316 const DMPalette *dpal, const BOOL alpha, 1364 const DMPalette *dpal,
1317 const float maxDist, const int noMatchColor, 1365 const float maxDist, const int noMatchColor,
1318 const BOOL removeUnused) 1366 const BOOL alpha, const BOOL removeUnused)
1319 { 1367 {
1320 DMPalette *tpal = NULL; 1368 DMPalette *tpal = NULL;
1321 const DMPalette *ppal; 1369 const DMPalette *ppal;
1322 BOOL *used = NULL; 1370 BOOL *used = NULL;
1323 int *mapping = NULL, *mapped = NULL; 1371 int *mapping = NULL, *mapped = NULL;
1508 } 1556 }
1509 1557
1510 1558
1511 int dmMapImageColors(DMImage **pdst, const DMImage *src, 1559 int dmMapImageColors(DMImage **pdst, const DMImage *src,
1512 const DMMapValue *mapTable, const int nmapTable, 1560 const DMMapValue *mapTable, const int nmapTable,
1513 const BOOL removeUnused) 1561 const float maxDist, const int noMatchColor,
1562 const BOOL alpha, const BOOL removeUnused)
1514 { 1563 {
1515 DMPalette *tpal = NULL; 1564 DMPalette *tpal = NULL;
1516 BOOL *mapped = NULL, *used = NULL; 1565 BOOL *mapped = NULL, *used = NULL;
1517 int *mapping = NULL; 1566 int *mapping = NULL;
1518 int nused, res = DMERR_OK; 1567 int nused, res = DMERR_OK;
1568 BOOL fail = FALSE;
1519 1569
1520 if (pdst == NULL || src == NULL || mapTable == NULL) 1570 if (pdst == NULL || src == NULL || mapTable == NULL)
1521 return DMERR_NULLPTR; 1571 return DMERR_NULLPTR;
1522 1572
1523 if (src->pal == NULL || src->pixfmt != DM_PIXFMT_PALETTE) 1573 if (src->pal == NULL || src->pixfmt != DM_PIXFMT_PALETTE)
1547 { 1597 {
1548 dmErrorMsg("Could not allocate memory for remap palette.\n"); 1598 dmErrorMsg("Could not allocate memory for remap palette.\n");
1549 goto out; 1599 goto out;
1550 } 1600 }
1551 1601
1552 dmMsg(1, "Remapping %d input image of %d colors.\n", 1602 dmMsg(1, "Remapping %d input image of %d colors, %s, ",
1553 optNRemapTable, src->pal->ncolors); 1603 optNRemapTable, src->pal->ncolors,
1604 alpha ? "match alpha" : "ignore alpha");
1605
1606 if (noMatchColor < 0)
1607 dmPrint(1, "fail on 'no match'\n");
1608 else
1609 dmPrint(1, "use color #%d if no match\n", noMatchColor);
1554 1610
1555 // Match and mark mapped colors 1611 // Match and mark mapped colors
1556 for (int index = 0; index < nmapTable; index++) 1612 for (int index = 0; index < nmapTable; index++)
1557 { 1613 {
1558 const DMMapValue *map = &mapTable[index]; 1614 const DMMapValue *map = &mapTable[index];
1559 if (map->triplet) 1615 if (map->triplet)
1560 { 1616 {
1561 BOOL found = FALSE; 1617 float closestDist = 1000000, dist = 0;
1618 int closestDC = -1;
1619
1562 for (int n = 0; n < src->pal->ncolors; n++) 1620 for (int n = 0; n < src->pal->ncolors; n++)
1563 { 1621 {
1564 if (dmExactCompareColor(&src->pal->colors[n], &map->color, map->alpha)) 1622 dist = dmGetColorDist(&src->pal->colors[n], &map->color, map->alpha && alpha);
1565 { 1623 if (dist < closestDist)
1566 dmMsg(3, "RGBA match #%02x%02x%02x%02x: %d -> %d\n", 1624 {
1625 closestDist = dist;
1626 closestDC = n;
1627 }
1628 }
1629
1630 // Did we find a close-enough match?
1631 if (maxDist >= 0 && closestDist > maxDist)
1632 {
1633 // No, either error out or use noMatchColor color index
1634 if (noMatchColor < 0)
1635 {
1636 DMColor *dcol = &src->pal->colors[closestDC];
1637
1638 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",
1639 index, map->color.r, map->color.g, map->color.b, map->color.a,
1640 closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist, maxDist);
1641
1642 fail = TRUE;
1643 }
1644 else
1645 {
1646 DMColor *dcol = &src->pal->colors[noMatchColor];
1647 closestDC = noMatchColor;
1648
1649 dmMsg(3, "RGBA noMatch #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n",
1567 map->color.r, map->color.g, map->color.b, map->color.a, 1650 map->color.r, map->color.g, map->color.b, map->color.a,
1568 n, 1651 map->to,
1569 map->to); 1652 closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist);
1570 1653
1571 mapping[n] = map->to; 1654 mapping[closestDC] = map->to;
1572 mapped[map->to] = TRUE; 1655 mapped[map->to] = TRUE;
1573 found = TRUE; 1656 }
1574 } 1657 }
1575 } 1658 else
1576 1659 {
1577 if (!found) 1660 DMColor *dcol = &src->pal->colors[closestDC];
1578 { 1661
1579 dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x\n", 1662 dmMsg(3, "RGBA match #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n",
1580 index, 1663 map->color.r, map->color.g, map->color.b, map->color.a,
1581 map->color.r, map->color.g, map->color.b, map->color.a); 1664 map->to,
1665 closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist);
1666
1667 mapping[closestDC] = map->to;
1668 mapped[map->to] = TRUE;
1582 } 1669 }
1583 } 1670 }
1584 else 1671 else
1585 { 1672 {
1586 dmMsg(3, "Map index: %d -> %d\n", 1673 dmMsg(3, "Map index: %d -> %d\n",
1587 map->from, map->to); 1674 map->from, map->to);
1588 1675
1589 mapping[map->from] = map->to; 1676 mapping[map->from] = map->to;
1590 mapped[map->to] = TRUE; 1677 mapped[map->to] = TRUE;
1591 } 1678 }
1679 }
1680
1681 if (fail)
1682 {
1683 res = DMERR_INVALID_DATA;
1684 goto out;
1592 } 1685 }
1593 1686
1594 // Fill the unmapped colors 1687 // Fill the unmapped colors
1595 if (removeUnused) 1688 if (removeUnused)
1596 { 1689 {