# HG changeset patch # User Matti Hamalainen # Date 1352242543 -7200 # Node ID 73bfe73553ebe666eacc3519ac1a3c931d78fccb # Parent 0359697eeb4643d4059772f0492c73f83bd36074 Implement palette remapping option for image outputs. diff -r 0359697eeb46 -r 73bfe73553eb gfxconv.c --- a/gfxconv.c Wed Nov 07 00:55:12 2012 +0200 +++ b/gfxconv.c Wed Nov 07 00:55:43 2012 +0200 @@ -15,6 +15,12 @@ //#define UNFINISHED 1 +#define DM_MAX_COLORS 256 + +#define ASC_NBITS 8 +#define ASC_NCOLORS 4 +static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#"; + enum { FFMT_AUTO = 0, @@ -91,9 +97,13 @@ static const int nconvFormatList = sizeof(convFormatList) / sizeof(convFormatList[0]); -#define ASC_NBITS 8 -#define ASC_NCOLORS 4 -static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#"; +typedef struct +{ + BOOL triplet; + DMColor color; + int from, to; +} DMMapValue; + char *optInFilename = NULL, @@ -107,7 +117,10 @@ optForcedFormat = -1; int optInSkip = 0; BOOL optInMulticolor = FALSE, - optSequential = FALSE; + optSequential = FALSE, + optRemapColors = FALSE; +int optNRemapTable = 0; +DMMapValue optRemapTable[DM_MAX_COLORS]; int optColors[C64_MAX_COLORS]; DMImageSpec optSpec = @@ -127,7 +140,8 @@ { 1, 'i', "informat", "Set input format ([s]prite, [c]har, [b]itmap, [i]mage)", OPT_ARGREQ }, { 2, 'm', "multicolor", "Input is multicolor", OPT_NONE }, { 4, 's', "skip", "Skip bytes in input", OPT_ARGREQ }, - { 5, 'f', "format", "Output format (see list below)", OPT_ARGREQ }, + { 5, 'f', "format", "Output format (see --formats)", OPT_ARGREQ }, + { 17, 'F', "formats", "Output format (see list below)", OPT_NONE }, { 8, 'q', "sequential", "Output sequential files (image output only)", OPT_NONE }, { 6, 'c', "colormap", "Color mappings (see below for information)", OPT_ARGREQ }, { 7, 'n', "numitems", "How many 'items' to view (default: all)", OPT_ARGREQ }, @@ -137,18 +151,16 @@ { 12, 'P', "paletted", "Use indexed/paletted output (png, pcx output only)", OPT_NONE }, { 13, 'B', "bplanes", "Bits per pixel OR # of bitplanes (certain output formats)", OPT_ARGREQ }, { 14, 'I', "interleave", "Interleave scanlines (default: output whole planes)", OPT_NONE }, + { 16, 'R', "remap", "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>] | -R @map.txt)", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); -void argShowHelp() +void argShowFormats() { int i; - dmPrintBanner(stdout, dmProgName, "[options] "); - dmArgsPrintHelp(stdout, optList, optListN); - printf("\n" "Available input/output formats:\n" " EXT | I | O | Description\n" @@ -174,13 +186,20 @@ dmC64ImageTypeNames[fmt->type], fmt->name); } +} + + +void argShowHelp() +{ + dmPrintBanner(stdout, dmProgName, "[options] "); + dmArgsPrintHelp(stdout, optList, optListN); printf( "\n" "Color map definitions are used for ANSI and image output, to declare what\n" "output colors of the C64 palette are used for each single color/multi color\n" "bit-combination. For example, if the input is multi color sprite or char,\n" - "you can define colors like: -c 0:8:3:15 .. for single color: -c 0:1\n" + "you can define colors like: -c 0,8,3,15 .. for single color: -c 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 color that can be used for transparency.\n" @@ -223,6 +242,178 @@ } +static BOOL dmParseMapOptionMapItem(char *opt, DMMapValue *value, const int nmax, const char *msg) +{ + char *split = strchr(opt, ':'); + + if (split == NULL) + { + dmError("Invalid %s value '%s', expected <(#|%)RRGGBB|[$|0x]index>:<[$|0x]index>.\n", msg, opt); + return FALSE; + } + + *split = 0; + + if (*opt == '#' || *opt == '%') + { + int colR, colG, colB, colA; + + if (sscanf(opt + 1, "%2x%2x%2x%2x", &colR, &colG, &colB, &colA) != 4 && + sscanf(opt + 1, "%2X%2X%2X%2X", &colR, &colG, &colB, &colA) != 4) + { + colA = 0; + if (sscanf(opt + 1, "%2x%2x%2x", &colR, &colG, &colB) != 3 && + sscanf(opt + 1, "%2X%2X%2X", &colR, &colG, &colB) != 3) + { + dmError("Invalid %s value '%s', expected a hex triplet after #.\n", msg, opt); + return FALSE; + } + } + value->color.r = colR; + value->color.g = colG; + value->color.b = colB; + value->color.a = colA; + value->triplet = TRUE; + } + else + { + if (!dmGetIntVal(opt, &value->from)) + { + dmError("Invalid %s value '%s', could not parse.\n", msg, opt); + return FALSE; + } + value->triplet = FALSE; + } + + if (!dmGetIntVal(split + 1, &value->to)) + { + dmError("Invalid %s value '%s', could not parse.\n", msg, opt); + return FALSE; + } + + if (!value->triplet && (value->from < 0 || value->from > 255)) + { + dmError("Invalid %s map source color index value %d.\n", msg, value->from); + return FALSE; + } + + if (value->to < 0 || value->to > nmax) + { + dmError("Invalid %s map destination color index value %d.\n", msg, value->to); + return FALSE; + } + + return TRUE; +} + + +static BOOL dmParseMapOptionItem(char *opt, char *end, void *pvalue, const int index, const int nmax, const BOOL requireIndex, const char *msg) +{ + // Trim whitespace + if (end != NULL) + { + *end = 0; + for (end--; end > opt && *end && isspace(*end); end--) + *end = 0; + } + while (*opt && isspace(*opt)) opt++; + + // Parse item based on mode + if (requireIndex) + { + DMMapValue *value = (DMMapValue *) pvalue; + if (!dmParseMapOptionMapItem(opt, value, nmax, msg)) + return FALSE; + } + else + { + int *value = (int *) pvalue; + char *split = strchr(opt, ':'); + if (split != NULL) + { + dmError("Unexpected ':' in indexed %s '%s'.\n", msg, opt); + return FALSE; + } + + if (!dmGetIntVal(opt, &value[index])) + { + dmError("Invalid %s value '%s', could not parse.\n", msg, opt); + return FALSE; + } + } + + return TRUE; +} + + +BOOL dmParseMapOptionString(char *opt, void *values, int *nvalues, const int nmax, const BOOL requireIndex, const char *msg) +{ + char *end, *start = opt; + + *nvalues = 0; + while (*nvalues < nmax && *start && (end = strchr(start, ',')) != NULL) + { + if (!dmParseMapOptionItem(start, end, values, *nvalues, nmax, requireIndex, msg)) + return FALSE; + + start = end + 1; + (*nvalues)++; + } + + if (*start && *nvalues < nmax) + { + if (!dmParseMapOptionItem(start, NULL, values, *nvalues, nmax, requireIndex, msg)) + return FALSE; + + (*nvalues)++; + } + + return TRUE; +} + + +int dmParseColorRemapFile(const char *filename, DMMapValue *values, int *nvalue, const int nmax) +{ + FILE *fp; + char line[512]; + int res = DMERR_OK; + + if ((fp = fopen(filename, "r")) == NULL) + { + res = dmGetErrno(); + dmError("Could not open color remap file '%s' for reading, %d: %s\n", + res, dmErrorStr(res)); + return res; + } + + while (fgets(line, sizeof(line), fp)) + { + char *start = line; + while (*start && isspace(*start)) start++; + + if (*start != 0 && *start != ';') + { + if (!dmParseMapOptionMapItem(line, &values[*nvalue], nmax, "mapping file")) + goto error; + else + { + (*nvalue)++; + if (*nvalue >= nmax) + { + dmError("Too many mapping pairs in '%s', maximum is %d.\n", + filename, nmax); + goto error; + } + } + } + } + +error: + fclose(fp); + return res; +} + + BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) @@ -232,6 +423,11 @@ exit(0); break; + case 17: + argShowFormats(); + exit(0); + break; + case 15: dmVerbosity++; break; @@ -283,29 +479,17 @@ case 6: { - int index = 0, tmp; - char *s, *p = optArg; + int index, ncolors; + if (!dmParseMapOptionString(optArg, optColors, + &ncolors, C64_MAX_COLORS, FALSE, "color table option")) + return FALSE; - while (index < C64_MAX_COLORS && *p != 0 && (s = strchr(p, ':')) != NULL) - { - *s = 0; - if (sscanf(p, "%d", &tmp) == 1) - optColors[index++] = tmp; - p = s + 1; - } - - if (*p && index < C64_MAX_COLORS) - { - if (sscanf(p, "%d", &tmp) == 1) - optColors[index++] = tmp; - } - dmMsg(1, "Set color table: "); - for (tmp = 0; tmp < index; tmp++) + for (index = 0; index < ncolors; index++) { dmPrint(1, "[%d:%d]%s", - tmp, optColors[tmp], - (tmp < index - 1) ? ", " : ""); + index, optColors[index], + (index < ncolors) ? ", " : ""); } dmPrint(1, "\n"); } @@ -367,6 +551,33 @@ optSpec.interleave = TRUE; break; + + case 16: + if (optArg[0] == '@') + { + if (optArg[1] != 0) + { + int res; + if ((res = dmParseColorRemapFile(optArg + 1, + optRemapTable, &optNRemapTable, DM_MAX_COLORS)) != DMERR_OK) + return FALSE; + } + else + { + dmError("No remap filename given.\n"); + return FALSE; + } + } + else + { + if (!dmParseMapOptionString(optArg, optRemapTable, + &optNRemapTable, DM_MAX_COLORS, TRUE, "color remap option")) + return FALSE; + } + + optRemapColors = TRUE; + break; + default: dmError("Unknown option '%s'.\n", currArg); return FALSE; @@ -518,6 +729,117 @@ #endif +int dmRemapImageColors(DMImage *image) +{ + dmMsg(1, "Remapping %d output image colors.\n", optNRemapTable); + DMColor *npal = dmCalloc(image->ncolors, sizeof(DMColor)); + int *dpal = dmMalloc(image->ncolors * sizeof(int)); + BOOL *spal = dmCalloc(image->ncolors, sizeof(BOOL)); + int index, xc, yc; + + if (npal == NULL || spal == NULL || dpal == NULL) + { + dmError("Could not allocate memory for remapped palette.\n"); + return DMERR_MALLOC; + } + + for (index = 0; index < image->ncolors; index++) + dpal[index] = -1; + + // Find and mark mapped colors + for (index = 0; index < optNRemapTable; index++) + { + DMMapValue *map = &optRemapTable[index]; + if (map->triplet) + { + BOOL found = FALSE; + int n; + for (n = 0; n < image->ncolors; n++) + { + if (memcmp(&(image->pal[n]), &(map->color), sizeof(DMColor)) == 0) + { + 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); + + dpal[map->to] = n; + spal[n] = TRUE; + found = TRUE; + } + } + + if (!found) + { + 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); + } + } + else + { + dmMsg(3, "Map index %d: %d -> %d\n", + index, + map->from, map->to); + + dpal[map->to] = map->from; + spal[map->from] = TRUE; + } + } + + // Fill in the rest + dmMsg(3, "Placing non-mapped palette entries.\n"); + for (index = 0; index < image->ncolors; index++) + { + if (dpal[index] < 0) + { + int src; + for (src = 0; src < image->ncolors; src++) + { + if (!spal[src]) + { + dpal[index] = src; + spal[src] = TRUE; + break; + } + } + } + } + + // Copy palette entries + dmMsg(3, "Creating new palette.\n"); + for (index = 0; index < image->ncolors; index++) + { + if (dpal[index] >= 0 && dpal[index] < image->ncolors) + { + memcpy(&npal[index], &(image->pal[dpal[index]]), sizeof(DMColor)); + } + } + + // Remap image + dmMsg(3, "Remapping image.\n"); + for (yc = 0; yc < image->height; yc++) + { + Uint8 *dp = image->data + image->pitch * yc; + for (xc = 0; xc < image->width; xc++) + { + Uint8 col = dp[xc]; + if (col < image->ncolors && dpal[col] >= 0 && dpal[col] < image->ncolors) + { + dp[xc] = dpal[col]; + } + } + } + + // Set new palette, free memory + dmFree(image->pal); + image->pal = npal; + dmFree(spal); + dmFree(dpal); + return DMERR_OK; +} + + int dmWriteImage(const char *filename, DMImage *image, DMImageSpec *spec, int iformat, BOOL info) { if (info) @@ -529,6 +851,14 @@ spec->scale); } + // Perform color remapping + if (optRemapColors) + { + int res; + if ((res = dmRemapImageColors(image)) != DMERR_OK) + return res; + } + switch (iformat) { #ifdef DM_USE_LIBPNG @@ -608,7 +938,7 @@ } default: - return FALSE; + return DMERR_INVALID_DATA; } } @@ -806,10 +1136,10 @@ // Default colors for (i = 0; i < C64_MAX_COLORS; i++) - optColors[i] = i + 1; + optColors[i] = i; // Initialize and parse commandline - dmInitProg("gfxconv", "Simple graphics converter", "0.5", NULL, NULL); + dmInitProg("gfxconv", "Simple graphics converter", "0.6", NULL, NULL); if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, TRUE))