Mercurial > hg > dmlib
view tools/gfxconv.c @ 2078:b2f1ce24f81b
Be more informative when attempting to figure out broken PCX file.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 10 Dec 2018 15:41:58 +0200 |
parents | 838ed06b3927 |
children | 9b6027d51f76 |
line wrap: on
line source
/* * gfxconv - Convert various graphics formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2018 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 "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_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", }; 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 } DMConvFormat; static const DMConvFormat baseFormatList[] = { { "ASCII text" , "asc" , DM_FMT_WR , FFMT_ASCII , 0 }, { "ANSI colored text" , "ansi" , DM_FMT_WR , FFMT_ANSI , 0 }, { "C64 bitmap image" , NULL , DM_FMT_RDWR , FFMT_BITMAP , -1 }, { "C64 character/font data" , "chr" , DM_FMT_RDWR , FFMT_CHAR , 0 }, { "C64 sprite data" , "spr" , DM_FMT_RDWR , FFMT_SPRITE , 0 }, }; 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[C64_NCOLORS]; DMImageConvSpec optSpec = { .scaleX = 1, .scaleY = 1, .nplanes = 4, .bpp = 8, .planar = FALSE, .format = 0, .compression = FCMP_BEST, }; static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 15, 'v', "verbose", "Increase verbosity", OPT_NONE }, { 3, 'o', "output", "Output filename", OPT_ARGREQ }, { 4, 's', "skip", "Skip N bytes in input from start (negative value will be offset from input end)", OPT_ARGREQ }, { 1, 'i', "informat", "Set input format (spr[:mc|sc], chr[:mc|sc] or any supported image or C64 bitmap format, see --formats)", OPT_ARGREQ }, { 5, 'f', "format", "Set output format (spr[:mc|sc], chr[:mc|sc] or any supported image or C64 bitmap format, see --formats)", OPT_ARGREQ }, { 17, 'F', "formats", "List supported input/output formats", OPT_NONE }, { 8, 'q', "sequential", "Output sequential files (image output only)", OPT_NONE }, { 6, 'm', "colormap", "Set color index mapping (see below for information)", OPT_ARGREQ }, { 7, 'n', "numitems", "How many 'items' to output (default: all)", OPT_ARGREQ }, { 11, 'w', "width", "Item width (number of items per row, min 1)", OPT_ARGREQ }, { 9, 'S', "scale", "Scale output image by specified value(s) (see below)", OPT_ARGREQ }, { 12, 'P', "paletted", "Use indexed/paletted output IF possible.", OPT_NONE }, { 13, 'N', "nplanes", "# of bitplanes (some output formats)", OPT_ARGREQ }, { 18, 'B', "bpp", "Bits per pixel (some output formats)", OPT_ARGREQ }, { 14, 'I', "interleave", "Interleaved/planar output (some output formats)", OPT_NONE }, { 20, 'C', "compress", "Use compression -C <0-9>, 0 = disable, default is 9", OPT_ARGREQ }, { 16, 'R', "remap", "Remap output image colors (-R <(#RRGGBB|index):index>[,<..>][+remove] | -R @map.txt[+remove])", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowFormats() { 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\n", fmt->fext ? fmt->fext : "", (fmt->flags & DM_FMT_RD) ? 'R' : ' ', (fmt->flags & DM_FMT_WR) ? 'W' : ' ', fmt->name); } printf( "\n" "(Not all input->output combinations are actually supported.)\n" "\n" "Available C64 bitmap formats (-f <frmt>):\n" " frmt | RW | Type | Description\n" "------+----+-----------------+-------------------------------------\n" ); for (int i = 0; i < ndmC64ImageFormats; i++) { const DMC64ImageFormat *fmt = &dmC64ImageFormats[i]; char buf[64]; printf("%-6s| %c%c | %-15s | %s%s\n", fmt->fext, (fmt->flags & DM_FMT_RD) ? 'R' : ' ', (fmt->flags & DM_FMT_WR) ? 'W' : ' ', dmC64GetImageTypeString(buf, sizeof(buf), fmt->format->type, FALSE), fmt->name, fmt->flags & DM_FMT_BROKEN ? " [BROKEN]" : ""); } printf("%d formats supported.\n", ndmC64ImageFormats); } void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] <input file>"); dmArgsPrintHelp(stdout, optList, optListN, 0); printf( "\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 mapping definitions are used for ANSI and image output, to declare what\n" "output colors of the C64 palette are used for each single color/multi color\n" "bit-combination. For example, if the input is multi color sprite or char,\n" "you can define colors like: -m 0,8,3,15 .. for 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.\n" "\n" ); } // // 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; } static BOOL dmParseMapOptionMapItem(const char *popt, DMMapValue *value, const unsigned int nmax, const char *msg) { char *end, *split, *opt = dm_strdup(popt); if (opt == NULL) goto error; if ((end = split = strchr(opt, ':')) == NULL) { dmErrorMsg("Invalid %s value '%s', expected <(#|%)RRGGBB|[$|0x]index>:<[$|0x]index>.\n", msg, opt); goto error; } // Trim whitespace *end = 0; for (end--; end > opt && *end && isspace(*end); end--) *end = 0; // Parse either a hex triplet color definition or a normal value if (*opt == '#' || *opt == '%') { unsigned int colR, colG, colB, colA; if (sscanf(opt + 1, "%2x%2x%2x%2x", &colR, &colG, &colB, &colA) == 4 || sscanf(opt + 1, "%2X%2X%2X%2X", &colR, &colG, &colB, &colA) == 4) { value->alpha = TRUE; value->color.a = colA; } else if (sscanf(opt + 1, "%2x%2x%2x", &colR, &colG, &colB) != 3 && sscanf(opt + 1, "%2X%2X%2X", &colR, &colG, &colB) != 3) { dmErrorMsg("Invalid %s value '%s', expected a hex triplet, got '%s'.\n", msg, popt, opt + 1); goto error; } value->color.r = colR; value->color.g = colG; value->color.b = colB; value->triplet = TRUE; } else { if (!dmGetIntVal(opt, &value->from, NULL)) { dmErrorMsg("Invalid %s value '%s', could not parse source value '%s'.\n", msg, popt, opt); goto error; } value->triplet = FALSE; } // Trim whitespace split++; while (*split && isspace(*split)) split++; // Parse destination value if (!dmGetIntVal(split, &value->to, NULL)) { dmErrorMsg("Invalid %s value '%s', could not parse destination value '%s'.\n", msg, popt, split); goto error; } if (!value->triplet && value->from > 255) { dmErrorMsg("Invalid %s map source color index value %d, must be [0..255].\n", msg, value->from); goto error; } if (value->to > nmax) { dmErrorMsg("Invalid %s map destination color index value %d, must be [0..%d].\n", msg, value->to, nmax); goto error; } dmFree(opt); return TRUE; error: dmFree(opt); return FALSE; } static BOOL dmParseMapOptionItem(char *opt, char *end, void *pvalue, const int index, const int nmax, const BOOL requireIndex, const char *msg) { // Trim whitespace if (end != NULL) { *end = 0; for (end--; end > opt && *end && isspace(*end); end--) *end = 0; } while (*opt && isspace(*opt)) opt++; // Parse item based on mode if (requireIndex) { DMMapValue *value = (DMMapValue *) pvalue; if (!dmParseMapOptionMapItem(opt, &value[index], nmax, msg)) return FALSE; } else { unsigned int *value = (unsigned int *) pvalue; char *split = strchr(opt, ':'); if (split != NULL) { dmErrorMsg("Unexpected ':' in indexed %s '%s'.\n", msg, opt); return FALSE; } if (!dmGetIntVal(opt, &value[index], 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(); dmError(res, "Could not open color remap file '%s' for reading, %d: %s\n", res, dmErrorStr(res)); return res; } while (fgets(line, sizeof(line), fp)) { char *start = line; line[sizeof(line) - 1] = 0; while (*start && isspace(*start)) start++; if (*start != 0 && *start != ';') { if (!dmParseMapOptionMapItem(line, &values[*nvalue], nmax, "mapping file")) goto error; else { (*nvalue)++; if (*nvalue >= nmax) { dmErrorMsg("Too many mapping pairs in '%s', maximum is %d.\n", filename, nmax); goto error; } } } } error: fclose(fp); return res; } BOOL 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 17: argShowFormats(); exit(0); break; case 15: dmVerbosity++; break; case 1: { 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 3: optOutFilename = optArg; break; case 4: if (!dmGetIntVal(optArg, &optInSkip, &optInSkipNeg)) { dmErrorMsg("Invalid skip value argument '%s'.\n", optArg); return FALSE; } break; case 5: { 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 6: { int index, ncolors; if (!dmParseMapOptionString(optArg, optColorMap, &ncolors, C64_NCOLORS, FALSE, "color index option")) return FALSE; dmMsg(1, "Set color index mapping: "); for (index = 0; index < ncolors; index++) { dmPrint(1, "[%d:%d]%s", index, optColorMap[index], (index < ncolors) ? ", " : ""); } dmPrint(1, "\n"); } break; case 7: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1) { dmErrorMsg("Invalid count value argument '%s' [1 .. MAXINT]\n", optArg); return FALSE; } optItemCount = tmpUInt; break; case 8: optSequential = TRUE; break; case 9: { 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 11: 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 12: optUsePalette = TRUE; break; case 13: 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 18: if (!dmGetIntVal(optArg, &tmpUInt, NULL) || tmpUInt < 1 || tmpUInt > 32) { dmErrorMsg("Invalid number of bits per pixel value '%s' [1 .. 32]\n", optArg); return FALSE; } optSpec.nplanes = tmpUInt; break; case 14: optSpec.planar = TRUE; break; case 16: 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 (!dmParseMapOptionString(optArg, optRemapTable, &optNRemapTable, DM_MAX_COLORS, TRUE, "color remap option")) return FALSE; } optRemapColors = TRUE; break; case 19: { int tx0, ty0, tx1, ty1; if (strcasecmp(optArg, "auto") == 0) { optCropMode = CROP_AUTO; } else if (sscanf(optArg, "%d:%d-%d:%d", &tx0, &ty0, &tx1, &ty1) == 4) { optCropMode = CROP_SIZE; optCropX0 = tx0; optCropY0 = ty0; optCropW = tx1 - tx0 + 1; optCropH = ty1 - ty0 + 1; } else if (sscanf(optArg, "%d:%d:%d:%d", &tx0, &ty0, &tx1, &ty1) == 4) { optCropMode = CROP_SIZE; optCropX0 = tx0; optCropY0 = ty0; optCropW = tx1; optCropH = ty1; } else { dmErrorMsg("Invalid crop mode / argument '%s'.\n", optArg); return FALSE; } } break; case 20: 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; default: dmErrorMsg("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optInFilename) optInFilename = currArg; else { dmErrorMsg("Source filename already specified, extraneous argument '%s'.\n", currArg); return FALSE; } return TRUE; } void dmPrintByte(FILE *out, int byte, int format, BOOL multicolor) { int i; if (multicolor) { for (i = DM_ASC_NBITS; i; i -= 2) { int val = (byte & (3ULL << (i - 2))) >> (i - 2); char ch; switch (format) { case FFMT_ASCII: ch = dmASCIIPalette[val]; fprintf(out, "%c%c", ch, ch); break; case FFMT_ANSI: fprintf(out, "\x1b[%sm##\x1b[0m", dmC64GetANSIFromC64Color(optColorMap[val])); break; } } } else { for (i = DM_ASC_NBITS; i; i--) { int val = (byte & (1ULL << (i - 1))) >> (i - 1); 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 < C64_CHR_HEIGHT_UT; yc++) { fprintf(outFile, "%04" DM_PRIx_SIZE_T " : ", offs + yc); dmPrintByte(outFile, buf[yc], fmt, multicolor); 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 < C64_SPR_HEIGHT_UT; yc++) { fprintf(outFile, "%04" DM_PRIx_SIZE_T " ", offs + bufOffs); for (xc = 0; xc < C64_SPR_WIDTH_UT; xc++) { dmPrintByte(outFile, buf[bufOffs], fmt, multicolor); fprintf(outFile, " "); bufOffs++; } fprintf(outFile, "\n"); } } int dmRemapImageColors(DMImage **pdst, const DMImage *src) { DMColor *npal = dmCalloc(src->ncolors, sizeof(DMColor)); int *mapping = dmMalloc(src->ncolors * sizeof(int)); BOOL *mapped = dmMalloc(src->ncolors * sizeof(BOOL)); BOOL *used = dmMalloc(src->ncolors * sizeof(BOOL)); int n, index, xc, yc, ncolors; DMImage *dst; if ((dst = *pdst = dmImageAlloc(src->width, src->height, src->format, src->bpp)) == NULL) { return dmError(DMERR_MALLOC, "Could not allocate memory for remapped image.\n"); } dmMsg(1, "Remapping %d output image colors of %d colors.\n", optNRemapTable, src->ncolors); if (npal == NULL || mapping == NULL || mapped == NULL || used == NULL) { return dmError(DMERR_MALLOC, "Could not allocate memory for reused palette.\n"); } for (index = 0; index < src->ncolors; index++) { mapping[index] = -1; mapped[index] = used[index] = FALSE; } // Find used colors dmMsg(2, "Scanning image for used colors...\n"); for (ncolors = yc = 0; yc < src->height; yc++) { const Uint8 *dp = src->data + src->pitch * yc; for (xc = 0; xc < src->width; xc++) { Uint8 col = dp[xc]; if (col < src->ncolors && !used[col]) { used[col] = TRUE; ncolors++; } } } dmMsg(2, "Found %d used colors, creating remap-table.\n", ncolors); // Match and mark mapped colors for (index = 0; index < optNRemapTable; index++) { DMMapValue *map = &optRemapTable[index]; if (map->triplet) { BOOL found = FALSE; for (n = 0; n < src->ncolors; n++) { if (dmCompareColor(&(src->pal[n]), &(map->color), map->alpha)) { dmMsg(3, "RGBA match #%02x%02x%02x%02x: %d -> %d\n", map->color.r, map->color.g, map->color.b, map->color.a, n, map->to); mapping[n] = map->to; mapped[map->to] = TRUE; found = TRUE; } } if (!found) { dmMsg(3, "No RGBA match found for map index %d, #%02x%02x%02x%02x\n", index, map->color.r, map->color.g, map->color.b, map->color.a); } } else { dmMsg(3, "Map index: %d -> %d\n", map->from, map->to); mapping[map->from] = map->to; mapped[map->to] = TRUE; } } // Fill in the rest if (optRemapRemove) dmMsg(2, "Removing unused colors.\n"); for (index = 0; index < src->ncolors; index++) if (mapping[index] < 0 && (!optRemapRemove || (optRemapRemove && used[index]))) { for (n = 0; n < src->ncolors; n++) if (!mapped[n]) { mapping[index] = n; mapped[n] = TRUE; break; } } // Calculate final number of palette colors ncolors = 0; for (index = 0; index < src->ncolors; index++) { if (mapping[index] + 1 > ncolors) ncolors = mapping[index] + 1; } // Copy palette entries for (index = 0; index < src->ncolors; index++) { if (mapping[index] >= 0) { memcpy(&npal[mapping[index]], &(src->pal[index]), sizeof(DMColor)); } } // Remap image dmMsg(1, "Remapping image to %d colors...\n", ncolors); for (yc = 0; yc < src->height; yc++) { Uint8 *sp = src->data + src->pitch * yc; Uint8 *dp = dst->data + dst->pitch * yc; for (xc = 0; xc < src->width; xc++) { Uint8 col = sp[xc]; if (col < src->ncolors && mapping[col] >= 0 && mapping[col] < src->ncolors) dp[xc] = mapping[col]; else dp[xc] = 0; } } // Set new palette, free memory dst->pal = npal; dst->ncolors = ncolors; dmFree(mapping); dmFree(mapped); dmFree(used); return DMERR_OK; } int 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; // Try to do some simple fixups if ((dst->fmt->type & D64_FMT_FLI) && (src->fmt->type & D64_FMT_FLI) == 0) { dmMsg(1, "Upconverting multicolor to FLI.\n"); for (int i = 0; i < dst->nbanks; 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->fmt->type & D64_FMT_FLI) && (dst->fmt->type & D64_FMT_FLI) == 0) { dmMsg(1, "Downconverting FLI to multicolor.\n"); } // Do per opcode copies for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++) { const DMC64EncDecOp *op = fmtGetEncDecOp(dstFmt, i); 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: dmC64GetOpMemBlock(src, op->subject, op->bank, (const DMC64MemBlock **) &srcBlk); dmC64GetOpMemBlock(dst, op->subject, op->bank, (const DMC64MemBlock **) &dstBlk); 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=%d ($%04x)\n", blkname, i, 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=%d ($%04x)\n", blkname, i, 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=%d ($%04x)\n", blkname, i, 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=%d ($%04x)\n", blkname, i, 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: dmMemset(dstBlk->data, op->offs, size); break; default: return dmError(DMERR_INTERNAL, "Unhandled op type %d in " "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n", op->type, i, 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 error; } // 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); error: dmGrowBufFree(&buf); return res; } int dmWriteIFFMasterRAWHeaderFile( const char *hdrFilename, const char *dataFilename, const char *prefix, const DMImage *img, const DMImageConvSpec *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.\n", hdrFilename); } res = dmWriteIFFMasterRAWHeader(fp, dataFilename, prefix, img, spec); dmf_close(fp); return res; } int dmWriteImage(const char *filename, DMImage *pimage, DMImageConvSpec *spec, const DMImageFormat *fmt, BOOL info) { 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); } if (info) { 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) { if ((res = dmRemapImageColors(&image, pimage)) != DMERR_OK) return res; allocated = TRUE; } // Determine number of planes, if paletted if (spec->format == DM_COLFMT_PALETTE) { spec->nplanes = 0; for (int n = 8; n >= 0;) { if ((image->ncolors - 1) & (1 << n)) { spec->nplanes = n + 1; break; } else n--; } } spec->fmtid = fmt->fmtid; // Do some format-specific adjustments and other things switch (fmt->fmtid) { case DM_IMGFMT_PNG: spec->format = optUsePalette ? DM_COLFMT_PALETTE : DM_COLFMT_RGBA; break; case DM_IMGFMT_PPM: spec->format = DM_COLFMT_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 err; } // Replace any non-alphanumerics in palette ID for (int i = 0; prefix[i]; i++) prefix[i] = isalnum(prefix[i]) ? tolower(prefix[i]) : '_'; if (info) { 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->format = optUsePalette ? DM_COLFMT_PALETTE : DM_COLFMT_RGB; break; } // If no error has occured thus far, write the image if (res == DMERR_OK) { DMResource *fp; if (info) { char *str; switch (spec->format) { case DM_COLFMT_PALETTE : str = "indexed/paletted"; break; case DM_COLFMT_RGB : str = "24bit RGB"; break; case DM_COLFMT_RGBA : str = "32bit RGBA"; break; default : str = "???"; break; } dmMsg(2, "Using %s output.\n", str); } if ((res = dmf_open_stdio(filename, "wb", &fp)) != DMERR_OK) { dmErrorMsg("Could not open file '%s' for writing.\n", filename); goto err; } res = fmt->write(fp, image, spec); dmf_close(fp); } err: if (allocated) dmImageFree(image); return res; } static Uint8 dmConvertByte(const Uint8 *sp, const BOOL multicolor) { Uint8 byte = 0; int xc; if (multicolor) { for (xc = 0; xc < 8 / 2; xc++) { Uint8 pixel = sp[xc * 2] & 3; byte |= pixel << (6 - (xc * 2)); } } else { for (xc = 0; xc < 8; xc++) { Uint8 pixel = sp[xc] == 0 ? 0 : 1; byte |= pixel << (7 - xc); } } return byte; } BOOL dmConvertImage2Char(Uint8 *buf, const DMImage *image, const int xoffs, const int yoffs, const BOOL multicolor) { int yc; if (xoffs < 0 || yoffs < 0 || xoffs + C64_CHR_WIDTH_PX > image->width || yoffs + C64_CHR_HEIGHT_PX > image->height) return FALSE; for (yc = 0; yc < C64_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 + C64_SPR_WIDTH_PX > image->width || yoffs + C64_SPR_HEIGHT_PX > image->height) return FALSE; for (yc = 0; yc < C64_SPR_HEIGHT_UT; yc++) { for (xc = 0; xc < C64_SPR_WIDTH_PX / C64_SPR_WIDTH_UT; xc++) { const Uint8 *sp = image->data + ((yc + yoffs) * image->pitch) + (xc * 8) + xoffs; buf[(yc * C64_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 *buf = NULL; size_t outBufSize; char *outType; switch (outFormat) { case FFMT_CHAR: outBufSize = C64_CHR_SIZE; outBlockW = image->width / C64_CHR_WIDTH_PX; outBlockH = image->height / C64_CHR_HEIGHT_PX; outType = "char"; break; case FFMT_SPRITE: outBufSize = C64_SPR_SIZE; outBlockW = image->width / C64_SPR_WIDTH_PX; outBlockH = image->height / C64_SPR_HEIGHT_PX; outType = "sprite"; break; default: ret = dmError(DMERR_INVALID_ARGS, "Invalid output format %d, internal error.\n", outFormat); goto error; } if (outBlockW < 1 || outBlockH < 1) { ret = dmError(DMERR_INVALID_ARGS, "Source image dimensions too small for conversion, block dimensions %d x %d.\n", outBlockW, outBlockH); goto error; } if ((outFile = fopen(filename, "wb")) == NULL) { ret = dmGetErrno(); dmErrorMsg("Could not open '%s' for writing, %d: %s.\n", filename, ret, dmErrorStr(ret)); goto error; } if ((buf = dmMalloc(outBufSize)) == NULL) { dmErrorMsg("Could not allocate %d bytes for conversion buffer.\n", outBufSize); goto error; } dmMsg(1, "Writing %d x %d = %d blocks of %s data...\n", outBlockW, outBlockH, outBlockW * outBlockH, outType); for (by = 0; by < outBlockH; by++) for (bx = 0; bx < outBlockW; bx++) { switch (outFormat) { case FFMT_CHAR: if (!dmConvertImage2Char(buf, image, bx * C64_CHR_WIDTH_PX, by * C64_CHR_HEIGHT_PX, multicolor)) { ret = DMERR_DATA_ERROR; goto error; } break; case FFMT_SPRITE: if (!dmConvertImage2Sprite(buf, image, bx * C64_SPR_WIDTH_PX, by * C64_SPR_HEIGHT_PX, multicolor)) { ret = DMERR_DATA_ERROR; goto error; } break; } if (!dm_fwrite_str(outFile, buf, outBufSize)) { ret = dmGetErrno(); dmError(ret, "Error writing data block %d,%d to '%s', %d: %s\n", bx, by, filename, ret, dmErrorStr(ret)); goto error; } } fclose(outFile); dmFree(buf); return 0; error: if (outFile != NULL) fclose(outFile); dmFree(buf); return ret; } int dmDumpSpritesAndChars(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 = C64_CHR_SIZE; outWidth = C64_CHR_WIDTH_UT; outWidthPX = C64_CHR_WIDTH_PX; outHeight = C64_CHR_HEIGHT_UT; break; case FFMT_SPRITE: outSize = C64_SPR_SIZE; outWidth = C64_SPR_WIDTH_UT; outWidthPX = C64_SPR_WIDTH_PX; outHeight = C64_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 error; } 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 error; } outImage = dmImageAlloc(outWidthPX, outHeight, DM_COLFMT_PALETTE, -1); dmMsg(1, "Outputting sequence of %d images @ %d x %d -> %d x %d.\n", optItemCount, outImage->width, outImage->height, outImage->width * optSpec.scaleX, outImage->height * optSpec.scaleY); } else { int outIWidth, outIHeight; if (optItemCount <= 0) { dmErrorMsg("Single-image output requires count to be set (-n).\n"); goto error; } outIWidth = optPlanedWidth; outIHeight = (optItemCount / optPlanedWidth); if (optItemCount % optPlanedWidth) outIHeight++; outImage = dmImageAlloc(outWidthPX * outIWidth, outIHeight * outHeight, DM_COLFMT_PALETTE, -1); } dmSetDefaultC64Palette(outImage); 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); break; } 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 error; } ret = dmWriteImage(outFilename, outImage, &optSpec, &dmImageFormatList[optOutFormat], TRUE); 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], TRUE); 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 error; } } error: return ret; } int main(int argc, char *argv[]) { FILE *inFile = NULL; DMC64ImageConvSpec imageSpecC64; 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; // Default color mapping for (i = 0; i < C64_NCOLORS; i++) optColorMap[i] = i; // Initialize c64 image conversion spec memset(&imageSpecC64, 0, sizeof(imageSpecC64)); // Initialize list of additional conversion formats dmC64InitializeFormats(); nconvFormatList = ndmImageFormatList + 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 < 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 error; } inFile = stdin; optInFilename = "stdin"; } else if ((inFile = fopen(optInFilename, "rb")) == NULL) { int res = dmGetErrno(); dmErrorMsg("Error opening input file '%s', %d: %s\n", optInFilename, res, dmErrorStr(res)); goto error; } // 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 (dmReadDataFile(inFile, NULL, &dataBufOrig, &dataSizeOrig) != 0) goto error; 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 error; } if (optInSkipNeg) { dataBuf = dataBufOrig + dataSizeOrig - optInSkip; dataSize = optInSkip; dataRealOffs = dataSizeOrig - optInSkip; dmMsg(1, "Input skip -%d (-0x%x). Offset %d (0x%x), size %d (0x%x).\n", optInSkip, optInSkip, dataRealOffs, dataRealOffs, dataSize, dataSize); } else { dataBuf = dataBufOrig + optInSkip; dataSize = dataSizeOrig - optInSkip; dataRealOffs = optInSkip; dmMsg(1, "Input skip %d (0x%x), size %d (0x%x).\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; int res; if (optForcedInSubFormat >= 0) { forced = &dmC64ImageFormats[optForcedInSubFormat]; dmMsg(0, "Forced '%s' format image, type %d, %s\n", forced->name, forced->format->type, 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->type, 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 error; } } 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) { optInType = FFMT_IMAGE; optInFormat = index; dmMsg(1, "Probed '%s' format image.\n", ifmt->name); } } if (optInType == FFMT_AUTO) { dmErrorMsg("No input format specified, and could not be determined automatically.\n"); goto error; } 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); } if (optScaleMode != SCALE_SET) { int scaleX = 1, scaleY = 1; if (inC64Fmt != NULL) { scaleX = inC64Fmt->format->aspectX; scaleY = inC64Fmt->format->aspectY; } switch (optScaleMode) { case SCALE_AUTO: optSpec.scaleX = scaleX; optSpec.scaleY = scaleY; break; case SCALE_RELATIVE: optSpec.scaleX *= scaleX; optSpec.scaleY *= scaleY; break; } } switch (optInType) { case FFMT_SPRITE: case FFMT_CHAR: dmDumpSpritesAndChars(dataBuf, dataSize, dataRealOffs); break; case FFMT_BITMAP: { int res = DMERR_OK; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for bitmap formats.\n"); goto error; } switch (optOutType) { case FFMT_IMAGE: case FFMT_CHAR: case FFMT_SPRITE: res = dmC64ConvertBMP2Image(&outImage, inC64Image, inC64Fmt, &imageSpecC64); if (res != DMERR_OK || outImage == NULL) { dmErrorMsg("Error in bitmap to image conversion.\n"); goto error; } switch (optOutType) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, outImage, &optSpec, &dmImageFormatList[optOutFormat], TRUE); break; case FFMT_CHAR: case FFMT_SPRITE: res = dmWriteSpritesAndChars(optOutFilename, outImage, optOutType, optInMulticolor); break; } break; case FFMT_BITMAP: if ((res = dmConvertC64Bitmap(&outC64Image, inC64Image, &dmC64ImageFormats[optOutFormat], inC64Fmt)) != DMERR_OK) { dmErrorMsg("Error in bitmap format conversion.\n"); goto error; } 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]; int res = DMERR_OK; DMResource *fp; if (optOutFilename == NULL) { dmErrorMsg("Output filename not set, required for image formats.\n"); goto error; } if ((res = dmf_open_memio(NULL, optInFilename, dataBuf, dataSize, &fp)) != DMERR_OK) { dmErrorMsg("Could not create MemIO handle for input.\n"); goto error; } // 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 error; switch (optOutType) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, inImage, &optSpec, &dmImageFormatList[optOutFormat], TRUE); 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], &imageSpecC64); if (res != DMERR_OK || tmpC64Image == NULL) { dmC64ImageFree(tmpC64Image); dmErrorMsg("Error in image to bitmap conversion: %s\n", dmErrorStr(res)); goto error; } if ((res = dmConvertC64Bitmap(&outC64Image, tmpC64Image, &dmC64ImageFormats[optOutFormat], &dmC64ImageFormats[optOutFormat])) != DMERR_OK) { dmC64ImageFree(tmpC64Image); dmErrorMsg("Error in bitmap format conversion: %s\n", dmErrorStr(res)); goto error; } res = dmWriteBitmap(optOutFilename, outC64Image, &dmC64ImageFormats[optOutFormat]); dmC64ImageFree(tmpC64Image); } break; default: dmErrorMsg("Unsupported output format for image conversion.\n"); break; } if (res != DMERR_OK) { dmErrorMsg("Error writing output (%s), probably unsupported output format for bitmap/image conversion.\n", dmErrorStr(res)); } } break; } error: dmFree(convFormatList); dmFree(dataBufOrig); dmC64ImageFree(inC64Image); dmC64ImageFree(outC64Image); dmImageFree(inImage); dmImageFree(outImage); return 0; }