comparison gfxconv.c @ 473:73bfe73553eb

Implement palette remapping option for image outputs.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 07 Nov 2012 00:55:43 +0200
parents 3f5163208ef6
children 95d1facfdb77
comparison
equal deleted inserted replaced
472:0359697eeb46 473:73bfe73553eb
13 #include "libgfx.h" 13 #include "libgfx.h"
14 #include "lib64gfx.h" 14 #include "lib64gfx.h"
15 15
16 //#define UNFINISHED 1 16 //#define UNFINISHED 1
17 17
18 #define DM_MAX_COLORS 256
19
20 #define ASC_NBITS 8
21 #define ASC_NCOLORS 4
22 static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#";
23
18 enum 24 enum
19 { 25 {
20 FFMT_AUTO = 0, 26 FFMT_AUTO = 0,
21 27
22 FFMT_ASCII, 28 FFMT_ASCII,
89 }; 95 };
90 96
91 static const int nconvFormatList = sizeof(convFormatList) / sizeof(convFormatList[0]); 97 static const int nconvFormatList = sizeof(convFormatList) / sizeof(convFormatList[0]);
92 98
93 99
94 #define ASC_NBITS 8 100 typedef struct
95 #define ASC_NCOLORS 4 101 {
96 static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#"; 102 BOOL triplet;
103 DMColor color;
104 int from, to;
105 } DMMapValue;
106
97 107
98 108
99 char *optInFilename = NULL, 109 char *optInFilename = NULL,
100 *optOutFilename = NULL; 110 *optOutFilename = NULL;
101 int optInFormat = FFMT_AUTO, 111 int optInFormat = FFMT_AUTO,
105 optItemCount = -1, 115 optItemCount = -1,
106 optPlanedWidth = 1, 116 optPlanedWidth = 1,
107 optForcedFormat = -1; 117 optForcedFormat = -1;
108 int optInSkip = 0; 118 int optInSkip = 0;
109 BOOL optInMulticolor = FALSE, 119 BOOL optInMulticolor = FALSE,
110 optSequential = FALSE; 120 optSequential = FALSE,
121 optRemapColors = FALSE;
122 int optNRemapTable = 0;
123 DMMapValue optRemapTable[DM_MAX_COLORS];
111 int optColors[C64_MAX_COLORS]; 124 int optColors[C64_MAX_COLORS];
112 125
113 DMImageSpec optSpec = 126 DMImageSpec optSpec =
114 { 127 {
115 .scale = 1, 128 .scale = 1,
125 { 15, 'v', "verbose", "Increase verbosity", OPT_NONE }, 138 { 15, 'v', "verbose", "Increase verbosity", OPT_NONE },
126 { 3, 'o', "output", "Output filename", OPT_ARGREQ }, 139 { 3, 'o', "output", "Output filename", OPT_ARGREQ },
127 { 1, 'i', "informat", "Set input format ([s]prite, [c]har, [b]itmap, [i]mage)", OPT_ARGREQ }, 140 { 1, 'i', "informat", "Set input format ([s]prite, [c]har, [b]itmap, [i]mage)", OPT_ARGREQ },
128 { 2, 'm', "multicolor", "Input is multicolor", OPT_NONE }, 141 { 2, 'm', "multicolor", "Input is multicolor", OPT_NONE },
129 { 4, 's', "skip", "Skip bytes in input", OPT_ARGREQ }, 142 { 4, 's', "skip", "Skip bytes in input", OPT_ARGREQ },
130 { 5, 'f', "format", "Output format (see list below)", OPT_ARGREQ }, 143 { 5, 'f', "format", "Output format (see --formats)", OPT_ARGREQ },
144 { 17, 'F', "formats", "Output format (see list below)", OPT_NONE },
131 { 8, 'q', "sequential", "Output sequential files (image output only)", OPT_NONE }, 145 { 8, 'q', "sequential", "Output sequential files (image output only)", OPT_NONE },
132 { 6, 'c', "colormap", "Color mappings (see below for information)", OPT_ARGREQ }, 146 { 6, 'c', "colormap", "Color mappings (see below for information)", OPT_ARGREQ },
133 { 7, 'n', "numitems", "How many 'items' to view (default: all)", OPT_ARGREQ }, 147 { 7, 'n', "numitems", "How many 'items' to view (default: all)", OPT_ARGREQ },
134 { 9, 'S', "scale", "Scale output by x (image output only)", OPT_ARGREQ }, 148 { 9, 'S', "scale", "Scale output by x (image output only)", OPT_ARGREQ },
135 { 10, 'b', "bformat", "Force input bitmap format (see below)", OPT_ARGREQ }, 149 { 10, 'b', "bformat", "Force input bitmap format (see below)", OPT_ARGREQ },
136 { 11, 'w', "width", "Item width (number of items per row, min 1)", OPT_ARGREQ }, 150 { 11, 'w', "width", "Item width (number of items per row, min 1)", OPT_ARGREQ },
137 { 12, 'P', "paletted", "Use indexed/paletted output (png, pcx output only)", OPT_NONE }, 151 { 12, 'P', "paletted", "Use indexed/paletted output (png, pcx output only)", OPT_NONE },
138 { 13, 'B', "bplanes", "Bits per pixel OR # of bitplanes (certain output formats)", OPT_ARGREQ }, 152 { 13, 'B', "bplanes", "Bits per pixel OR # of bitplanes (certain output formats)", OPT_ARGREQ },
139 { 14, 'I', "interleave", "Interleave scanlines (default: output whole planes)", OPT_NONE }, 153 { 14, 'I', "interleave", "Interleave scanlines (default: output whole planes)", OPT_NONE },
154 { 16, 'R', "remap", "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>] | -R @map.txt)", OPT_ARGREQ },
140 }; 155 };
141 156
142 static const int optListN = sizeof(optList) / sizeof(optList[0]); 157 static const int optListN = sizeof(optList) / sizeof(optList[0]);
143 158
144 159
145 void argShowHelp() 160 void argShowFormats()
146 { 161 {
147 int i; 162 int i;
148
149 dmPrintBanner(stdout, dmProgName, "[options] <input file>");
150 dmArgsPrintHelp(stdout, optList, optListN);
151 163
152 printf("\n" 164 printf("\n"
153 "Available input/output formats:\n" 165 "Available input/output formats:\n"
154 " EXT | I | O | Description\n" 166 " EXT | I | O | Description\n"
155 "------+---+---+--------------------------------\n" 167 "------+---+---+--------------------------------\n"
172 printf("%3d | %-5s | %-15s | %s\n", 184 printf("%3d | %-5s | %-15s | %s\n",
173 i, fmt->extension, 185 i, fmt->extension,
174 dmC64ImageTypeNames[fmt->type], 186 dmC64ImageTypeNames[fmt->type],
175 fmt->name); 187 fmt->name);
176 } 188 }
189 }
190
191
192 void argShowHelp()
193 {
194 dmPrintBanner(stdout, dmProgName, "[options] <input file>");
195 dmArgsPrintHelp(stdout, optList, optListN);
177 196
178 printf( 197 printf(
179 "\n" 198 "\n"
180 "Color map definitions are used for ANSI and image output, to declare what\n" 199 "Color map definitions are used for ANSI and image output, to declare what\n"
181 "output colors of the C64 palette are used for each single color/multi color\n" 200 "output colors of the C64 palette are used for each single color/multi color\n"
182 "bit-combination. For example, if the input is multi color sprite or char,\n" 201 "bit-combination. For example, if the input is multi color sprite or char,\n"
183 "you can define colors like: -c 0:8:3:15 .. for single color: -c 0:1\n" 202 "you can define colors like: -c 0,8,3,15 .. for single color: -c 0,1\n"
184 "The numbers are palette indexes, and the order is for bit(pair)-values\n" 203 "The numbers are palette indexes, and the order is for bit(pair)-values\n"
185 "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n" 204 "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
186 "special color that can be used for transparency.\n" 205 "special color that can be used for transparency.\n"
187 ); 206 );
188 } 207 }
221 } 240 }
222 return FALSE; 241 return FALSE;
223 } 242 }
224 243
225 244
245 static BOOL dmParseMapOptionMapItem(char *opt, DMMapValue *value, const int nmax, const char *msg)
246 {
247 char *split = strchr(opt, ':');
248
249 if (split == NULL)
250 {
251 dmError("Invalid %s value '%s', expected <(#|%)RRGGBB|[$|0x]index>:<[$|0x]index>.\n", msg, opt);
252 return FALSE;
253 }
254
255 *split = 0;
256
257 if (*opt == '#' || *opt == '%')
258 {
259 int colR, colG, colB, colA;
260
261 if (sscanf(opt + 1, "%2x%2x%2x%2x", &colR, &colG, &colB, &colA) != 4 &&
262 sscanf(opt + 1, "%2X%2X%2X%2X", &colR, &colG, &colB, &colA) != 4)
263 {
264 colA = 0;
265 if (sscanf(opt + 1, "%2x%2x%2x", &colR, &colG, &colB) != 3 &&
266 sscanf(opt + 1, "%2X%2X%2X", &colR, &colG, &colB) != 3)
267 {
268 dmError("Invalid %s value '%s', expected a hex triplet after #.\n", msg, opt);
269 return FALSE;
270 }
271 }
272 value->color.r = colR;
273 value->color.g = colG;
274 value->color.b = colB;
275 value->color.a = colA;
276 value->triplet = TRUE;
277 }
278 else
279 {
280 if (!dmGetIntVal(opt, &value->from))
281 {
282 dmError("Invalid %s value '%s', could not parse.\n", msg, opt);
283 return FALSE;
284 }
285 value->triplet = FALSE;
286 }
287
288 if (!dmGetIntVal(split + 1, &value->to))
289 {
290 dmError("Invalid %s value '%s', could not parse.\n", msg, opt);
291 return FALSE;
292 }
293
294 if (!value->triplet && (value->from < 0 || value->from > 255))
295 {
296 dmError("Invalid %s map source color index value %d.\n", msg, value->from);
297 return FALSE;
298 }
299
300 if (value->to < 0 || value->to > nmax)
301 {
302 dmError("Invalid %s map destination color index value %d.\n", msg, value->to);
303 return FALSE;
304 }
305
306 return TRUE;
307 }
308
309
310 static BOOL dmParseMapOptionItem(char *opt, char *end, void *pvalue, const int index, const int nmax, const BOOL requireIndex, const char *msg)
311 {
312 // Trim whitespace
313 if (end != NULL)
314 {
315 *end = 0;
316 for (end--; end > opt && *end && isspace(*end); end--)
317 *end = 0;
318 }
319 while (*opt && isspace(*opt)) opt++;
320
321 // Parse item based on mode
322 if (requireIndex)
323 {
324 DMMapValue *value = (DMMapValue *) pvalue;
325 if (!dmParseMapOptionMapItem(opt, value, nmax, msg))
326 return FALSE;
327 }
328 else
329 {
330 int *value = (int *) pvalue;
331 char *split = strchr(opt, ':');
332 if (split != NULL)
333 {
334 dmError("Unexpected ':' in indexed %s '%s'.\n", msg, opt);
335 return FALSE;
336 }
337
338 if (!dmGetIntVal(opt, &value[index]))
339 {
340 dmError("Invalid %s value '%s', could not parse.\n", msg, opt);
341 return FALSE;
342 }
343 }
344
345 return TRUE;
346 }
347
348
349 BOOL dmParseMapOptionString(char *opt, void *values, int *nvalues, const int nmax, const BOOL requireIndex, const char *msg)
350 {
351 char *end, *start = opt;
352
353 *nvalues = 0;
354 while (*nvalues < nmax && *start && (end = strchr(start, ',')) != NULL)
355 {
356 if (!dmParseMapOptionItem(start, end, values, *nvalues, nmax, requireIndex, msg))
357 return FALSE;
358
359 start = end + 1;
360 (*nvalues)++;
361 }
362
363 if (*start && *nvalues < nmax)
364 {
365 if (!dmParseMapOptionItem(start, NULL, values, *nvalues, nmax, requireIndex, msg))
366 return FALSE;
367
368 (*nvalues)++;
369 }
370
371 return TRUE;
372 }
373
374
375 int dmParseColorRemapFile(const char *filename, DMMapValue *values, int *nvalue, const int nmax)
376 {
377 FILE *fp;
378 char line[512];
379 int res = DMERR_OK;
380
381 if ((fp = fopen(filename, "r")) == NULL)
382 {
383 res = dmGetErrno();
384 dmError("Could not open color remap file '%s' for reading, %d: %s\n",
385 res, dmErrorStr(res));
386 return res;
387 }
388
389 while (fgets(line, sizeof(line), fp))
390 {
391 char *start = line;
392 while (*start && isspace(*start)) start++;
393
394 if (*start != 0 && *start != ';')
395 {
396 if (!dmParseMapOptionMapItem(line, &values[*nvalue], nmax, "mapping file"))
397 goto error;
398 else
399 {
400 (*nvalue)++;
401 if (*nvalue >= nmax)
402 {
403 dmError("Too many mapping pairs in '%s', maximum is %d.\n",
404 filename, nmax);
405 goto error;
406 }
407 }
408 }
409 }
410
411 error:
412 fclose(fp);
413 return res;
414 }
415
416
226 BOOL argHandleOpt(const int optN, char *optArg, char *currArg) 417 BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
227 { 418 {
228 switch (optN) 419 switch (optN)
229 { 420 {
230 case 0: 421 case 0:
231 argShowHelp(); 422 argShowHelp();
423 exit(0);
424 break;
425
426 case 17:
427 argShowFormats();
232 exit(0); 428 exit(0);
233 break; 429 break;
234 430
235 case 15: 431 case 15:
236 dmVerbosity++; 432 dmVerbosity++;
281 } 477 }
282 break; 478 break;
283 479
284 case 6: 480 case 6:
285 { 481 {
286 int index = 0, tmp; 482 int index, ncolors;
287 char *s, *p = optArg; 483 if (!dmParseMapOptionString(optArg, optColors,
288 484 &ncolors, C64_MAX_COLORS, FALSE, "color table option"))
289 while (index < C64_MAX_COLORS && *p != 0 && (s = strchr(p, ':')) != NULL) 485 return FALSE;
290 { 486
291 *s = 0;
292 if (sscanf(p, "%d", &tmp) == 1)
293 optColors[index++] = tmp;
294 p = s + 1;
295 }
296
297 if (*p && index < C64_MAX_COLORS)
298 {
299 if (sscanf(p, "%d", &tmp) == 1)
300 optColors[index++] = tmp;
301 }
302
303 dmMsg(1, "Set color table: "); 487 dmMsg(1, "Set color table: ");
304 for (tmp = 0; tmp < index; tmp++) 488 for (index = 0; index < ncolors; index++)
305 { 489 {
306 dmPrint(1, "[%d:%d]%s", 490 dmPrint(1, "[%d:%d]%s",
307 tmp, optColors[tmp], 491 index, optColors[index],
308 (tmp < index - 1) ? ", " : ""); 492 (index < ncolors) ? ", " : "");
309 } 493 }
310 dmPrint(1, "\n"); 494 dmPrint(1, "\n");
311 } 495 }
312 break; 496 break;
313 497
363 } 547 }
364 break; 548 break;
365 549
366 case 14: 550 case 14:
367 optSpec.interleave = TRUE; 551 optSpec.interleave = TRUE;
552 break;
553
554
555 case 16:
556 if (optArg[0] == '@')
557 {
558 if (optArg[1] != 0)
559 {
560 int res;
561 if ((res = dmParseColorRemapFile(optArg + 1,
562 optRemapTable, &optNRemapTable, DM_MAX_COLORS)) != DMERR_OK)
563 return FALSE;
564 }
565 else
566 {
567 dmError("No remap filename given.\n");
568 return FALSE;
569 }
570 }
571 else
572 {
573 if (!dmParseMapOptionString(optArg, optRemapTable,
574 &optNRemapTable, DM_MAX_COLORS, TRUE, "color remap option"))
575 return FALSE;
576 }
577
578 optRemapColors = TRUE;
368 break; 579 break;
369 580
370 default: 581 default:
371 dmError("Unknown option '%s'.\n", currArg); 582 dmError("Unknown option '%s'.\n", currArg);
372 return FALSE; 583 return FALSE;
516 return 0; 727 return 0;
517 } 728 }
518 #endif 729 #endif
519 730
520 731
732 int dmRemapImageColors(DMImage *image)
733 {
734 dmMsg(1, "Remapping %d output image colors.\n", optNRemapTable);
735 DMColor *npal = dmCalloc(image->ncolors, sizeof(DMColor));
736 int *dpal = dmMalloc(image->ncolors * sizeof(int));
737 BOOL *spal = dmCalloc(image->ncolors, sizeof(BOOL));
738 int index, xc, yc;
739
740 if (npal == NULL || spal == NULL || dpal == NULL)
741 {
742 dmError("Could not allocate memory for remapped palette.\n");
743 return DMERR_MALLOC;
744 }
745
746 for (index = 0; index < image->ncolors; index++)
747 dpal[index] = -1;
748
749 // Find and mark mapped colors
750 for (index = 0; index < optNRemapTable; index++)
751 {
752 DMMapValue *map = &optRemapTable[index];
753 if (map->triplet)
754 {
755 BOOL found = FALSE;
756 int n;
757 for (n = 0; n < image->ncolors; n++)
758 {
759 if (memcmp(&(image->pal[n]), &(map->color), sizeof(DMColor)) == 0)
760 {
761 dmMsg(3, "RGBA match #%02x%02x%02x%02x: %d -> %d\n",
762 map->color.r, map->color.g, map->color.b, map->color.a,
763 n,
764 map->to);
765
766 dpal[map->to] = n;
767 spal[n] = TRUE;
768 found = TRUE;
769 }
770 }
771
772 if (!found)
773 {
774 dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x\n",
775 index,
776 map->color.r, map->color.g, map->color.b, map->color.a);
777 }
778 }
779 else
780 {
781 dmMsg(3, "Map index %d: %d -> %d\n",
782 index,
783 map->from, map->to);
784
785 dpal[map->to] = map->from;
786 spal[map->from] = TRUE;
787 }
788 }
789
790 // Fill in the rest
791 dmMsg(3, "Placing non-mapped palette entries.\n");
792 for (index = 0; index < image->ncolors; index++)
793 {
794 if (dpal[index] < 0)
795 {
796 int src;
797 for (src = 0; src < image->ncolors; src++)
798 {
799 if (!spal[src])
800 {
801 dpal[index] = src;
802 spal[src] = TRUE;
803 break;
804 }
805 }
806 }
807 }
808
809 // Copy palette entries
810 dmMsg(3, "Creating new palette.\n");
811 for (index = 0; index < image->ncolors; index++)
812 {
813 if (dpal[index] >= 0 && dpal[index] < image->ncolors)
814 {
815 memcpy(&npal[index], &(image->pal[dpal[index]]), sizeof(DMColor));
816 }
817 }
818
819 // Remap image
820 dmMsg(3, "Remapping image.\n");
821 for (yc = 0; yc < image->height; yc++)
822 {
823 Uint8 *dp = image->data + image->pitch * yc;
824 for (xc = 0; xc < image->width; xc++)
825 {
826 Uint8 col = dp[xc];
827 if (col < image->ncolors && dpal[col] >= 0 && dpal[col] < image->ncolors)
828 {
829 dp[xc] = dpal[col];
830 }
831 }
832 }
833
834 // Set new palette, free memory
835 dmFree(image->pal);
836 image->pal = npal;
837 dmFree(spal);
838 dmFree(dpal);
839 return DMERR_OK;
840 }
841
842
521 int dmWriteImage(const char *filename, DMImage *image, DMImageSpec *spec, int iformat, BOOL info) 843 int dmWriteImage(const char *filename, DMImage *image, DMImageSpec *spec, int iformat, BOOL info)
522 { 844 {
523 if (info) 845 if (info)
524 { 846 {
525 dmMsg(1, "Outputting %s image %d x %d -> %d x %d [%d]\n", 847 dmMsg(1, "Outputting %s image %d x %d -> %d x %d [%d]\n",
526 dmImageFormatList[iformat].fext, 848 dmImageFormatList[iformat].fext,
527 image->width, image->height, 849 image->width, image->height,
528 image->width * spec->scale, image->height * spec->scale, 850 image->width * spec->scale, image->height * spec->scale,
529 spec->scale); 851 spec->scale);
852 }
853
854 // Perform color remapping
855 if (optRemapColors)
856 {
857 int res;
858 if ((res = dmRemapImageColors(image)) != DMERR_OK)
859 return res;
530 } 860 }
531 861
532 switch (iformat) 862 switch (iformat)
533 { 863 {
534 #ifdef DM_USE_LIBPNG 864 #ifdef DM_USE_LIBPNG
606 if (info) dmMsg(2, "%d bitplanes, %s interleave.\n", spec->nplanes, spec->interleave ? "with" : "without"); 936 if (info) dmMsg(2, "%d bitplanes, %s interleave.\n", spec->nplanes, spec->interleave ? "with" : "without");
607 return dmWriteIFFMasterRAWImage(filename, image, spec); 937 return dmWriteIFFMasterRAWImage(filename, image, spec);
608 } 938 }
609 939
610 default: 940 default:
611 return FALSE; 941 return DMERR_INVALID_DATA;
612 } 942 }
613 } 943 }
614 944
615 945
616 int dmDumpSpritesAndChars(FILE *inFile) 946 int dmDumpSpritesAndChars(FILE *inFile)
804 size_t dataSize; 1134 size_t dataSize;
805 int i; 1135 int i;
806 1136
807 // Default colors 1137 // Default colors
808 for (i = 0; i < C64_MAX_COLORS; i++) 1138 for (i = 0; i < C64_MAX_COLORS; i++)
809 optColors[i] = i + 1; 1139 optColors[i] = i;
810 1140
811 // Initialize and parse commandline 1141 // Initialize and parse commandline
812 dmInitProg("gfxconv", "Simple graphics converter", "0.5", NULL, NULL); 1142 dmInitProg("gfxconv", "Simple graphics converter", "0.6", NULL, NULL);
813 1143
814 if (!dmArgsProcess(argc, argv, optList, optListN, 1144 if (!dmArgsProcess(argc, argv, optList, optListN,
815 argHandleOpt, argHandleFile, TRUE)) 1145 argHandleOpt, argHandleFile, TRUE))
816 exit(1); 1146 exit(1);
817 1147