view libgfx.c @ 435:e4a3f183e463

Modularize some more.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 03 Nov 2012 16:08:30 +0200
parents
children 86f956e4580f
line wrap: on
line source

/*
 * Functions for reading and converting various restricted
 * C64/etc and/or indexed/paletted 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 "libgfx.h"
#include "dmfile.h"
#include "dmbstr.h"

#ifdef DM_USE_LIBPNG
#include <png.h>
#endif


DMImage * dmImageAlloc(int width, int height)
{
    DMImage *img = dmCalloc(1, sizeof(DMImage));
    if (img == NULL)
        return NULL;
    
    img->width = img->pitch = width;
    img->height = height;
    img->data = dmMalloc(width * height * sizeof(Uint8));
    if (img->data == NULL)
    {
        dmFree(img);
        return NULL;
    }
    
    return img;
}


void dmImageFree(DMImage *img)
{
    if (img != NULL)
    {
        if (!img->constpal)
        {
            dmFree(img->pal);
        }
        dmFree(img->data);
        dmFree(img);
    }
}


int dmImageGetBytesPerPixel(int format)
{
    switch (format)
    {
        case DM_IFMT_PALETTE   : return 1;

        case DM_IFMT_RGB_PLANE :
        case DM_IFMT_RGB       : return 3;

        case DM_IFMT_RGBA      : return 4;

        default:                 return 0;
    }
}


int dmWriteImageData(DMImage *img, void *cbdata, BOOL (*writeRowCB)(void *, Uint8 *, size_t), const DMImageSpec *spec)
{
    int x, y, yscale, xscale, res = 0, rowSize, rowWidth;
    Uint8 *row = NULL;

    // Allocate memory for row buffer
    rowWidth = img->width * spec->scale;
    rowSize = rowWidth * dmImageGetBytesPerPixel(spec->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 (spec->format)
            {
                case DM_IFMT_PALETTE:
                    for (xscale = 0; xscale < spec->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 < spec->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 < spec->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 < spec->scale; xscale++)
                    {
                        *ptr1++ = qr;
                        *ptr2++ = qg;
                        *ptr3++ = qb;
                    }
                    break;
            }
        }

        for (yscale = 0; yscale < spec->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, DMImageSpec *spec)
{
    int xc, yc, plane, res;
    DMBitStream bs;
    
    if ((res = dmInitBitStream(&bs, fp)) != DMERR_OK)
        return res;

    if (spec->interleave)
    {
        // Output bitplanes in interleaved format (each plane of line sequentially)
        for (yc = 0; yc < img->height; yc++)
        {
            for (plane = 0; plane < spec->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 < spec->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, DMImageSpec *spec)
{
    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, spec);

    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, DMImageSpec *spec)
{
    // Write PPM header
    fprintf(fp,
        "P6\n%d %d\n255\n",
        img->width * spec->scale,
        img->height * spec->scale);

    // Write image data
    spec->format = DM_IFMT_RGB;
    return dmWriteImageData(img, (void *) fp, dmWritePPMRow, spec);
}


int dmWritePPMImage(const char *filename, DMImage *img, DMImageSpec *spec)
{
    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, spec);

    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, DMImageSpec *spec)
{
    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 (spec->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", spec->format);
            goto error;
    }
 
    png_set_IHDR(png_ptr, info_ptr,
        img->width * spec->scale,
        img->height * spec->scale,
        8,                    /* bits per component */
        fmt,
        PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_DEFAULT,
        PNG_FILTER_TYPE_DEFAULT);

    // Palette
    if (spec->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, spec);

    // 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, DMImageSpec *spec)
{
    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, spec);

    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 = TRUE;
    if (pcx->bufOffs > 0)
        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);
}

static BOOL dmWritePCXRow(void *cbdata, Uint8 *row, size_t len)
{
    DMPCXData *pcx = (DMPCXData *) cbdata;
    int plane;
    size_t soffs = 0;
    
//    fprintf(stderr, "%d, %d * %d = %d\n", len, pcx->header->bpl, pcx->header->nplanes, pcx->header->nplanes * pcx->header->bpl);

    pcx->bufOffs = 0;

    for (plane = 0; plane < pcx->header->nplanes; plane++)
    {
        Uint8 data = dmPCXGetByte(row, len, soffs++),
              count = 1;

//        size_t blen = pcx->header->bpl * pcx->header->nplanes;
        size_t blen = pcx->header->bpl;
        while (soffs < blen)
        {
            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, DMImageSpec *spec)
{
    DMPCXData pcx;
    DMPCXHeader hdr;
    int res;

    // Create output file
    pcx.buf    = NULL;
    pcx.format = spec->paletted ? DM_IFMT_PALETTE : DM_IFMT_RGB_PLANE;
    pcx.header = &hdr;
    pcx.fp     = fp;

    // Create PCX header
    memset(&hdr, 0, sizeof(hdr));
    if (spec->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 * spec->scale;
    hdr.vres         = img->height * spec->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 * spec->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",
        spec->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, spec);

    // Write VGA palette
    if (spec->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, DMImageSpec *spec)
{
    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, spec);
    
    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;
}


DMImageFormat dmImageFormatList[IMGFMT_LAST] =
{
    {
        "PNG", "Portable Network Graphics",
        fmtProbePNG,
        NULL, NULL,
#ifdef DM_USE_LIBPNG
        dmWritePNGImage, dmWritePNGImageFILE,
#else
        NULL, NULL,
#endif
    },
    {
        "PPM", "Portable PixMap",
        NULL,
        NULL, NULL,
        dmWritePPMImage, dmWritePPMImageFILE,
    },
    {
        "PCX", "Z-Soft Paintbrush",
        fmtProbePCX,
        dmReadPCXImage, dmReadPCXImageFILE,
        dmWritePCXImage, dmWritePCXImageFILE,
    },
    {
        "ARAW", "IFFMaster Amiga RAW",
        NULL,
        NULL, NULL,
        dmWriteIFFMasterRAWImage, dmWriteIFFMasterRAWImageFILE,
    }
};


int dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **pfmt, int *index)
{
    int i, scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;

    for (i = 0; i < IMGFMT_LAST; i++)
    {
        DMImageFormat *fmt = &dmImageFormatList[i];
        int score = fmt->probe(buf, len);
        if (score > scoreMax)
        {
            scoreMax = score;
            scoreIndex = i;
        }
    }

    if (scoreIndex >= 0)
    {
        *pfmt = &dmImageFormatList[scoreIndex];
        *index = scoreIndex;
        return scoreMax;
    }
    else
        return DM_PROBE_SCORE_FALSE;
}