Mercurial > hg > dmlib
view tools/gfxconv.c @ 1401:aaed8fa9a11f
Fix jssASCIItoStr() bounds check.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 30 Oct 2017 18:01:25 +0200 |
parents | 3bcf02e5a375 |
children | 4c7b456d7f0b |
line wrap: on
line source
/* * gfxconv - Convert various graphics formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2017 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #include "dmmutex.h" #include "libgfx.h" #include "lib64gfx.h" #define DM_MAX_COLORS 256 #define DM_ASC_NBITS 8 #define DM_ASC_NCOLORS 4 static const char dmASCIIPalette[DM_ASC_NCOLORS] = ".:X#"; enum { FFMT_AUTO = 0, FFMT_ASCII, FFMT_ANSI, FFMT_IMAGE, FFMT_CHAR, FFMT_SPRITE, FFMT_BITMAP, FFMT_LAST }; enum { CROP_NONE = 0, CROP_AUTO, CROP_SIZE, }; typedef struct { char *name; // Descriptive name of the format char *fext; // File extension BOOL in, out; // Can read/write? int format; // Format identifier int subformat; // Subformat identifier } DMConvFormat; static DMConvFormat convFormatList[] = { { "ASCII text", "asc", FALSE, TRUE, FFMT_ASCII , 0, }, { "ANSI colored text", "ansi", FALSE, TRUE, FFMT_ANSI , 0, }, { "PNG image file", "png", TRUE, TRUE, FFMT_IMAGE , IMGFMT_PNG, }, { "PPM image file", "ppm", FALSE, TRUE, FFMT_IMAGE , IMGFMT_PPM, }, { "PCX image file", "pcx", TRUE, TRUE, FFMT_IMAGE , IMGFMT_PCX, }, { "IFF ILBM file", "lbm", TRUE, FALSE, FFMT_IMAGE , IMGFMT_ILBM, }, { "Bitplaned RAW (intl/non-intl) image file", "raw", FALSE, TRUE, FFMT_IMAGE , IMGFMT_RAW, }, { "IFFMaster RAW image file", "araw", FALSE, TRUE, FFMT_IMAGE , IMGFMT_ARAW, }, { "C64 bitmap image file", NULL, TRUE, TRUE, FFMT_BITMAP , -1, }, { "C64 character/font data", "chr", TRUE, TRUE, FFMT_CHAR , 0 }, { "C64 sprite data", "spr", TRUE, TRUE, FFMT_SPRITE , 0 }, }; static const int nconvFormatList = sizeof(convFormatList) / sizeof(convFormatList[0]); typedef struct { BOOL triplet, alpha; DMColor color; unsigned int from, to; } DMMapValue; char *optInFilename = NULL, *optOutFilename = NULL; int optInFormat = FFMT_AUTO, optOutFormat = FFMT_ASCII, optInSubFormat = IMGFMT_PNG, optOutSubFormat = IMGFMT_PNG, optItemCount = -1, optPlanedWidth = 1, optForcedFormat = -1; unsigned int optInSkip = 0; int optCropMode = CROP_NONE, optCropX0, optCropY0, optCropW, optCropH; BOOL optInMulticolor = FALSE, optSequential = FALSE, optRemapColors = FALSE, optRemapRemove = FALSE; int optNRemapTable = 0; DMMapValue optRemapTable[DM_MAX_COLORS]; int optColors[C64_MAX_COLORS]; DMImageConvSpec optSpec = { .scaleX = 1, .scaleY = 1, .nplanes = 4, .bpp = 8, .planar = FALSE, .paletted = FALSE, .format = 0, }; static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 15, 'v', "verbose", "Increase verbosity", OPT_NONE }, { 3, 'o', "output", "Output filename", OPT_ARGREQ }, { 1, 'i', "informat", "Set input format (sprite[:mc:sc], char[:mc|sc], bitmap[:<bformat>], image)", OPT_ARGREQ }, { 2, 'm', "multicolor", "Input is multicolor / output in multicolor", OPT_NONE }, { 4, 's', "skip", "Skip bytes in input", OPT_ARGREQ }, { 5, 'f', "format", "Output format (see --formats)", OPT_ARGREQ }, { 17, 0 , "formats", "List available input/output formats", 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 output (default: all)", OPT_ARGREQ }, { 11, 'w', "width", "Item width (number of items per row, min 1)", OPT_ARGREQ }, { 9, 'S', "scale", "Scale output image by <n> or <x>:<y> integer factor(s). " "-S <n> scales both height and width by <n>.", OPT_ARGREQ }, { 12, 'P', "paletted", "Use indexed/paletted output IF possible.", OPT_NONE }, { 13, 'N', "nplanes", "# of bitplanes (some output formats)", OPT_ARGREQ }, { 18, 'B', "bpp", "Bits per pixel (some output formats)", OPT_ARGREQ }, { 14, 'I', "interleave", "Interleaved/planar output (some output formats)", OPT_NONE }, { 16, 'R', "remap", "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>][+remove] | -R @map.txt[+remove])", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowFormats() { int i; printf( "Available input/output formats:\n" " Ext | I | O | Description\n" "------+---+---+-----------------------------------------------\n" ); for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; printf("%-5s | %c | %c | %s\n", fmt->fext ? fmt->fext : "", fmt->in ? 'X' : ' ', fmt->out ? 'X' : ' ', fmt->name); } printf( "\n" "(Not all input->output combinations are actually supported.)\n" "\n" "Available bitmap formats (-f bitmap:<bfrm>):\n" " bfrm | Type | Description\n" "------+-----------------+-------------------------------------\n" ); for (i = 0; i < ndmC64ImageFormats; i++) { const DMC64ImageFormat *fmt = &dmC64ImageFormats[i]; char buf[64]; printf("%-5s | %-15s | %s\n", fmt->fext, dmC64GetImageTypeString(buf, sizeof(buf), fmt->type), fmt->name); } } void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] <input file>"); dmArgsPrintHelp(stdout, optList, optListN, 0); printf( "\n" "Palette / color 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 usually desirable, for example\n" "when converting multiple images to same palette.\n" "\n" "Color map defs\n" "--------------\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" "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" "\n" ); } int dmGetConvFormat(int format, int subformat) { int i; for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; if (fmt->format == format && fmt->subformat == subformat) return i; } return -1; } BOOL dmGetC64FormatByExt(const char *fext, int *format, int *subformat) { int i; if (fext == NULL) return FALSE; for (i = 0; i < ndmC64ImageFormats; i++) { const DMC64ImageFormat *fmt = &dmC64ImageFormats[i]; if (fmt->fext != NULL && strcasecmp(fext, fmt->fext) == 0) { *format = FFMT_BITMAP; *subformat = i; return TRUE; } } return FALSE; } BOOL dmGetFormatByExt(const char *fext, int *format, int *subformat) { int i; if (fext == NULL) return FALSE; for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; if (fmt->fext != NULL && strcasecmp(fext, fmt->fext) == 0) { *format = fmt->format; *subformat = fmt->subformat; return TRUE; } } return FALSE; } static BOOL dmParseMapOptionMapItem(const char *popt, DMMapValue *value, const unsigned int nmax, const char *msg) { char *end, *split, *opt = dm_strdup(popt); if (opt == NULL) goto error; if ((end = split = strchr(opt, ':')) == NULL) { dmErrorMsg("Invalid %s value '%s', expected <(#|%)RRGGBB|[$|0x]index>:<[$|0x]index>.\n", msg, opt); goto error; } // Trim whitespace *end = 0; for (end--; end > opt && *end && isspace(*end); end--) *end = 0; // Parse either a hex triplet color definition or a normal value if (*opt == '#' || *opt == '%') { unsigned 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) { value->alpha = TRUE; value->color.a = colA; } else if (sscanf(opt + 1, "%2x%2x%2x", &colR, &colG, &colB) != 3 && sscanf(opt + 1, "%2X%2X%2X", &colR, &colG, &colB) != 3) { dmErrorMsg("Invalid %s value '%s', expected a hex triplet, got '%s'.\n", msg, popt, opt + 1); goto error; } value->color.r = colR; value->color.g = colG; value->color.b = colB; value->triplet = TRUE; } else { if (!dmGetIntVal(opt, &value->from)) { dmErrorMsg("Invalid %s value '%s', could not parse source value '%s'.\n", msg, popt, opt); goto error; } value->triplet = FALSE; } // Trim whitespace split++; while (*split && isspace(*split)) split++; // Parse destination value if (!dmGetIntVal(split, &value->to)) { dmErrorMsg("Invalid %s value '%s', could not parse destination value '%s'.\n", msg, popt, split); goto error; } if (!value->triplet && value->from > 255) { dmErrorMsg("Invalid %s map source color index value %d, must be [0..255].\n", msg, value->from); goto error; } if (value->to > nmax) { dmErrorMsg("Invalid %s map destination color index value %d, must be [0..%d].\n", msg, value->to, nmax); goto error; } dmFree(opt); return TRUE; error: dmFree(opt); return FALSE; } 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[index], nmax, msg)) return FALSE; } else { unsigned int *value = (unsigned int *) pvalue; char *split = strchr(opt, ':'); if (split != NULL) { dmErrorMsg("Unexpected ':' in indexed %s '%s'.\n", msg, opt); return FALSE; } if (!dmGetIntVal(opt, &value[index])) { dmErrorMsg("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 *start = opt; *nvalues = 0; while (*start && *nvalues < nmax) { char *end = strchr(start, ','); if (!dmParseMapOptionItem(start, end, values, *nvalues, nmax, requireIndex, msg)) return FALSE; (*nvalues)++; if (!end) break; start = end + 1; } 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(res, "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) { dmErrorMsg("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) { case 0: argShowHelp(); exit(0); break; case 17: argShowFormats(); exit(0); break; case 15: dmVerbosity++; break; case 1: { switch (tolower(optArg[0])) { case 's': optInFormat = FFMT_SPRITE; break; case 'c': optInFormat = FFMT_CHAR; break; case 'b': optInFormat = FFMT_BITMAP; break; case 'i': optInFormat = FFMT_IMAGE; break; default: dmErrorMsg("Invalid input format '%s'.\n", optArg); return FALSE; } char *tmp = strchr(optArg, ':'); if (tmp != NULL) { tmp++; switch (optInFormat) { case FFMT_SPRITE: case FFMT_CHAR: if (strcasecmp(tmp, "mc") == 0) optInMulticolor = TRUE; else if (strcasecmp(tmp, "sc") == 0) optInMulticolor = FALSE; else { dmErrorMsg("Invalid input subformat for sprite/char: '%s', should be 'mc' or 'sc'.\n", tmp); return FALSE; } break; case FFMT_BITMAP: if (!dmGetC64FormatByExt(tmp, &optInFormat, &optInSubFormat)) { dmErrorMsg("Invalid bitmap subformat '%s', see format list for valid bformats.\n", tmp); return FALSE; } break; } } } break; case 2: optInMulticolor = TRUE; break; case 3: optOutFilename = optArg; break; case 4: if (!dmGetIntVal(optArg, &optInSkip)) { dmErrorMsg("Invalid skip value argument '%s'.\n", optArg); return FALSE; } break; case 5: if (!dmGetFormatByExt(optArg, &optOutFormat, &optOutSubFormat) && !dmGetC64FormatByExt(optArg, &optOutFormat, &optOutSubFormat)) { dmErrorMsg("Invalid output format '%s'.\n", optArg); return FALSE; } break; case 6: { int index, ncolors; if (!dmParseMapOptionString(optArg, optColors, &ncolors, C64_MAX_COLORS, FALSE, "color table option")) return FALSE; dmMsg(1, "Set color table: "); for (index = 0; index < ncolors; index++) { dmPrint(1, "[%d:%d]%s", index, optColors[index], (index < ncolors) ? ", " : ""); } dmPrint(1, "\n"); } break; case 7: if (sscanf(optArg, "%d", &optItemCount) != 1) { dmErrorMsg("Invalid count value argument '%s'.\n", optArg); return FALSE; } break; case 8: optSequential = TRUE; break; case 9: if (sscanf(optArg, "%d:%d", &optSpec.scaleX, &optSpec.scaleY) != 2) { if (sscanf(optArg, "%d", &optSpec.scaleX) == 1) optSpec.scaleY = optSpec.scaleX; else { dmErrorMsg("Invalid scale option value '%s', should be <n> or <w>:<h>.\n", optArg); return FALSE; } } if (optSpec.scaleX < 1 || optSpec.scaleX > 50) { dmErrorMsg("Invalid X scale value '%d'.\n", optSpec.scaleX); return FALSE; } if (optSpec.scaleY < 1 || optSpec.scaleY > 50) { dmErrorMsg("Invalid Y scale value '%d'.\n", optSpec.scaleY); return FALSE; } break; case 11: if (sscanf(optArg, "%d", &optPlanedWidth) != 1) { dmErrorMsg("Invalid planed width value argument '%s'.\n", optArg); return FALSE; } if (optPlanedWidth < 1 || optPlanedWidth > 512) { dmErrorMsg("Invalid planed width value '%d' [1..512].\n", optPlanedWidth); return FALSE; } break; case 12: optSpec.paletted = TRUE; break; case 13: { int tmp = atoi(optArg); if (tmp < 1 || tmp > 8) { dmErrorMsg("Invalid number of bitplanes value '%s'.\n", optArg); return FALSE; } optSpec.nplanes = tmp; } break; case 18: { int tmp = atoi(optArg); if (tmp < 1 || tmp > 32) { dmErrorMsg("Invalid number of bits per pixel value '%s'.\n", optArg); return FALSE; } optSpec.nplanes = tmp; } break; case 14: optSpec.planar = TRUE; break; case 16: { char *tmp; if ((tmp = dm_strrcasecmp(optArg, "+remove")) != NULL) { optRemapRemove = TRUE; *tmp = 0; } if (optArg[0] == '@') { if (optArg[1] != 0) { int res; if ((res = dmParseColorRemapFile(optArg + 1, optRemapTable, &optNRemapTable, DM_MAX_COLORS)) != DMERR_OK) return FALSE; } else { dmErrorMsg("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; case 19: { int tx0, ty0, tx1, ty1; if (strcasecmp(optArg, "auto") == 0) { optCropMode = CROP_AUTO; } else if (sscanf(optArg, "%d:%d-%d:%d", &tx0, &ty0, &tx1, &ty1) == 4) { optCropMode = CROP_SIZE; optCropX0 = tx0; optCropY0 = ty0; optCropW = tx1 - tx0 + 1; optCropH = ty1 - ty0 + 1; } else if (sscanf(optArg, "%d:%d:%d:%d", &tx0, &ty0, &tx1, &ty1) == 4) { optCropMode = CROP_SIZE; optCropX0 = tx0; optCropY0 = ty0; optCropW = tx1; optCropH = ty1; } else { dmErrorMsg("Invalid crop mode / argument '%s'.\n", optArg); return FALSE; } } break; default: dmErrorMsg("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optInFilename) optInFilename = currArg; else { dmErrorMsg("Source filename already specified, extraneous argument '%s'.\n", currArg); return FALSE; } return TRUE; } void dmPrintByte(FILE *out, int byte, int format, BOOL multicolor) { int i; if (multicolor) { for (i = DM_ASC_NBITS; i; i -= 2) { int val = (byte & (3ULL << (i - 2))) >> (i - 2); char ch; switch (format) { case FFMT_ASCII: ch = dmASCIIPalette[val]; fprintf(out, "%c%c", ch, ch); break; case FFMT_ANSI: fprintf(out, "%c[0;%d;%dm##%c[0m", 0x1b, 1, 31 + optColors[val], 0x1b); break; } } } else { for (i = DM_ASC_NBITS; i; i--) { int val = (byte & (1ULL << (i - 1))) >> (i - 1); char ch; switch (format) { case FFMT_ASCII: ch = val ? '#' : '.'; fputc(ch, out); break; case FFMT_ANSI: fprintf(out, "%c[0;%d;%dm %c[0m", 0x1b, 1, 31 + optColors[val], 0x1b); break; } } } } void dmDumpCharASCII(FILE *outFile, const Uint8 *buf, int *offs, int format, BOOL multicolor) { int yc; for (yc = 0; yc < C64_CHR_HEIGHT; yc++) { fprintf(outFile, "%04x : ", *offs); dmPrintByte(outFile, buf[yc], format, multicolor); fprintf(outFile, "\n"); (*offs)++; } } void dmDumpSpriteASCII(FILE *outFile, const Uint8 *buf, int *offs, int format, BOOL multicolor) { int bufOffs, xc, yc; for (bufOffs = yc = 0; yc < C64_SPR_HEIGHT; yc++) { fprintf(outFile, "%04x : ", *offs); for (xc = 0; xc < C64_SPR_WIDTH; xc++) { dmPrintByte(outFile, buf[bufOffs], format, multicolor); fprintf(outFile, " "); bufOffs++; (*offs)++; } fprintf(outFile, "\n"); } (*offs)++; } int dmRemapImageColors(DMImage **pdst, const DMImage *src) { DMColor *npal = dmCalloc(src->ncolors, sizeof(DMColor)); int *mapping = dmMalloc(src->ncolors * sizeof(int)); BOOL *mapped = dmMalloc(src->ncolors * sizeof(BOOL)); BOOL *used = dmMalloc(src->ncolors * sizeof(BOOL)); int n, index, xc, yc, ncolors; DMImage *dst; if ((dst = *pdst = dmImageAlloc(src->width, src->height, src->format, src->bpp)) == NULL) { return dmError(DMERR_MALLOC, "Could not allocate memory for remapped image.\n"); } dmMsg(1, "Remapping %d output image colors of %d colors.\n", optNRemapTable, src->ncolors); if (npal == NULL || mapping == NULL || mapped == NULL || used == NULL) { return dmError(DMERR_MALLOC, "Could not allocate memory for reused palette.\n"); } for (index = 0; index < src->ncolors; index++) { mapping[index] = -1; mapped[index] = used[index] = FALSE; } // Find used colors dmMsg(2, "Scanning image for used colors...\n"); for (ncolors = yc = 0; yc < src->height; yc++) { const Uint8 *dp = src->data + src->pitch * yc; for (xc = 0; xc < src->width; xc++) { Uint8 col = dp[xc]; if (col < src->ncolors && !used[col]) { used[col] = TRUE; ncolors++; } } } dmMsg(2, "Found %d used colors, creating remap-table.\n", ncolors); // Match and mark mapped colors for (index = 0; index < optNRemapTable; index++) { DMMapValue *map = &optRemapTable[index]; if (map->triplet) { BOOL found = FALSE; for (n = 0; n < src->ncolors; n++) { if (dmCompareColor(&(src->pal[n]), &(map->color), map->alpha)) { 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; } } 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\n", map->from, map->to); mapping[map->from] = map->to; mapped[map->to] = TRUE; } } // Fill in the rest if (optRemapRemove) { dmMsg(2, "Removing unused colors.\n"); for (index = 0; index < src->ncolors; index++) if (mapping[index] < 0 && used[index]) { for (n = 0; n < src->ncolors; n++) if (!mapped[n]) { mapping[index] = n; mapped[n] = TRUE; break; } } } else { for (index = 0; index < src->ncolors; index++) if (mapping[index] < 0) { for (n = 0; n < src->ncolors; n++) if (!mapped[n]) { mapping[index] = n; mapped[n] = TRUE; break; } } } // Calculate final number of palette colors ncolors = 0; for (index = 0; index < src->ncolors; index++) { if (mapping[index] + 1 > ncolors) ncolors = mapping[index] + 1; } // Copy palette entries for (index = 0; index < src->ncolors; index++) { if (mapping[index] >= 0) { memcpy(&npal[mapping[index]], &(src->pal[index]), sizeof(DMColor)); } } // Remap image dmMsg(1, "Remapping image to %d colors...\n", ncolors); for (yc = 0; yc < src->height; yc++) { Uint8 *sp = src->data + src->pitch * yc; Uint8 *dp = dst->data + dst->pitch * yc; for (xc = 0; xc < src->width; xc++) { Uint8 col = sp[xc]; if (col < src->ncolors && mapping[col] >= 0 && mapping[col] < src->ncolors) dp[xc] = mapping[col]; else dp[xc] = 0; } } // Set new palette, free memory dst->pal = npal; dst->ncolors = ncolors; dmFree(mapping); dmFree(mapped); dmFree(used); return DMERR_OK; } int dmWriteBitmap(const char *filename, DMC64Image *image, int iformat, BOOL enableFixUps) { FILE *outFile = NULL; Uint8 *buf = NULL; size_t bufSize; int res = DMERR_OK; const DMC64ImageFormat *fmt = &dmC64ImageFormats[iformat]; dmMsg(1, "Converting to %s format bitmap.\n", fmt->name); if (image->type != fmt->type && enableFixUps) { // Try to do some simple fixups if ((fmt->type & D64_FMT_FLI) && (image->type & D64_FMT_FLI) == 0) { dmMsg(1, "Upconverting multicolor to FLI.\n"); int i; for (i = 1; i < image->nbanks; i++) { memcpy(image->color[i], image->color[0], C64_SCR_COLOR_SIZE); memcpy(image->screen[i], image->screen[0], C64_SCR_SCREEN_SIZE); } } } if ((res = dmC64EncodeGenericBMP(&buf, &bufSize, image, fmt)) != DMERR_OK) goto error; dmMsg(2, "Result: %d bytes\n", bufSize); if ((outFile = fopen(filename, "wb")) == NULL) { res = dmGetErrno(); dmError(res, "Error opening output file '%s', %d: %s\n", filename, res, dmErrorStr(res)); goto error; } if (!dm_fwrite_str(outFile, buf, bufSize)) { res = dmGetErrno(); dmError(res, "Error writing image data to '%s', %d: %s\n", filename, res, dmErrorStr(res)); } error: if (outFile != NULL) fclose(outFile); dmFree(buf); return res; } void dmOutputImageBitFormat(const int format, const BOOL info) { if (info) { char *str; switch (format) { case DM_IFMT_PALETTE : str = "Indexed 8bpp"; break; case DM_IFMT_RGB : str = "24bit RGB"; break; case DM_IFMT_RGBA : str = "32bit RGBA"; break; default : str = "???"; break; } dmMsg(2, "%s output.\n", str); } } int dmWriteImage(const char *filename, DMImage *pimage, DMImageConvSpec *spec, int iformat, BOOL info) { int res = DMERR_OK; if (info) { dmMsg(1, "Outputting %s image %d x %d -> %d x %d [%d x %d]\n", dmImageFormatList[iformat].fext, pimage->width, pimage->height, pimage->width * spec->scaleX, pimage->height * spec->scaleY, spec->scaleX, spec->scaleY); } // Perform color remapping DMImage *image = pimage; BOOL allocated = FALSE; if (optRemapColors) { int res; if ((res = dmRemapImageColors(&image, pimage)) != DMERR_OK) return res; allocated = TRUE; } switch (iformat) { #ifdef DM_USE_LIBPNG case IMGFMT_PNG: spec->format = spec->paletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA; dmOutputImageBitFormat(spec->format, info); res = dmWritePNGImage(filename, image, spec); break; #endif case IMGFMT_PPM: spec->format = DM_IFMT_RGB; dmOutputImageBitFormat(spec->format, info); res = dmWritePPMImage(filename, image, spec); break; case IMGFMT_PCX: spec->format = spec->paletted ? DM_IFMT_PALETTE : DM_IFMT_RGB; dmOutputImageBitFormat(spec->format, info); res = dmWritePCXImage(filename, image, spec); break; case IMGFMT_RAW: case IMGFMT_ARAW: { // Open data file for writing FILE *fp; char * dataFilename = dm_strdup_fext(filename, "%s.inc"); if ((fp = fopen(dataFilename, "w")) == NULL) { res = dmError(DMERR_FOPEN, "Could not create '%s'.\n", dataFilename); goto err; } dmFree(dataFilename); if (fp != NULL) { // Strip extension char *palID = dm_strdup_fext(filename, "img_%s"); // Replace any non-alphanumerics for (int i = 0; palID[i]; i++) { if (isalnum(palID[i])) palID[i] = tolower(palID[i]); else palID[i] = '_'; } if (iformat == IMGFMT_ARAW) { fprintf(fp, "%s_width: dw.w %d\n" "%s_height: dw.w %d\n" "%s_nplanes: dw.w %d\n" "%s_ncolors: dw.w %d\n" "%s_palette:\n", palID, image->width, palID, image->height, palID, spec->nplanes, palID, image->ncolors, palID); dmWriteIFFMasterRAWPalette(fp, image, 1 << optSpec.nplanes, NULL, NULL); fprintf(fp, "%s: incbin \"%s\"\n", palID, filename); } else { fprintf(fp, "%s_width: dw.w %d\n" "%s_height: dw.w %d\n" "%s_nplanes: dw.w %d\n", palID, image->width, palID, image->height, palID, spec->nplanes); } fclose(fp); dmFree(palID); } if (info) { dmMsg(2, "%d bitplanes, %s planes.\n", spec->nplanes, spec->planar ? "planar/interleaved" : "non-interleaved"); } res = dmWriteRAWImage(filename, image, spec); } break; default: res = DMERR_INVALID_DATA; } err: if (allocated) dmImageFree(image); return res; } static Uint8 dmConvertByte(const Uint8 *sp, const BOOL multicolor) { Uint8 byte = 0; int xc; if (multicolor) { for (xc = 0; xc < 8 / 2; xc++) { Uint8 pixel = sp[xc * 2] & 3; byte |= pixel << (6 - (xc * 2)); } } else { for (xc = 0; xc < 8; xc++) { Uint8 pixel = sp[xc] == 0 ? 0 : 1; byte |= pixel << (7 - xc); } } return byte; } BOOL dmConvertImage2Char(Uint8 *buf, const DMImage *image, const int xoffs, const int yoffs, const BOOL multicolor) { int yc; if (xoffs < 0 || yoffs < 0 || xoffs + C64_CHR_WIDTH_PX > image->width || yoffs + C64_CHR_HEIGHT > image->height) return FALSE; for (yc = 0; yc < C64_CHR_HEIGHT; yc++) { const Uint8 *sp = image->data + ((yc + yoffs) * image->pitch) + xoffs; buf[yc] = dmConvertByte(sp, multicolor); } return TRUE; } BOOL dmConvertImage2Sprite(Uint8 *buf, const DMImage *image, const int xoffs, const int yoffs, const BOOL multicolor) { int yc, xc; if (xoffs < 0 || yoffs < 0 || xoffs + C64_SPR_WIDTH_PX > image->width || yoffs + C64_SPR_HEIGHT > image->height) return FALSE; for (yc = 0; yc < C64_SPR_HEIGHT; yc++) { for (xc = 0; xc < C64_SPR_WIDTH_PX / C64_SPR_WIDTH; xc++) { const Uint8 *sp = image->data + ((yc + yoffs) * image->pitch) + (xc * 8) + xoffs; buf[(yc * C64_SPR_WIDTH) + xc] = dmConvertByte(sp, multicolor); } } return TRUE; } int dmWriteSpritesAndChars(const char *filename, DMImage *image, int outFormat, BOOL multicolor) { int ret = DMERR_OK; int outBlockW, outBlockH, bx, by; FILE *outFile = NULL; Uint8 *buf = NULL; size_t outBufSize; char *outType; switch (outFormat) { case FFMT_CHAR: outBufSize = C64_CHR_SIZE; outBlockW = image->width / C64_CHR_WIDTH_PX; outBlockH = image->height / C64_CHR_HEIGHT; outType = "char"; break; case FFMT_SPRITE: outBufSize = C64_SPR_SIZE; outBlockW = image->width / C64_SPR_WIDTH_PX; outBlockH = image->height / C64_SPR_HEIGHT; outType = "sprite"; break; default: ret = dmError(DMERR_INVALID_ARGS, "Invalid output format %d, internal error.\n", outFormat); goto error; } if (outBlockW < 1 || outBlockH < 1) { ret = dmError(DMERR_INVALID_ARGS, "Source image dimensions too small for conversion, block dimensions %d x %d.\n", outBlockW, outBlockH); goto error; } if ((outFile = fopen(filename, "wb")) == NULL) { ret = dmGetErrno(); dmErrorMsg("Could not open '%s' for writing, %d: %s.\n", filename, ret, dmErrorStr(ret)); goto error; } if ((buf = dmMalloc(outBufSize)) == NULL) { dmErrorMsg("Could not allocate %d bytes for conversion buffer.\n", outBufSize); goto error; } dmMsg(1, "Writing %d x %d = %d blocks of %s data...\n", outBlockW, outBlockH, outBlockW * outBlockH, outType); for (by = 0; by < outBlockH; by++) for (bx = 0; bx < outBlockW; bx++) { switch (outFormat) { case FFMT_CHAR: if (!dmConvertImage2Char(buf, image, bx * C64_CHR_WIDTH_PX, by * C64_CHR_HEIGHT, multicolor)) { ret = DMERR_DATA_ERROR; goto error; } break; case FFMT_SPRITE: if (!dmConvertImage2Sprite(buf, image, bx * C64_SPR_WIDTH_PX, by * C64_SPR_HEIGHT, multicolor)) { ret = DMERR_DATA_ERROR; goto error; } break; } if (!dm_fwrite_str(outFile, buf, outBufSize)) { ret = dmGetErrno(); dmError(ret, "Error writing data block %d,%d to '%s', %d: %s\n", bx, by, filename, ret, dmErrorStr(ret)); goto error; } } fclose(outFile); dmFree(buf); return 0; error: if (outFile != NULL) fclose(outFile); dmFree(buf); return ret; } int dmDumpSpritesAndChars(FILE *inFile) { int dataOffs, itemCount, outWidth, outWidthPX, outHeight; int ret = DMERR_OK; size_t bufSize; Uint8 *bufData; switch (optInFormat) { case FFMT_CHAR: bufSize = C64_CHR_SIZE; outWidth = C64_CHR_WIDTH; outWidthPX = C64_CHR_WIDTH_PX; outHeight = C64_CHR_HEIGHT; break; case FFMT_SPRITE: bufSize = C64_SPR_SIZE; outWidth = C64_SPR_WIDTH; outWidthPX = C64_SPR_WIDTH_PX; outHeight = C64_SPR_HEIGHT; break; default: return dmError(DMERR_INTERNAL, "Invalid input format %d, internal error.\n", optInFormat); } if ((bufData = dmMalloc(bufSize)) == NULL) { return dmError(DMERR_INTERNAL, "Could not allocate temporary buffer of %d bytes.\n", bufSize); } dataOffs = optInSkip; itemCount = 0; if (optOutFormat == FFMT_ANSI || optOutFormat == FFMT_ASCII) { BOOL error = FALSE; FILE *outFile; if (optOutFilename == NULL) outFile = stdout; else if ((outFile = fopen(optOutFilename, "w")) == NULL) { ret = dmGetErrno(); dmError(ret, "Error opening output file '%s', %d: %s\n", optOutFilename, ret, dmErrorStr(ret)); goto error; } while (!feof(inFile) && !error && (optItemCount < 0 || itemCount < optItemCount)) { dmMemset(bufData, 0, bufSize); if (fread(bufData, 1, bufSize, inFile) != bufSize) { dmErrorMsg("Could not read full bufferful (%d bytes) of data at 0x%x.\n", bufSize, dataOffs); error = TRUE; } fprintf(outFile, "---- : -------------- #%d\n", itemCount); switch (optInFormat) { case FFMT_CHAR: dmDumpCharASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); break; case FFMT_SPRITE: dmDumpSpriteASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); break; } itemCount++; } fclose(outFile); } else if (optOutFormat == FFMT_IMAGE) { DMImage *outImage = NULL; char *outFilename = NULL; int outX = 0, outY = 0, err; if (optSequential) { if (optOutFilename == NULL) { dmErrorMsg("Sequential image output requires filename template.\n"); goto error; } outImage = dmImageAlloc(outWidthPX, outHeight, DM_IFMT_PALETTE, -1); dmMsg(1, "Outputting sequence of %d images @ %d x %d -> %d x %d.\n", optItemCount, outImage->width, outImage->height, outImage->width * optSpec.scaleX, outImage->height * optSpec.scaleY); } else { int outIWidth, outIHeight; if (optItemCount <= 0) { dmErrorMsg("Single-image output requires count to be set (-n).\n"); goto error; } outIWidth = optPlanedWidth; outIHeight = (optItemCount / optPlanedWidth); if (optItemCount % optPlanedWidth) outIHeight++; outImage = dmImageAlloc(outWidthPX * outIWidth, outIHeight * outHeight, DM_IFMT_PALETTE, -1); } outImage->constpal = TRUE; outImage->pal = dmC64Palette; outImage->ncolors = C64_NCOLORS; outImage->ctransp = 255; while (!feof(inFile) && (optItemCount < 0 || itemCount < optItemCount)) { dmMemset(bufData, 0, bufSize); if (fread(bufData, 1, bufSize, inFile) != bufSize) { dmErrorMsg("Could not read full bufferful (%d bytes) of data at 0x%x.\n", bufSize, dataOffs); break; } if ((err = dmC64ConvertCSDataToImage(outImage, outX * outWidthPX, outY * outHeight, bufData, outWidth, outHeight, optInMulticolor, optColors)) != DMERR_OK) { dmErrorMsg("Internal error in conversion of raw data to bitmap: %d.\n", err); break; } if (optSequential) { outFilename = dm_strdup_printf("%s%04d.%s", optOutFilename, itemCount, convFormatList[optOutFormat].fext); if (outFilename == NULL) { dmErrorMsg("Could not allocate memory for filename template?\n"); goto error; } ret = dmWriteImage(optOutFilename, outImage, &optSpec, optOutSubFormat, TRUE); if (ret != DMERR_OK) { dmErrorMsg("Error writing output image, %d: %s.\n", ret, dmErrorStr(ret)); } dmFree(outFilename); } else { if (++outX >= optPlanedWidth) { outX = 0; outY++; } } itemCount++; } if (!optSequential) { ret = dmWriteImage(optOutFilename, outImage, &optSpec, optOutSubFormat, TRUE); if (ret != DMERR_OK) { dmError(ret, "Error writing output image, %d: %s.\n", ret, dmErrorStr(ret)); } } dmImageFree(outImage); } else if (optOutFormat == FFMT_BITMAP) { if (optSequential) { ret = dmError(DMERR_INVALID_ARGS, "Sequential output not supported for spr/char -> bitmap conversion.\n"); goto error; } } dmFree(bufData); return DMERR_OK; error: dmFree(bufData); return ret; } int main(int argc, char *argv[]) { FILE *inFile = NULL; const DMC64ImageFormat *cfmt = NULL; DMC64Image *cimage = NULL; Uint8 *dataBuf = NULL; size_t dataSize; int i; // Default colors for (i = 0; i < C64_MAX_COLORS; i++) optColors[i] = i; // Initialize and parse commandline dmInitProg("gfxconv", "Simple graphics converter", "0.91", NULL, NULL); if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); #ifndef DM_USE_LIBPNG if (optOutFormat == IMGFMT_PNG) { dmErrorMsg("PNG output format support not compiled in, sorry.\n"); goto error; } #endif // Determine input format, if not specified' if (optInFormat == FFMT_AUTO && optInFilename != NULL) { char *dext = strrchr(optInFilename, '.'); dmMsg(4, "Trying to determine file format by extension.\n"); if (dext) { if (!dmGetFormatByExt(dext + 1, &optInFormat, &optInSubFormat)) dmGetC64FormatByExt(dext + 1, &optInFormat, &optInSubFormat); } } if (optInFilename == NULL) { if (optInFormat == FFMT_AUTO) { dmErrorMsg("Standard input cannot be used without specifying input format.\n"); dmErrorMsg("Perhaps you should try using --help\n"); goto error; } inFile = stdin; } else if ((inFile = fopen(optInFilename, "rb")) == NULL) { int res = dmGetErrno(); dmErrorMsg("Error opening input file '%s', %d: %s\n", optInFilename, res, dmErrorStr(res)); goto error; } if (dmReadDataFile(inFile, NULL, &dataBuf, &dataSize) != 0) goto error; if (optInFormat == FFMT_AUTO || optInFormat == FFMT_BITMAP) { // Probe for format const DMC64ImageFormat *forced = NULL; int res; if (optForcedFormat >= 0) { forced = &dmC64ImageFormats[optForcedFormat]; dmMsg(0,"Forced %s format image, type %d, %s\n", forced->name, forced->type, forced->fext); } res = dmC64DecodeBMP(&cimage, dataBuf, dataSize, optInSkip, optInSkip + 2, &cfmt, forced); if (forced == NULL && cfmt != NULL) { dmMsg(1,"Probed %s format image, type %d, %s\n", cfmt->name, cfmt->type, cfmt->fext); } if (res == DMERR_OK) optInFormat = FFMT_BITMAP; else { dmErrorMsg("Could not decode input image.\n"); exit(3); } } if (optInFormat == FFMT_AUTO || optInFormat == FFMT_IMAGE) { DMImageFormat *ifmt = NULL; int index; dmMsg(4, "Trying to probe image formats.\n"); if (dmImageProbeGeneric(dataBuf + optInSkip, dataSize - optInSkip, &ifmt, &index) > 0) { optInFormat = FFMT_IMAGE; optInSubFormat = index; dmMsg(2, "Probed %s format image.\n", ifmt->fext); } } if (optInFormat == FFMT_AUTO) { dmErrorMsg("No input format specified, and could not be determined automatically.\n"); exit(1); } // Skip, if needed if (fseek(inFile, optInSkip, SEEK_SET) != 0) { int res = dmGetErrno(); dmErrorMsg("Could not seek to file position %d (0x%x): %s\n", optInSkip, optInSkip, dmErrorStr(res)); goto error; } int inFormat = dmGetConvFormat(optInFormat, optInSubFormat), outFormat = dmGetConvFormat(optOutFormat, optOutSubFormat); if (inFormat != -1 && outFormat != -1) { char *inFmtName = convFormatList[inFormat].name, *inFmtExt = convFormatList[inFormat].fext, *outFmtName = convFormatList[outFormat].name, *outFmtExt = convFormatList[outFormat].fext; if (optInFormat == FFMT_BITMAP) inFmtExt = cfmt->name; dmMsg(1, "Attempting conversion %s (%s) -> %s (%s)\n", inFmtName, inFmtExt, outFmtName, outFmtExt); } switch (optInFormat) { case FFMT_SPRITE: case FFMT_CHAR: dmDumpSpritesAndChars(inFile); break; case FFMT_BITMAP: { DMImage *outImage = NULL; int res = DMERR_OK; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for bitmap formats.\n"); goto error; } switch (optOutFormat) { case FFMT_IMAGE: res = dmC64ConvertBMP2Image(&outImage, cimage, cfmt); if (res != DMERR_OK || outImage == NULL) { dmErrorMsg("Error in bitmap to image conversion.\n"); goto error; } res = dmWriteImage(optOutFilename, outImage, &optSpec, optOutSubFormat, TRUE); break; case FFMT_BITMAP: res = dmWriteBitmap(optOutFilename, cimage, optOutSubFormat, TRUE); break; case FFMT_CHAR: case FFMT_SPRITE: res = dmC64ConvertBMP2Image(&outImage, cimage, cfmt); if (res != DMERR_OK || outImage == NULL) { dmErrorMsg("Error in bitmap to template image conversion.\n"); goto error; } res = dmWriteSpritesAndChars(optOutFilename, outImage, optOutFormat, optInMulticolor); break; default: dmErrorMsg("Unsupported output format for bitmap/image conversion.\n"); break; } dmImageFree(outImage); } break; case FFMT_IMAGE: { DMImage *outImage = NULL; int res = DMERR_OK; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for image formats.\n"); goto error; } // Read input DMImageFormat *ifmt = &dmImageFormatList[optInSubFormat]; if (ifmt->readFILE != NULL) res = ifmt->readFILE(inFile, &outImage); else dmErrorMsg("Unsupported input image format for bitmap/image conversion.\n"); if (res != DMERR_OK || outImage == NULL) break; switch (optOutFormat) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, outImage, &optSpec, optOutSubFormat, TRUE); break; case FFMT_CHAR: case FFMT_SPRITE: res = dmWriteSpritesAndChars(optOutFilename, outImage, optOutFormat, optInMulticolor); break; default: dmErrorMsg("Unsupported output format for bitmap/image conversion.\n"); break; } if (res != DMERR_OK) { dmErrorMsg("Error writing output (%s), probably unsupported output format for bitmap/image conversion.\n", dmErrorStr(res)); } dmImageFree(outImage); } break; } error: if (inFile != NULL) fclose(inFile); dmFree(dataBuf); dmC64ImageFree(cimage); return 0; }