Mercurial > hg > dmlib
view tools/gfxconv.c @ 2476:90eec3e1f85f
Use DMResource I/O instead of stdio for writing JSSMOD module.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 27 Apr 2020 18:33:18 +0300 |
parents | 16c57206cef7 |
children | f3d9cdb0a295 |
line wrap: on
line source
/* * gfxconv - Convert various graphics formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2020 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "dmtool.h" #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #include "libgfx.h" #include "lib64gfx.h" #include "lib64util.h" #include "dmmutex.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_BITMAP, FFMT_CHAR, FFMT_SPRITE, FFMT_IMAGE, FFMT_DUMP, FFMT_PALETTE, FFMT_LAST }; enum { FCMP_NONE = 0, FCMP_BEST = 9 }; static const char *formatTypeList[FFMT_LAST] = { "AUTO", "ASCII text", "ANSI text", "C64 bitmap image", "C64 character/font data", "C64 sprite data", "Image", "CDump", "Palette", }; enum { CROP_NONE = 0, CROP_AUTO, CROP_SIZE, }; enum { SCALE_SET, SCALE_RELATIVE, SCALE_AUTO, }; typedef struct { char *name; // Descriptive name of the format char *fext; // File extension int flags; // DM_FMT_* flags, see libgfx.h int type; // Format type int format; // Subformat identifier char *help; } DMConvFormat; static const DMConvFormat baseFormatList[] = { { "ASCII text" , "asc" , DM_FMT_WR , FFMT_ASCII , 0 , NULL }, { "ANSI colored text" , "ansi" , DM_FMT_WR , FFMT_ANSI , 0 , NULL }, { "C64 bitmap image" , "bitmap", DM_FMT_RDWR , FFMT_BITMAP , -1 , NULL }, { "C64 character/font data" , "chr" , DM_FMT_RDWR , FFMT_CHAR , 0 , " (chr:mc/chr:sc for multi/singlecolor)" }, { "C64 sprite data" , "spr" , DM_FMT_RDWR , FFMT_SPRITE , 0 , " (spr:mc/spr:sc for multi/singlecolor)" }, { "C64 bitmap image dump" , "dump" , DM_FMT_WR , FFMT_DUMP , 0 , NULL }, { "Palette data" , "pal" , DM_FMT_RDWR , FFMT_PALETTE , -1 , NULL }, }; static const int nbaseFormatList = sizeof(baseFormatList) / sizeof(baseFormatList[0]); static DMConvFormat *convFormatList = NULL; static int nconvFormatList = 0; typedef struct { BOOL triplet, alpha; DMColor color; unsigned int from, to; } DMMapValue; char *optInFilename = NULL, *optOutFilename = NULL; int optInType = FFMT_AUTO, optOutType = FFMT_AUTO, optInFormat = -1, optOutFormat = -1, optItemCount = -1, optPlanedWidth = 1, optForcedInSubFormat = -1; unsigned int optInSkip = 0; BOOL optInSkipNeg = FALSE; int optCropMode = CROP_NONE, optCropX0, optCropY0, optCropW, optCropH; BOOL optInMulticolor = FALSE, optSequential = FALSE, optRemapColors = FALSE, optRemapRemove = FALSE, optUsePalette = FALSE; int optNRemapTable = 0, optScaleMode = SCALE_AUTO; DMMapValue optRemapTable[DM_MAX_COLORS]; int optColorMap[D64_NCOLORS]; char *optCharROMFilename = NULL; DMC64Palette *optC64Palette = NULL; char *optPaletteFile = NULL; DMPalette *optPaletteData = NULL; DMImageWriteSpec optSpec = { .scaleX = 1, .scaleY = 1, .nplanes = 0, .bpp = 8, .planar = FALSE, .pixfmt = 0, .compression = FCMP_BEST, }; DMC64ImageConvSpec optC64Spec; static const DMOptArg optList[] = { { 0, '?', "help" , "Show this help", OPT_NONE }, { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE }, { 2, 'v', "verbose" , "Be more verbose", OPT_NONE }, { 10, 'o', "output" , "Output filename", OPT_ARGREQ }, { 12, 's', "skip" , "Skip N bytes in input from start (negative value will be offset from input end)", OPT_ARGREQ }, { 14, 'i', "informat" , "Set input format (see --formats)", OPT_ARGREQ }, { 16, 'f', "format" , "Set output format (see --formats)", OPT_ARGREQ }, { 18, 'F', "formats" , "List supported input/output formats", OPT_NONE }, { 20, 'q', "sequential" , "Output sequential files (image output only)", OPT_NONE }, { 22, 'm', "colormap" , "Set color index mapping (see below for information)", OPT_ARGREQ }, { 24, 'n', "numitems" , "How many 'items' to output (default: all)", OPT_ARGREQ }, { 26, 'w', "width" , "Item width (number of items per row, min 1)", OPT_ARGREQ }, { 28, 'S', "scale" , "Scale output image by specified value(s) (see below)", OPT_ARGREQ }, { 30, 'P', "paletted" , "Use indexed/paletted output IF possible.", OPT_NONE }, { 32, 'N', "nplanes" , "# of bitplanes (some output formats)", OPT_ARGREQ }, { 34, 'B', "bpp" , "Bits per plane (some output formats)", OPT_ARGREQ }, { 36, 'I', "interleave" , "Interleaved/planar output (some output formats)", OPT_NONE }, { 38, 'C', "compress" , "Use compression -C <0-9>, 0 = disable, default is 9", OPT_ARGREQ }, { 42, 'R', "remap" , "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>][+remove] | -R auto[+remove] | -R @map.txt[+remove])", OPT_ARGREQ }, { 44, 0, "char-rom" , "Set character ROM file to be used.", OPT_ARGREQ }, { 46, 'p', "palette" , "Set C64 palette to be used (see list with -p help).", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowFormats() { printf( "Available input/output formats (-f <frmt>):\n" " frmt | RW | Description\n" "------+----+-------------------------------------------------------\n" ); for (int i = 0; i < nconvFormatList; i++) { const DMConvFormat *fmt = &convFormatList[i]; printf("%-6s| %c%c | %s%s\n", fmt->fext ? fmt->fext : "", (fmt->flags & DM_FMT_RD) ? 'R' : ' ', (fmt->flags & DM_FMT_WR) ? 'W' : ' ', fmt->name, fmt->help != NULL ? fmt->help : ""); } printf( "\n" "(Not all input->output combinations are actually supported.)\n" "\n" ); argShowC64Formats(stdout, TRUE); } void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] [<input file>]"); dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2); fprintf(stdout, "\n" "Output image scaling (-S)\n" "-------------------------\n" "Scaling option '-S <n>', '-S <x>:<y>', '-S <x>:<y>*<n>' can be used to set\n" "the direct or relative scale integer factor(s). '-S <n>' sets both height\n" "and width scale factor to <n>. '-S <x>:<y>*<n>' scales width by X*n and\n" "height Y*n. Certain input formats set their default aspect/scale factors.\n" "By prepending -S parameters with asterisk ('*') you can scale relative to\n" "those values. (e.g. '-S *2' for example.) NOTE! Only integer scale factors\n" "can be used at the moment.\n" "\n" "Palette 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 always desirable, for example\n" "when converting multiple images to same palette. You can also specify the\n" "+remove option by itself to remove all unused colors: -R +remove\n" "\n" "Color index mapping (-m)\n" "------------------------\n" "Color index map definitions are used for sprite/char data input (and ANSI\n" "output), to set what colors of the C64 palette are used for each single\n" "color/multi color bit-combination.\n" "For example, if the input is multi color sprite or char, you can define\n" "colors like: -m 0,8,3,15 .. for hires/single color: -m 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 transparency color index; -m 255,2 would use transparency for\n" "'0' bits and and C64 color 2 for '1' bits.\n" "\n" "Default character ROM file for this build is:\n" "%s\n", DM_DEF_CHARGEN ); } // // Replace filename extension based on format pattern. // Usage: res = dm_strdup_fext(orig_filename, "foo_%s.cmp"); // char *dm_strdup_fext(const char *filename, const char *fmt) { char *result, *tmp, *fext; if (filename == NULL || (tmp = dm_strdup(filename)) == NULL) return NULL; if ((fext = strrchr(tmp, '.')) != NULL) { char *fpath = strrchr(tmp, DM_DIR_SEPARATOR); if (fpath == NULL || (fpath != NULL && fext > fpath)) *fext = 0; } result = dm_strdup_printf(fmt, tmp); dmFree(tmp); return result; } // // Return a "matching" ANSI colour code for given C64 palette index. // As the standard 16 ANSI colours are not exact match and some C64 // colours cant be represented, this is an imperfect conversion. // const char *dmC64GetANSIFromC64Color(const int col) { switch (col) { case 0: return "0;30"; // Black case 1: return "0;1;37"; // White case 2: return "0;31"; // Red case 3: return "0;36"; case 4: return "0;35"; case 5: return "0;32"; case 6: return "0;34"; case 7: return "0;1;33"; case 8: return "0;33"; case 9: return "0;31"; case 10: return "0;1;31"; case 11: return "0;1;30"; case 12: return "0;1;30"; case 13: return "0;1;32"; case 14: return "0;1;34"; case 15: return "0;37"; default: return "0"; } } BOOL dmGetConvFormat(const int type, const int format, DMConvFormat *pfmt) { for (int i = 0; i < nconvFormatList; i++) { const DMConvFormat *fmt = &convFormatList[i]; if (fmt->type == type && fmt->format == format) { memcpy(pfmt, fmt, sizeof(DMConvFormat)); return TRUE; } } for (int i = 0; i < nconvFormatList; i++) { const DMConvFormat *fmt = &convFormatList[i]; if (fmt->type == type && type == FFMT_BITMAP) { const DMConvFormat *fmt = &convFormatList[i]; const DMC64ImageFormat *cfmt = &dmC64ImageFormats[format]; memcpy(pfmt, fmt, sizeof(DMConvFormat)); pfmt->fext = cfmt->name; return TRUE; } } return FALSE; } BOOL dmGetC64FormatByExt(const char *fext, int *type, int *format) { if (fext == NULL) return FALSE; for (int i = 0; i < ndmC64ImageFormats; i++) { const DMC64ImageFormat *fmt = &dmC64ImageFormats[i]; if (fmt->fext != NULL && strcasecmp(fext, fmt->fext) == 0) { *type = FFMT_BITMAP; *format = i; return TRUE; } } return FALSE; } BOOL dmGetFormatByExt(const char *fext, int *type, int *format) { if (fext == NULL) return FALSE; for (int i = 0; i < nconvFormatList; i++) { const DMConvFormat *fmt = &convFormatList[i]; if (fmt->fext != NULL && strcasecmp(fext, fmt->fext) == 0) { *type = fmt->type; *format = fmt->format; return TRUE; } } return FALSE; } 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 out; if ((end = split = strchr(opt, ':')) == NULL) { dmErrorMsg("Invalid %s value '%s', expected <(#|%%)RRGGBB|[$|0x]index>:<[$|0x]index>.\n", msg, opt); goto out; } // 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 out; } value->color.r = colR; value->color.g = colG; value->color.b = colB; value->triplet = TRUE; } else { if (!dmGetIntVal(opt, &value->from, NULL)) { dmErrorMsg("Invalid %s value '%s', could not parse source value '%s'.\n", msg, popt, opt); goto out; } value->triplet = FALSE; } // Trim whitespace split++; while (*split && isspace(*split)) split++; // Parse destination value if (!dmGetIntVal(split, &value->to, NULL)) { dmErrorMsg("Invalid %s value '%s', could not parse destination value '%s'.\n", msg, popt, split); goto out; } if (!value->triplet && value->from > 255) { dmErrorMsg("Invalid %s map source color index value %d, must be [0..255].\n", msg, value->from); goto out; } if (value->to > nmax) { dmErrorMsg("Invalid %s map destination color index value %d, must be [0..%d].\n", msg, value->to, nmax); goto out; } dmFree(opt); return TRUE; out: dmFree(opt); return FALSE; } 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], NULL)) { 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(); return dmError(res, "Could not open color remap file '%s' for reading: %s.\n", filename, dmErrorStr(res)); } while (fgets(line, sizeof(line), fp)) { char *start = line; line[sizeof(line) - 1] = 0; while (*start && isspace(*start)) start++; if (*start != 0 && *start != ';') { if (!dmParseMapOptionMapItem(line, &values[*nvalue], nmax, "mapping file")) goto out; (*nvalue)++; if (*nvalue >= nmax) { dmErrorMsg("Too many mapping pairs in '%s', maximum is %d.\n", filename, nmax); goto out; } } } out: fclose(fp); return res; } BOOL dmParseFormatOption(const char *msg1, const char *msg2, char *optArg, int *format, int *subFormat) { char *flags = strchr(optArg, ':'); if (flags != NULL) *flags++ = 0; if (!dmGetFormatByExt(optArg, format, subFormat) && !dmGetC64FormatByExt(optArg, format, subFormat)) { dmErrorMsg("Invalid %s format '%s', see -F / --formats for format list.\n", msg1, optArg); return FALSE; } if (flags != NULL) { switch (*format) { case FFMT_SPRITE: case FFMT_CHAR: if (strcasecmp(flags, "mc") == 0) optInMulticolor = TRUE; else if (strcasecmp(flags, "sc") == 0) optInMulticolor = FALSE; else { dmErrorMsg("Invalid %s format flags for sprite/char '%s', should be 'mc' or 'sc'.\n", msg1, flags); return FALSE; } break; default: dmErrorMsg("%s format '%s' does not support any flags ('%s').\n", msg2, optArg, flags); return FALSE; } } return TRUE; } BOOL dmParseIntValWithSep(char **arg, unsigned int *value, char *last, const char sep) { char *ptr = *arg, *end, *start; // Skip any whitespace at start while (*ptr != 0 && isspace(*ptr)) ptr++; start = ptr; // Find next not-xdigit/separator while (*ptr != 0 && (isxdigit(*ptr) || *ptr == 'x' || *ptr == '$') && *ptr != sep) ptr++; end = ptr; // Skip whitespace again while (*ptr != 0 && isspace(*ptr)) ptr++; // Return last character in "last" *last = *ptr; // Set end to NUL *end = 0; // And if last character is not NUL, move ptr if (*last != 0) ptr++; *arg = ptr; return dmGetIntVal(start, value, NULL); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { unsigned int tmpUInt; char *tmpStr; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmPrintLicense(stdout); exit(0); break; case 2: dmVerbosity++; break; case 10: optOutFilename = optArg; break; case 12: if (!dmGetIntVal(optArg, &optInSkip, &optInSkipNeg)) { dmErrorMsg("Invalid skip value argument '%s'.\n", optArg); return FALSE; } break; case 14: { DMConvFormat fmt; if (!dmParseFormatOption("input", "Input", optArg, &optInType, &optForcedInSubFormat)) return FALSE; dmGetConvFormat(optInType, optForcedInSubFormat, &fmt); if ((fmt.flags & DM_FMT_RD) == 0) { dmErrorMsg("Invalid input format '%s', does not support reading.\n", fmt.name); return FALSE; } } break; case 16: { DMConvFormat fmt; if (!dmParseFormatOption("output", "Output", optArg, &optOutType, &optOutFormat)) return FALSE; dmGetConvFormat(optOutType, optOutFormat, &fmt); if ((fmt.flags & DM_FMT_WR) == 0) { dmErrorMsg("Invalid output format '%s', does not support writing.\n", fmt.name); return FALSE; } } break; case 18: argShowFormats(); exit(0); break; case 20: optSequential = TRUE; break; case 22: { int ncolors; if (!dmParseMapOptionString(optArg, optColorMap, &ncolors, D64_NCOLORS, FALSE, "color index option")) return FALSE; dmMsg(1, "Set color index mapping: "); for (int index = 0; index < ncolors; index++) { dmPrint(1, "[%d:%d]%s", index, optColorMap[index], (index < ncolors) ? ", " : ""); } dmPrint(1, "\n"); } break; case 24: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1) { dmErrorMsg("Invalid count value argument '%s' [1 .. MAXINT]\n", optArg); return FALSE; } optItemCount = tmpUInt; break; case 26: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1 || tmpUInt > 512) { dmErrorMsg("Invalid planed width value '%s' [1 .. 512]\n", optArg); return FALSE; } optPlanedWidth = tmpUInt; break; case 28: { BOOL error = FALSE; unsigned int tmpUInt2; char *tmpStr = dm_strdup(optArg), *tmpOpt = tmpStr, sep; // Check for "relative scale mode specifier if (*tmpOpt == '*') { tmpOpt++; optScaleMode = SCALE_RELATIVE; } else optScaleMode = SCALE_SET; // Parse the values if (dmParseIntValWithSep(&tmpOpt, &tmpUInt, &sep, ':')) { if (sep == ':' && dmParseIntValWithSep(&tmpOpt, &tmpUInt2, &sep, '*')) { optSpec.scaleX = tmpUInt; optSpec.scaleY = tmpUInt2; if (sep == '*' && dmParseIntValWithSep(&tmpOpt, &tmpUInt, &sep, 0)) { optSpec.scaleX *= tmpUInt; optSpec.scaleY *= tmpUInt; } error = (sep != 0); } else if (sep == 0) { optSpec.scaleX = optSpec.scaleY = tmpUInt; } else error = TRUE; } else error = TRUE; dmFree(tmpStr); if (error) { dmErrorMsg( "Invalid scale option value '%s', should be <n>, <w>:<h> or <w>:<h>*<n>.\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 30: optUsePalette = TRUE; break; case 32: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1 || tmpUInt > 8) { dmErrorMsg("Invalid number of bitplanes value '%s' [1 .. 8]\n", optArg); return FALSE; } optSpec.nplanes = tmpUInt; break; case 34: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1 || tmpUInt > 32) { dmErrorMsg("Invalid number of bits per plane value '%s' [1 .. 32]\n", optArg); return FALSE; } optSpec.bpp = tmpUInt; break; case 36: optSpec.planar = TRUE; break; case 38: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt > FCMP_BEST) { dmErrorMsg("Invalid compression setting '%s' [%d .. %d]\n", optArg, FCMP_NONE, FCMP_BEST); return FALSE; } optSpec.compression = tmpUInt; break; case 40: { 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; case 42: if ((tmpStr = dm_strrcasecmp(optArg, "+remove")) != NULL) { optRemapRemove = TRUE; *tmpStr = 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 (dm_strcasecmp(optArg, "auto") != 0) { if (!dmParseMapOptionString(optArg, optRemapTable, &optNRemapTable, DM_MAX_COLORS, TRUE, "color remap option")) return FALSE; } optRemapColors = TRUE; break; case 44: optCharROMFilename = optArg; break; case 46: return argHandleC64PaletteOption(optArg, &optC64Palette, &optPaletteFile); default: dmErrorMsg("Unimplemented option argument '%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, const Uint8 byte, const int format, const BOOL multicolor, const BOOL dir) { if (multicolor) { for (int i = 0; i < DM_ASC_NBITS; i += 2) { int k = dir ? i : (DM_ASC_NBITS - i - 1); Uint8 val = (byte & (3ULL << k)) >> k; char ch; switch (format) { case FFMT_ASCII: ch = dmASCIIPalette[val]; fprintf(out, "%c%c", ch, ch); break; case FFMT_ANSI: fprintf(out, "\x1b[%sm##\x1b[0m", dmC64GetANSIFromC64Color(optColorMap[val])); break; } } } else { for (int i = 0; i < DM_ASC_NBITS; i++) { int k = dir ? i : (DM_ASC_NBITS - i - 1); Uint8 val = (byte & (1ULL << k)) >> k; switch (format) { case FFMT_ASCII: fputc(val ? '#' : '.', out); break; case FFMT_ANSI: fprintf(out, "\x1b[%sm#\x1b[0m", dmC64GetANSIFromC64Color(optColorMap[val])); break; } } } } void dmDumpCharASCII(FILE *outFile, const Uint8 *buf, const size_t offs, const int fmt, const BOOL multicolor) { for (size_t yc = 0; yc < D64_CHR_HEIGHT_UT; yc++) { fprintf(outFile, "%04" DM_PRIx_SIZE_T " : ", offs + yc); dmPrintByte(outFile, buf[yc], fmt, multicolor, FALSE); fprintf(outFile, "\n"); } } void dmDumpSpriteASCII(FILE *outFile, const Uint8 *buf, const size_t offs, const int fmt, BOOL multicolor) { size_t bufOffs, xc, yc; for (bufOffs = yc = 0; yc < D64_SPR_HEIGHT_UT; yc++) { fprintf(outFile, "%04" DM_PRIx_SIZE_T " ", offs + bufOffs); for (xc = 0; xc < D64_SPR_WIDTH_UT; xc++) { dmPrintByte(outFile, buf[bufOffs], fmt, multicolor, FALSE); fprintf(outFile, " "); bufOffs++; } fprintf(outFile, "\n"); } } BOOL dmExactCompareColor(const DMColor *c1, const DMColor *c2, const BOOL alpha) { if (c1->r == c2->r && c1->g == c2->g && c1->b == c2->b) return alpha ? (c1->a == c2->a) : TRUE; else return FALSE; } int dmRemoveUnusedImageColors(DMImage **pdst, const DMImage *src) { DMPalette *tmpPal = NULL; int *mapping = dmMalloc(src->pal->ncolors * sizeof(int)); BOOL *mapped = dmMalloc(src->pal->ncolors * sizeof(BOOL)); BOOL *used = dmMalloc(src->pal->ncolors * sizeof(BOOL)); int n, index, xc, yc, ncolors, res = DMERR_OK; DMImage *dst; if (mapping == NULL || mapped == NULL || used == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for reused palette.\n"); goto out; } if ((res = dmPaletteAlloc(&tmpPal, src->pal->ncolors, -1)) != DMERR_OK) { dmErrorMsg("Could not allocate memory for remap palette.\n"); goto out; } if ((dst = *pdst = dmImageAlloc(src->width, src->height, src->pixfmt, src->bpp)) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for remapped image.\n"); goto out; } dmMsg(1, "Remapping %d output image colors of %d colors.\n", optNRemapTable, src->pal->ncolors); // Find used colors dmMsg(2, "Scanning image for used colors...\n"); for (index = 0; index < src->pal->ncolors; index++) { mapping[index] = -1; mapped[index] = used[index] = FALSE; } 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->pal->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->pal->ncolors; n++) { if (dmExactCompareColor(&(src->pal->colors[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->pal->ncolors; index++) if (mapping[index] < 0 && (!optRemapRemove || (optRemapRemove && used[index]))) { for (n = 0; n < src->pal->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->pal->ncolors; index++) { if (mapping[index] + 1 > ncolors) ncolors = mapping[index] + 1; } // Copy palette entries for (index = 0; index < src->pal->ncolors; index++) { if (mapping[index] >= 0) { memcpy(&tmpPal->colors[mapping[index]], &(src->pal->colors[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->pal->ncolors && mapping[col] >= 0 && mapping[col] < src->pal->ncolors) dp[xc] = mapping[col]; else dp[xc] = 0; } } // Set new palette, free memory if ((res = dmPaletteCopy(&dst->pal, tmpPal)) != DMERR_OK) { res = dmError(DMERR_MALLOC, "Could not allocate memory for final remapped palette.\n"); goto out; } out: dmFree(mapping); dmFree(mapped); dmFree(used); return res; } int dmDumpC64Block(const char *fprefix, const char *fext, const DMC64MemBlock *blk, const int index) { int res = DMERR_OK; if (blk != NULL && blk->data != NULL) { char *filename = dm_strdup_printf("%s_%s_%d.bin", fprefix, fext, index); if (filename == NULL) return DMERR_MALLOC; res = dmWriteDataFile(NULL, filename, blk->data, blk->size); dmFree(filename); } return res; } int dmDumpC64Bitmap(const char *fprefix, const DMC64Image *img) { int res = DMERR_OK; for (int i = 0; i < img->nblocks; i++) { res = dmDumpC64Block(fprefix, "bitmap", &img->bitmap[i], i); res = dmDumpC64Block(fprefix, "color", &img->color[i], i); res = dmDumpC64Block(fprefix, "screen", &img->screen[i], i); res = dmDumpC64Block(fprefix, "extradata", &img->extraData[i], i); } return res; } int dmConvertC64Bitmap(DMC64Image **pdst, const DMC64Image *src, const DMC64ImageFormat *dstFmt, const DMC64ImageFormat *srcFmt) { DMC64Image *dst; DMC64MemBlock *srcBlk = NULL, *dstBlk = NULL; const char *blkname = NULL; if (pdst == NULL || dstFmt == NULL || src == NULL || srcFmt == NULL) return DMERR_NULLPTR; // Allocate the destination image if ((dst = *pdst = dmC64ImageAlloc(dstFmt)) == NULL) return DMERR_MALLOC; // Copy rest of the structure .. dst->d020 = src->d020; dst->bgcolor = src->bgcolor; dst->d022 = src->d022; dst->d023 = src->d023; dst->d024 = src->d024; // And some extraInfo fields .. dst->extraInfo[D64_EI_CHAR_CASE] = src->extraInfo[D64_EI_CHAR_CASE]; dst->extraInfo[D64_EI_CHAR_CUSTOM] = src->extraInfo[D64_EI_CHAR_CUSTOM]; // Try to do some simple fixups if ((dst->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK) == D64_FMT_MC && (src->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK) == D64_FMT_HIRES) { dmC64MemBlockCopy(&dst->screen[0], &src->screen[0]); } else if ((dst->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK) == D64_FMT_HIRES && (src->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK) == D64_FMT_MC) { // XXX TODO: Handle FLI mc->hires differently? } if ((dst->extraInfo[D64_EI_MODE] & D64_FMT_FLI) && (src->extraInfo[D64_EI_MODE] & D64_FMT_FLI) == 0) { dmMsg(1, "Upconverting multicolor to FLI.\n"); for (int i = 0; i < dst->nblocks; i++) { if (dst->color[i].data == NULL) dmC64MemBlockCopy(&dst->color[i], &src->color[0]); if (dst->screen[i].data == NULL) dmC64MemBlockCopy(&dst->screen[i], &src->screen[0]); if (dst->bitmap[i].data == NULL) dmC64MemBlockCopy(&dst->bitmap[i], &src->bitmap[0]); } } else if ((src->extraInfo[D64_EI_MODE] & D64_FMT_FLI) && (dst->extraInfo[D64_EI_MODE] & D64_FMT_FLI) == 0) { dmMsg(1, "Downconverting FLI to multicolor.\n"); } // Do per opcode copies for (int opn = 0; opn < D64_MAX_ENCDEC_OPS; opn++) { const DMC64EncDecOp *op = fmtGetEncDecOp(dstFmt, opn); size_t size; if (op->type == DO_LAST) break; size = dmC64GetOpSubjectSize(op, dstFmt->format); switch (op->type) { case DO_COPY: case DO_SET_MEM: case DO_SET_MEM_HI: case DO_SET_MEM_LO: case DO_SET_OP: srcBlk = (DMC64MemBlock *) dmC64GetOpMemBlock(src, op->subject, op->bank); dstBlk = (DMC64MemBlock *) dmC64GetOpMemBlock(dst, op->subject, op->bank); blkname = dmC64GetOpSubjectName(op->subject); // Skip if we did previous fixups/upconverts if (dstBlk != NULL && dstBlk->data != NULL) break; if (srcBlk != NULL && srcBlk->data != NULL && srcBlk->size >= size) { // The block exists in source and is of sufficient size, so copy it dmMsg(3, "Copying whole block '%s' " "op #%d, offs=%d ($%04x), bank=%d, size=%" DM_PRIu_SIZE_T " ($%04" DM_PRIx_SIZE_T ")\n", blkname, opn, op->offs, op->offs, op->bank, size, size); dmC64MemBlockCopy(dstBlk, srcBlk); } else switch (op->subject) { case DS_COLOR_RAM: case DS_SCREEN_RAM: case DS_BITMAP_RAM: case DS_CHAR_DATA: case DS_EXTRA_DATA: if ((dmC64MemBlockAlloc(dstBlk, size)) != DMERR_OK) { return dmError(DMERR_MALLOC, "Could not allocate '%s' block! " "op #%d, offs=%d ($%04x), bank=%d, size=%" DM_PRIu_SIZE_T " ($%04" DM_PRIx_SIZE_T ")\n", blkname, opn, op->offs, op->offs, op->bank, size, size); } if (srcBlk == NULL || srcBlk->data == NULL) { dmMsg(3, "Creating whole block '%s' " "op #%d, offs=%d ($%04x), bank=%d, size=%" DM_PRIu_SIZE_T " ($%04" DM_PRIx_SIZE_T ")\n", blkname, opn, op->offs, op->offs, op->bank, size, size); } else { dmMsg(3, "Creating block '%s' from partial data " "op #%d, offs=%d ($%04x), bank=%d, size=%" DM_PRIu_SIZE_T " ($%04" DM_PRIx_SIZE_T ")\n", blkname, opn, op->offs, op->offs, op->bank, size, size); } switch (op->type) { case DO_COPY: // If some data exists, copy it. Rest is zero. // Otherwise just set to zero. if (srcBlk != NULL && srcBlk->data != NULL) memcpy(dstBlk->data, srcBlk->data, srcBlk->size); break; case DO_SET_MEM: // Leave allocate data to zero. break; case DO_SET_OP: memset(dstBlk->data, op->offs, size); break; default: return dmError(DMERR_INTERNAL, "Unhandled op type #%d in " "op #%d, offs=%d ($%04x), bank=%d, size=%" DM_PRIu_SIZE_T " ($%04" DM_PRIx_SIZE_T ")\n", op->type, opn, op->offs, op->offs, op->bank, size, size); } break; } break; } } return DMERR_OK; } int dmWriteBitmap(const char *filename, const DMC64Image *image, const DMC64ImageFormat *fmt) { int res = DMERR_OK; DMGrowBuf buf; dmGrowBufInit(&buf); // Encode to target format dmMsg(1, "Encoding C64 bitmap data to format '%s'\n", fmt->name); if ((res = dmC64EncodeBMP(&buf, image, fmt)) != DMERR_OK) { dmErrorMsg("Error encoding bitmap data: %s\n", dmErrorStr(res)); goto out; } // And output the file dmMsg(1, "Writing output file '%s', %" DM_PRIu_SIZE_T " bytes.\n", filename, buf.len); res = dmWriteDataFile(NULL, filename, buf.data, buf.len); out: dmGrowBufFree(&buf); return res; } int dmWriteIFFMasterRAWHeaderFile( const char *hdrFilename, const char *dataFilename, const char *prefix, const DMImage *img, const DMImageWriteSpec *spec) { DMResource *fp; int res; if ((res = dmf_open_stdio(hdrFilename, "wb", &fp)) != DMERR_OK) { return dmError(res, "RAW: Could not open file '%s' for writing: %s\n", hdrFilename, dmErrorStr(res)); } res = dmWriteIFFMasterRAWHeader(fp, dataFilename, prefix, img, spec); dmf_close(fp); return res; } int dmWriteImage(const char *filename, DMImage *pimage, DMImageWriteSpec *spec, const DMImageFormat *fmt) { int res = DMERR_OK; // Check if writing is even supported if (fmt->write == NULL || (fmt->flags & DM_FMT_WR) == 0) { return dmError(DMERR_NOT_SUPPORTED, "Writing of '%s' format is not supported.\n", fmt->name); } dmMsg(1, "Outputting '%s' image %d x %d -> %d x %d [%d x %d]\n", fmt->name, 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 && image->pixfmt == DM_PIXFMT_PALETTE) { if ((res = dmRemoveUnusedImageColors(&image, pimage)) != DMERR_OK) return res; allocated = TRUE; } // Determine number of planes, if paletted if (spec->nplanes == 0) { if (image->pixfmt == DM_PIXFMT_PALETTE && image->pal != NULL) spec->nplanes = dmGetNPlanesFromNColors(image->pal->ncolors); else if (image->pixfmt == DM_PIXFMT_GRAYSCALE) spec->nplanes = image->bpp; } if (spec->nplanes <= 0) spec->nplanes = 4; spec->fmtid = fmt->fmtid; // Do some format-specific adjustments and other things switch (fmt->fmtid) { case DM_IMGFMT_PNG: if (optUsePalette) spec->pixfmt = (image->pixfmt == DM_PIXFMT_GRAYSCALE) ? DM_PIXFMT_GRAYSCALE : DM_PIXFMT_PALETTE; else spec->pixfmt = DM_PIXFMT_RGBA; break; case DM_IMGFMT_PPM: if (optUsePalette && image->pixfmt == DM_PIXFMT_GRAYSCALE) spec->pixfmt = DM_PIXFMT_GRAYSCALE; else spec->pixfmt = DM_PIXFMT_RGB; break; case DM_IMGFMT_RAW: case DM_IMGFMT_ARAW: { char *prefix = NULL, *hdrFilename = NULL; if ((hdrFilename = dm_strdup_fext(filename, "%s.inc")) == NULL || (prefix = dm_strdup_fext(filename, "img_%s")) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for filename strings? :O\n"); goto out; } // Replace any non-alphanumerics in palette ID for (int i = 0; prefix[i]; i++) prefix[i] = isalnum(prefix[i]) ? tolower(prefix[i]) : '_'; dmMsg(2, "%d bitplanes, %s planes output.\n", spec->nplanes, spec->planar ? "planar/interleaved" : "non-interleaved"); dmMsg(2, "%s datafile '%s', ID prefix '%s'.\n", fmt->fmtid == DM_IMGFMT_ARAW ? "ARAW" : "RAW", hdrFilename, prefix); res = dmWriteIFFMasterRAWHeaderFile( hdrFilename, filename, prefix, image, spec); dmFree(prefix); dmFree(hdrFilename); } break; default: spec->pixfmt = optUsePalette ? DM_PIXFMT_PALETTE : DM_PIXFMT_RGB; break; } // If no error has occured thus far, write the image if (res == DMERR_OK) { DMResource *fp; char *str; switch (spec->pixfmt) { case DM_PIXFMT_PALETTE : str = "indexed/paletted"; break; case DM_PIXFMT_RGB : str = "24bit RGB"; break; case DM_PIXFMT_RGBA : str = "32bit RGBA"; break; case DM_PIXFMT_GRAYSCALE : str = "grayscale"; break; default : str = "???"; break; } dmMsg(1, "Using %s output.\n", str); if ((res = dmf_open_stdio(filename, "wb", &fp)) != DMERR_OK) { dmErrorMsg("Could not open file '%s' for writing: %s\n", filename, dmErrorStr(res)); goto out; } res = fmt->write(fp, image, spec); dmf_close(fp); } out: if (allocated) dmImageFree(image); return res; } int dmWritePalette(const char *filename, const DMPalette *palette, const DMPaletteFormat *fmt) { DMResource *fp = NULL; int res; // Check if writing is even supported if (fmt->write == NULL || (fmt->flags & DM_FMT_WR) == 0) { return dmError(DMERR_NOT_SUPPORTED, "Writing of '%s' format is not supported.\n", fmt->name); } dmMsg(1, "Outputting '%s' format palette of %d entries.\n", fmt->name, palette->ncolors); if ((res = dmf_open_stdio(filename, "wb", &fp)) != DMERR_OK) { dmErrorMsg("Could not open file '%s' for writing: %s\n", filename, dmErrorStr(res)); goto out; } res = fmt->write(fp, palette); out: dmf_close(fp); return res; } 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 + D64_CHR_WIDTH_PX > image->width || yoffs + D64_CHR_HEIGHT_PX > image->height) return FALSE; for (yc = 0; yc < D64_CHR_HEIGHT_UT; 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 + D64_SPR_WIDTH_PX > image->width || yoffs + D64_SPR_HEIGHT_PX > image->height) return FALSE; for (yc = 0; yc < D64_SPR_HEIGHT_UT; yc++) { for (xc = 0; xc < D64_SPR_WIDTH_PX / D64_SPR_WIDTH_UT; xc++) { const Uint8 *sp = image->data + ((yc + yoffs) * image->pitch) + (xc * 8) + xoffs; buf[(yc * D64_SPR_WIDTH_UT) + xc] = dmConvertByte(sp, multicolor); } } return TRUE; } int dmWriteSpritesAndChars(const char *filename, DMImage *image, int outFormat, const BOOL multicolor) { int ret = DMERR_OK; int outBlockW, outBlockH, bx, by; FILE *outFile = NULL; Uint8 *tmpBuf = NULL; size_t outBufSize; char *outType; switch (outFormat) { case FFMT_CHAR: outBufSize = D64_CHR_SIZE; outBlockW = image->width / D64_CHR_WIDTH_PX; outBlockH = image->height / D64_CHR_HEIGHT_PX; outType = "char"; break; case FFMT_SPRITE: outBufSize = D64_SPR_SIZE; outBlockW = image->width / D64_SPR_WIDTH_PX; outBlockH = image->height / D64_SPR_HEIGHT_PX; outType = "sprite"; break; default: ret = dmError(DMERR_INVALID_ARGS, "Invalid output format %d, internal error.\n", outFormat); goto out; } 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 out; } if ((outFile = fopen(filename, "wb")) == NULL) { ret = dmGetErrno(); dmErrorMsg("Could not open '%s' for writing: %s.\n", filename, dmErrorStr(ret)); goto out; } if ((tmpBuf = dmMalloc(outBufSize)) == NULL) { dmErrorMsg("Could not allocate %" DM_PRIu_SIZE_T " bytes for conversion buffer.\n", outBufSize); goto out; } 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(tmpBuf, image, bx * D64_CHR_WIDTH_PX, by * D64_CHR_HEIGHT_PX, multicolor)) { ret = DMERR_DATA_ERROR; goto out; } break; case FFMT_SPRITE: if (!dmConvertImage2Sprite(tmpBuf, image, bx * D64_SPR_WIDTH_PX, by * D64_SPR_HEIGHT_PX, multicolor)) { ret = DMERR_DATA_ERROR; goto out; } break; } if (!dm_fwrite_str(outFile, tmpBuf, outBufSize)) { ret = dmGetErrno(); dmError(ret, "Error writing data block %d,%d to '%s': %s.\n", bx, by, filename, dmErrorStr(ret)); goto out; } } out: // Cleanup if (outFile != NULL) fclose(outFile); dmFree(tmpBuf); return ret; } int dmDumpSpritesAndChars(const Uint8 *dataBuf, const size_t dataSize, const size_t realOffs) { int ret = DMERR_OK, itemCount, outWidth, outWidthPX, outHeight; size_t offs, outSize; switch (optInType) { case FFMT_CHAR: outSize = D64_CHR_SIZE; outWidth = D64_CHR_WIDTH_UT; outWidthPX = D64_CHR_WIDTH_PX; outHeight = D64_CHR_HEIGHT_UT; break; case FFMT_SPRITE: outSize = D64_SPR_SIZE; outWidth = D64_SPR_WIDTH_UT; outWidthPX = D64_SPR_WIDTH_PX; outHeight = D64_SPR_HEIGHT_UT; break; default: return dmError(DMERR_INTERNAL, "Invalid input format %d, internal error.\n", optInType); } offs = 0; itemCount = 0; if (optOutType == FFMT_ANSI || optOutType == 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': %s.\n", optOutFilename, dmErrorStr(ret)); goto out; } while (offs + outSize < dataSize && !error && (optItemCount < 0 || itemCount < optItemCount)) { fprintf(outFile, "---- : -------------- #%d\n", itemCount); switch (optInType) { case FFMT_CHAR: dmDumpCharASCII(outFile, dataBuf + offs, realOffs + offs, optOutType, optInMulticolor); break; case FFMT_SPRITE: dmDumpSpriteASCII(outFile, dataBuf + offs, realOffs + offs, optOutType, optInMulticolor); break; } offs += outSize; itemCount++; } fclose(outFile); } else if (optOutType == 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 out; } outImage = dmImageAlloc(outWidthPX, outHeight, DM_PIXFMT_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 out; } outIWidth = optPlanedWidth; outIHeight = (optItemCount / optPlanedWidth); if (optItemCount % optPlanedWidth) outIHeight++; outImage = dmImageAlloc(outWidthPX * outIWidth, outIHeight * outHeight, DM_PIXFMT_PALETTE, -1); } if ((err = dmC64SetImagePalette(outImage, &optC64Spec, FALSE)) != DMERR_OK) { dmErrorMsg("Could not allocate C64 palette for output image: %d\n", err); goto out; } while (offs + outSize < dataSize && (optItemCount < 0 || itemCount < optItemCount)) { if ((err = dmC64ConvertCSDataToImage(outImage, outX * outWidthPX, outY * outHeight, dataBuf + offs, outWidth, outHeight, optInMulticolor, optColorMap)) != DMERR_OK) { dmErrorMsg("Internal error in conversion of raw data to bitmap: %d.\n", err); goto out; } if (optSequential) { outFilename = dm_strdup_printf("%s%04d.%s", optOutFilename, itemCount, convFormatList[optOutType].fext); if (outFilename == NULL) { dmErrorMsg("Could not allocate memory for filename template?\n"); goto out; } ret = dmWriteImage(outFilename, outImage, &optSpec, &dmImageFormatList[optOutFormat]); if (ret != DMERR_OK) { dmErrorMsg("Error writing output image '%s': %s.\n", outFilename, dmErrorStr(ret)); } dmFree(outFilename); } else { if (++outX >= optPlanedWidth) { outX = 0; outY++; } } offs += outSize; itemCount++; } if (!optSequential) { ret = dmWriteImage(optOutFilename, outImage, &optSpec, &dmImageFormatList[optOutFormat]); if (ret != DMERR_OK) { dmError(ret, "Error writing output image '%s': %s.\n", optOutFilename, dmErrorStr(ret)); } } dmImageFree(outImage); } else if (optOutType == FFMT_BITMAP) { if (optSequential) { ret = dmError(DMERR_INVALID_ARGS, "Sequential output not supported for spr/char -> bitmap conversion.\n"); goto out; } } out: return ret; } int main(int argc, char *argv[]) { FILE *inFile = NULL; const DMC64ImageFormat *inC64Fmt = NULL; DMConvFormat inFormat, outFormat; DMC64Image *inC64Image = NULL, *outC64Image = NULL; DMImage *inImage = NULL, *outImage = NULL; Uint8 *dataBuf = NULL, *dataBufOrig = NULL; size_t dataSize, dataSizeOrig, dataRealOffs; int i, n, res; // Default color mapping for (i = 0; i < D64_NCOLORS; i++) optColorMap[i] = i; // Initialize c64 image conversion spec memset(&optC64Spec, 0, sizeof(optC64Spec)); // Initialize list of additional conversion formats if ((res = dmLib64GFXInit()) != DMERR_OK) { dmErrorMsg("Could not initialize lib64gfx: %s\n", dmErrorStr(res)); goto exit; } nconvFormatList = ndmImageFormatList + ndmPaletteFormatList + nbaseFormatList; convFormatList = dmCalloc(nconvFormatList, sizeof(DMConvFormat)); for (n = i = 0; i < ndmImageFormatList; i++) { const DMImageFormat *sfmt = &dmImageFormatList[i]; DMConvFormat *dfmt = &convFormatList[n++]; dfmt->name = sfmt->name; dfmt->fext = sfmt->fext; dfmt->flags = sfmt->flags; dfmt->type = FFMT_IMAGE; dfmt->format = sfmt->fmtid; } for (i = 0; i < ndmPaletteFormatList; i++) { const DMPaletteFormat *sfmt = &dmPaletteFormatList[i]; DMConvFormat *dfmt = &convFormatList[n++]; dfmt->name = sfmt->name; dfmt->fext = sfmt->fext; dfmt->flags = sfmt->flags; dfmt->type = FFMT_PALETTE; dfmt->format = sfmt->fmtid; } for (i = 0; i < nbaseFormatList; i++) memcpy(&convFormatList[n++], &baseFormatList[i], sizeof(DMConvFormat)); // Initialize and parse commandline dmInitProg("gfxconv", "Simple graphics converter", "0.94", NULL, NULL); if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); // Determine input format, if not specified if (optInType == FFMT_AUTO && optInFilename != NULL) { char *dext = strrchr(optInFilename, '.'); if (dext) { if (dmGetFormatByExt(dext + 1, &optInType, &optInFormat) || dmGetC64FormatByExt(dext + 1, &optInType, &optInFormat)) { dmMsg(3, "Guessed input type as %s\n", formatTypeList[optInType]); } } } if (optInFilename == NULL) { if (optInType == FFMT_AUTO) { dmErrorMsg("Standard input cannot be used without specifying input format.\n"); dmErrorMsg("Perhaps you should try using --help\n"); goto exit; } inFile = stdin; optInFilename = "stdin"; } else if ((inFile = fopen(optInFilename, "rb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error opening input file '%s': %s\n", optInFilename, dmErrorStr(res)); goto exit; } // Determine output format, if not specified if (optOutType == FFMT_AUTO && optOutFilename != NULL) { char *dext = strrchr(optOutFilename, '.'); if (dext) { if (dmGetFormatByExt(dext + 1, &optOutType, &optOutFormat) || dmGetC64FormatByExt(dext + 1, &optOutType, &optOutFormat)) { dmMsg(3, "Guessed output type as %s\n", formatTypeList[optOutType]); } } } else if (optOutType == FFMT_AUTO) optOutType = FFMT_ASCII; // Read the input .. dmMsg(1, "Reading input from '%s'.\n", optInFilename); if ((res = dmReadDataFile(inFile, NULL, &dataBufOrig, &dataSizeOrig)) != DMERR_OK) { dmErrorMsg("Could not read input: %s.\n", dmErrorStr(res)); goto exit; } fclose(inFile); // Check and compute the input skip if (optInSkip > dataSizeOrig) { dmErrorMsg("Input skip value %d (0x%x) is larger than input size %" DM_PRIu_SIZE_T ".\n", optInSkip, optInSkip, dataSizeOrig); goto exit; } if (optInSkipNeg) { dataBuf = dataBufOrig + dataSizeOrig - optInSkip; dataSize = optInSkip; dataRealOffs = dataSizeOrig - optInSkip; dmMsg(1, "Input skip -%d (-0x%x). " "Offset %" DM_PRIu_SIZE_T " (0x%" DM_PRIx_SIZE_T "), " "size %" DM_PRIu_SIZE_T " (0x%" DM_PRIx_SIZE_T ").\n", optInSkip, optInSkip, dataRealOffs, dataRealOffs, dataSize, dataSize); } else { dataBuf = dataBufOrig + optInSkip; dataSize = dataSizeOrig - optInSkip; dataRealOffs = optInSkip; dmMsg(1, "Input skip %d (0x%x), " "size %" DM_PRIu_SIZE_T " (0x%" DM_PRIx_SIZE_T ").\n", optInSkip, optInSkip, dataSize, dataSize); } // Check for forced input format here if (optForcedInSubFormat >= 0) optInFormat = optForcedInSubFormat; // Perform probing, if required if (optInType == FFMT_AUTO || optInType == FFMT_BITMAP) { // Probe for format const DMC64ImageFormat *forced = NULL; DMGrowBuf tbuf; if (optForcedInSubFormat >= 0) { forced = &dmC64ImageFormats[optForcedInSubFormat]; dmMsg(0, "Forced '%s' format image, type %d, %s\n", forced->name, forced->format->mode, forced->fext); } res = dmC64DecodeBMP(&inC64Image, dmGrowBufConstCreateFrom(&tbuf, dataBuf, dataSize), 0, 2, &inC64Fmt, forced); if (forced == NULL && inC64Fmt != NULL && res == DMERR_OK) { dmMsg(1, "Probed '%s' format image, type %d, %s\n", inC64Fmt->name, inC64Fmt->format->mode, inC64Fmt->fext); optInType = FFMT_BITMAP; } else if (res != DMERR_OK && (forced != NULL || optInType == FFMT_BITMAP)) { dmErrorMsg("Could not decode input image: %s.\n", dmErrorStr(res)); goto exit; } } if (optInType == FFMT_AUTO || optInType == FFMT_IMAGE) { const DMImageFormat *ifmt = NULL; int index; dmMsg(4, "Trying to probe image formats.\n"); if (dmImageProbeGeneric(dataBuf, dataSize, &ifmt, &index) > 0 && ifmt->read != NULL) { optInType = FFMT_IMAGE; optInFormat = index; dmMsg(1, "Probed '%s' format image.\n", ifmt->name); } } if (optInType == FFMT_AUTO || optInType == FFMT_PALETTE) { const DMPaletteFormat *pfmt = NULL; int index; dmMsg(4, "Trying to probe palette formats.\n"); if (dmPaletteProbeGeneric(dataBuf, dataSize, &pfmt, &index) > 0 && pfmt->read != NULL) { optInType = FFMT_PALETTE; optInFormat = index; dmMsg(1, "Probed '%s' format palette.\n", pfmt->name); } } if (optInType == FFMT_AUTO) { dmErrorMsg("No input format specified, and could not be determined automatically.\n"); goto exit; } if (dmGetConvFormat(optInType, optInFormat, &inFormat) && dmGetConvFormat(optOutType, optOutFormat, &outFormat)) { dmMsg(1, "Attempting conversion %s (%s) -> %s (%s)\n", inFormat.name, inFormat.fext, outFormat.name, outFormat.fext); } // Check if we need to scale the output if (optScaleMode != SCALE_SET) { // Default to 1:1 scalefactors int scaleX = 1, scaleY = 1; // For C64 formats, use the aspect ratios from them if (inC64Fmt != NULL) { scaleX = inC64Fmt->format->aspectX; scaleY = inC64Fmt->format->aspectY; } // Then, depending on the scaling mode, apply scale switch (optScaleMode) { case SCALE_AUTO: optSpec.scaleX = scaleX; optSpec.scaleY = scaleY; break; case SCALE_RELATIVE: optSpec.scaleX *= scaleX; optSpec.scaleY *= scaleY; break; } } // Handle palette stuff that is generic for different operation modes if (optPaletteFile != NULL && (res = dmHandleExternalPalette(optPaletteFile, &optPaletteData)) != DMERR_OK) goto exit; switch (optInType) { case FFMT_SPRITE: case FFMT_CHAR: case FFMT_BITMAP: if (optPaletteData == NULL) { // No palette file specified, use internal palette if (optC64Palette == NULL) optC64Palette = &dmC64DefaultPalettes[0]; dmMsg(1, "Using internal palette '%s' (%s).\n", optC64Palette->name, optC64Palette->desc); if ((res = dmC64PaletteFromC64Palette(&optPaletteData, optC64Palette, FALSE)) != DMERR_OK) { dmErrorMsg("Could not set up palette: %s.\n", dmErrorStr(res)); goto exit; } } if (optPaletteData->ncolors < D64_NCOLORS) { dmErrorMsg("Palette does not have enough colors (%d < %d)\n", optPaletteData->ncolors, D64_NCOLORS); goto exit; } if (optPaletteData->ncolors > D64_NCOLORS) { dmMsg(1, "Palette has %d colors, using only first %d.\n", optPaletteData->ncolors, D64_NCOLORS); } optC64Spec.pal = optPaletteData; break; default: if (optC64Palette != NULL) { dmMsg(1, "Using internal palette '%s' (%s).\n", optC64Palette->name, optC64Palette->desc); if ((res = dmC64PaletteFromC64Palette(&optPaletteData, optC64Palette, FALSE)) != DMERR_OK) { dmErrorMsg("Could not set up palette: %s.\n", dmErrorStr(res)); goto exit; } } } switch (optInType) { case FFMT_SPRITE: case FFMT_CHAR: dmDumpSpritesAndChars(dataBuf, dataSize, dataRealOffs); break; case FFMT_PALETTE: { const DMPaletteFormat *pfmt = &dmPaletteFormatList[optInFormat]; DMResource *fp; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for palette formats.\n"); goto exit; } // Read palette file if ((res = dmf_open_memio(NULL, optInFilename, dataBuf, dataSize, &fp)) != DMERR_OK) { dmErrorMsg("Could not create MemIO handle for input.\n"); goto exit; } // Read input if (pfmt->read != NULL) res = pfmt->read(fp, &optPaletteData); else dmErrorMsg("Unsupported input palette format.\n"); dmf_close(fp); if (res != DMERR_OK || optPaletteData == NULL) goto exit; res = dmWritePalette(optOutFilename, optPaletteData, &dmPaletteFormatList[optOutFormat]); } break; case FFMT_BITMAP: if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for bitmap formats.\n"); goto exit; } switch (optOutType) { case FFMT_IMAGE: case FFMT_CHAR: case FFMT_SPRITE: // Set character data if required if ((inC64Image->extraInfo[D64_EI_MODE] & D64_FMT_CHAR) && inC64Image->charData[0].data == NULL) { // Check character ROM filename if (optCharROMFilename == NULL) optCharROMFilename = DM_DEF_CHARGEN; // Attempt to read character ROM dmMsg(1, "Using character ROM file '%s'.\n", optCharROMFilename); if ((res = dmReadDataFile(NULL, optCharROMFilename, &inC64Image->charData[0].data, &inC64Image->charData[0].size)) != DMERR_OK) { dmErrorMsg("Could not read character ROM from '%s'.\n", optCharROMFilename); goto exit; } } // Convert the image res = dmC64ConvertBMP2Image(&outImage, inC64Image, &optC64Spec); if (res != DMERR_OK || outImage == NULL) { dmErrorMsg("Error in bitmap to image conversion: %s.\n", dmErrorStr(res)); goto exit; } switch (optOutType) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, outImage, &optSpec, &dmImageFormatList[optOutFormat]); break; case FFMT_CHAR: case FFMT_SPRITE: res = dmWriteSpritesAndChars(optOutFilename, outImage, optOutType, optInMulticolor); break; } break; case FFMT_PALETTE: res = dmWritePalette(optOutFilename, optPaletteData, &dmPaletteFormatList[optOutFormat]); break; case FFMT_DUMP: dmDumpC64Bitmap(optOutFilename, inC64Image); break; case FFMT_BITMAP: if ((res = dmConvertC64Bitmap(&outC64Image, inC64Image, &dmC64ImageFormats[optOutFormat], inC64Fmt)) != DMERR_OK) { dmErrorMsg("Error in bitmap format conversion.\n"); goto exit; } if (dmVerbosity >= 2) { dmPrint(0, "INPUT:\n"); dmC64ImageDump(stderr, inC64Image, inC64Fmt, " "); dmPrint(0, "OUTPUT:\n"); dmC64ImageDump(stderr, outC64Image, &dmC64ImageFormats[optOutFormat], " "); } res = dmWriteBitmap(optOutFilename, outC64Image, &dmC64ImageFormats[optOutFormat]); break; default: dmErrorMsg("Unsupported output format for bitmap conversion.\n"); break; } break; case FFMT_IMAGE: { const DMImageFormat *ifmt = &dmImageFormatList[optInFormat]; DMResource *fp; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for image formats.\n"); goto exit; } if ((res = dmf_open_memio(NULL, optInFilename, dataBuf, dataSize, &fp)) != DMERR_OK) { dmErrorMsg("Could not create MemIO handle for input.\n"); goto exit; } // Read input if (ifmt->read != NULL) res = ifmt->read(fp, &inImage); else dmErrorMsg("Unsupported input image format for image conversion.\n"); dmf_close(fp); if (res != DMERR_OK || inImage == NULL) goto exit; switch (optOutType) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, inImage, &optSpec, &dmImageFormatList[optOutFormat]); break; case FFMT_PALETTE: if (inImage->pal == NULL || inImage->pixfmt != DM_PIXFMT_PALETTE) { dmErrorMsg("Source image is not a paletted format or has no palette.\n"); goto exit; } res = dmWritePalette(optOutFilename, inImage->pal, &dmPaletteFormatList[optOutFormat]); break; case FFMT_CHAR: case FFMT_SPRITE: res = dmWriteSpritesAndChars(optOutFilename, inImage, optOutType, optInMulticolor); break; case FFMT_BITMAP: { DMC64Image *tmpC64Image = NULL; res = dmC64ConvertImage2BMP(&tmpC64Image, inImage, &dmC64ImageFormats[optOutFormat], &optC64Spec); if (res != DMERR_OK || tmpC64Image == NULL) { dmC64ImageFree(tmpC64Image); dmErrorMsg("Error in image to bitmap conversion: %s.\n", dmErrorStr(res)); goto exit; } if ((res = dmConvertC64Bitmap(&outC64Image, tmpC64Image, &dmC64ImageFormats[optOutFormat], &dmC64ImageFormats[optOutFormat])) != DMERR_OK) { dmC64ImageFree(tmpC64Image); dmErrorMsg("Error in bitmap format conversion: %s.\n", dmErrorStr(res)); goto exit; } res = dmWriteBitmap(optOutFilename, outC64Image, &dmC64ImageFormats[optOutFormat]); dmC64ImageFree(tmpC64Image); } break; default: dmErrorMsg("Unsupported output format for image conversion.\n"); break; } } break; } if (res != DMERR_OK) { dmErrorMsg("Error writing output data: %s\n", dmErrorStr(res)); } exit: // Cleanup dmFree(convFormatList); dmFree(dataBufOrig); dmPaletteFree(optPaletteData); dmC64ImageFree(inC64Image); dmC64ImageFree(outC64Image); dmImageFree(inImage); dmImageFree(outImage); dmLib64GFXClose(); return 0; }