view src/libgfx.c @ 1286:b812fad6f33e

Work on libgfx.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 18 Aug 2017 15:20:44 +0300
parents e4bda4909d72
children 32051ad352c8
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


int dmGFXErrorMode = DM_ERRMODE_FAIL;



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


int dmImageGetBytesPerPixel(const 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 -1;
    }
}


DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp)
{
    DMImage *img = dmMalloc0(sizeof(DMImage));
    if (img == NULL)
        return NULL;

    img->width   = width;
    img->height  = height;
    img->format  = format;
    img->bpp     = (bpp <= 0) ? dmImageGetBytesPerPixel(format) * 8 : bpp;
    img->pitch   = width * img->bpp;
    img->size = img->pitch * img->height;
    img->ctransp = -1;

    if ((img->data = dmMalloc(img->size)) == 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);
}


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, int (*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->scaleX;
    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 * img->bpp)], qr, qg, qb, qa;
            switch (spec->format)
            {
                case DM_IFMT_PALETTE:
                    for (xscale = 0; xscale < spec->scaleX; 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->scaleX; 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->scaleX; 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->scaleX; xscale++)
                    {
                        *ptr1++ = qr;
                        *ptr2++ = qg;
                        *ptr3++ = qb;
                    }
                    break;
            }
        }

        for (yscale = 0; yscale < spec->scaleY; yscale++)
        {
            if ((res = writeRowCB(cbdata, row, rowSize)) != DMERR_OK)
                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 dmWriteRAWImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec)
{
    int xc, yc, plane, res;
    DMBitStreamContext bs;

    if ((res = dmInitBitStreamFILE(&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 dmWriteRAWImage(const char *filename, DMImage *img, DMImageSpec *spec)
{
    FILE *fp;
    int res;

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

    res = dmWriteRAWImageFILE(fp, img, spec);

    fclose(fp);
    return res;
}


static int dmWritePPMRow(void *cbdata, Uint8 *row, size_t len)
{
    if (fwrite(row, sizeof(Uint8), len, (FILE *) cbdata) == len)
        return DMERR_OK;
    else
        return DMERR_FWRITE;
}


int dmWritePPMImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec)
{
    // Write PPM header
    fprintf(fp,
        "P6\n%d %d\n255\n",
        img->width * spec->scaleX,
        img->height * spec->scaleY);

    // 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)
    {
        return dmError(DMERR_FOPEN,
            "PPM: could not open file '%s' for writing.\n",
            filename);
    }

    res = dmWritePPMImageFILE(fp, img, spec);

    fclose(fp);
    return res;
}


#ifdef DM_USE_LIBPNG
static int dmWritePNGRow(void *cbdata, Uint8 *row, size_t len)
{
    png_structp png_ptr = cbdata;
    (void) len;

    if (setjmp(png_jmpbuf(png_ptr)))
        return DMERR_INTERNAL;

    png_write_row(png_ptr, row);

    return DMERR_OK;
}


int dmWritePNGImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec)
{
    png_structp png_ptr = NULL;
    png_infop info_ptr = 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)
    {
        res = dmError(DMERR_MALLOC,
            "PNG: png_create_write_struct() failed.\n");
        goto error;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        res = dmError(DMERR_INIT_FAIL,
            "PNG: png_create_info_struct(%p) failed.\n",
            png_ptr);
        goto error;
    }

    if (setjmp(png_jmpbuf(png_ptr)))
    {
        res = dmError(DMERR_INIT_FAIL,
            "PNG: Error during image writing..\n");
        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:
            res = dmError(DMERR_NOT_SUPPORTED,
                "PNG: Unsupported image format %d.\n",
                spec->format);
            goto error;
    }

    png_set_IHDR(png_ptr, info_ptr,
        img->width * spec->scaleX,
        img->height * spec->scaleY,
        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->scaleX,
        img->height * spec->scaleY,
        8, fmt);

    // Palette
    if (spec->format == DM_IFMT_PALETTE)
    {
        int i;
        png_colorp palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));

        if (palette == NULL)
        {
            res = dmError(DMERR_MALLOC,
                "PNG: Could not allocate palette structure.");
            goto error;
        }

        dmMemset(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:
    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)
    {
        return dmError(DMERR_FOPEN,
            "PNG: could not open file '%s' for writing.\n",
            filename);
    }

    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)
    {
        res = dmError(DMERR_MALLOC,
            "PNG: png_create_write_struct() failed.\n");
        goto error;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        res = dmError(DMERR_INIT_FAIL,
            "PNG: png_create_info_struct(%p) failed.\n",
            png_ptr);
        goto error;
    }

    if (setjmp(png_jmpbuf(png_ptr)))
    {
        res = dmError(DMERR_INIT_FAIL,
            "PNG: Error during image reading..\n");
        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)
    {
        res = dmError(DMERR_INVALID_DATA,
            "PNG: Invalid width or height (%d x %d)\n",
            width, height);
        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)
            {
                res = dmError(DMERR_NOT_SUPPORTED,
                    "PNG: Unsupported bit depth for grayscale image: %d\n",
                    bit_depth);
                goto error;
            }
            break;

        case PNG_COLOR_TYPE_PALETTE:
            png_set_packing(png_ptr);
            break;

        default:
            res = dmError(DMERR_NOT_SUPPORTED,
                "PNG: RGB/RGBA images not supported for loading.\n");
            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, DM_IFMT_PALETTE, bit_depth)) == NULL)
    {
        res = dmError(DMERR_MALLOC,
            "PNG: Could not allocate image data.\n");
        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)
    {
        return dmError(DMERR_FOPEN,
            "PNG: Could not open file '%s' for reading.\n",
            filename);
    }

    res = dmReadPNGImageFILE(fp, img);

    fclose(fp);
    return res;
}
#endif


#define DMPCX_PAL_COLORS  16 // Number of internal palette colors

typedef struct
{
    Uint8 r,g,b;
} DMPCXColor;


typedef struct
{
    Uint8 manufacturer,     // always 0x0a
            version,        // Z-Soft PCX Paintbrush version:
                            // 0 = v2.5, 2 = v2.8 with palette, 3 = v2.8 without palette, 5 = v3.0 or better
            encoding,       // usually 0x01 = RLE, 0x00 = uncompressed
            bitsPerPlane;   // bits per pixel per plane

    Uint16 xmin, ymin, xmax, ymax;
    Uint16 hres, vres;      // resolution in DPI, can be image dimensions as well.
    DMPCXColor colorMap[DMPCX_PAL_COLORS];
    Uint8 reserved;         // should be set to 0
    Uint8 nplanes;          // number of planes
    Uint16 bpl;             // bytes per plane LINE
    Uint16 palInfo;         // 1 = color/BW, 2 = grayscale
    Uint16 hScreenSize, vScreenSize;
    Uint8 filler[54];
} 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 int dmWritePCXRow(void *cbdata, Uint8 *row, size_t len)
{
    DMPCXData *pcx = (DMPCXData *) cbdata;
    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 (int 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 DMERR_FWRITE;
                }
                else
                {
                    if (!dmPCXPutByte(pcx, 0xC0 | count) ||
                        !dmPCXPutByte(pcx, data))
                        return DMERR_FWRITE;
                }

                data = dmPCXGetByte(row, len, soffs++);
                count = 1;
            }
        }

        if (count > 1)
        {
            if (!dmPCXPutByte(pcx, 0xC0 | count) ||
                !dmPCXPutByte(pcx, data))
                return DMERR_FWRITE;
        }

        if (!dmPCXFlush(pcx))
            return DMERR_FWRITE;
    }


    return DMERR_OK;
}


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
    dmMemset(&hdr, 0, sizeof(hdr));
    if (spec->paletted)
    {
        for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : 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.bitsPerPlane = 8;
    hdr.hres         = img->width * spec->scaleX;
    hdr.vres         = img->height * spec->scaleY;
    hdr.xmin         = hdr.ymin = 0;
    hdr.xmax         = hdr.hres - 1;
    hdr.ymax         = hdr.vres - 1;
    hdr.nplanes      = dmImageGetBytesPerPixel(pcx.format);
    hdr.palInfo      = 1;
    hdr.hScreenSize  = hdr.hres;
    hdr.vScreenSize  = hdr.vres;

    res = img->width * spec->scaleX;
    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.bitsPerPlane, hdr.bpl);

    pcx.bufLen       = hdr.bpl * 4;
    if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
    {
        res = dmError(DMERR_MALLOC,
            "PCX: Could not allocate %d bytes for RLE compression buffer.\n",
            pcx.bufLen);
        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.bitsPerPlane))
    {
        res = dmError(DMERR_FWRITE,
            "PCX: Could not write basic header data.\n");
        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))
    {
        res = dmError(DMERR_FWRITE,
            "PCX: Could not write image dimensions.\n");
        goto error;
    }

    if (!dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)))
    {
        res = dmError(DMERR_FWRITE,
            "PCX: Could not write colormap.\n");
        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_le16(pcx.fp, hdr.hScreenSize) ||
        !dm_fwrite_le16(pcx.fp, hdr.vScreenSize) ||
        !dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler)))
    {
        res = dmError(DMERR_FWRITE,
            "PCX: Could not write header remainder.\n");
        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)
    {
        return dmError(DMERR_FOPEN,
            "PCX: Could not open file '%s' for writing.\n",
            filename);
    }

    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)
        {
            BOOL skip = FALSE;
            count = data & 0x3F;
            if (count == 0)
            {
                switch (dmGFXErrorMode)
                {
                    case DM_ERRMODE_RECOV_1:
                        // Use as literal
                        skip = TRUE;
                        count = 1;
                        break;

                    case DM_ERRMODE_RECOV_2:
                        // Ignore completely
                        skip = TRUE;
                        break;

                    case DM_ERRMODE_FAIL:
                    default:
                        // Error out on "invalid" data
                        return FALSE;
                }
            }

            if (!skip && !dm_fread_byte(fp, &data))
                return FALSE;
        }
        else
            count = 1;

        while (count-- && offs < bufLen)
            buf[offs++] = data;

        // Check for remaining output count, error out if we wish to
        if (count > 0 && dmGFXErrorMode == DM_ERRMODE_FAIL)
            return FALSE;

    } while (offs < bufLen);

    return TRUE;
}


int dmReadPCXImageFILE(FILE *fp, DMImage **pimg)
{
    DMImage *img;
    DMPCXData pcx;
    DMPCXHeader hdr;
    int res = 0;
    BOOL isPaletted;

    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.bitsPerPlane))
    {
        res = dmError(DMERR_FREAD,
            "PCX: Could not read basic header data.\n");
        goto error;
    }

    if (hdr.manufacturer != 10 ||
        hdr.version != 5 ||
        hdr.encoding != 1)
    {
        res = dmError(DMERR_NOT_SUPPORTED,
            "PCX: Not a PCX file, or unsupported variant.\n");
        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))
    {
        res = dmError(DMERR_FREAD,
            "PCX: Could not read image dimensions.\n");
        goto error;
    }

    if (!dm_fread_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)))
    {
        res = dmError(DMERR_FREAD,
            "PCX: Could not read colormap.\n");
        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_le16(fp, &hdr.hScreenSize) ||
        !dm_fread_le16(fp, &hdr.vScreenSize) ||
        !dm_fread_str(fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler)))
    {
        res = dmError(DMERR_FREAD,
            "PCX: Could not read header remainder.\n");
        goto error;
    }

    if (hdr.nplanes < 1 || hdr.nplanes > 8)
    {
        res = dmError(DMERR_NOT_SUPPORTED,
            "PCX: Unsupported number of bitplanes %d.\n",
            hdr.nplanes);
        goto error;
    }

    dmMsg(3, "PCX: nplanes=%d, bpp=%d, bpl=%d\n",
        hdr.nplanes, hdr.bitsPerPlane, hdr.bpl);

    isPaletted = (hdr.bitsPerPlane * hdr.nplanes) < 8;
    if (!isPaletted)
    {
        res = dmError(DMERR_NOT_SUPPORTED,
            "PCX: Non-indexed (truecolour) PCX images not supported for loading.\n");
        goto error;
    }

    // Allocate image
    if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1,
        isPaletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
        isPaletted ? (hdr.bitsPerPlane * hdr.nplanes) : -1)) == NULL)
    {
        res = dmError(DMERR_MALLOC,
            "PCX: Could not allocate image structure.\n");
        goto error;
    }

    if (hdr.bpl < (img->width * hdr.bitsPerPlane) / 8)
    {
        res = dmError(DMERR_MALLOC,
            "PCX: The bytes per plane line value %d is smaller than width*bpp/8 = %d!\n",
            hdr.bpl, (img->width * hdr.bitsPerPlane) / 8);
        goto error;
    }

    pcx.bufLen = hdr.nplanes * hdr.bpl;
    if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
    {
        res = dmError(DMERR_MALLOC,
            "PCX: Could not allocate RLE buffer.\n");
        goto error;
    }

    // Read image data
    Uint8 *dp = img->data;
    for (int yc = 0; yc < img->height; yc++)
    {
        // Decode row of RLE'd data
        if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen))
        {
            res = dmError(DMERR_INVALID_DATA,
                "PCX: Error decoding RLE compressed data.\n");
            goto error;
        }

        // Decode bitplanes
        switch (hdr.bitsPerPlane)
        {
            case 8:
                {
                    // Actually bytes and bits per plane per pixel ..
                    const int bytesPerPlane = hdr.bitsPerPlane / 8;

                    for (int nplane = 0; nplane < hdr.nplanes; nplane++)
                    {
                        Uint8 *dptr = dp + (nplane * bytesPerPlane),
                              *sptr = pcx.buf + (hdr.bpl * nplane);

                        memcpy(dptr, sptr, img->width * bytesPerPlane);
                    }
                }
                break;

/*
            case 1:
                for (int nplane = 0; nplane < hdr.nplanes; nplane++)
                {
                    Uint8 *dptr = dp,
                          *sptr = pcx.buf + (hdr.bpl * nplane);

                    for (int xc = 0; xc < img->width; xc++)
                }
                break;
*/

            default:
                res = dmError(DMERR_NOT_SUPPORTED,
                    "PCX: Unsupported number of bits per plane %d.\n",
                    hdr.bitsPerPlane);
                goto error;
        }

        dp += img->pitch;
    }

    // Read additional VGA palette, if available
    if (isPaletted)
    {
        int ncolors;
        Uint8 tmpb;
        BOOL read;

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

        if (!dmImageAllocPalette(img, ncolors, -1))
        {
            res = dmError(DMERR_MALLOC,
                "PCX: Could not allocate palette data!\n");
            goto error;
        }

        if (read)
        {
            // Okay, attempt to read the palette data
            if (!dmReadPaletteData(fp, img->pal, ncolors))
            {
                res = dmError(DMERR_FREAD,
                    "PCX: Error reading palette.\n");
                goto error;
            }
        }
        else
        {
            // If the extra palette is not available, copy the colors from
            // the header palette to our internal palette structure.
            for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : img->ncolors); i++)
            {
                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)
    {
        return dmError(DMERR_FOPEN,
            "PCX: Could not open file '%s' for reading.\n",
            filename);
    }

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


static BOOL dmReadIFFChunk(FILE *fp, DMIFFChunk *chunk)
{
    if (!dm_fread_be32(fp, &chunk->id) ||
        !dm_fread_be32(fp, &chunk->size))
    {
        dmError(DMERR_FREAD,
            "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 int 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);

        if (fseeko(fp, chunk->size - used, SEEK_CUR) != 0)
        {
            return dmError(DMERR_FSEEK,
                "ILBM: Failed to skip chunk end.\n");
        }
        else
            return DMERR_OK;
    }
    else
        return DMERR_OK;
}

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

    dest->count++;

    if (chunk->size < minSize)
    {
        return dmError(DMERR_OUT_OF_DATA,
            "ILBM: Chunk is too small.\n");
    }

    return DMERR_OK;
}


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,
        iff->bmhd.nplanes < 8 ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
        iff->bmhd.nplanes < 8 ? iff->bmhd.nplanes : -1)) == 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);

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

        for (plane = 0; plane < nplanes; plane++)
        {
            // Decompress or read data
            if (!dmIFFReadOneRow(fp, iff, buf, bufLen))
            {
                res = dmError(DMERR_FREAD,
                    "ILBM: Error in reading image plane #%d @ %d.\n",
                    plane, yc);
                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))
            {
                res = dmError(DMERR_FREAD,
                    "ILBM: Error in reading mask plane.\n");
                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,
        iff->bmhd.nplanes < 8 ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
        iff->bmhd.nplanes < 8 ? iff->bmhd.nplanes : -1)) == 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))
        {
            res = dmError(DMERR_FREAD,
                "ILBM: Error in reading image row #%d.\n", yc);
            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;

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

    // Read IFF FORM header
    if (!dmReadIFFChunk(fp, &chunk) ||
        chunk.id != IFF_ID_FORM ||
        chunk.size < 32)
    {
        return dmError(DMERR_NOT_SUPPORTED,
            "ILBM: Not a IFF file (%08X vs %08X / %d).\n",
            chunk.id, IFF_ID_FORM, chunk.size);
    }

    // Check IFF ILBM signature
    if (!dm_fread_be32(fp, &idILBM) ||
        (idILBM != IFF_ID_ILBM && idILBM != IFF_ID_PBM))
    {
        return dmError(DMERR_INVALID_DATA,
            "ILBM: Not a ILBM file.\n");
    }

    iff.planar = (idILBM == IFF_ID_ILBM);

    while (!parsed && !feof(fp))
    {
        if (!dmReadIFFChunk(fp, &chunk))
        {
            return dmError(DMERR_FREAD,
                "ILBM: Error reading IFF ILBM data.\n");
        }

        switch (chunk.id)
        {
            case IFF_ID_BMHD:
                // Check for multiple occurences of BMHD
                if ((res = dmCheckIFFChunk(&iff.chBMHD, &chunk, FALSE, sizeof(iff.bmhd))) != DMERR_OK)
                    return res;

                // 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))
                {
                    return dmError(DMERR_FREAD,
                        "ILBM: Error reading BMHD chunk.\n");
                }

                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))
                {
                    return dmError(DMERR_NOT_SUPPORTED,
                        "ILBM: Unsupported features, refusing to load.\n");
                }

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


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

                // Check for sanity
                if (chunk.size % 3 != 0)
                {
                    // Non-fatal
                    dmError(DMERR_INVALID_DATA,
                    "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))
                    {
                        return dmError(DMERR_MALLOC,
                            "ILBM: Could not allocate palette data.\n");
                    }
                    if (!dmReadPaletteData(fp, iff.pal, iff.ncolors))
                    {
                        return dmError(DMERR_FREAD,
                            "ILBM: Error reading CMAP.\n");
                    }
                }

                if (iff.chBMHD.count && iff.chBODY.count)
                    parsed = TRUE;
                break;

            case IFF_ID_BODY:
                // Check for multiple occurences of CMAP
                if ((res = dmCheckIFFChunk(&iff.chBODY, &chunk, FALSE, 1)) != DMERR_OK)
                    return res;

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

                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 ((res = dmSkipIFFChunkRest(fp, &chunk, read)) != DMERR_OK)
                    return res;

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


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

                dmMsg(3, "ILBM: CAMG value 0x%08x\n", iff.camg);

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

                if ((res = dmSkipIFFChunkRest(fp, &chunk, sizeof(Uint32))) != DMERR_OK)
                    return res;
                break;


            default:
                {
                    dmMsg(4, "Unknown chunk ID '%s', size %d\n",
                        dmGetIFFChunkID(&chunk), chunk.size);

                    if (fseeko(fp, chunk.size, SEEK_CUR) != 0)
                    {
                        return dmError(DMERR_FSEEK,
                            "ILBM: Error skipping in file.");
                    }
                }
                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)
        {
            void *ptmp;

            if (iff.ncolors > 128)
            {
                return dmError(DMERR_NOT_SUPPORTED,
                    "ILBM: Halfbrite enabled, but ncolors > 128.\n");
            }

            if ((ptmp = dmRealloc(iff.pal, sizeof(DMColor) * iff.ncolors * 2)) == NULL)
            {
                dmFree(iff.pal);
                iff.pal = NULL;
                return DMERR_MALLOC;
            }
            else
                iff.pal = ptmp;

            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)
    {
        return dmError(DMERR_FOPEN,
            "ILBM: Could not open file '%s' for reading.\n",
            filename);
    }

    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,
    },
    {
        "RAW", "Plain bitplaned (interleaved or non-interleaved) RAW",
        NULL,
        NULL, NULL,
        dmWriteRAWImage, dmWriteRAWImageFILE,
    },
    {
        "ARAW", "IFFMaster Amiga RAW",
        NULL,
        NULL, NULL,
        dmWriteRAWImage, dmWriteRAWImageFILE,
    }
};


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