Mercurial > hg > dmlib
view tools/gfxconv.c @ 2586:9807ae37ad69
Require stdbool.h, we require C11 now.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 08 Dec 2022 15:59:22 +0200 |
parents | bb44c48cffac |
children | d683f47e4dd9 |
line wrap: on
line source
/* * gfxconv - Convert various graphics formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2022 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, }; enum { REMAP_NONE = 0, REMAP_AUTO, REMAP_MAPPED, }; 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, optShowHelp = 0; unsigned int optInSkip = 0; bool optInSkipNeg = false; int optCropMode = CROP_NONE, optCropX0, optCropY0, optCropW, optCropH; bool optInMulticolor = false, optSequential = false, optRemapRemove = false, optRemapMatchAlpha = false, optUsePalette = false; int optRemapMode = REMAP_NONE; int optNRemapTable = 0, optScaleMode = SCALE_AUTO; float optRemapMaxDist = -1; int optRemapNoMatchColor = -1; DMMapValue optRemapTable[DM_MAX_COLORS]; int optColorMap[D64_NCOLORS]; const 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 }, { 3, 0, "longhelp" , "Show a longer 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 " "(a negative value will be offset " "from end of input data)", 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 map definitions (see '-m help')", 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 (see '-S help')", 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. " "(Not all formats support compression and the meaning " "apart from 0 or >= 1 depends on the format.)", OPT_ARGREQ }, { 42, 'R', "remap" , "Remap output image colors (see '-R help')", OPT_ARGREQ }, { 44, 0, "char-rom" , "Set character ROM file to be used.", OPT_ARGREQ }, { 46, 'p', "palette" , "Set palette to be used (see '-p help'). " "For paletted image file input, this will replace the " "image's original palette. Color remapping will not be " "done unless -R option is also specified.", 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" ); } void argShowHelp(void) { dmPrintBanner(stdout, dmProgName, "[options] [<input file>]"); dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2); fprintf(stdout, "\n" "Default C64 character ROM file for this build is:\n" "%s\n" "\n", dmGetChargenROMPath() ); } const char * argGetHelpTopic(const int opt) { switch (opt) { case 22: return "Color map definitions (-m)\n" "--------------------------\n" "Color map definitions are used for sprite/char data input (and ANSI text\n" "output), to set what colors of the C64 palette are used for each single\n" "color/multi color bit-combination.\n" "\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" "\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).\n" "\n" "NOTICE! 255 is a special transparency color index; -m 255,2 would use\n" "transparency for '0' bits and and C64 color 2 for '1' bits.\n"; case 28: return "Output image scaling (-S)\n" "-------------------------\n" "Scaling can be done in direct or relative mode with integer scale factors.\n" "\n" "'-S [*]<n>' sets both height and width scale factor to <n> directly or\n" " relative to the certain input format's default aspect/scale factors.\n" " * = scale relatively, e.g. '-S *2'.\n" "\n" "'-S <W>:<H>[*<n>]' scales width by W*n and height H*n.\n" " If <n> is not specified, it defaults to 1.\n"; case 42: return "Palette remapping (-R)\n" "----------------------\n" "Indexed palette color remapping can be performed via the '-R' option in\n" "several different ways:\n" "\n" " 1) '-R auto'\n" " will remap input image to a destination palette specified with the\n" " '-p' option (which may be a supported palette file, another paletted\n" " image file, or one of the internal gfxconv palettes, see '-p help')\n" "\n" " Example: '-R auto+remove -p pepto' would remap the input image to the\n" " internal Pepto's C64 palette, removing any unused palette entries.\n" "\n" " 2) '-R <#RRGGBBaa|index>:<index>[,<(#RRGGBBaa|index):<index>[,...]]'\n" " can be used to specify single RGB(A) color quadruplets/triplets or\n" " palette indices to be remapped to destination palette indices. Any\n" " unspecified indices will be automatically mapped. Specifying alpha\n" " channel is optional, and will require +alpha flag/modifier to be\n" " enabled for comparisions to take alpha channel into account.\n" "\n" " Example: '-R #000000:0,#ffffff:1' would map black and white to indices 0 and 1.\n" "\n" " 3) '-R @<filename>'\n" " can be used to specify a mapping file, which is a text file with one\n" " remap definition per line in same format as above. All empty lines and\n" " lines starting with a semicolor (;) will be ignored as comments. Also\n" " any extra whitespace separating items will be ignored as well.\n" "\n" "Optional modifier flags can be specified as well:\n" "\n" " +remove Remove all unused colors from the resulting palette.\n" "\n" " +alpha Enable alpha value matching in color comparisions.\n" " NOTE! This may result in unexpected behaviour.\n" "\n" " +max=<f> Set the maximum color distance/delta acceptable for\n" " matching colors. Default is -1, meaning closest possible\n" " that can be found even if the match is poor. Any value \n" " above or equal to 0 will be considered strict limit, see\n" " the 'nomatch' modifier below.\n" " Range: -1, 0 .. 1.0\n" "\n" " +nomatch=<n>\n" " If no acceptable match is found (see +max modifier)\n" " then use this (<n>) color index. This may have unexpected\n" " results with +remove modifier. The default value of 'n'\n" " is -1, which will result in error if no match is found.\n" ""; default: return NULL; } } // // 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; } char *dmParseValWithSep(char **arg, char *last, bool (*isok)(const int ch), 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 && (isok == NULL || isok(*ptr)) && strchr(sep, *ptr) == NULL) 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 start; } bool dmParseIntValTok(const int ch) { return isxdigit(ch) || ch == 'x' || ch == '$'; } bool dmParseIntValWithSep(char **arg, unsigned int *value, char *last, const char *sep) { return dmGetIntVal(dmParseValWithSep(arg, last, dmParseIntValTok, sep), value, NULL); } bool argHandleOpt(const int optN, char *optArg, char *currArg) { unsigned int tmpUInt; char *tmpStr; switch (optN) { case 0: optShowHelp = 1; break; case 3: optShowHelp = 2; 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: optShowHelp = 3; break; case 20: optSequential = true; break; case 22: if (strcasecmp(optArg, "help") == 0) { fprintf(stdout, "\n%s\n", argGetHelpTopic(optN)); exit(0); } else { 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: if (strcasecmp(optArg, "help") == 0) { fprintf(stdout, "\n%s\n", argGetHelpTopic(optN)); exit(0); } else { 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, "")) { 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> or 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: if (strcasecmp(optArg, "auto") == 0) { optCropMode = CROP_AUTO; } else { unsigned int tx0, ty0, tx1, ty1; char sep, modeSep = 0, *tmpTok, *tmpStr; bool ok; if ((tmpTok = tmpStr = dm_strdup(optArg)) == NULL) { dmErrorMsg("Could not allocate memory for temporary string.\n"); return false; } // Check for 'x0:y0-x1:y1' pattern ok = dmParseIntValWithSep(&tmpTok, &tx0, &sep, ":") && sep == ':' && dmParseIntValWithSep(&tmpTok, &ty0, &modeSep, ":-") && (sep == ':' || sep == '-') && dmParseIntValWithSep(&tmpTok, &tx1, &sep, ":") && sep == ':' && dmParseIntValWithSep(&tmpTok, &ty1, &sep, ""); dmFree(tmpStr); if (ok) { optCropMode = CROP_SIZE; optCropX0 = tx0; optCropY0 = ty0; if (modeSep == '-') { DM_SWAP_IF(unsigned int, tx0, tx1); DM_SWAP_IF(unsigned int, ty0, ty1); optCropW = tx1 - tx0; optCropH = ty1 - ty0; dmMsg(1, "Crop coordinates %d, %d - %d, %d [dim %d, %d]\n", tx0, ty0, tx1, ty1, optCropW, optCropH); } else { optCropW = tx1; optCropH = ty1; dmMsg(1, "Crop coordinates %d, %d - %d, %d [dim %d, %d]\n", tx0, ty0, tx0 + tx1, ty0 + ty1, optCropW, optCropH); } } else { dmErrorMsg("Invalid crop mode / argument '%s'.\n", optArg); return false; } } break; case 42: if (strcasecmp(optArg, "help") == 0) { fprintf(stdout, "\n%s\n", argGetHelpTopic(optN)); exit(0); } // Check if any flags/sub-options are specified if ((tmpStr = strchr(optArg, '+')) != NULL) { *tmpStr++ = 0; do { // Parse one sub-option char sep, *topt = dmParseValWithSep(&tmpStr, &sep, NULL, "+"); // Check what option we have if (strcasecmp(topt, "remove") == 0) optRemapRemove = true; else if (strcasecmp(topt, "alpha") == 0) optRemapMatchAlpha = true; else if (strncasecmp(topt, "max=", 4) == 0) { char *start = topt + 4, *end; optRemapMaxDist = strtof(start, &end); if (end == start) { dmErrorMsg("Invalid or missing value parameter for -R option flag: '%s'.\n", topt); return false; } } else if (strncasecmp(topt, "nomatch=", 8) == 0) { char *start = topt + 8, *end; optRemapNoMatchColor = strtol(start, &end, 10); if (end == start) { dmErrorMsg("Invalid or missing value parameter for -R option flag: '%s'.\n", topt); return false; } if (optRemapNoMatchColor < -1 || optRemapNoMatchColor > 255) { dmErrorMsg("Invalid remap no-match color value %d. Should be [-1 .. 255].\n", optRemapNoMatchColor); return false; } } else { dmErrorMsg("Unknown -R option flag '%s'.\n", topt); return false; } } while (*tmpStr != 0); } // Check which remap mode is being requested if (strcasecmp(optArg, "auto") == 0) { if (optRemapMode != REMAP_NONE && optRemapMode != REMAP_AUTO) { dmErrorMsg("Remap mode already set to something else than 'auto'. You can only have one remapping mode.\n"); return false; } optRemapMode = REMAP_AUTO; } else { if (optRemapMode != REMAP_NONE && optRemapMode != REMAP_MAPPED) { dmErrorMsg("Remap mode already set to something else than 'mapped'. You can only have one remapping mode.\n"); return false; } 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; } optRemapMode = REMAP_MAPPED; } if (optRemapMatchAlpha) { dmErrorMsg("WARNING: Palette alpha matching may have unexpected results.\n"); } 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"); } } // XXX TODO: we need to evaluate the color vector itself, not just the distance float dmGetColorDist(const DMColor *c1, const DMColor *c2, const bool alpha) { const float dr = (c1->r - c2->r) / 255.0, dg = (c1->g - c2->g) / 255.0, db = (c1->b - c2->b) / 255.0; if (alpha) { const float da = (c1->a - c2->a) / 255.0; return (dr * dr + dg * dg + db * db + da * da) / 4.0; } else return (dr * dr + dg * dg + db * db) / 3.0; } int dmScanUsedColors(const DMImage *src, const bool warn, bool *used, int *nused) { bool warned = false; *nused = 0; if (src == NULL || used == NULL || nused == NULL) return DMERR_NULLPTR; if (src->pal == NULL || src->pixfmt != DM_PIXFMT_PALETTE) { return dmError(DMERR_INVALID_DATA, "Source image is not paletted.\n"); } for (int index = 0; index < src->pal->ncolors; index++) used[index] = false; for (int yc = 0; yc < src->height; yc++) { const Uint8 *dp = src->data + src->pitch * yc; for (int xc = 0; xc < src->width; xc++) { Uint8 col = dp[xc]; if (col < src->pal->ncolors) { if (!used[col]) { used[col] = true; (*nused)++; } } else if (warn && !warned) { dmErrorMsg("Image contains color indices that are out of bounds of the palette.\n"); warned = true; } } } return DMERR_OK; } int dmDoRemapImageColors(DMImage **pdst, const DMImage *src, const int *mapping, const DMPalette *dpal) { DMImage *dst = NULL; int res = DMERR_OK; if (pdst == NULL || src == NULL || mapping == NULL) return DMERR_NULLPTR; // Allocate target image if ((dst = *pdst = dmImageAlloc(src->width, src->height, src->pixfmt, -1)) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate image for re-mapped data.\n"); goto out; } for (int yc = 0; yc < src->height; yc++) { Uint8 *sp = src->data + src->pitch * yc; Uint8 *dp = dst->data + dst->pitch * yc; for (int xc = 0; xc < src->width; xc++) { dp[xc] = mapping[sp[xc]]; } } if ((res = dmPaletteCopy(&dst->pal, dpal)) != DMERR_OK) { dmErrorMsg("Error installing remapped palette to destination image: %s\n", dmErrorStr(res)); goto out; } out: return res; } int dmRemapImageColors(DMImage **pdst, const DMImage *src, const DMPalette *dpal, const float maxDist, const int noMatchColor, const bool alpha, const bool removeUnused) { DMPalette *tpal = NULL; const DMPalette *ppal; bool *used = NULL; int *mapping = NULL, *mapped = NULL; int res = DMERR_OK; bool fail = false; if (pdst == NULL || src == NULL || dpal == NULL) return DMERR_NULLPTR; if (src->pal == NULL || src->pixfmt != DM_PIXFMT_PALETTE) { res = dmError(DMERR_INVALID_DATA, "Source image is not paletted.\n"); goto out; } dmMsg(1, "Remapping image from %d to %d colors @ maxDist=", src->pal->ncolors, dpal->ncolors); if (maxDist < 0) dmPrint(1, "auto"); else dmPrint(1, "%1.3f", maxDist); dmPrint(1, ", %s, ", alpha ? "match alpha" : "ignore alpha"); if (noMatchColor < 0) dmPrint(1, "fail on 'no match'\n"); else dmPrint(1, "use color #%d if no match\n", noMatchColor); // Allocate remapping tables if ((mapped = dmMalloc0(src->pal->ncolors * sizeof(*mapped))) == NULL || (used = dmMalloc0(src->pal->ncolors * sizeof(*used))) == NULL || (mapping = dmMalloc(src->pal->ncolors * sizeof(*mapping))) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for color remap tables.\n"); goto out; } for (int index = 0; index < src->pal->ncolors; index++) mapping[index] = -1; // Populate remap table for (int sc = 0; sc < src->pal->ncolors; sc++) { // Check if we can find a match in destination palette dpal float closestDist = 1000000, dist = 0; int closestDC = -1; for (int dc = 0; dc < dpal->ncolors; dc++) { dist = dmGetColorDist(&src->pal->colors[sc], &dpal->colors[dc], alpha); if (dist < closestDist) { closestDist = dist; closestDC = dc; } } // Did we find a close-enough match? if (maxDist >= 0 && closestDist > maxDist) { // No, either error out or use noMatchColor color index if (noMatchColor < 0) { DMColor *dcol = &dpal->colors[closestDC]; dmPrint(0, "No match for source color #%d. Closest: #%d (%02x %02x %02x) [dist=%1.3f > %1.3f]\n", sc, closestDC, dcol->r, dcol->g, dcol->b, closestDist, maxDist); fail = true; } else { closestDC = noMatchColor; } } else { DMColor *scol = &src->pal->colors[sc], *dcol = &dpal->colors[closestDC]; dmPrint(3, "Palette match #%d (%02x %02x %02x) -> #%d (%02x %02x %02x) [dist=%1.3f]\n", sc, scol->r, scol->g, scol->b, closestDC, dcol->r, dcol->g, dcol->b, closestDist); } mapping[sc] = closestDC; } if (fail) { res = DMERR_INVALID_DATA; goto out; } // Remove unused colors if requested if (removeUnused) { int nused; if (noMatchColor >= 0) { dmErrorMsg("WARNING! Removing unused colors with 'no-match' color index set may have unintended results.\n"); } // Get the actually used colors if ((res = dmScanUsedColors(src, true, used, &nused)) != DMERR_OK) goto out; dmMsg(2, "Found %d used color indices.\n", nused); // Remove duplicates from the mapped colour indices for (int index = 0; index < src->pal->ncolors; index++) { for (int n = 0; n < src->pal->ncolors; n++) if (n != index && mapping[index] == mapping[n] && used[n] && used[index]) { used[n] = false; } } if (noMatchColor >= 0) used[noMatchColor] = true; // Re-count number of actually used indices nused = 0; for (int index = 0; index < src->pal->ncolors; index++) if (used[index]) nused++; dmMsg(2, "After mapped dupe removal, %d color indices used.\n", nused); if ((res = dmPaletteAlloc(&tpal, nused, -1)) != DMERR_OK) { dmErrorMsg("Could not allocate memory for remap palette.\n"); goto out; } // Copy colors from dpal to tpal, also mapping the reordered indices nused = 0; for (int index = 0; index < src->pal->ncolors; index++) if (used[index]) { // Copy the color to tpal memcpy(&tpal->colors[nused], &dpal->colors[mapping[index]], sizeof(DMColor)); // Save current mapping to mapped[] mapped[nused] = mapping[index]; // Reorder the mapping mapping[index] = nused; nused++; } else { // "Unused" color, find matching mapping from mapped[] for (int n = 0; n < nused; n++) if (mapping[index] == mapped[n]) { mapping[index] = n; break; } } ppal = tpal; } else ppal = dpal; // Perform image data remapping res = dmDoRemapImageColors(pdst, src, mapping, ppal); out: dmPaletteFree(tpal); dmFree(mapping); dmFree(mapped); dmFree(used); return res; } int dmMapImageColors(DMImage **pdst, const DMImage *src, const DMMapValue *mapTable, const int nmapTable, const float maxDist, const int noMatchColor, const bool alpha, const bool removeUnused) { DMPalette *tpal = NULL; bool *mapped = NULL, *used = NULL; int *mapping = NULL; int nused, res = DMERR_OK; bool fail = false; if (pdst == NULL || src == NULL || mapTable == NULL) return DMERR_NULLPTR; if (src->pal == NULL || src->pixfmt != DM_PIXFMT_PALETTE) { res = dmError(DMERR_INVALID_DATA, "Source image is not paletted.\n"); goto out; } // Allocate remapping tables if ((mapped = dmMalloc(src->pal->ncolors * sizeof(*mapped))) == NULL || (used = dmMalloc(src->pal->ncolors * sizeof(*used))) == NULL || (mapping = dmMalloc(src->pal->ncolors * sizeof(*mapping))) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for color remap tables.\n"); goto out; } for (int index = 0; index < src->pal->ncolors; index++) { mapping[index] = -1; mapped[index] = false; } if ((res = dmPaletteAlloc(&tpal, src->pal->ncolors, -1)) != DMERR_OK) { dmErrorMsg("Could not allocate memory for remap palette.\n"); goto out; } dmMsg(1, "Remapping %d input image of %d colors, %s, ", optNRemapTable, src->pal->ncolors, alpha ? "match alpha" : "ignore alpha"); if (noMatchColor < 0) dmPrint(1, "fail on 'no match'\n"); else dmPrint(1, "use color #%d if no match\n", noMatchColor); // Match and mark mapped colors for (int index = 0; index < nmapTable; index++) { const DMMapValue *map = &mapTable[index]; if (map->triplet) { float closestDist = 1000000, dist = 0; int closestDC = -1; for (int n = 0; n < src->pal->ncolors; n++) { dist = dmGetColorDist(&src->pal->colors[n], &map->color, map->alpha && alpha); if (dist < closestDist) { closestDist = dist; closestDC = n; } } // Did we find a close-enough match? if (maxDist >= 0 && closestDist > maxDist) { // No, either error out or use noMatchColor color index if (noMatchColor < 0) { DMColor *dcol = &src->pal->colors[closestDC]; dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x. Closest: #%d (#%02x%02x%02x%02x) [dist=%1.3f > %1.3f]\n", index, map->color.r, map->color.g, map->color.b, map->color.a, closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist, maxDist); fail = true; } else { DMColor *dcol = &src->pal->colors[noMatchColor]; closestDC = noMatchColor; dmMsg(3, "RGBA noMatch #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n", map->color.r, map->color.g, map->color.b, map->color.a, map->to, closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist); mapping[closestDC] = map->to; mapped[map->to] = true; } } else { DMColor *dcol = &src->pal->colors[closestDC]; dmMsg(3, "RGBA match #%02x%02x%02x%02x: #%d -> #%d #%02x%02x%02x%02x [dist=%1.3f]\n", map->color.r, map->color.g, map->color.b, map->color.a, map->to, closestDC, dcol->r, dcol->g, dcol->b, dcol->a, closestDist); mapping[closestDC] = map->to; mapped[map->to] = true; } } else { dmMsg(3, "Map index: %d -> %d\n", map->from, map->to); mapping[map->from] = map->to; mapped[map->to] = true; } } if (fail) { res = DMERR_INVALID_DATA; goto out; } // Fill the unmapped colors if (removeUnused) { dmMsg(2, "Scanning for used colors.\n"); if ((res = dmScanUsedColors(src, true, used, &nused)) != DMERR_OK) goto out; dmMsg(2, "Removing unused colors: %d -> %d.\n", src->pal->ncolors, nused); } for (int index = 0; index < src->pal->ncolors; index++) if (mapping[index] < 0 && (!removeUnused || used[index])) { for (int n = 0; n < src->pal->ncolors; n++) if (!mapped[n]) { mapping[index] = n; mapped[n] = true; break; } } // Copy mapped palette entries for (int index = 0; index < src->pal->ncolors; index++) if (mapping[index] >= 0) { memcpy(&tpal->colors[mapping[index]], &src->pal->colors[index], sizeof(DMColor)); } // Perform image data remapping res = dmDoRemapImageColors(pdst, src, mapping, tpal); out: dmPaletteFree(tpal); 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, "chardata", &img->charData[i], i); if ((size_t) i < sizeof(img->extraData) / sizeof(img->extraData[0])) 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, "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; DMImage *image = pimage; bool allocated = false; // 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); if (image->pixfmt == DM_PIXFMT_PALETTE) { switch (optRemapMode) { case REMAP_NONE: if (optPaletteData != NULL) { DMPalette *tpal; dmMsg(1, "Replacing image palette %d colors with %d colors.\n", image->pal->ncolors, optPaletteData->ncolors); if ((res = dmPaletteCopy(&tpal, optPaletteData)) != DMERR_OK) return res; if (image->pal->ncolors != optPaletteData->ncolors) { dmMsg(1, "Trying to resize to %d colors.\n", image->pal->ncolors); if ((res = dmPaletteResize(&tpal, image->pal->ncolors)) != DMERR_OK) return res; } dmPaletteFree(image->pal); image->pal = tpal; } break; case REMAP_MAPPED: if (optPaletteData != NULL) { dmErrorMsg( "WARNING: Color remapping requested, but palette replacement (-p) set. This will have no effect.\n"); } if ((res = dmMapImageColors( &image, pimage, optRemapTable, optNRemapTable, optRemapMaxDist, optRemapNoMatchColor, optRemapMatchAlpha, optRemapRemove)) != DMERR_OK) goto out; allocated = true; break; case REMAP_AUTO: if (optPaletteData == NULL) { dmErrorMsg( "Color auto-remapping requested, but target palette not set? (-p option)\n"); goto out; } if ((res = dmRemapImageColors( &image, pimage, optPaletteData, optRemapMaxDist, optRemapNoMatchColor, optRemapMatchAlpha, optRemapRemove)) != DMERR_OK) goto out; allocated = true; break; } } else if (optRemapMode != REMAP_NONE) { dmErrorMsg("Color remapping requested, but image is not paletted?\n"); goto out; } // 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 = DMERR_OK; // 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 out; } 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.95", NULL, NULL); if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) goto out; switch (optShowHelp) { case 1: argShowHelp(); goto out; case 2: argShowHelp(); argShowFormats(); argShowC64Formats(stdout, true, true); argShowC64PaletteHelp(stdout); for (int n = 0; n < optListN; n++) { const char *str = argGetHelpTopic(optList[n].id); if (str != NULL) fprintf(stdout, "\n%s\n", str); } goto out; case 3: argShowFormats(); argShowC64Formats(stdout, true, dmVerbosity > 0); goto out; } // 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 --help or --longhelp\n"); goto out; } 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 out; } // 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 out; } 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 out; } 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), -1, -1, &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 out; } } 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 out; } 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 out; 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 out; } } if (optPaletteData->ncolors < D64_NCOLORS) { dmErrorMsg("Palette does not have enough colors (%d < %d)\n", optPaletteData->ncolors, D64_NCOLORS); goto out; } 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 out; } } } 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 out; } // 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 out; } // 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) { dmErrorMsg("Palette could not be read.\n"); goto out; } } if (optPaletteData == NULL) goto out; switch (optOutType) { case FFMT_PALETTE: res = dmWritePalette(optOutFilename, optPaletteData, &dmPaletteFormatList[optOutFormat]); break; case FFMT_IMAGE: // Allocate image if ((inImage = dmImageAlloc(16, 16, DM_PIXFMT_PALETTE, dmGetNPlanesFromNColors(optPaletteData->ncolors))) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for image.\n"); goto out; } if ((res = dmPaletteCopy(&inImage->pal, optPaletteData)) != DMERR_OK) { dmErrorMsg("Could not allocate image palette: %s\n", dmErrorStr(res)); goto out; } res = dmWriteImage(optOutFilename, inImage, &optSpec, &dmImageFormatList[optOutFormat]); break; default: dmErrorMsg("Unsupported output format for palette conversion.\n"); break; } break; case FFMT_BITMAP: if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for bitmap formats.\n"); goto out; } 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 = dmGetChargenROMPath(); // 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 out; } } // 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 out; } 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 out; } 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 out; } if ((res = dmf_open_memio(NULL, optInFilename, dataBuf, dataSize, &fp)) != DMERR_OK) { dmErrorMsg("Could not create MemIO handle for input.\n"); goto out; } // 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 out; 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 out; } 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 out; } if ((res = dmConvertC64Bitmap(&outC64Image, tmpC64Image, &dmC64ImageFormats[optOutFormat], &dmC64ImageFormats[optOutFormat])) != DMERR_OK) { dmC64ImageFree(tmpC64Image); dmErrorMsg("Error in bitmap format conversion: %s.\n", dmErrorStr(res)); goto out; } 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)); } out: // Cleanup dmFree(convFormatList); dmFree(dataBufOrig); dmPaletteFree(optPaletteData); dmC64ImageFree(inC64Image); dmC64ImageFree(outC64Image); dmImageFree(inImage); dmImageFree(outImage); dmLib64GFXClose(); return res; }