Mercurial > hg > dmlib
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 { |