Mercurial > hg > dmlib
view gfxconv.c @ 428:983c557f2fbf
Cosmetics.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 03 Nov 2012 11:10:59 +0200 |
parents | 11b3adb3bdb1 |
children | e2f8c9cbc17a |
line wrap: on
line source
/* * gfxconv - Convert various graphics formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include <errno.h> #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #include "dmbstr.h" #include "dmmutex.h" #include "lib64gfx.h" //#define UNFINISHED 1 #ifdef DM_USE_LIBPNG #include <png.h> #endif enum { IMGFMT_PNG, IMGFMT_PPM, IMGFMT_PCX, IMGFMT_ARAW, IMGFMT_LAST }; static const char *imageFormatList[IMGFMT_LAST] = { "PNG", "PPM", "PCX", "ARAW", }; enum { FFMT_AUTO = 0, FFMT_ASCII, FFMT_ANSI, FFMT_IMAGE, FFMT_CHAR, FFMT_SPRITE, FFMT_BITMAP, FFMT_LAST }; typedef struct { char *name; char *fext; BOOL in, out; int format; int subformat; } DMConvFormat; static DMConvFormat convFormatList[] = { { "ASCII text", "asc", FALSE, TRUE, FFMT_ASCII , 0, }, { "ANSI colored text", "ansi", FALSE, TRUE, FFMT_ANSI , 0, }, { "PNG image file", "png", FALSE, TRUE, FFMT_IMAGE , IMGFMT_PNG, }, { "PPM image file", "ppm", FALSE, TRUE, FFMT_IMAGE , IMGFMT_PPM, }, { "PCX image file", "pcx", TRUE, TRUE, FFMT_IMAGE , IMGFMT_PCX, }, { "IFFMaster RAW image file", "araw", FALSE, TRUE, FFMT_IMAGE , IMGFMT_ARAW, }, { "C64 bitmap image file", NULL, TRUE, FALSE, FFMT_BITMAP , 0, }, { "C64 character/font data", "chr", TRUE, FALSE, FFMT_CHAR , 0 }, { "C64 sprite data", "spr", TRUE, FALSE, FFMT_SPRITE , 0 }, }; static const int nconvFormatList = sizeof(convFormatList) / sizeof(convFormatList[0]); #define ASC_NBITS 8 #define ASC_NCOLORS 4 static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#"; char *optInFilename = NULL, *optOutFilename = NULL; int optInFormat = FFMT_AUTO, optOutFormat = FFMT_ASCII, optInSubFormat = IMGFMT_PNG, optOutSubFormat = IMGFMT_PNG, optItemCount = -1, optScale = 2, optPlanedWidth = 1, optForcedFormat = -1, optBPP = 4; int optInSkip = 0; BOOL optInMulticolor = FALSE, optSequential = FALSE, optPaletted = FALSE, optInterleave = FALSE; int optColors[C64_MAX_COLORS]; static DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, {15, 'v', "verbose", "Increase verbosity", OPT_NONE }, { 3, 'o', "output", "Output filename", OPT_ARGREQ }, { 1, 'i', "informat", "Set input format ([s]prite, [c]har, [b]itmap, [i]mage)", OPT_ARGREQ }, { 2, 'm', "multicolor", "Input is multicolor", OPT_NONE }, { 4, 's', "skip", "Skip bytes in input", OPT_ARGREQ }, { 5, 'f', "format", "Output format (see list below)", OPT_ARGREQ }, { 8, 'q', "sequential", "Output sequential files (image output only)", OPT_NONE }, { 6, 'c', "colormap", "Color mappings (see below for information)", OPT_ARGREQ }, { 7, 'n', "numitems", "How many 'items' to view (default: all)", OPT_ARGREQ }, { 9, 'S', "scale", "Scale output by x (image output only)", OPT_ARGREQ }, {10, 'b', "bformat", "Force input bitmap format (see below)", OPT_ARGREQ }, {11, 'w', "width", "Item width (number of items per row, min 1)", OPT_ARGREQ }, {12, 'P', "paletted", "Use indexed/paletted output (png, pcx output only)", OPT_NONE }, {13, 'B', "bplanes", "Bits per pixel OR # of bitplanes (certain output formats)", OPT_ARGREQ }, {14, 'I', "interleave", "Interleave scanlines (default: output whole planes)", OPT_NONE }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { int i; dmPrintBanner(stdout, dmProgName, "[options] <input file>"); dmArgsPrintHelp(stdout, optList, optListN); printf("\nAvailable output formats:\n"); for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; printf("%3d | %-5s | %s\n", i, fmt->fext ? fmt->fext : "", fmt->name); } printf("\nAvailable bitmap formats:\n"); for (i = 0; i < ndmC64ImageFormats; i++) { DMC64ImageFormat *fmt = &dmC64ImageFormats[i]; printf("%3d | %-5s | %-15s | %s\n", i, fmt->extension, dmC64ImageTypeNames[fmt->type], fmt->name); } printf( "\n" "Color map definitions are used for ANSI, PCX, PPM and PNG output, to declare what\n" "output colors of the C64 palette are used for each single color/multi color\n" "bit-combination. For example, if the input is multi color sprite or char,\n" "you can define colors like: -c 0,8,3,15 .. for single color: -c 0,1\n" "The numbers are palette indexes, and the order is for bit(pair)-values\n" "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n" "special color that can be used for transparency.\n" ); } int dmGetConvFormat(int format, int subformat) { int i; for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; if (fmt->format == format && fmt->subformat == subformat) return i; } return -1; } BOOL dmGetFormatByExt(const char *fext, int *format, int *subformat) { int i; for (i = 0; i < nconvFormatList; i++) { DMConvFormat *fmt = &convFormatList[i]; if (strcasecmp(fext, fmt->fext) == 0) { *format = fmt->format; *subformat = fmt->subformat; return TRUE; } } return FALSE; } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 15: dmVerbosity++; break; case 1: switch (tolower(optArg[0])) { case 's': optInFormat = FFMT_SPRITE; break; case 'c': optInFormat = FFMT_CHAR; break; case 'b': optInFormat = FFMT_BITMAP; break; case 'i': optInFormat = FFMT_IMAGE; break; default: dmError("Invalid input format '%s'.\n", optArg); return FALSE; } break; case 2: optInMulticolor = TRUE; break; case 3: optOutFilename = optArg; break; case 4: if (!dmGetIntVal(optArg, &optInSkip)) { dmError("Invalid skip value argument '%s'.\n", optArg); return FALSE; } break; case 5: if (!dmGetFormatByExt(optArg, &optOutFormat, &optOutSubFormat)) { dmError("Invalid output format '%s'.\n", optArg); return FALSE; } break; case 6: { int index = 0, tmp; char *s, *p = optArg; while (index < C64_MAX_COLORS && *p != 0 && (s = strchr(p, ':')) != NULL) { *s = 0; if (sscanf(p, "%d", &tmp) == 1) optColors[index++] = tmp; p = s + 1; } if (*p && index < C64_MAX_COLORS) { if (sscanf(p, "%d", &tmp) == 1) optColors[index++] = tmp; } dmMsg(1, "Set color table: "); for (tmp = 0; tmp < index; tmp++) { dmPrint(1, "[%d:%d]%s", tmp, optColors[tmp], (tmp < index - 1) ? ", " : ""); } dmPrint(1, "\n"); } break; case 7: if (sscanf(optArg, "%d", &optItemCount) != 1) { dmError("Invalid count value argument '%s'.\n", optArg); return FALSE; } break; case 8: optSequential = TRUE; break; case 9: { int tmp = atoi(optArg); if (tmp < 1 || tmp > 50) { dmError("Invalid scale value '%s'.\n", optArg); return FALSE; } optScale = tmp; } break; case 11: { int tmp = atoi(optArg); if (tmp < 1 || tmp > 512) { dmError("Invalid width value '%s'.\n", optArg); return FALSE; } optPlanedWidth = tmp; } break; case 12: optPaletted = TRUE; break; case 13: { int tmp = atoi(optArg); if (tmp < 1 || tmp > 8) { dmError("Invalid bitplanes/bpp value '%s'.\n", optArg); return FALSE; } optBPP = tmp; } break; case 14: optInterleave = TRUE; break; default: dmError("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optInFilename) optInFilename = currArg; else { dmError("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 = ASC_NBITS; i; i -= 2) { int val = (byte & (3ULL << (i - 2))) >> (i - 2); char ch; switch (format) { case FFMT_ASCII: ch = dmASCIIPalette[val]; fprintf(out, "%c%c", ch, ch); break; case FFMT_ANSI: fprintf(out, "%c[0;%d;%dm##%c[0m", 0x1b, 1, 31 + optColors[val], 0x1b); break; } } } else { for (i = ASC_NBITS; i; i--) { int val = (byte & (1ULL << (i - 1))) >> (i - 1); char ch; switch (format) { case FFMT_ASCII: ch = val ? '#' : '.'; fputc(ch, out); break; case FFMT_ANSI: fprintf(out, "%c[0;%d;%dm %c[0m", 0x1b, 1, 31 + optColors[val], 0x1b); break; } } } } void dmDumpCharASCII(FILE *outFile, const Uint8 *buf, int *offs, int format, BOOL multicolor) { int yc; for (yc = 0; yc < C64_CHR_HEIGHT; yc++) { fprintf(outFile, "%04x : ", *offs); dmPrintByte(outFile, buf[yc], format, multicolor); fprintf(outFile, "\n"); (*offs)++; } } void dmDumpSpriteASCII(FILE *outFile, const Uint8 *buf, int *offs, int format, BOOL multicolor) { int bufOffs, xc, yc; for (bufOffs = yc = 0; yc < C64_SPR_HEIGHT; yc++) { fprintf(outFile, "%04x : ", *offs); for (xc = 0; xc < C64_SPR_WIDTH; xc++) { dmPrintByte(outFile, buf[bufOffs], format, multicolor); fprintf(outFile, " "); bufOffs++; (*offs)++; } fprintf(outFile, "\n"); } (*offs)++; } int dmWriteImageData(DMImage *img, void *cbdata, BOOL (*writeRowCB)(void *, Uint8 *, size_t), int scale, int format) { int x, y, yscale, xscale, res = 0, rowSize, rowWidth; Uint8 *row = NULL; // Allocate memory for row buffer rowWidth = img->width * scale; rowSize = rowWidth * dmImageGetBytesPerPixel(format); if ((row = dmMalloc(rowSize + 16)) == NULL) { res = DMERR_MALLOC; goto done; } // Generate the image for (y = 0; y < img->height; y++) { Uint8 *ptr = row, *ptr1 = row, *ptr2 = ptr1 + rowWidth, *ptr3 = ptr2 + rowWidth; for (x = 0; x < img->width; x++) { Uint8 c = img->data[(y * img->pitch) + x], qr, qg, qb, qa; switch (format) { case DM_IFMT_PALETTE: for (xscale = 0; xscale < scale; xscale++) *ptr++ = c; break; case DM_IFMT_RGBA: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; qa = (c == img->ctrans) ? 0 : 255; for (xscale = 0; xscale < scale; xscale++) { *ptr++ = qr; *ptr++ = qg; *ptr++ = qb; *ptr++ = qa; } break; case DM_IFMT_RGB: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; for (xscale = 0; xscale < scale; xscale++) { *ptr++ = qr; *ptr++ = qg; *ptr++ = qb; } break; case DM_IFMT_RGB_PLANE: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; for (xscale = 0; xscale < scale; xscale++) { *ptr1++ = qr; *ptr2++ = qg; *ptr3++ = qb; } break; } } for (yscale = 0; yscale < scale; yscale++) { if (!writeRowCB(cbdata, row, rowSize)) { res = DMERR_FWRITE; goto done; } } } done: dmFree(row); return res; } #define DMCOL(x) (((x) >> 4) & 0xf) int dmWriteIFFMasterRAWPalette(const char *filename, DMImage *img, int ncolors) { FILE *fp; int i; if ((fp = fopen(filename, "w")) == NULL) { dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename); return -15; } for (i = 0; i < ncolors; i++) { int color; if (i < img->ncolors) { color = (DMCOL(img->pal[i].r) << 8) | (DMCOL(img->pal[i].g) << 4) | (DMCOL(img->pal[i].b)); } else color = 0; fprintf(fp, "\tdc.w $%04X\n", color); } return 0; } int dmWriteIFFMasterRAWImageFILE(FILE *fp, DMImage *img, int scale, int nplanes, BOOL interleave) { int xc, yc, plane, res; DMBitStream bs; if ((res = dmInitBitStream(&bs, fp)) != DMERR_OK) return res; if (interleave) { // Output bitplanes in interleaved format (each plane of line sequentially) for (yc = 0; yc < img->height; yc++) { for (plane = 0; plane < nplanes; plane++) { Uint8 *sp = img->data + yc * img->pitch; for (xc = 0; xc < img->width; xc++) { if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1)) return DMERR_FWRITE; } } } } else { // Output each bitplane in sequence for (plane = 0; plane < nplanes; plane++) { for (yc = 0; yc < img->height; yc++) { Uint8 *sp = img->data + yc * img->pitch; for (xc = 0; xc < img->width; xc++) { if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1)) return DMERR_FWRITE; } } } } return dmFlushBitStream(&bs); } int dmWriteIFFMasterRAWImage(const char *filename, DMImage *img, int scale, int nplanes, BOOL interleave) { FILE *fp; int res; if ((fp = fopen(filename, "wb")) == NULL) { dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWriteIFFMasterRAWImageFILE(fp, img, scale, nplanes, interleave); fclose(fp); return res; } static BOOL dmWritePPMRow(void *cbdata, Uint8 *row, size_t len) { return fwrite(row, sizeof(Uint8), len, (FILE *) cbdata) == len; } int dmWritePPMImageFILE(FILE *fp, DMImage *img, int scale) { // Write PPM header fprintf(fp, "P6\n%d %d\n255\n", img->width * scale, img->height * scale); // Write image data return dmWriteImageData(img, (void *) fp, dmWritePPMRow, scale, DM_IFMT_RGB); } int dmWritePPMImage(const char *filename, DMImage *img, int scale) { FILE *fp; int res; // Create output file if ((fp = fopen(filename, "wb")) == NULL) { dmError("PPM: could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWritePPMImageFILE(fp, img, scale); fclose(fp); return res; } #ifdef DM_USE_LIBPNG static BOOL dmWritePNGRow(void *cbdata, Uint8 *row, size_t len) { png_structp png_ptr = cbdata; (void) len; if (setjmp(png_jmpbuf(png_ptr))) return FALSE; png_write_row(png_ptr, row); return TRUE; } int dmWritePNGImageFILE(FILE *fp, DMImage *img, int scale, int format) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_colorp palette = NULL; int fmt, res = DMERR_OK; // Create PNG structures png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { dmError("PNG: png_create_write_struct() failed.\n"); res = DMERR_MALLOC; goto error; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { dmError("PNG: png_create_info_struct(%p) failed.\n", png_ptr); res = DMERR_INIT_FAIL; goto error; } if (setjmp(png_jmpbuf(png_ptr))) { dmError("PNG: Error during image writing..\n"); res = DMERR_INIT_FAIL; goto error; } png_init_io(png_ptr, fp); // Write PNG header info switch (format) { case DM_IFMT_PALETTE: fmt = PNG_COLOR_TYPE_PALETTE; break; case DM_IFMT_RGB : fmt = PNG_COLOR_TYPE_RGB; break; case DM_IFMT_RGBA : fmt = PNG_COLOR_TYPE_RGB_ALPHA; break; default: dmError("PNG: Internal error, unsupported image format %d.\n", format); goto error; } png_set_IHDR(png_ptr, info_ptr, img->width * scale, img->height * scale, 8, /* bits per component */ fmt, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Palette if (format == DM_IFMT_PALETTE) { int i; palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (palette == NULL) { dmError("PNG: Could not allocate palette structure."); goto error; } memset(palette, 0, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); for (i = 0; i < img->ncolors; i++) { palette[i].red = img->pal[i].r; palette[i].green = img->pal[i].g; palette[i].blue = img->pal[i].b; } png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH); } // png_set_gAMA(png_ptr, info_ptr, 2.2); png_write_info(png_ptr, info_ptr); // Write compressed image data dmWriteImageData(img, (void *) png_ptr, dmWritePNGRow, scale, format); // Write footer png_write_end(png_ptr, NULL); error: png_free(png_ptr, palette); palette = NULL; if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); return res; } int dmWritePNGImage(const char *filename, DMImage *img, int scale, int format) { int res; FILE *fp; if ((fp = fopen(filename, "wb")) == NULL) { dmError("PNG: could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWritePNGImageFILE(fp, img, scale, format); fclose(fp); return res; } #endif typedef struct { Uint8 r,g,b; } DMPCXColor; typedef struct { Uint8 manufacturer, version, encoding, bpp; Uint16 xmin, ymin, xmax, ymax; Uint16 hres, vres; DMPCXColor colormap[16]; Uint8 reserved; Uint8 nplanes; Uint16 bpl; Uint16 palinfo; Uint8 filler[58]; } DMPCXHeader; typedef struct { DMPCXHeader *header; Uint8 *buf; size_t bufLen, bufOffs; int format; FILE *fp; } DMPCXData; static inline Uint8 dmPCXGetByte(Uint8 *row, const size_t len, const size_t soffs) { return (soffs < len) ? row[soffs] : 0; } static BOOL dmPCXFlush(DMPCXData *pcx) { BOOL ret = fwrite(pcx->buf, sizeof(Uint8), pcx->bufOffs, pcx->fp) == pcx->bufOffs; pcx->bufOffs = 0; return ret; } static inline BOOL dmPCXPutByte(DMPCXData *pcx, const Uint8 val) { if (pcx->bufOffs < pcx->bufLen) { pcx->buf[pcx->bufOffs++] = val; return TRUE; } else return dmPCXFlush(pcx); } BOOL dmWritePCXRow(void *cbdata, Uint8 *row, size_t len) { DMPCXData *pcx = (DMPCXData *) cbdata; int plane; size_t soffs = 0; for (plane = 0; plane < pcx->header->nplanes; plane++) { Uint8 data = dmPCXGetByte(row, len, soffs++), count = 1; pcx->bufOffs = 0; while (soffs < pcx->header->bpl) { if (data == dmPCXGetByte(row, len, soffs) && count < 63) { count++; soffs++; } else { if (count == 1 && (data & 0xC0) != 0xC0) { if (!dmPCXPutByte(pcx, data)) return FALSE; } else { if (!dmPCXPutByte(pcx, 0xC0 | count) || !dmPCXPutByte(pcx, data)) return FALSE; } data = dmPCXGetByte(row, len, soffs++); count = 1; } } if (count > 1) { if (!dmPCXPutByte(pcx, 0xC0 | count) || !dmPCXPutByte(pcx, data)) return FALSE; } if (!dmPCXFlush(pcx)) return FALSE; } return TRUE; } int dmWritePCXImageFILE(FILE *fp, DMImage *img, int scale, BOOL paletted) { DMPCXData pcx; DMPCXHeader hdr; int res; // Create output file pcx.buf = NULL; pcx.format = paletted ? DM_IFMT_PALETTE : DM_IFMT_RGB_PLANE; pcx.header = &hdr; pcx.fp = fp; // Create PCX header memset(&hdr, 0, sizeof(hdr)); if (paletted) { int i; for (i = 0; i < (img->ncolors > 16 ? 16 : img->ncolors); i++) { hdr.colormap[i].r = img->pal[i].r; hdr.colormap[i].g = img->pal[i].g; hdr.colormap[i].b = img->pal[i].b; } } hdr.manufacturer = 10; hdr.version = 5; hdr.encoding = 1; hdr.bpp = 8; hdr.hres = img->width * scale; hdr.vres = img->height * scale; hdr.xmin = hdr.ymin = 0; hdr.xmax = hdr.hres - 1; hdr.ymax = hdr.vres - 1; hdr.nplanes = dmImageGetBytesPerPixel(pcx.format); hdr.palinfo = 1; res = (img->width * scale); hdr.bpl = res / 2; if (res % 2) hdr.bpl++; hdr.bpl *= 2; dmMsg(2, "PCX: paletted=%d, nplanes=%d, bpp=%d, bpl=%d\n", paletted, hdr.nplanes, hdr.bpp, hdr.bpl); pcx.bufLen = hdr.bpl * 4; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { dmError("PCX: Could not allocate %d bytes for RLE compression buffer.\n", pcx.bufLen); res = DMERR_MALLOC; goto error; } // Write PCX header if (!dm_fwrite_byte(pcx.fp, hdr.manufacturer) || !dm_fwrite_byte(pcx.fp, hdr.version) || !dm_fwrite_byte(pcx.fp, hdr.encoding) || !dm_fwrite_byte(pcx.fp, hdr.bpp)) { dmError("PCX: Could not write basic header data.\n"); res = DMERR_FWRITE; goto error; } if (!dm_fwrite_le16(pcx.fp, hdr.xmin) || !dm_fwrite_le16(pcx.fp, hdr.ymin) || !dm_fwrite_le16(pcx.fp, hdr.xmax) || !dm_fwrite_le16(pcx.fp, hdr.ymax) || !dm_fwrite_le16(pcx.fp, hdr.hres) || !dm_fwrite_le16(pcx.fp, hdr.vres)) { dmError("PCX: Could not write image dimensions.\n"); res = DMERR_FWRITE; goto error; } if (!dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.colormap, sizeof(hdr.colormap))) { dmError("PCX: Could not write colormap.\n"); res = DMERR_FWRITE; goto error; } if (!dm_fwrite_byte(pcx.fp, hdr.reserved) || !dm_fwrite_byte(pcx.fp, hdr.nplanes) || !dm_fwrite_le16(pcx.fp, hdr.bpl) || !dm_fwrite_le16(pcx.fp, hdr.palinfo) || !dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { dmError("PCX: Could not write header remainder.\n"); res = DMERR_FWRITE; goto error; } // Write image data res = dmWriteImageData(img, (void *) &pcx, dmWritePCXRow, scale, pcx.format); // Write VGA palette if (paletted) { int i; dm_fwrite_byte(pcx.fp, 0x0C); dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors); for (i = 0; i < img->ncolors; i++) { dm_fwrite_byte(pcx.fp, img->pal[i].r); dm_fwrite_byte(pcx.fp, img->pal[i].g); dm_fwrite_byte(pcx.fp, img->pal[i].b); } // Pad the palette, if necessary for (; i < 256; i++) { dm_fwrite_byte(pcx.fp, 0); dm_fwrite_byte(pcx.fp, 0); dm_fwrite_byte(pcx.fp, 0); } } error: dmFree(pcx.buf); return res; } int dmWritePCXImage(const char *filename, DMImage *img, int scale, BOOL paletted) { FILE *fp; int res; if ((fp = fopen(filename, "wb")) == NULL) { dmError("PCX: Could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWritePCXImageFILE(fp, img, scale, paletted); fclose(fp); return res; } static BOOL dmPCXDecodeRLERow(FILE *fp, Uint8 *buf, const size_t bufLen) { size_t offs = 0; do { int count; Uint8 data; if (!dm_fread_byte(fp, &data)) return FALSE; if ((data & 0xC0) == 0xC0) { count = data & 0x3F; if (!dm_fread_byte(fp, &data)) return FALSE; } else count = 1; while (count-- && offs < bufLen) buf[offs++] = data; } while (offs < bufLen); return TRUE; } int dmReadPCXImageFILE(FILE *fp, DMImage **pimg) { DMImage *img; DMPCXData pcx; DMPCXHeader hdr; BOOL paletted; int res = 0, yc, xc; Uint8 *dp; pcx.buf = NULL; // Read PCX header if (!dm_fread_byte(fp, &hdr.manufacturer) || !dm_fread_byte(fp, &hdr.version) || !dm_fread_byte(fp, &hdr.encoding) || !dm_fread_byte(fp, &hdr.bpp)) { dmError("PCX: Could not read basic header data.\n"); res = DMERR_FREAD; goto error; } if (hdr.manufacturer != 10 || hdr.version != 5 || hdr.encoding != 1 || hdr.bpp != 8) { dmError("PCX: Not a PCX file, or unsupported variant.\n"); res = DMERR_FREAD; goto error; } if (!dm_fread_le16(fp, &hdr.xmin) || !dm_fread_le16(fp, &hdr.ymin) || !dm_fread_le16(fp, &hdr.xmax) || !dm_fread_le16(fp, &hdr.ymax) || !dm_fread_le16(fp, &hdr.hres) || !dm_fread_le16(fp, &hdr.vres)) { dmError("PCX: Could not read image dimensions.\n"); res = DMERR_FREAD; goto error; } if (!dm_fread_str(fp, (Uint8 *) &hdr.colormap, sizeof(hdr.colormap))) { dmError("PCX: Could not read colormap.\n"); res = DMERR_FREAD; goto error; } if (!dm_fread_byte(fp, &hdr.reserved) || !dm_fread_byte(fp, &hdr.nplanes) || !dm_fread_le16(fp, &hdr.bpl) || !dm_fread_le16(fp, &hdr.palinfo) || !dm_fread_str(fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { dmError("PCX: Could not read header remainder.\n"); res = DMERR_FREAD; goto error; } if (hdr.nplanes != 3 && hdr.nplanes != 1) { dmError("PCX: Unsupported number of bitplanes %d.\n", hdr.nplanes); res = DMERR_FREAD; goto error; } // Allocate image if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1)) == NULL) { dmError("PCX: Could not allocate image structure.\n"); res = DMERR_MALLOC; goto error; } paletted = hdr.nplanes == 1; pcx.bufLen = hdr.nplanes * hdr.bpl; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { dmError("PCX: Could not allocate RLE buffer.\n"); res = DMERR_MALLOC; goto error; } // Read image data dp = img->data; for (yc = 0; yc < img->height; yc++) { // Decode row of RLE'd data if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen)) { dmError("PCX: Error decoding RLE data.\n"); res = DMERR_INVALID_DATA; goto error; } // Decode bitplanes switch (hdr.nplanes) { case 1: memcpy(dp, pcx.buf, img->width); break; case 3: { Uint8 *dptr = dp, *sptr1 = pcx.buf, *sptr2 = sptr1 + hdr.bpl, *sptr3 = sptr2 + hdr.bpl; for (xc = 0; xc < img->width; xc++) { *dptr++ = *sptr1++; *dptr++ = *sptr2++; *dptr++ = *sptr3++; } } break; } dp += img->pitch; } // Read VGA palette if (paletted) { int i; Uint8 tmpb; BOOL read; if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C) { read = FALSE; img->ncolors = 16; } else { read = TRUE; img->ncolors = 256; } if ((img->pal = dmCalloc(img->ncolors, sizeof(DMColor))) == NULL) { dmError("PCX: Could not allocate palette data!\n"); res = DMERR_MALLOC; goto error; } if (read) { for (i = 0; i < img->ncolors; i++) { Uint8 tmpR, tmpG, tmpB; if (!dm_fread_byte(fp, &tmpR) || !dm_fread_byte(fp, &tmpG) || !dm_fread_byte(fp, &tmpB)) goto error; img->pal[i].r = tmpR; img->pal[i].g = tmpG; img->pal[i].b = tmpB; } } else { for (i = 0; i < img->ncolors; i++) { if (i < 16) { img->pal[i].r = hdr.colormap[i].r; img->pal[i].g = hdr.colormap[i].g; img->pal[i].b = hdr.colormap[i].b; } } } } error: dmFree(pcx.buf); return res; } int dmReadPCXImage(const char *filename, DMImage **pimg) { FILE *fp; int res; if ((fp = fopen(filename, "rb")) == NULL) { dmError("PCX: Could not open file '%s' for reading.\n", filename); return -15; } res = dmReadPCXImageFILE(fp, pimg); fclose(fp); return res; } static int fmtProbePNG(const Uint8 *buf, const size_t len) { if (len > 64 && buf[0] == 0x89 && buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G' && buf[4] == 0x0d && buf[5] == 0x0a) return DM_PROBE_SCORE_GOOD; return DM_PROBE_SCORE_FALSE; } static int fmtProbePCX(const Uint8 *buf, const size_t len) { if (len > 128 + 64 && buf[0] == 10 && buf[1] == 5 && buf[2] == 1 && buf[3] == 8) return DM_PROBE_SCORE_GOOD; return DM_PROBE_SCORE_FALSE; } #ifdef UNFINISHED int dmConvertBMP2(DMImage *screen, const DM64Image *img) { int yc; Uint8 *dp = screen->data; for (yc = 0; yc < screen->height; yc++) { Uint8 *d = dp; const int y = yc / 8, yb = yc & 7; const int scroffsy = y * C64_SCR_CH_WIDTH; const int bmoffsy = y * C64_SCR_WIDTH; int xc; for (xc = 0; xc < screen->width / 2; xc++) { const int x = xc / 4; const int scroffs = scroffsy + x; const int b = img->bitmap[0][bmoffsy + (x * 8) + yb]; const int v = 6 - ((xc * 2) & 6); Uint8 c; switch ((b >> v) & 3) { case 0: c = img->bgcolor; break; case 1: c = img->screen[0][scroffs] >> 4; break; case 2: c = img->screen[0][scroffs] & 15; break; case 3: c = img->color[0][scroffs] & 15; break; } *d++ = c; *d++ = c; } dp += screen->pitch; } return 0; } #endif int dmWriteImage(char *filename, DMImage *image, BOOL info) { if (info) { dmMsg(1, "Outputting %s image %d x %d -> %d x %d [%d]\n", imageFormatList[optOutSubFormat], image->width, image->height, image->width * optScale, image->height * optScale, optScale); } switch (optOutSubFormat) { #ifdef DM_USE_LIBPNG case IMGFMT_PNG: if (info) dmMsg(2, "%s output.\n", optPaletted ? "Indexed 8bpp" : "32bit RGBA"); return dmWritePNGImage(filename, image, optScale, optPaletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA); #endif case IMGFMT_PPM: if (info) dmMsg(2, "24bit RGB output.\n"); return dmWritePPMImage(filename, image, optScale); case IMGFMT_PCX: if (info) dmMsg(2, "%s output.\n", optPaletted ? "Indexed 8bpp" : "24bit RGB"); return dmWritePCXImage(filename, image, optScale, optPaletted); case IMGFMT_ARAW: { int res; char *palFilename = dm_strdup_printf("%s.pal", filename); res = dmWriteIFFMasterRAWPalette(palFilename, image, 1 << optBPP); dmFree(palFilename); if (res != DMERR_OK) return res; if (info) dmMsg(2, "%d bitplanes, %s interleave.\n", optBPP, optInterleave ? "with" : "without"); return dmWriteIFFMasterRAWImage(filename, image, optScale, optBPP, optInterleave); } default: return FALSE; } } int dmDumpSpritesAndChars(FILE *inFile) { int dataOffs, itemCount, outWidth, outWidthPX, outHeight; size_t bufSize; Uint8 *bufData; switch (optInFormat) { case FFMT_CHAR: bufSize = C64_CHR_SIZE; outWidth = C64_CHR_WIDTH; outWidthPX = C64_CHR_WIDTH_PX; outHeight = C64_CHR_HEIGHT; break; case FFMT_SPRITE: bufSize = C64_SPR_SIZE; outWidth = C64_SPR_WIDTH; outWidthPX = C64_SPR_WIDTH_PX; outHeight = C64_SPR_HEIGHT; break; default: dmError("Invalid input format %d, internal error.\n", optInFormat); return -1; } if ((bufData = dmMalloc(bufSize)) == NULL) { dmError("Could not allocate temporary buffer of %d bytes.\n", bufSize); return -2; } dataOffs = optInSkip; itemCount = 0; if (optOutFormat == FFMT_ANSI || optOutFormat == FFMT_ASCII) { BOOL error = FALSE; FILE *outFile; if (optOutFilename == NULL) outFile = stdout; else if ((outFile = fopen(optOutFilename, "w")) == NULL) { int res = errno; dmError("Error opening output file '%s'. (%s)\n", optOutFilename, strerror(res)); goto error; } while (!feof(inFile) && !error && (optItemCount < 0 || itemCount < optItemCount)) { memset(bufData, 0, bufSize); if (fread(bufData, 1, bufSize, inFile) != bufSize) { dmError("Could not read full bufferful (%d bytes) of data at 0x%x.\n", bufSize, dataOffs); error = TRUE; } fprintf(outFile, "---- : -------------- #%d\n", itemCount); switch (optInFormat) { case FFMT_CHAR: dmDumpCharASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); break; case FFMT_SPRITE: dmDumpSpriteASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); break; } itemCount++; } fclose(outFile); } else if (optOutFormat == FFMT_IMAGE) { DMImage *outImage = NULL; char *outFilename = NULL; int outX = 0, outY = 0, err; if (optSequential) { if (optOutFilename == NULL) { dmError("Sequential image output requires filename template.\n"); goto error; } outImage = dmImageAlloc(outWidthPX, outHeight); dmMsg(1, "Outputting sequence of %d images @ %d x %d -> %d x %d.\n", optItemCount, outImage->width, outImage->height, outImage->width * optScale, outImage->height * optScale); } else { int outIWidth, outIHeight; if (optItemCount <= 0) { dmError("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); } outImage->constpal = TRUE; outImage->pal = dmC64Palette; outImage->ncolors = C64_NCOLORS; outImage->ctrans = 255; while (!feof(inFile) && (optItemCount < 0 || itemCount < optItemCount)) { memset(bufData, 0, bufSize); if (fread(bufData, 1, bufSize, inFile) != bufSize) { dmError("Could not read full bufferful (%d bytes) of data at 0x%x.\n", bufSize, dataOffs); break; } if ((err = dmC64ConvertCSData(outImage, outX * outWidthPX, outY * outHeight, bufData, outWidth, outHeight, optInMulticolor, optColors)) != DMERR_OK) { dmError("Internal error in conversion of raw data to bitmap: %d.\n", err); break; } if (optSequential) { outFilename = dm_strdup_printf("%s%04d.%s", optOutFilename, itemCount, convFormatList[optOutFormat].fext); if (outFilename == NULL) { dmError("Could not allocate memory for filename template?\n"); goto error; } dmWriteImage(outFilename, outImage, FALSE); dmFree(outFilename); } else { if (++outX >= optPlanedWidth) { outX = 0; outY++; } } itemCount++; } if (!optSequential) { dmWriteImage(optOutFilename, outImage, TRUE); } dmImageFree(outImage); } dmFree(bufData); return 0; error: dmFree(bufData); return -1; } int main(int argc, char *argv[]) { FILE *inFile; DMC64ImageFormat *cfmt; DMC64Image cimage; Uint8 *dataBuf = NULL; size_t dataSize; int i; // Default colors for (i = 0; i < C64_MAX_COLORS; i++) optColors[i] = i + 1; // Initialize and parse commandline dmInitProg("gfxconv", "Simple c64 graphics converter", "0.4", NULL, NULL); if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, TRUE)) exit(1); #ifndef DM_USE_LIBPNG if (optOutFormat == IMGFMT_PNG) { dmError("PNG output format support not compiled in, sorry.\n"); goto error; } #endif // Determine input format, if not specified' if (optInFormat == FFMT_AUTO && optInFilename != NULL) { char *dext = strrchr(optInFilename, '.'); if (dext) { dmGetFormatByExt(dext + 1, &optInFormat, &optInSubFormat); } } if (optInFilename == NULL) { if (optInFormat == FFMT_AUTO) { dmError("Standard input cannot be used without specifying input format.\n"); goto error; } inFile = stdin; } else if ((inFile = fopen(optInFilename, "rb")) == NULL) { int res = errno; dmError("Error opening input file '%s'. (%s)\n", optInFilename, strerror(res)); goto error; } if (dmReadDataFile(inFile, NULL, &dataBuf, &dataSize) != 0) goto error; if (optInFormat == FFMT_AUTO || optInFormat == FFMT_BITMAP) { // Probe for format DMC64ImageFormat *forced = NULL; int res; if (optForcedFormat >= 0) { forced = &dmC64ImageFormats[optForcedFormat]; dmMsg(0,"Forced %s format image, type %d, %s\n", forced->name, forced->type, forced->extension); } res = dmC64DecodeBMP(&cimage, dataBuf, dataSize, optInSkip, optInSkip + 2, &cfmt, forced); if (forced == NULL && cfmt != NULL) { dmMsg(1,"Probed %s format image, type %d, %s\n", cfmt->name, cfmt->type, cfmt->extension); } if (res == 0) optInFormat = FFMT_BITMAP; } if (optInFormat == FFMT_AUTO) { // XXX, needs a proper probe loop if (fmtProbePNG(dataBuf + optInSkip, dataSize - optInSkip)) { optInFormat = FFMT_IMAGE; optInSubFormat = IMGFMT_PNG; } else if (fmtProbePCX(dataBuf + optInSkip, dataSize - optInSkip)) { optInFormat = FFMT_IMAGE; optInSubFormat = IMGFMT_PCX; } } if (optInFormat == FFMT_AUTO) { dmError("No input format specified, and could not be determined automatically.\n"); exit(1); } // Skip, if needed if (fseek(inFile, optInSkip, SEEK_SET) != 0) { int res = errno; dmError("Could not seek to file position %d (0x%x): %s\n", optInSkip, optInSkip, strerror(res)); goto error; } int inFormat = dmGetConvFormat(optInFormat, optInSubFormat), outFormat = dmGetConvFormat(optOutFormat, optOutSubFormat); if (inFormat != -1 && outFormat != -1) { char *inFmtName = convFormatList[inFormat].name, *inFmtExt = convFormatList[inFormat].fext, *outFmtName = convFormatList[outFormat].name, *outFmtExt = convFormatList[outFormat].fext; if (optInFormat == FFMT_BITMAP) inFmtExt = cfmt->name; dmMsg(1, "Attempting conversion %s (%s) -> %s (%s)\n", inFmtName, inFmtExt, outFmtName, outFmtExt); } switch (optInFormat) { case FFMT_SPRITE: case FFMT_CHAR: dmDumpSpritesAndChars(inFile); break; case FFMT_BITMAP: { DMImage *img = NULL; int res = DMERR_OK; if (optOutFilename == NULL) { dmError("Output filename not set, required for image formats.\n"); goto error; } switch (optOutFormat) { case FFMT_IMAGE: if ((img = dmImageAlloc(C64_SCR_WIDTH, C64_SCR_HEIGHT)) == NULL) { dmError("Could not allocate output image surface %d x %d.\n", C64_SCR_WIDTH, C64_SCR_HEIGHT); goto error; } img->pal = (DMColor *) &dmC64Palette; img->ncolors = C64_NCOLORS; img->constpal = TRUE; if (cfmt->convert != NULL) res = cfmt->convert(img, &cimage); else res = dmC64ConvertGenericBMP2Image(img, &cimage); if (res != DMERR_OK || img == NULL) { dmError("Error in bitmap to image conversion.\n"); goto error; } res = dmWriteImage(optOutFilename, img, TRUE); break; default: dmError("Unsupported output format for bitmap/image conversion.\n"); break; } dmImageFree(img); } break; case FFMT_IMAGE: { DMImage *img; int res = DMERR_OK; if (optOutFilename == NULL) { dmError("Output filename not set, required for image formats.\n"); goto error; } // Read input switch (optInSubFormat) { case IMGFMT_PCX: res = dmReadPCXImageFILE(inFile, &img); break; // case IMGFMT_PNG: res = dmReadPNGImageFILE(inFile, &img); break; // case IMGFMT_ARAW: res = dmReadARAWImageFILE(inFile, &img, optBPP); break; default: dmError("Unsupported input image format for bitmap/image conversion.\n"); break; } if (res != DMERR_OK || img == NULL) break; switch (optOutFormat) { case FFMT_IMAGE: res = dmWriteImage(optOutFilename, img, TRUE); break; default: dmError("Unsupported output format for bitmap/image conversion.\n"); break; } dmImageFree(img); } break; } fclose(inFile); exit(0); return 0; error: return -3; exit(3); }