changeset 1307:43b13dbbdcd1

Moved libgfx to tools/ as it's not really a very generic piece of code that belongs to the engine ..
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 19 Aug 2017 15:18:29 +0300
parents 696c58784635
children 8f71ca1900ea
files Makefile.gen src/libgfx.c src/libgfx.h tools/libgfx.c tools/libgfx.h
diffstat 5 files changed, 2132 insertions(+), 2133 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Sat Aug 19 13:02:34 2017 +0300
+++ b/Makefile.gen	Sat Aug 19 15:18:29 2017 +0300
@@ -249,7 +249,6 @@
 
 ### Dependancies
 ifeq ($(DM_BUILD_TOOLS),yes)
-DMLIB_OBJS += libgfx.o
 ifeq ($(DM_USE_STDIO),yes)
 TOOL_BINARIES+= objlink data2inc gfxconv gentab
 ifeq ($(SUP_MODLOAD),yes)
@@ -414,7 +413,7 @@
 	@echo " CC $+"
 	@$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS)
 
-$(OBJPATH)libgfx.o: $(DMLIBSRC)libgfx.c $(DMLIBSRC)libgfx.h
+$(OBJPATH)libgfx.o: $(DMLIB)tools/libgfx.c $(DMLIB)tools/libgfx.h
 	@echo " CC $+"
 	@$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS) $(LIBPNG_CFLAGS)
 
@@ -513,11 +512,11 @@
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(TOOL_LDFLAGS)
 
-$(TOOL_BINPATH)gfxconv$(EXEEXT): $(OBJPATH)gfxconv.o $(OBJPATH)lib64gfx.o $(DMLIB_A)
+$(TOOL_BINPATH)gfxconv$(EXEEXT): $(OBJPATH)gfxconv.o $(OBJPATH)lib64gfx.o $(OBJPATH)libgfx.o $(DMLIB_A)
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(TOOL_LDFLAGS) $(LIBPNG_LDFLAGS) $(DM_ZLIB_LDFLAGS)
 
-$(TOOL_BINPATH)view64$(EXEEXT): $(OBJPATH)view64.o $(OBJPATH)lib64gfx.o $(DMLIB_A)
+$(TOOL_BINPATH)view64$(EXEEXT): $(OBJPATH)view64.o $(OBJPATH)lib64gfx.o $(OBJPATH)libgfx.o $(DMLIB_A)
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(LIBPNG_LDFLAGS) $(DM_ZLIB_LDFLAGS) $(SDL_LDFLAGS)
 
--- a/src/libgfx.c	Sat Aug 19 13:02:34 2017 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1998 +0,0 @@
-/*
- * 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       : 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 *, const Uint8 *, const size_t), const DMImageConvSpec *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,
-                *ptr4 = ptr3 + rowWidth;
-
-        for (x = 0; x < img->width; x++)
-        {
-            Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8],
-                  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;
-
-                    if (spec->planar)
-                    {
-                        for (xscale = 0; xscale < spec->scaleX; xscale++)
-                        {
-                            *ptr1++ = qr;
-                            *ptr2++ = qg;
-                            *ptr3++ = qb;
-                            *ptr4++ = qa;
-                        }
-                    }
-                    else
-                    {
-                        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;
-
-                    if (spec->planar)
-                    {
-                        for (xscale = 0; xscale < spec->scaleX; xscale++)
-                        {
-                            *ptr1++ = qr;
-                            *ptr2++ = qg;
-                            *ptr3++ = qb;
-                        }
-                    }
-                    else
-                    {
-                        for (xscale = 0; xscale < spec->scaleX; xscale++)
-                        {
-                            *ptr1++ = qr;
-                            *ptr1++ = qg;
-                            *ptr1++ = 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, const DMImageConvSpec *spec)
-{
-    int xc, yc, plane, res;
-    DMBitStreamContext bs;
-
-    if ((res = dmInitBitStreamFILE(&bs, fp)) != DMERR_OK)
-        return res;
-
-    if (spec->planar)
-    {
-        // Output bitplanes in planerd 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, const DMImageConvSpec *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, const Uint8 *row, const 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, const DMImageConvSpec *spec)
-{
-    DMImageConvSpec tmpSpec;
-
-    // Write PPM header
-    fprintf(fp,
-        "P6\n%d %d\n255\n",
-        img->width * spec->scaleX,
-        img->height * spec->scaleY);
-
-    // Write image data
-    memcpy(&tmpSpec, spec, sizeof(DMImageConvSpec));
-    tmpSpec.format = DM_IFMT_RGB;
-    return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec);
-}
-
-
-int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *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, const Uint8 *row, const 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, const DMImageConvSpec *spec)
-{
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-    int fmt, res;
-
-    // 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;
-    }
-
-    res = DMERR_OK;
-    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(2, "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, const DMImageConvSpec *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(2, "PNG: %d x %d, depth=%d, type=%d\n",
-        width, height, bit_depth, color_type);
-
-    if ((*pimg = img = dmImageAlloc(width, height,
-        DM_IFMT_PALETTE,
-        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
-        -1 /* 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(2, "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(2, "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 PC Paintbrush version:
-                            // 0 = v2.5
-                            // 2 = v2.8 with palette,
-                            // 3 = v2.8 without palette
-                            // 4 = PC Paintbrush for Windows
-                            // 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;
-    FILE *fp;
-} DMPCXData;
-
-
-// Returns one byte from row buffer (of length len) at offset soffs,
-// OR zero if the offset is outside buffer.
-static inline Uint8 dmPCXGetByte(const 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 dmPCXPutData(DMPCXData *pcx, const Uint8 data, const int count)
-{
-    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;
-    }
-    return DMERR_OK;
-}
-
-
-static int dmWritePCXRow(void *cbdata, const Uint8 *row, const size_t len)
-{
-    DMPCXData *pcx = (DMPCXData *) cbdata;
-    int err;
-    size_t soffs = 0;
-
-    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;
-        while (soffs < blen)
-        {
-            if (data == dmPCXGetByte(row, len, soffs) && count < 0x3F)
-            {
-                count++;
-                soffs++;
-            }
-            else
-            {
-                if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
-                    return err;
-
-                data = dmPCXGetByte(row, len, soffs++);
-                count = 1;
-            }
-        }
-
-        if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
-            return err;
-
-        if (!dmPCXFlush(pcx))
-            return DMERR_FWRITE;
-    }
-
-
-    return DMERR_OK;
-}
-
-
-int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *pspec)
-{
-    DMPCXData pcx;
-    DMPCXHeader hdr;
-    DMImageConvSpec spec;
-    int res;
-
-    // Always force planar for PCX
-    memcpy(&spec, pspec, sizeof(DMImageConvSpec));
-    spec.planar = TRUE;
-
-    // Create output file
-    pcx.buf    = NULL;
-    pcx.header = &hdr;
-    pcx.fp     = fp;
-
-    // Create PCX header
-    dmMemset(&hdr, 0, sizeof(hdr));
-    if (spec.paletted && img->pal != NULL)
-    {
-        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.hres         = img->width * spec.scaleX;
-    hdr.vres         = img->height * spec.scaleY;
-    hdr.xmin         = hdr.ymin = 0;
-    hdr.xmax         = (img->width * spec.scaleX) - 1;
-    hdr.ymax         = (img->height * spec.scaleY) - 1;
-    hdr.palInfo      = 1;
-    hdr.hScreenSize  = hdr.hres;
-    hdr.vScreenSize  = hdr.vres;
-
-    // TODO XXX .. maybe actually compute these asdf
-    hdr.bitsPerPlane = 8;
-    hdr.nplanes      = dmImageGetBytesPerPixel(spec.format);
-
-    res = img->width * spec.scaleX;
-    hdr.bpl = res / 2;
-    if (res % 2) hdr.bpl++;
-    hdr.bpl *= 2;
-
-    dmMsg(2,
-        "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
-        hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
-        hdr.hres, hdr.vres,
-        hdr.hScreenSize, hdr.vScreenSize);
-
-    dmMsg(2, "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s, planar=%s\n",
-        hdr.nplanes, hdr.bitsPerPlane, hdr.bpl,
-        spec.paletted ? "yes" : "no",
-        spec.planar ? "yes" : "no"
-        );
-
-    // TODO XXX this is also bogus
-    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;
-        dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors);
-
-        dm_fwrite_byte(pcx.fp, 0x0C);
-
-        for (i = 0; i < img->ncolors; i++)
-        {
-            if (!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))
-            {
-                res = dmError(DMERR_FWRITE,
-                    "PCX: Could not write palette data.\n");
-                goto error;
-            }
-        }
-
-        // Pad the palette, if necessary
-        for (; i < 256; i++)
-        {
-            if (!dm_fwrite_byte(pcx.fp, 0) ||
-                !dm_fwrite_byte(pcx.fp, 0) ||
-                !dm_fwrite_byte(pcx.fp, 0))
-            {
-                res = dmError(DMERR_FWRITE,
-                    "PCX: Could not write palette data.\n");
-                goto error;
-            }
-        }
-    }
-
-error:
-    dmFree(pcx.buf);
-    return res;
-}
-
-
-int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *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) ||
-        !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) ||
-        !dm_fread_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) ||
-        !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 image 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 (hdr.nplanes == 4 && hdr.bitsPerPlane == 4)
-    {
-        dmMsg(2,
-            "PCX: Probably invalid combination of nplanes and bpp, attempting to fix ..\n");
-        
-        hdr.bitsPerPlane = 1;
-    }
-
-    isPaletted = (hdr.bitsPerPlane * hdr.nplanes) <= 8;
-
-    dmMsg(2,
-        "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
-        hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
-        hdr.hres, hdr.vres,
-        hdr.hScreenSize, hdr.vScreenSize);
-
-    dmMsg(2,
-        "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s\n",
-        hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, isPaletted ? "yes" : "no");
-
-    if (hdr.nplanes < 1 || hdr.nplanes > 8)
-    {
-        res = dmError(DMERR_NOT_SUPPORTED,
-            "PCX: Unsupported number of bitplanes %d.\n",
-            hdr.nplanes);
-        goto error;
-    }
-
-    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,
-        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
-        // isPaletted ? (hdr.bitsPerPlane * hdr.nplanes) : -1
-        -1
-        )) == NULL)
-    {
-        res = dmError(DMERR_MALLOC,
-            "PCX: Could not allocate image structure.\n");
-        goto error;
-    }
-
-    // Sanity check bytes per line value
-    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;
-    }
-
-    dmMsg(2,
-        "PCX: bufLen=%d\n",
-        pcx.bufLen);
-
-    // 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 32:
-            case 24:
-            case 16:
-            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:
-                memset(dp, 0, img->width);
-
-                for (int nplane = 0; nplane < hdr.nplanes; nplane++)
-                {
-                    Uint8 *sptr = pcx.buf + (hdr.bpl * nplane);
-
-                    for (int xc = 0; xc < img->width; xc++)
-                    {
-                        const int px = 7 - (xc & 7);
-                        dp[xc] |= ((sptr[xc / 8] & (1 << px)) >> px) << nplane;
-                    }
-                }
-                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
-            dmMsg(2, "PCX: Reading palette of %d colors\n", ncolors);
-            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.
-            dmMsg(2, "PCX: Initializing palette from header of %d colors\n", ncolors);
-            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,
-        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
-        //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
-        -1
-        )) == NULL)
-        return DMERR_MALLOC;
-
-    // Allocate planar decoding buffer
-    bufLen = ((img->width + 15) / 16) * 2;
-    if ((buf = dmMalloc(bufLen)) == NULL)
-        return DMERR_MALLOC;
-
-    dmMsg(2, "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,
-        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
-        //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
-        -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(2, "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(2, "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(2, "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(2, "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(2, "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[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 (planar or non-planar) 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;
-}
--- a/src/libgfx.h	Sat Aug 19 13:02:34 2017 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- * Functions for loading and saving bitmap images
- * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2012 Tecnic Software productions (TNSP)
- *
- * Please read file 'COPYING' for information on license and distribution.
- */
-#ifndef LIBMGFX_H
-#define LIBMGFX_H 1
-
-#include "dmlib.h"
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-enum
-{
-    IMGFMT_PNG,
-    IMGFMT_PPM,
-    IMGFMT_PCX,
-    IMGFMT_ILBM,
-    IMGFMT_RAW,
-    IMGFMT_ARAW,
-
-    IMGFMT_LAST
-};
-
-
-enum
-{
-    DM_IFMT_PALETTE,
-    DM_IFMT_RGB,
-    DM_IFMT_RGBA,
-};
-
-
-enum
-{
-    DM_ERRMODE_FAIL     = 0,
-    DM_ERRMODE_RECOV_1,
-    DM_ERRMODE_RECOV_2,
-};
-
-
-// Bitmapped image struct 
-typedef struct
-{
-    int format;     // one of types specified by DM_IFMT_*
-    int width, height;
-    int pitch;      // bytes per scanline
-    int bpp;        // bits per pixel
-
-    int ncolors;    // number of colors in palette, if any
-    int ctransp;    // transparency color index
-    BOOL constpal;  // is the palette a const?
-    DMColor *pal;   // pointer to palette struct, NULL if no palette
-
-    size_t size;
-    Uint8 *data;
-} DMImage;
-
-
-typedef struct
-{
-    int format;
-    int scaleX, scaleY;
-    int nplanes, bpp;
-    BOOL planar, paletted;
-} DMImageConvSpec;
-
-
-typedef struct
-{
-    char *fext;
-    char *desc;
-    int  (*probe)(const Uint8 *buf, const size_t len);
-    int  (*read)(const char *filename, DMImage **pimg);
-    int  (*readFILE)(FILE *fp, DMImage **pimg);
-    int  (*write)(const char *filename, DMImage *pimg, const DMImageConvSpec *spec);
-    int  (*writeFILE)(FILE *fp, DMImage *pimg, const DMImageConvSpec *spec);
-} DMImageFormat;
-
-
-// Probe scores
-#define DM_PROBE_SCORE_MAX     1000
-#define DM_PROBE_SCORE_GOOD    750
-#define DM_PROBE_SCORE_AVG     500
-#define DM_PROBE_SCORE_MAYBE   250
-#define DM_PROBE_SCORE_FALSE   0
-
-
-extern DMImageFormat dmImageFormatList[IMGFMT_LAST];
-extern int dmGFXErrorMode;
-
-
-DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp);
-void      dmImageFree(DMImage *img);
-int       dmImageGetBytesPerPixel(const int format);
-int       dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **fmt, int *index);
-
-BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha);
-
-
-int dmWriteImageData(DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *spec);
-
-int dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors, const char *indent, const char *type);
-int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
-int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
-
-int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
-int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
-
-#ifdef DM_USE_LIBPNG
-int dmWritePNGImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
-int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
-#endif
-
-int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
-int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
-int dmReadPCXImageFILE(FILE *fp, DMImage **pimg);
-int dmReadPCXImage(const char *filename, DMImage **pimg);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // LIBMGFX_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/libgfx.c	Sat Aug 19 15:18:29 2017 +0300
@@ -0,0 +1,1998 @@
+/*
+ * 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       : 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 *, const Uint8 *, const size_t), const DMImageConvSpec *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,
+                *ptr4 = ptr3 + rowWidth;
+
+        for (x = 0; x < img->width; x++)
+        {
+            Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8],
+                  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;
+
+                    if (spec->planar)
+                    {
+                        for (xscale = 0; xscale < spec->scaleX; xscale++)
+                        {
+                            *ptr1++ = qr;
+                            *ptr2++ = qg;
+                            *ptr3++ = qb;
+                            *ptr4++ = qa;
+                        }
+                    }
+                    else
+                    {
+                        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;
+
+                    if (spec->planar)
+                    {
+                        for (xscale = 0; xscale < spec->scaleX; xscale++)
+                        {
+                            *ptr1++ = qr;
+                            *ptr2++ = qg;
+                            *ptr3++ = qb;
+                        }
+                    }
+                    else
+                    {
+                        for (xscale = 0; xscale < spec->scaleX; xscale++)
+                        {
+                            *ptr1++ = qr;
+                            *ptr1++ = qg;
+                            *ptr1++ = 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, const DMImageConvSpec *spec)
+{
+    int xc, yc, plane, res;
+    DMBitStreamContext bs;
+
+    if ((res = dmInitBitStreamFILE(&bs, fp)) != DMERR_OK)
+        return res;
+
+    if (spec->planar)
+    {
+        // Output bitplanes in planerd 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, const DMImageConvSpec *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, const Uint8 *row, const 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, const DMImageConvSpec *spec)
+{
+    DMImageConvSpec tmpSpec;
+
+    // Write PPM header
+    fprintf(fp,
+        "P6\n%d %d\n255\n",
+        img->width * spec->scaleX,
+        img->height * spec->scaleY);
+
+    // Write image data
+    memcpy(&tmpSpec, spec, sizeof(DMImageConvSpec));
+    tmpSpec.format = DM_IFMT_RGB;
+    return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec);
+}
+
+
+int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *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, const Uint8 *row, const 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, const DMImageConvSpec *spec)
+{
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    int fmt, res;
+
+    // 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;
+    }
+
+    res = DMERR_OK;
+    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(2, "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, const DMImageConvSpec *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(2, "PNG: %d x %d, depth=%d, type=%d\n",
+        width, height, bit_depth, color_type);
+
+    if ((*pimg = img = dmImageAlloc(width, height,
+        DM_IFMT_PALETTE,
+        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
+        -1 /* 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(2, "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(2, "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 PC Paintbrush version:
+                            // 0 = v2.5
+                            // 2 = v2.8 with palette,
+                            // 3 = v2.8 without palette
+                            // 4 = PC Paintbrush for Windows
+                            // 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;
+    FILE *fp;
+} DMPCXData;
+
+
+// Returns one byte from row buffer (of length len) at offset soffs,
+// OR zero if the offset is outside buffer.
+static inline Uint8 dmPCXGetByte(const 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 dmPCXPutData(DMPCXData *pcx, const Uint8 data, const int count)
+{
+    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;
+    }
+    return DMERR_OK;
+}
+
+
+static int dmWritePCXRow(void *cbdata, const Uint8 *row, const size_t len)
+{
+    DMPCXData *pcx = (DMPCXData *) cbdata;
+    int err;
+    size_t soffs = 0;
+
+    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;
+        while (soffs < blen)
+        {
+            if (data == dmPCXGetByte(row, len, soffs) && count < 0x3F)
+            {
+                count++;
+                soffs++;
+            }
+            else
+            {
+                if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
+                    return err;
+
+                data = dmPCXGetByte(row, len, soffs++);
+                count = 1;
+            }
+        }
+
+        if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
+            return err;
+
+        if (!dmPCXFlush(pcx))
+            return DMERR_FWRITE;
+    }
+
+
+    return DMERR_OK;
+}
+
+
+int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *pspec)
+{
+    DMPCXData pcx;
+    DMPCXHeader hdr;
+    DMImageConvSpec spec;
+    int res;
+
+    // Always force planar for PCX
+    memcpy(&spec, pspec, sizeof(DMImageConvSpec));
+    spec.planar = TRUE;
+
+    // Create output file
+    pcx.buf    = NULL;
+    pcx.header = &hdr;
+    pcx.fp     = fp;
+
+    // Create PCX header
+    dmMemset(&hdr, 0, sizeof(hdr));
+    if (spec.paletted && img->pal != NULL)
+    {
+        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.hres         = img->width * spec.scaleX;
+    hdr.vres         = img->height * spec.scaleY;
+    hdr.xmin         = hdr.ymin = 0;
+    hdr.xmax         = (img->width * spec.scaleX) - 1;
+    hdr.ymax         = (img->height * spec.scaleY) - 1;
+    hdr.palInfo      = 1;
+    hdr.hScreenSize  = hdr.hres;
+    hdr.vScreenSize  = hdr.vres;
+
+    // TODO XXX .. maybe actually compute these asdf
+    hdr.bitsPerPlane = 8;
+    hdr.nplanes      = dmImageGetBytesPerPixel(spec.format);
+
+    res = img->width * spec.scaleX;
+    hdr.bpl = res / 2;
+    if (res % 2) hdr.bpl++;
+    hdr.bpl *= 2;
+
+    dmMsg(2,
+        "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
+        hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
+        hdr.hres, hdr.vres,
+        hdr.hScreenSize, hdr.vScreenSize);
+
+    dmMsg(2, "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s, planar=%s\n",
+        hdr.nplanes, hdr.bitsPerPlane, hdr.bpl,
+        spec.paletted ? "yes" : "no",
+        spec.planar ? "yes" : "no"
+        );
+
+    // TODO XXX this is also bogus
+    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;
+        dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors);
+
+        dm_fwrite_byte(pcx.fp, 0x0C);
+
+        for (i = 0; i < img->ncolors; i++)
+        {
+            if (!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))
+            {
+                res = dmError(DMERR_FWRITE,
+                    "PCX: Could not write palette data.\n");
+                goto error;
+            }
+        }
+
+        // Pad the palette, if necessary
+        for (; i < 256; i++)
+        {
+            if (!dm_fwrite_byte(pcx.fp, 0) ||
+                !dm_fwrite_byte(pcx.fp, 0) ||
+                !dm_fwrite_byte(pcx.fp, 0))
+            {
+                res = dmError(DMERR_FWRITE,
+                    "PCX: Could not write palette data.\n");
+                goto error;
+            }
+        }
+    }
+
+error:
+    dmFree(pcx.buf);
+    return res;
+}
+
+
+int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *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) ||
+        !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) ||
+        !dm_fread_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) ||
+        !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 image 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 (hdr.nplanes == 4 && hdr.bitsPerPlane == 4)
+    {
+        dmMsg(2,
+            "PCX: Probably invalid combination of nplanes and bpp, attempting to fix ..\n");
+        
+        hdr.bitsPerPlane = 1;
+    }
+
+    isPaletted = (hdr.bitsPerPlane * hdr.nplanes) <= 8;
+
+    dmMsg(2,
+        "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
+        hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
+        hdr.hres, hdr.vres,
+        hdr.hScreenSize, hdr.vScreenSize);
+
+    dmMsg(2,
+        "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s\n",
+        hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, isPaletted ? "yes" : "no");
+
+    if (hdr.nplanes < 1 || hdr.nplanes > 8)
+    {
+        res = dmError(DMERR_NOT_SUPPORTED,
+            "PCX: Unsupported number of bitplanes %d.\n",
+            hdr.nplanes);
+        goto error;
+    }
+
+    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,
+        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
+        // isPaletted ? (hdr.bitsPerPlane * hdr.nplanes) : -1
+        -1
+        )) == NULL)
+    {
+        res = dmError(DMERR_MALLOC,
+            "PCX: Could not allocate image structure.\n");
+        goto error;
+    }
+
+    // Sanity check bytes per line value
+    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;
+    }
+
+    dmMsg(2,
+        "PCX: bufLen=%d\n",
+        pcx.bufLen);
+
+    // 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 32:
+            case 24:
+            case 16:
+            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:
+                memset(dp, 0, img->width);
+
+                for (int nplane = 0; nplane < hdr.nplanes; nplane++)
+                {
+                    Uint8 *sptr = pcx.buf + (hdr.bpl * nplane);
+
+                    for (int xc = 0; xc < img->width; xc++)
+                    {
+                        const int px = 7 - (xc & 7);
+                        dp[xc] |= ((sptr[xc / 8] & (1 << px)) >> px) << nplane;
+                    }
+                }
+                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
+            dmMsg(2, "PCX: Reading palette of %d colors\n", ncolors);
+            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.
+            dmMsg(2, "PCX: Initializing palette from header of %d colors\n", ncolors);
+            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,
+        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
+        //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
+        -1
+        )) == NULL)
+        return DMERR_MALLOC;
+
+    // Allocate planar decoding buffer
+    bufLen = ((img->width + 15) / 16) * 2;
+    if ((buf = dmMalloc(bufLen)) == NULL)
+        return DMERR_MALLOC;
+
+    dmMsg(2, "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,
+        // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
+        //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
+        -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(2, "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(2, "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(2, "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(2, "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(2, "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[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 (planar or non-planar) 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/libgfx.h	Sat Aug 19 15:18:29 2017 +0300
@@ -0,0 +1,131 @@
+/*
+ * Functions for loading and saving bitmap images
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2012 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifndef LIBMGFX_H
+#define LIBMGFX_H 1
+
+#include "dmlib.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+enum
+{
+    IMGFMT_PNG,
+    IMGFMT_PPM,
+    IMGFMT_PCX,
+    IMGFMT_ILBM,
+    IMGFMT_RAW,
+    IMGFMT_ARAW,
+
+    IMGFMT_LAST
+};
+
+
+enum
+{
+    DM_IFMT_PALETTE,
+    DM_IFMT_RGB,
+    DM_IFMT_RGBA,
+};
+
+
+enum
+{
+    DM_ERRMODE_FAIL     = 0,
+    DM_ERRMODE_RECOV_1,
+    DM_ERRMODE_RECOV_2,
+};
+
+
+// Bitmapped image struct 
+typedef struct
+{
+    int format;     // one of types specified by DM_IFMT_*
+    int width, height;
+    int pitch;      // bytes per scanline
+    int bpp;        // bits per pixel
+
+    int ncolors;    // number of colors in palette, if any
+    int ctransp;    // transparency color index
+    BOOL constpal;  // is the palette a const?
+    DMColor *pal;   // pointer to palette struct, NULL if no palette
+
+    size_t size;
+    Uint8 *data;
+} DMImage;
+
+
+typedef struct
+{
+    int format;
+    int scaleX, scaleY;
+    int nplanes, bpp;
+    BOOL planar, paletted;
+} DMImageConvSpec;
+
+
+typedef struct
+{
+    char *fext;
+    char *desc;
+    int  (*probe)(const Uint8 *buf, const size_t len);
+    int  (*read)(const char *filename, DMImage **pimg);
+    int  (*readFILE)(FILE *fp, DMImage **pimg);
+    int  (*write)(const char *filename, DMImage *pimg, const DMImageConvSpec *spec);
+    int  (*writeFILE)(FILE *fp, DMImage *pimg, const DMImageConvSpec *spec);
+} DMImageFormat;
+
+
+// Probe scores
+#define DM_PROBE_SCORE_MAX     1000
+#define DM_PROBE_SCORE_GOOD    750
+#define DM_PROBE_SCORE_AVG     500
+#define DM_PROBE_SCORE_MAYBE   250
+#define DM_PROBE_SCORE_FALSE   0
+
+
+extern DMImageFormat dmImageFormatList[IMGFMT_LAST];
+extern int dmGFXErrorMode;
+
+
+DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp);
+void      dmImageFree(DMImage *img);
+int       dmImageGetBytesPerPixel(const int format);
+int       dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **fmt, int *index);
+
+BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha);
+
+
+int dmWriteImageData(DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *spec);
+
+int dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors, const char *indent, const char *type);
+int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
+int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
+
+int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
+int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
+
+#ifdef DM_USE_LIBPNG
+int dmWritePNGImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
+int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
+#endif
+
+int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec);
+int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec);
+int dmReadPCXImageFILE(FILE *fp, DMImage **pimg);
+int dmReadPCXImage(const char *filename, DMImage **pimg);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // LIBMGFX_H