view libgfx.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents b89598501cec
children d400e32b62d9
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


BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha)
{
    if (c1->r == c2->r &&
        c1->g == c2->g &&
        c1->b == c2->b)
        return alpha ? (c1->a == c2->a) : TRUE;
    else
        return FALSE;
}


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);
    }
}


BOOL dmPaletteAlloc(DMColor **ppal, int ncolors, int ctransp)
{
    int i;

    if (ppal == NULL)
        return FALSE;

    // Allocate desired amount of palette
    if ((*ppal = dmCalloc(ncolors, sizeof(DMColor))) == NULL)
        return FALSE;

    // Set alpha values to max, except for transparent color
    for (i = 0; i < ncolors; i++)
    {
        (*ppal)[i].a = (i == ctransp) ? 0 : 255;
    }

    return TRUE;
}


BOOL dmImageAllocPalette(DMImage *img, int ncolors, int ctransp)
{
    if (img == NULL)
        return FALSE;
    
    img->ncolors = ncolors;
    img->ctransp = ctransp;
    return dmPaletteAlloc(&(img->pal), ncolors, ctransp);
}


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


static BOOL dmReadPaletteData(FILE *fp, DMColor *pal, int ncolors)
{
    int i;
    
    for (i = 0; i < ncolors; i++)
    {
        Uint8 colR, colG, colB;
        if (!dm_fread_byte(fp, &colR) ||
            !dm_fread_byte(fp, &colG) ||
            !dm_fread_byte(fp, &colB))
            return FALSE;

        pal[i].r = colR;
        pal[i].g = colG;
        pal[i].b = colB;
    }

    return TRUE;
}


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   *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++)
                        *ptr1++ = c;
                    break;

                case DM_IFMT_RGBA:
                    qr = img->pal[c].r;
                    qg = img->pal[c].g;
                    qb = img->pal[c].b;
                    qa = img->pal[c].a;
                
                    for (xscale = 0; xscale < spec->scale; xscale++)
                    {
                        *ptr1++ = qr;
                        *ptr1++ = qg;
                        *ptr1++ = qb;
                        *ptr1++ = 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++)
                    {
                        *ptr1++ = qr;
                        *ptr1++ = qg;
                        *ptr1++ = 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(FILE *fp, DMImage *img, int ncolors,
    const char *indent, const char *type)
{
    int i;

    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, "%s%s $%04X\n",
            indent != NULL ? indent : "\t",
            type != NULL ? type : "dc.w",
            color);
    }

    return DMERR_OK;
}


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: Unsupported image format %d.\n", spec->format);
            res = DMERR_NOT_SUPPORTED;
            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);

    dmMsg(3, "PNG: %d x %d, depth=%d, type=%d\n",
        img->width * spec->scale,
        img->height * spec->scale,
        8, fmt);

    // 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.");
            res = DMERR_MALLOC;
            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, img->ncolors);
    }

//    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;
}


int dmReadPNGImageFILE(FILE *fp, DMImage **pimg)
{
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    png_colorp palette = NULL;
    png_bytep *row_pointers = NULL;
    png_bytep trans = NULL;
    png_uint_32 width, height;
    int i, bit_depth, color_type, ncolors, ntrans;
    int res = DMERR_OK;
    DMImage *img;

    // Create PNG structures
    png_ptr = png_create_read_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 reading..\n");
        res = DMERR_INIT_FAIL;
        goto error;
    }

    png_init_io(png_ptr, fp);

    // Read image information
    png_read_info(png_ptr, info_ptr);

    png_get_IHDR(png_ptr, info_ptr, &width, &height,
        &bit_depth, &color_type, NULL, NULL, NULL);

    if (width < 1 || height < 1)
    {
        dmError("PNG: Invalid width or height (%d x %d)\n",
            width, height);
        res = DMERR_INVALID_DATA;
        goto error;
    }

    switch (color_type)
    {
        case PNG_COLOR_TYPE_GRAY:
            if (bit_depth < 8)
                png_set_expand_gray_1_2_4_to_8(png_ptr);

            if (bit_depth > 8)
            {
                dmError("PNG: Unsupported bit depth for grayscale image: %d\n",
                    bit_depth);
                res = DMERR_NOT_SUPPORTED;
                goto error;
            }
            break;
        
        case PNG_COLOR_TYPE_PALETTE:
            png_set_packing(png_ptr);
            break;

        default:
            dmError("PNG: RGB/RGBA images not supported for loading.\n");
            res = DMERR_NOT_SUPPORTED;
            goto error;
    }

    // Allocate image
    dmMsg(3, "PNG: %d x %d, depth=%d, type=%d\n",
        width, height, bit_depth, color_type);

    if ((*pimg = img = dmImageAlloc(width, height)) == NULL)
    {
        dmError("PNG: Could not allocate image data.\n");
        res = DMERR_MALLOC;
        goto error;
    }

    // ...
    row_pointers = png_malloc(png_ptr, height * sizeof(png_bytep));
    for (i = 0; i < img->height; i++)
        row_pointers[i] = img->data + (i * img->pitch);

    png_read_image(png_ptr, row_pointers);

    png_read_end(png_ptr, NULL);

    // Create palette
    switch (color_type)
    {
        case PNG_COLOR_TYPE_GRAY:
            ncolors = 256;
            dmMsg(3, "PNG: Generating %d color grayscale palette.\n", ncolors);

            if (!dmImageAllocPalette(img, ncolors, -1))
            {
                res = DMERR_MALLOC;
                goto error;
            }

            for (i = 0; i < img->ncolors; i++)
            {
                img->pal[i].r = img->pal[i].g = img->pal[i].b = i;
            }
            break;
        
        case PNG_COLOR_TYPE_PALETTE:
            png_get_PLTE(png_ptr, info_ptr, &palette, &ncolors);
            dmMsg(3, "PNG: Palette of %d colors found.\n", ncolors);            
            if (ncolors > 0 && palette != NULL)
            {
                if (!dmImageAllocPalette(img, ncolors, -1))
                {
                    res = DMERR_MALLOC;
                    goto error;
                }

                for (i = 0; i < img->ncolors; i++)
                {
                    img->pal[i].r = palette[i].red;
                    img->pal[i].g = palette[i].green;
                    img->pal[i].b = palette[i].blue;
                }
            }
            break;
    }

    if (color_type == PNG_COLOR_TYPE_PALETTE ||
        color_type == PNG_COLOR_TYPE_GRAY)
    {
        png_get_tRNS(png_ptr, info_ptr, &trans, &ntrans, NULL);
        if (trans != NULL && ntrans > 0)
        {
            for (i = 0; i < img->ncolors && i < ntrans; i++)
            {
                img->pal[i].a = trans[i];
                if (img->ctransp < 0 && trans[i] == 0)
                    img->ctransp = i;
            }
        }
    }    

error:
//    png_free(png_ptr, palette);

    if (png_ptr && info_ptr)
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    return res;
}


int dmReadPNGImage(const char *filename, DMImage **img)
{
    int res;
    FILE *fp;

    if ((fp = fopen(filename, "rb")) == NULL)
    {
        dmError("PNG: Could not open file '%s' for reading.\n", filename);
        return DMERR_FOPEN;
    }

    res = dmReadPNGImageFILE(fp, img);

    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(3, "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(3, "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, ncolors;
        Uint8 tmpb;
        BOOL read;

        if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C)
        {
            read = FALSE;
            ncolors = 16;
        }
        else
        {
            read = TRUE;
            ncolors = 256;
        }

        if (!dmImageAllocPalette(img, ncolors, -1))
        {
            dmError("PCX: Could not allocate palette data!\n");
            res = DMERR_MALLOC;
            goto error;
        }
        
        if (read)
        {
            if (!dmReadPaletteData(fp, img->pal, ncolors))
            {
                dmError("PCX: Error reading palette.\n");
                return DMERR_FREAD;
            }
        }
        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;
}


#define IFF_ID_FORM        0x464F524D // "FORM"
#define IFF_ID_ILBM        0x494C424D // "ILBM"
#define IFF_ID_PBM         0x50424D20 // "PBM "
#define IFF_ID_BMHD        0x424D4844 // "BMHD"
#define IFF_ID_CMAP        0x434D4150 // "CMAP"
#define IFF_ID_BODY        0x424F4459 // "BODY"
#define IFF_ID_CAMG        0x43414D47 // "CAMG"

#define IFF_MASK_NONE      0
#define IFF_MASK_HAS_MASK  1
#define IFF_MASK_TRANSP    2
#define IFF_MASK_LASSO     3

#define IFF_COMP_NONE      0
#define IFF_COMP_BYTERUN1  1

#define IFF_CAMG_LACE      0x00000004
#define IFF_CAMG_HALFBRITE 0x00000080
#define IFF_CAMG_HAM       0x00000800

typedef struct
{
    Uint32 id;
    Uint32 size;
    int count;
    char str[6];
} DMIFFChunk;


typedef struct
{
    Uint16 w, h;
    Sint16 x, y;
    Uint8  nplanes;
    Uint8  masking;
    Uint8  compression;
    Uint8  pad1;
    Uint16 transp;
    Uint8  xasp, yasp;
    Sint16 pagew, pageh;
} DMIFFBMHD;


typedef struct
{
    DMIFFChunk chBMHD, chCMAP, chBODY;
    DMIFFBMHD bmhd;
    Uint32 camg;
    int ncolors;
    DMColor *pal;
    BOOL paletted, planar;
} DMIFF;


static BOOL dmReadIFFChunk(FILE *fp, DMIFFChunk *chunk)
{
    if (!dm_fread_be32(fp, &chunk->id) ||
        !dm_fread_be32(fp, &chunk->size))
    {
        dmError("ILBM: Could not read IFF chunk header.\n");
        return FALSE;
    }
    else
        return TRUE;
}

static char * dmGetIFFChunkID(DMIFFChunk *chunk)
{
    chunk->str[0] = (chunk->id >> 24) & 0xff;
    chunk->str[1] = (chunk->id >> 16) & 0xff;
    chunk->str[2] = (chunk->id >> 8) & 0xff;
    chunk->str[3] = (chunk->id) & 0xff;
    chunk->str[4] = 0;
    return chunk->str;
}

static BOOL dmSkipIFFChunkRest(FILE *fp, const DMIFFChunk *chunk, const Uint32 used)
{
    if (chunk->size > used)
    {
        dmMsg(4, "ILBM: Skipping %d bytes (%d of %d consumed)\n",
            chunk->size - used, used, chunk->size);
        return fseeko(fp, chunk->size - used, SEEK_CUR) == 0;
    }
    else
        return TRUE;
}

static BOOL dmCheckIFFChunk(DMIFFChunk *dest, DMIFFChunk *chunk,
    const BOOL multi, const Uint32 minSize)
{
    if (dest->count > 0 && !multi)
    {
        dmError("ILBM: Multiple instances of chunk %s found.\n",
            dmGetIFFChunkID(chunk));
        return FALSE;
    }
    
    dest->count++;

    if (chunk->size < minSize)
        return FALSE;
    
    return TRUE;
}


static BOOL dmIFFDecodeByteRun1Row(FILE *fp, Uint8 *buf, const size_t bufLen)
{
    size_t offs = 0;
    do
    {
        Sint8 dcount;
        Uint8 data;

        if (!dm_fread_byte(fp, (Uint8 *) &dcount))
            return FALSE;
        
        if (dcount == -128)
        {
            if (!dm_fread_byte(fp, &data))
                return FALSE;
        }
        else
        if (dcount < 0)
        {
            int count = (-dcount) + 1;
            if (!dm_fread_byte(fp, &data))
                return FALSE;

            while (count-- && offs < bufLen)
                buf[offs++] = data;
        }
        else
        {
            int count = dcount + 1;
            while (count-- && offs < bufLen)
            {
                if (!dm_fread_byte(fp, &data))
                    return FALSE;

                buf[offs++] = data;
            }
        }
    } while (offs < bufLen);

    return TRUE;
}


static BOOL dmIFFReadOneRow(FILE *fp, DMIFF *iff, Uint8 *buf, const size_t bufLen)
{
    if (iff->bmhd.compression == IFF_COMP_BYTERUN1)
        return dmIFFDecodeByteRun1Row(fp, buf, bufLen);
    else
        return dm_fread_str(fp, buf, bufLen);
}


void dmDecodeBitPlane(Uint8 *dp, Uint8 *src, const int width, const int nplane)
{
    int xc;
    for (xc = 0; xc < width; xc++)
    {
        const Uint8 data = (src[xc / 8] >> (7 - (xc & 7))) & 1;
        dp[xc] |= (data << nplane);
    }
}


int dmDecodeILBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read)
{
    DMImage *img;
    Uint8 *buf;
    size_t bufLen;
    int yc, res = DMERR_OK;
    
    *read = 0;

    // Allocate image
    if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h)) == NULL)
        return DMERR_MALLOC;

    // Allocate planar decoding buffer
    bufLen = ((img->width + 15) / 16) * 2;
    if ((buf = dmMalloc(bufLen)) == NULL)
        return DMERR_MALLOC;

    dmMsg(3, "ILBM: plane row size %d bytes.\n", bufLen);
    
    // Decode the chunk
    for (yc = 0; yc < img->height; yc++)
    {
        int plane;
        const int nplanes = iff->bmhd.nplanes;
        Uint8 *dp = img->data + (yc * img->pitch);

        memset(dp, 0, img->pitch);

        for (plane = 0; plane < nplanes; plane++)
        {
            // Decompress or read data
            if (!dmIFFReadOneRow(fp, iff, buf, bufLen))
            {
                dmError("ILBM: Error in reading image plane #%d @ %d.\n", plane, yc);
                res = DMERR_FREAD;
                goto error;
            }

            // Decode bitplane
            dmDecodeBitPlane(dp, buf, img->width, plane);

            *read += bufLen;
        }

        // Read mask data
        if (iff->bmhd.masking == IFF_MASK_HAS_MASK)
        {
            int xc;

            // Decompress or read data
            if (!dmIFFReadOneRow(fp, iff, buf, bufLen))
            {
                dmError("ILBM: Error in reading mask plane.\n");
                res = DMERR_FREAD;
                goto error;
            }

            // Decode mask
            for (xc = 0; xc < img->width; xc++)
            {
                const Uint8 data = (buf[xc / 8] >> (7 - (xc & 7))) & 1;
                
                // Black out any pixels with mask bit 0
                if (!data)
                    dp[xc] = 0;
            }

            *read += bufLen;
        }
    }

error:
    dmFree(buf);
    return res;
}


int dmDecodePBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read)
{
    DMImage *img;
    int yc, res = DMERR_OK;

    *read = 0;

    // Allocate image
    if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h)) == NULL)
        return DMERR_MALLOC;

    // Decode the chunk
    for (yc = 0; yc < img->height; yc++)
    {
        Uint8 *dp = img->data + (yc * img->pitch);

        if (!dmIFFReadOneRow(fp, iff, dp, img->width))
        {
            dmError("ILBM: Error in reading image row #%d.\n", yc);
            res = DMERR_FREAD;
            goto error;
        }

        *read += img->width;
    }

error:
    return res;
}


int dmReadILBMImageFILE(FILE *fp, DMImage **pimg)
{
    Uint32 idILBM;
    DMIFFChunk chunk;
    DMIFF iff;
    Uint32 read;
    BOOL parsed = FALSE;
    int i, res = DMERR_OK;

    memset(&iff, 0, sizeof(iff));

    // Read IFF FORM header
    if (!dmReadIFFChunk(fp, &chunk) ||
        chunk.id != IFF_ID_FORM ||
        chunk.size < 32)
    {
        dmError("ILBM: Not a IFF file.\n");
        return DMERR_FREAD;
    }
    
    // Check IFF ILBM signature
    if (!dm_fread_be32(fp, &idILBM) ||
        (idILBM != IFF_ID_ILBM && idILBM != IFF_ID_PBM))
    {
        dmError("ILBM: Not a ILBM file.\n");
        return DMERR_INVALID_DATA;
    }

    iff.planar = (idILBM == IFF_ID_ILBM);

    while (!parsed && !feof(fp))
    {
        if (!dmReadIFFChunk(fp, &chunk))
        {
            dmError("ILBM: Error reading IFF ILBM data.\n");
            return DMERR_FREAD;
        }
        
        switch (chunk.id)
        {
            case IFF_ID_BMHD:
                // Check for multiple occurences of BMHD
                if (!dmCheckIFFChunk(&iff.chBMHD, &chunk, FALSE, sizeof(iff.bmhd)))
                    return DMERR_FREAD;

                // Read BMHD data
                if (!dm_fread_be16(fp, &iff.bmhd.w) ||
                    !dm_fread_be16(fp, &iff.bmhd.h) ||
                    !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.x) ||
                    !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.y) ||
                    !dm_fread_byte(fp, &iff.bmhd.nplanes) ||
                    !dm_fread_byte(fp, &iff.bmhd.masking) ||
                    !dm_fread_byte(fp, &iff.bmhd.compression) ||
                    !dm_fread_byte(fp, &iff.bmhd.pad1) ||
                    !dm_fread_be16(fp, &iff.bmhd.transp) ||
                    !dm_fread_byte(fp, &iff.bmhd.xasp) ||
                    !dm_fread_byte(fp, &iff.bmhd.yasp) ||
                    !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pagew) ||
                    !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pageh))
                {
                    dmError("ILBM: Error reading BMHD chunk.\n");
                    return DMERR_FREAD;
                }
                dmMsg(3, "ILBM: BMHD %d x %d @ %d, %d : nplanes=%d, comp=%d, mask=%d\n",
                    iff.bmhd.w, iff.bmhd.h, iff.bmhd.x, iff.bmhd.y,
                    iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking);
                
                // Sanity check
                if (iff.bmhd.nplanes < 1 || iff.bmhd.nplanes > 8 ||
                    (iff.bmhd.compression != IFF_COMP_NONE &&
                    iff.bmhd.compression != IFF_COMP_BYTERUN1) ||
                    (iff.bmhd.masking != IFF_MASK_NONE &&
                    iff.bmhd.masking != IFF_MASK_HAS_MASK &&
                    iff.bmhd.masking != IFF_MASK_TRANSP))
                {
                    dmError("ILBM: Unsupported features, refusing to load.\n");
                    return DMERR_NOT_SUPPORTED;
                }

                if (!dmSkipIFFChunkRest(fp, &chunk, sizeof(iff.bmhd)))
                    return DMERR_FREAD;
                break;


            case IFF_ID_CMAP:
                // Check for multiple occurences of CMAP
                if (!dmCheckIFFChunk(&iff.chCMAP, &chunk, FALSE, 3))
                    return DMERR_FREAD;

                // Check for sanity
                if (chunk.size % 3 != 0)
                    dmError("ILBM: CMAP chunk size not divisible by 3, possibly broken file.\n");

                iff.ncolors = chunk.size / 3;
                dmMsg(3, "ILBM: CMAP %d entries (%d bytes)\n",
                    iff.ncolors, chunk.size, 1 << iff.bmhd.nplanes);

                if (iff.bmhd.nplanes > 0 && iff.ncolors != 1 << iff.bmhd.nplanes)
                    dmMsg(3, "ILBM: Expected %d entries in CMAP.\n", 1 << iff.bmhd.nplanes);
                
                // Read palette
                if (iff.ncolors > 0)
                {
                    if (!dmPaletteAlloc(&iff.pal, iff.ncolors,
                        (iff.bmhd.masking == IFF_MASK_TRANSP) ? iff.bmhd.transp : -1))
                    {
                        dmError("ILBM: Could not allocate palette data.\n");
                        return DMERR_MALLOC;
                    }
                    if (!dmReadPaletteData(fp, iff.pal, iff.ncolors))
                    {
                        dmError("ILBM: Error reading CMAP.\n");
                        return DMERR_FREAD;
                    }
                }

                if (iff.chBMHD.count && iff.chBODY.count)
                    parsed = TRUE;
                break;
            
            case IFF_ID_BODY:
                // Check for multiple occurences of CMAP
                if (!dmCheckIFFChunk(&iff.chBODY, &chunk, FALSE, 1))
                    return DMERR_FREAD;

                // Check for sanity
                if (!iff.chBMHD.count)
                {
                    dmError("ILBM: BODY chunk before BMHD?\n");
                    return DMERR_INVALID_DATA;
                }

                dmMsg(3, "ILBM: BODY chunk size %d bytes\n", chunk.size);
                
                // Decode the body
                if (iff.planar)
                {
                    if ((res = dmDecodeILBMBody(fp, &iff, pimg, &read)) != DMERR_OK)
                        return res;
                }
                else
                {
                    if ((res = dmDecodePBMBody(fp, &iff, pimg, &read)) != DMERR_OK)
                        return res;
                }

                if (!dmSkipIFFChunkRest(fp, &chunk, read))
                    return DMERR_FREAD;

                if (iff.chCMAP.count)
                    parsed = TRUE;
                break;


            case IFF_ID_CAMG:
                if (!dm_fread_be32(fp, &iff.camg))
                {
                    dmError("ILBM: Error reading CAMG chunk.\n");
                    return DMERR_FREAD;
                }
                
                dmMsg(3, "ILBM: CAMG value 0x%08x\n", iff.camg);

                if ((iff.camg & IFF_CAMG_HAM))
                {
                    dmError("ILBM: HAM files are not supported.\n");
                    return DMERR_NOT_SUPPORTED;
                }

                if (!dmSkipIFFChunkRest(fp, &chunk, 4))
                    return DMERR_FREAD;
                break;

            
            default:
                {
                    dmMsg(4, "Unknown chunk ID '%s', size %d\n",
                        dmGetIFFChunkID(&chunk), chunk.size);
                    
                    if (fseeko(fp, chunk.size, SEEK_CUR) != 0)
                    {
                        dmError("ILBM: Error skipping in file.");
                        return DMERR_FREAD;
                    }
                }
                break;
        }
        
        if (chunk.size & 1)
            fgetc(fp);
    }

    // Set colormap after finishing
    if (iff.pal != NULL && iff.ncolors > 0 && *pimg != NULL)
    {
        // If halfbrite is used, duplicate the palette
        if (iff.camg & IFF_CAMG_HALFBRITE)
        {
            if (iff.ncolors > 128)
            {
                dmError("ILBM: Halfbrite enabled, but ncolors > 128.\n");
                return DMERR_NOT_SUPPORTED;
            }

            if ((iff.pal = dmRealloc(iff.pal, sizeof(DMColor) * iff.ncolors * 2)) == NULL)
                return DMERR_MALLOC;
            
            for (i = 0; i < iff.ncolors; i++)
            {
                int i2 = iff.ncolors + i;
                iff.pal[i2].r = iff.pal[i].r / 2;
                iff.pal[i2].g = iff.pal[i].g / 2;
                iff.pal[i2].b = iff.pal[i].b / 2;
            }
        }
    
        (*pimg)->ncolors = iff.ncolors;
        (*pimg)->pal = iff.pal;
    }

    return res;
}


int dmReadILBMImage(const char *filename, DMImage **pimg)
{
    FILE *fp;
    int res;

    if ((fp = fopen(filename, "rb")) == NULL)
    {
        dmError("ILBM: Could not open file '%s' for reading.\n", filename);
        return DMERR_FOPEN;
    }
    
    res = dmReadILBMImageFILE(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)
    {
        if (buf[12] == 'I' && buf[13] == 'H' &&
            buf[14] == 'D' && buf[15] == 'R')
            return DM_PROBE_SCORE_MAX;
        else
            return DM_PROBE_SCORE_GOOD;
    }

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbePCX(const Uint8 *buf, const size_t len)
{
    if (len > 128 + 32 &&
        buf[0] == 10 &&
        (buf[1] == 5 || buf[1] == 2 || buf[1] == 3) &&
        buf[2] == 1 &&
        (buf[3] == 8 || buf[3] == 4 || buf[3] == 3 || buf[3] == 1) &&
        buf[65] >= 1 && buf[65] <= 4)
        return DM_PROBE_SCORE_GOOD;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeILBM(const Uint8 *buf, const size_t len)
{
    if (len > 32 &&
        buf[ 0] == 'F' && buf[ 1] == 'O' &&
        buf[ 2] == 'R' && buf[ 3] == 'M' && (
        (buf[ 8] == 'I' && buf[ 9] == 'L' && buf[10] == 'B' && buf[11] == 'M') ||
        (buf[ 8] == 'P' && buf[ 9] == 'B' && buf[10] == 'M' && buf[11] == 0x20)
        ))
    {
        if (buf[12] == 'B' && buf[13] == 'M' &&
            buf[14] == 'H' && buf[15] == 'D')
            return DM_PROBE_SCORE_MAX;
        else
            return DM_PROBE_SCORE_GOOD;
    }

    return DM_PROBE_SCORE_FALSE;
}


DMImageFormat dmImageFormatList[IMGFMT_LAST] =
{
    {
        "PNG", "Portable Network Graphics",
        fmtProbePNG,
#ifdef DM_USE_LIBPNG
        dmReadPNGImage, dmReadPNGImageFILE,
        dmWritePNGImage, dmWritePNGImageFILE,
#else
        NULL, NULL,
        NULL, NULL,
#endif
    },
    {
        "PPM", "Portable PixMap",
        NULL,
        NULL, NULL,
        dmWritePPMImage, dmWritePPMImageFILE,
    },
    {
        "PCX", "Z-Soft Paintbrush",
        fmtProbePCX,
        dmReadPCXImage, dmReadPCXImageFILE,
        dmWritePCXImage, dmWritePCXImageFILE,
    },
    {
        "ILBM", "IFF ILBM",
        fmtProbeILBM,
        dmReadILBMImage, dmReadILBMImageFILE,
        NULL, NULL,
    },
    {
        "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];
        if (fmt->probe != NULL)
        {
            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;
}