view 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 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 "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;
}