Mercurial > hg > dmlib
diff gfxconv.c @ 407:59244a7ae37f
Move c64 utilities to the engine lib, as we benefit from a common framework.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 03 Nov 2012 02:19:51 +0200 |
parents | |
children | b529b7e8ff83 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfxconv.c Sat Nov 03 02:19:51 2012 +0200 @@ -0,0 +1,1630 @@ +/* + * 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 "dmmutex.h" +#include "lib64gfx.h" + +//#define UNFINISHED 1 + +#ifdef HAVE_LIBPNG +#include <png.h> +#endif + +enum +{ + INFMT_AUTO = 0, + INFMT_CHAR, + INFMT_SPRITE, + INFMT_BITMAP, + INFMT_IMAGE, +}; + +enum +{ + OUTFMT_ASCII, + OUTFMT_ANSI, + OUTFMT_PNG, + OUTFMT_PPM, + OUTFMT_PCX, + OUTFMT_ARAW, + +#ifdef UNFINISHED + OUTFMT_SPRITE, + OUTFMT_CHAR, +#endif + + OUTFMT_LAST +}; + +char * outFormatList[OUTFMT_LAST] = +{ + "ascii", + "ansi", + "png", + "ppm", + "pcx", + "araw", +#ifdef UNFINISHED + "spr", + "char", +#endif +}; + +static const int noutFormatList = sizeof(outFormatList) / sizeof(outFormatList[0]); + + +#define ASC_NBITS 8 +#define ASC_NCOLORS 4 +static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#"; + + +char *optInFilename = NULL, + *optOutFilename = NULL; +int optInFormat = INFMT_AUTO, + optOutFormat = OUTFMT_ASCII, + optItemCount = -1, + optScale = 2, + optPlanedWidth = 1, + optBPP = 4; +int optInSkip = 0; +BOOL optInMulticolor = FALSE, + optSequential = FALSE, + optPaletted = FALSE; +int optColors[C64_MAX_COLORS]; + + +static DMOptArg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 3, 'o', "output", "Output filename", OPT_ARGREQ }, + { 1, 'i', "informat", "Set input format ([s]prite, [c]har, [b]itmap)", 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 }, +#ifdef UNFINISHED + {10, 'b', "bformat", "Force input bitmap format (see below)", OPT_ARGREQ }, +#endif + {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', "bpp", "Bits per pixel (certain image output formats)", OPT_ARGREQ }, +}; + +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: "); + for (i = 0; i < noutFormatList; i++) + { + printf("%s", outFormatList[i]); + if (i < noutFormatList - 1) + printf(", "); + else + printf("\n"); + } + +#ifdef UNFINISHED + printf("\nAvailable bitmap formats:\n"); + for (i = 0; i < ndmC64ImageFormats; i++) + { + DM64ImageFormat *fmt = &dmC64ImageFormats[i]; + printf("%3d | %-5s | %-15s | %s\n", + i, fmt->extension, + dmC64ImageTypeNames[fmt->type], + fmt->name); + } +#endif + + 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" + ); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + switch (tolower(optArg[0])) + { + case 's': + optInFormat = INFMT_SPRITE; + break; + case 'c': + optInFormat = INFMT_CHAR; + 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: + { + int i, format = -1; + for (i = 0; i < noutFormatList; i++) + if (strcasecmp(optArg, outFormatList[i]) == 0) + { + format = i; + break; + } + + if (format < 0) + { + dmError("Invalid output format '%s'.\n", optArg); + return FALSE; + } + + optOutFormat = format; + } + 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; + + 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 OUTFMT_ASCII: + ch = dmASCIIPalette[val]; + fprintf(out, "%c%c", ch, ch); + break; + case OUTFMT_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 OUTFMT_ASCII: + ch = val ? '#' : '.'; + fputc(ch, out); + break; + case OUTFMT_ANSI: + fprintf(out, "%c[0;%d;%dm %c[0m", + 0x1b, + 1, + 31 + optColors[val], + 0x1b); + break; + } + } + } +} + + +void dmDumpCharASCII(FILE *outFile, const uint8_t *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_t *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_t *, size_t), int scale, int format) +{ + int x, y, yscale, xscale, res = 0, rowSize, rowWidth; + uint8_t *row = NULL; + + // Allocate memory for row buffer + rowWidth = img->width * scale; + rowSize = rowWidth * dmImageGetBytesPerPixel(format); + + if ((row = dmMalloc(rowSize + 16)) == NULL) + { + res = -16; + goto done; + } + + // Generate the image + for (y = 0; y < img->height; y++) + { + uint8_t *ptr = row, + *ptr1 = row, + *ptr2 = ptr1 + rowWidth, + *ptr3 = ptr2 + rowWidth; + + for (x = 0; x < img->width; x++) + { + uint8_t 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 = -32; + 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; +} + + +typedef struct +{ + int bpp; + DMImage *img; + FILE *fp; +} DMRawData; + + +static BOOL dmWriteIFFMasterRAWRow(void *cbdata, uint8_t *row, size_t len) +{ + DMRawData *raw = (DMRawData *) cbdata; + size_t i; + + for (i = 0; i < len; i++) + { + } + + return fwrite(row, sizeof(uint8_t), len, raw->fp) == len; +} + + +int dmWriteIFFMasterRAWImageFILE(FILE *fp, DMImage *img, int scale, int bpp) +{ + DMRawData raw; + + raw.fp = fp; + raw.img = img; + raw.bpp = bpp; + + return dmWriteImageData(img, (void *) &raw, dmWriteIFFMasterRAWRow, scale, DM_IFMT_PALETTE); +} + +int dmWriteIFFMasterRAWImage(const char *filename, DMImage *img, int scale, int bpp) +{ + FILE *fp; + int res; + + if ((fp = fopen(filename, "wb")) == NULL) + { + dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename); + return -15; + } + + res = dmWriteIFFMasterRAWImageFILE(fp, img, scale, bpp); + + fclose(fp); + return res; +} + + +static BOOL dmWritePPMRow(void *cbdata, uint8_t *row, size_t len) +{ + return fwrite(row, sizeof(uint8_t), 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 -15; + } + + res = dmWritePPMImageFILE(fp, img, scale); + + fclose(fp); + return res; +} + + +#ifdef HAVE_LIBPNG +static BOOL dmWritePNGRow(void *cbdata, uint8_t *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; + + // 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"); + 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); + goto error; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + dmError("PNG: Error during image writing..\n"); + 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); + + png_free(png_ptr, palette); + palette = NULL; + + // Deallocate shit + if (png_ptr && info_ptr) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + } + + return 0; + +error: + png_free(png_ptr, palette); + palette = NULL; + + if (png_ptr && info_ptr) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + } + return -15; +} + + +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 -15; + } + + res = dmWritePNGImageFILE(fp, img, scale, format); + + fclose(fp); + return res; +} +#endif + + +typedef struct +{ + uint8_t r,g,b; +} DMPCXColor; + + +typedef struct +{ + uint8_t manufacturer, + version, + encoding, + bpp; + uint16_t xmin, ymin, xmax, ymax; + uint16_t hres, vres; + DMPCXColor colormap[16]; + uint8_t reserved; + uint8_t nplanes; + uint16_t bpl; + uint16_t palinfo; + uint8_t filler[58]; +} DMPCXHeader; + +typedef struct +{ + DMPCXHeader *header; + uint8_t *buf; + size_t bufLen, bufOffs; + int format; + FILE *fp; +} DMPCXData; + + +static inline uint8_t dmPCXGetByte(uint8_t *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_t), pcx->bufOffs, pcx->fp) == pcx->bufOffs; + pcx->bufOffs = 0; + return ret; +} + +static inline BOOL dmPCXPutByte(DMPCXData *pcx, const uint8_t val) +{ + if (pcx->bufOffs < pcx->bufLen) + { + pcx->buf[pcx->bufOffs++] = val; + return TRUE; + } + else + return dmPCXFlush(pcx); +} + +BOOL dmWritePCXRow(void *cbdata, uint8_t *row, size_t len) +{ + DMPCXData *pcx = (DMPCXData *) cbdata; + int plane; + size_t soffs = 0; + + for (plane = 0; plane < pcx->header->nplanes; plane++) + { + uint8_t 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 dmWritePCXImage(const char *filename, 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; + if ((pcx.fp = fopen(filename, "wb")) == NULL) + { + dmError("PCX: Could not open file '%s' for writing.\n", filename); + res = -15; + goto error; + } + + // 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.bpl = (((img->width * scale) / 2) + 1) * 2; + hdr.palinfo = 1; + + dmMsg(1, "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 = -11; + 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 = -10; + 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 = -9; + goto error; + } + + if (!dm_fwrite_str(pcx.fp, (uint8_t *) &hdr.colormap, sizeof(hdr.colormap))) + { + dmError("PCX: Could not write colormap.\n"); + res = -8; + 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_t *) &hdr.filler, sizeof(hdr.filler))) + { + dmError("PCX: Could not write header remainder.\n"); + res = -7; + 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); + + 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: + if (pcx.fp != NULL) + fclose(pcx.fp); + + dmFree(pcx.buf); + + return res; +} + + +static BOOL dmPCXDecodeRLERow(FILE *fp, uint8_t *buf, const size_t bufLen) +{ + size_t offs = 0; + do + { + int count; + uint8_t 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_t *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 = -9; + } + + if (hdr.manufacturer != 10 || + hdr.version != 5 || + hdr.encoding != 1 || + hdr.bpp != 8) + { + dmError("PCX: Not a PCX file, or unsupported variant.\n"); + res = -11; + 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 = -8; + goto error; + } + + if (!dm_fread_str(fp, (uint8_t *) &hdr.colormap, sizeof(hdr.colormap))) + { + dmError("PCX: Could not read colormap.\n"); + res = -7; + 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_t *) &hdr.filler, sizeof(hdr.filler))) + { + dmError("PCX: Could not read header remainder.\n"); + res = -6; + goto error; + } + + if (hdr.nplanes != 3 && hdr.nplanes != 1) + { + dmError("PCX: Unsupported number of bitplanes %d.\n", hdr.nplanes); + res = -4; + 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 = -5; + 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 = -3; + 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 = -100; + goto error; + } + + // Decode bitplanes + switch (hdr.nplanes) + { + case 1: + memcpy(dp, pcx.buf, img->width); + break; + + case 3: + { + uint8_t *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_t tmpb; + + if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C) + goto error; + + for (i = 0; i < img->ncolors; i++) + { + if (!dm_fread_byte(fp, &tmpb)) + goto error; + img->pal[i].r = tmpb; + + if (!dm_fread_byte(fp, &tmpb)) + goto error; + img->pal[i].g = tmpb; + + if (!dm_fread_byte(fp, &tmpb)) + goto error; + img->pal[i].b = tmpb; + } + } + +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; +} + + +int fmtProbePNGImageFILE(FILE *fp) +{ + uint8_t buf[6]; +// if (!dm_fread_str(fp, + return DM_PROBE_SCORE_FALSE; +} + + +int fmtProbePCXImageFILE(FILE *fp) +{ + DMPCXHeader hdr; + + 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)) + return DM_PROBE_SCORE_FALSE; + + if (hdr.manufacturer == 10 && + hdr.version == 5 && + hdr.encoding == 1 && + hdr.bpp == 8) + return DM_PROBE_SCORE_GOOD; + + return DM_PROBE_SCORE_FALSE; +} + + +#ifdef UNFINISHED +int dmConvertBMP2(DMImage *screen, const DM64Image *img) +{ + int yc; + uint8_t *dp = screen->data; + + for (yc = 0; yc < screen->height; yc++) + { + uint8_t *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_t 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, int format, BOOL paletted, int scale, int bpp) +{ + switch (format) + { +#ifdef HAVE_LIBPNG + case OUTFMT_PNG: + return dmWritePNGImage(filename, image, scale, paletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA); +#endif + + case OUTFMT_PPM: + return dmWritePPMImage(filename, image, scale); + + case OUTFMT_PCX: + return dmWritePCXImage(filename, image, scale, paletted); + + case OUTFMT_ARAW: + { + int res; + char *palFilename = dm_strdup_printf("%s.pal", filename); + res = dmWriteIFFMasterRAWPalette(palFilename, image, 1 << bpp); + dmFree(palFilename); + if (res != 0) + return res; + + return dmWriteIFFMasterRAWImage(filename, image, scale, bpp); + } + + default: + return FALSE; + } +} + + +int dmDumpSpritesAndChars(FILE *inFile) +{ + int dataOffs, itemCount, outWidth, outWidthPX, outHeight; + size_t bufSize; + uint8_t *bufData; + + switch (optInFormat) + { + case INFMT_CHAR: + bufSize = C64_CHR_SIZE; + outWidth = C64_CHR_WIDTH; + outWidthPX = C64_CHR_WIDTH_PX; + outHeight = C64_CHR_HEIGHT; + break; + case INFMT_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 == OUTFMT_ANSI || optOutFormat == OUTFMT_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 INFMT_CHAR: + dmDumpCharASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); + break; + case INFMT_SPRITE: + dmDumpSpriteASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor); + break; + } + itemCount++; + } + + fclose(outFile); + } + else + if (optOutFormat == OUTFMT_PNG || optOutFormat == OUTFMT_PPM || optOutFormat == OUTFMT_PCX) + { + DMImage *outImage = NULL; + char *outFilename = NULL; + int outX = 0, outY = 0, err; + +#ifndef HAVE_LIBPNG + if (optOutFormat == OUTFMT_PNG) + { + dmError("PNG output format support not compiled in, sorry.\n"); + goto error; + } +#endif + + 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); + dmMsg(1, "Outputting image %d x %d -> %d x %d.\n", + outImage->width, outImage->height, + outImage->width * optScale, outImage->height * optScale); + } + + 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)) != 0) + { + 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, outFormatList[optOutFormat]); + if (outFilename == NULL) + { + dmError("Could not allocate memory for filename template?\n"); + goto error; + } + + dmWriteImage(outFilename, outImage, optOutFormat, optPaletted, optScale, optBPP); + dmFree(outFilename); + } + else + { + if (++outX >= optPlanedWidth) + { + outX = 0; + outY++; + } + } + + itemCount++; + } + + if (!optSequential) + { + dmWriteImage(optOutFilename, outImage, optOutFormat, optPaletted, optScale, optBPP); + } + + dmImageFree(outImage); + } + + dmFree(bufData); + return 0; + +error: + dmFree(bufData); + return -1; +} + + +int main(int argc, char *argv[]) +{ + FILE *inFile; + int i, optInImageFormat; + + // 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); + + // Determine input format, if not specified' + if (optInFormat == INFMT_AUTO && optInFilename != NULL) + { + char *dext = strrchr(optInFilename, '.'); + if (dext) + { + dext++; + if (!strcasecmp(dext, "fnt") || !strcasecmp(dext, "chr")) + optInFormat = INFMT_CHAR; + else if (!strcasecmp(dext, "spr")) + optInFormat = INFMT_SPRITE; + else if (!strcasecmp(dext, "png") || !strcasecmp(dext, "pcx")) + optInFormat = INFMT_IMAGE; + } + } + + if (optInFilename == NULL) + { + if (optInFormat == INFMT_AUTO) + { + dmError("Standard input cannot be used without specifying input format.\n"); + exit(3); + } + inFile = stdin; + } + else + if ((inFile = fopen(optInFilename, "rb")) == NULL) + { + int res = errno; + dmError("Error opening input file '%s'. (%s)\n", + optInFilename, strerror(res)); + exit(3); + } + + if (optInFormat == INFMT_AUTO) + { + // 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)); + exit(3); + } +#if 0 + if (optInFormat == INFMT_AUTO) + { + int ret = dmC64ProbeGeneric + } +#endif + + if (optInFormat == INFMT_AUTO || optInFormat == INFMT_IMAGE) + { + if (fmtProbePNGImageFILE(inFile)) + { + optInFormat = INFMT_IMAGE; + optInImageFormat = OUTFMT_PNG; + } + else + if (fmtProbePCXImageFILE(inFile)) + { + optInFormat = INFMT_IMAGE; + optInImageFormat = OUTFMT_PCX; + } + else + if (optInFormat == INFMT_IMAGE) + { + dmError("Unsupported image input format.\n"); + exit(4); + } + } + } + + if (optInFormat == INFMT_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)); + exit(3); + } + + switch (optInFormat) + { + case INFMT_SPRITE: + case INFMT_CHAR: + dmDumpSpritesAndChars(inFile); + break; + + case INFMT_BITMAP: + case INFMT_IMAGE: + { + DMImage *img; + int res; + + if (optOutFilename == NULL) + { + dmError("Output filename not set, required for image formats.\n"); + exit(3); + } + + // Read input + switch (optInImageFormat) + { + case OUTFMT_PCX: + res = dmReadPCXImageFILE(inFile, &img); + break; + case OUTFMT_PNG: +// res = dmReadPNGImageFILE(inFile, &img); + break; + } + + switch (optOutFormat) + { + case OUTFMT_PCX: + case OUTFMT_PPM: + case OUTFMT_PNG: + case OUTFMT_ARAW: + res = dmWriteImage(optOutFilename, img, optOutFormat, optPaletted, optScale, optBPP); + break; + } + } + break; + } + + fclose(inFile); + + exit(0); + return 0; +}