view tools/lib64gfx.c @ 2312:cd266022e4a8

Use dmMemset().
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 09 Jul 2019 10:43:20 +0300
parents 48b48251610a
children 5abb81daadd5
line wrap: on
line source

/*
 * Functions for reading and converting various restricted
 * C64/etc and/or indexed/paletted graphics formats.
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2019 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "lib64gfx.h"

#define BUF_SIZE_INITIAL   (16*1024)
#define BUF_SIZE_GROW      (4*1024)


//
// Some "default" C64 palettes
//
DMC64Palette dmC64DefaultPalettes[] =
{
    {
        "pepto",
        "Pepto's classic (default)",
        {
            { 0x00, 0x00, 0x00, 0xff },
            { 0xFF, 0xFF, 0xFF, 0xff },
            { 0x68, 0x37, 0x2B, 0xff },
            { 0x70, 0xA4, 0xB2, 0xff },
            { 0x6F, 0x3D, 0x86, 0xff },
            { 0x58, 0x8D, 0x43, 0xff },
            { 0x35, 0x28, 0x79, 0xff },
            { 0xB8, 0xC7, 0x6F, 0xff },
            { 0x6F, 0x4F, 0x25, 0xff },
            { 0x43, 0x39, 0x00, 0xff },
            { 0x9A, 0x67, 0x59, 0xff },
            { 0x44, 0x44, 0x44, 0xff },
            { 0x6C, 0x6C, 0x6C, 0xff },
            { 0x9A, 0xD2, 0x84, 0xff },
            { 0x6C, 0x5E, 0xB5, 0xff },
            { 0x95, 0x95, 0x95, 0xff },
        },
    },
    {
        "colodore",
        "Colodore",
        {
            { 0x00, 0x00, 0x00, 0xff },
            { 0xff, 0xff, 0xff, 0xff },
            { 0x96, 0x28, 0x2e, 0xff },
            { 0x5b, 0xd6, 0xce, 0xff },
            { 0x9f, 0x2d, 0xad, 0xff },
            { 0x41, 0xb9, 0x36, 0xff },
            { 0x27, 0x24, 0xc4, 0xff },
            { 0xef, 0xf3, 0x47, 0xff },
            { 0x9f, 0x48, 0x15, 0xff },
            { 0x5e, 0x35, 0x00, 0xff },
            { 0xda, 0x5f, 0x66, 0xff },
            { 0x47, 0x47, 0x47, 0xff },
            { 0x78, 0x78, 0x78, 0xff },
            { 0x91, 0xff, 0x84, 0xff },
            { 0x68, 0x64, 0xff, 0xff },
            { 0xae, 0xae, 0xae, 0xff },
        },
    },
    {
        "vice3",
        "VICE 3.3",
        {
            { 0x00, 0x00, 0x00, 0xff },
            { 0xfd, 0xfe, 0xfc, 0xff },
            { 0xbe, 0x1a, 0x24, 0xff },
            { 0x30, 0xe6, 0xc6, 0xff },
            { 0xb4, 0x1a, 0xe2, 0xff },
            { 0x1f, 0xd2, 0x1e, 0xff },
            { 0x21, 0x1b, 0xae, 0xff },
            { 0xdf, 0xf6, 0x0a, 0xff },
            { 0xb8, 0x41, 0x04, 0xff },
            { 0x6a, 0x33, 0x04, 0xff },
            { 0xfe, 0x4a, 0x57, 0xff },
            { 0x42, 0x45, 0x40, 0xff },
            { 0x70, 0x74, 0x6f, 0xff },
            { 0x59, 0xfe, 0x59, 0xff },
            { 0x5f, 0x53, 0xfe, 0xff },
            { 0xa4, 0xa7, 0xa2, 0xff },
        },
    },
};

const int ndmC64DefaultPalettes = sizeof(dmC64DefaultPalettes) / sizeof(dmC64DefaultPalettes[0]);


int dmC64PaletteFromC64Colors(DMPalette **ppal, const DMColor *colors, const BOOL mixed)
{
    int res;

    if (ppal == NULL || colors == NULL)
        return DMERR_NULLPTR;

    // Allocate and create new
    if (mixed)
    {
        // Mixed 256 color palette
        if ((res = dmPaletteAlloc(ppal, D64_NCOLORS * D64_NCOLORS, -1)) != DMERR_OK)
            return res;

        for (int n1 = 0, n = 0; n1 < D64_NCOLORS; n1++)
        {
            const DMColor *col1 = &colors[n1];
            for (int n2 = 0; n2 < D64_NCOLORS; n2++)
            {
                const DMColor *col2 = &colors[n2];
                (*ppal)->colors[n].r = (col1->r + col2->r) / 2;
                (*ppal)->colors[n].g = (col1->g + col2->g) / 2;
                (*ppal)->colors[n].b = (col1->b + col2->b) / 2;
                n++;
            }
        }
    }
    else
    {
        // Standard palette, just copy it
        if ((res = dmPaletteAlloc(ppal, D64_NCOLORS, 255)) != DMERR_OK)
            return res;

        memcpy((*ppal)->colors, colors, D64_NCOLORS * sizeof(DMColor));
    }

    return DMERR_OK;
}


int dmC64PaletteFromC64Palette(DMPalette **ppal, const DMC64Palette *cpal, const BOOL mixed)
{
    if (ppal == NULL || cpal == NULL)
        return DMERR_NULLPTR;

    return dmC64PaletteFromC64Colors(ppal, cpal->colors, mixed);
}


int dmC64SetImagePalette(DMImage *img, const DMC64ImageConvSpec *spec, const BOOL mixed)
{
    if (img == NULL || spec == NULL)
        return DMERR_NULLPTR;

    // Free previous palette
    if (!img->constpal)
        dmPaletteFree(img->pal);

    img->constpal = FALSE;

    // If specific palette is wanted, use it
    if (spec->pal != NULL)
    {
        if (spec->pal->ncolors > D64_NCOLORS)
            return dmPaletteCopy(&img->pal, spec->pal);
        else
        if (spec->pal->ncolors == D64_NCOLORS)
            return dmC64PaletteFromC64Colors(&img->pal, spec->pal->colors, mixed);
    }

    // Else, use the c64 palette specified
    return dmC64PaletteFromC64Palette(&img->pal, spec->cpal, mixed);
}


BOOL dmCompareAddr16(const DMGrowBuf *buf, const size_t offs, const Uint16 addr)
{
    return
        offs + 1 < buf->len &&
        buf->data[offs    ] == (addr & 0xff) &&
        buf->data[offs + 1] == ((addr >> 8) & 0xff);
}


int dmC64ImageGetNumBlocks(const DMC64ImageFormat *fmt)
{
    int nblocks = 0;
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = fmtGetEncDecOp(fmt, i);
        if (op->type == DO_LAST)
            break;

        if (op->bank > nblocks)
            nblocks = op->bank;
    }

    return nblocks + 1;
}


int dmC64MemBlockAlloc(DMC64MemBlock *blk, const size_t size)
{
    if ((blk->data = dmMalloc0(size)) == NULL)
        return DMERR_MALLOC;

    blk->size = size;
    return DMERR_OK;
}


int dmC64MemBlockReAlloc(DMC64MemBlock *blk, const size_t size)
{
    // Reallocate only if new size is larger
    if (size <= blk->size)
        return DMERR_OK;

    if ((blk->data = dmRealloc(blk->data, size)) == NULL)
        return DMERR_MALLOC;

    dmMemset(blk->data + blk->size, 0, size - blk->size);

    blk->size = size;
    return DMERR_OK;
}


int dmC64MemBlockCopy(DMC64MemBlock *dst, const DMC64MemBlock *src)
{
    if (src->data != NULL && src->size > 0)
    {
        dst->size = src->size;
        if ((dst->data = dmMalloc(src->size)) == NULL)
            return DMERR_MALLOC;

        memcpy(dst->data, src->data, src->size);
        return DMERR_OK;
    }
    else
        return DMERR_INVALID_DATA;
}


void dmC64MemBlockFree(DMC64MemBlock *blk)
{
    if (blk != NULL)
    {
        dmFreeR(&blk->data);
        blk->size = 0;
    }
}


static void dmC64SetupImageData(DMC64Image *img, const DMC64ImageFormat *fmt)
{
    img->fmt      = fmt->format;
    img->nblocks  = dmC64ImageGetNumBlocks(fmt);

    dmMemset(img->extraInfo, 0, sizeof(img->extraInfo));
    img->extraInfo[D64_EI_MODE] = fmt->format->mode;
}


DMC64Image *dmC64ImageAlloc(const DMC64ImageFormat *fmt)
{
    DMC64Image *img = dmMalloc0(sizeof(DMC64Image));

    if (img == NULL)
        return NULL;

    // Initialize image information
    dmC64SetupImageData(img, fmt);

    // Allocate banks
    if ((img->color = dmCalloc(img->nblocks, sizeof(DMC64MemBlock))) == NULL ||
        (img->bitmap = dmCalloc(img->nblocks, sizeof(DMC64MemBlock))) == NULL ||
        (img->screen = dmCalloc(img->nblocks, sizeof(DMC64MemBlock))) == NULL ||
        (img->charData = dmCalloc(img->nblocks, sizeof(DMC64MemBlock))) == NULL)
        goto err;

    return img;

err:
    dmC64ImageFree(img);
    return NULL;
}


void dmC64ImageFree(DMC64Image *img)
{
    if (img != NULL)
    {
        // Free the allocated areas
        for (int i = 0; i < img->nblocks; i++)
        {
            dmC64MemBlockFree(&img->color[i]);
            dmC64MemBlockFree(&img->bitmap[i]);
            dmC64MemBlockFree(&img->screen[i]);
            dmC64MemBlockFree(&img->charData[i]);
        }

        // Free the pointers to the areas
        dmFree(img->color);
        dmFree(img->bitmap);
        dmFree(img->screen);
        dmFree(img->charData);

        // Extra data ..
        for (int i = 0; i < D64_MAX_EXTRA_DATA; i++)
            dmC64MemBlockFree(&img->extraData[i]);

        dmMemset(img, 0, sizeof(DMC64Image));
        dmFree(img);
    }
}


int dmC64ConvertCSDataToImage(DMImage *img,
    int xoffs, int yoffs, const Uint8 *buf,
    int width, int height, BOOL multicolor,
    int *colors)
{
    int yc, widthpx = width * 8;
    Uint8 *dp;

    if (img == NULL)
        return DMERR_NULLPTR;

    if (xoffs < 0 || yoffs < 0 ||
        xoffs > img->width - widthpx ||
        yoffs > img->height - height)
        return DMERR_INVALID_ARGS;

    dp = img->data + (yoffs * img->pitch) + xoffs;

    if (multicolor)
    {
        for (yc = 0; yc < height; yc++)
        {
            const int offs = yc * width;
            Uint8 *d = dp;

            for (int xc = 0; xc < widthpx / 2; xc++)
            {
                const int b = buf[offs + (xc / 4)];
                const int v = 6 - ((xc * 2) & 6);
                const Uint8 c = colors[(b >> v) & 3];

                *d++ = c;
                *d++ = c;
            }

            dp += img->pitch;
        }
    }
    else
    {
        for (yc = 0; yc < height; yc++)
        {
            const int offs = yc * width;
            Uint8 *d = dp;

            for (int xc = 0; xc < widthpx; xc++)
            {
                const int b = buf[offs + (xc / 8)];
                const int v = 7 - (xc & 7);
                const Uint8 c = colors[(b >> v) & 1];

                *d++ = c;
            }

            dp += img->pitch;
        }
    }

    return DMERR_OK;
}


void dmGenericRLEAnalyze(const DMGrowBuf *buf, DMCompParams *cfg)
{
#define DM_STAT_MAX 256
    size_t *stats;

    // Allocate statistics counts buffer
    if ((stats = dmMalloc0(DM_STAT_MAX * sizeof(size_t))) == NULL)
        return;

    // Get statistics on the data
    for (size_t offs = 0; offs < buf->len; offs++)
        stats[buf->data[offs]]++;

    // According to compression type ..
    switch (cfg->type)
    {
        case DM_COMP_RLE_MARKER:
            {
                size_t selected = 0,
                    smallest = buf->len;

                // Find least used byte value
                for (size_t n = 0; n < DM_STAT_MAX; n++)
                {
                    if (stats[n] < smallest)
                    {
                        switch (cfg->flags & DM_RLE_RUNS_MASK)
                        {
                            case DM_RLE_BYTE_RUNS | DM_RLE_WORD_RUNS:
                                cfg->rleMarkerW = selected;
                                cfg->rleMarkerB = selected = n;
                                break;

                            case DM_RLE_BYTE_RUNS:
                                cfg->rleMarkerB = selected = n;
                                break;

                            case DM_RLE_WORD_RUNS:
                                cfg->rleMarkerW = selected = n;
                                break;
                        }
                        smallest = stats[n];
                    }
                }
            }
            break;

        case DM_COMP_RLE_MASK:
            cfg->rleMarkerMask = 0xC0;
            cfg->rleMarkerBits = 0xC0;
            cfg->rleCountMask  = 0x3f;
            break;
    }

    dmFree(stats);
}


//#define RLE_DEBUG

void dmSetupRLEBuffers(DMGrowBuf *dst, DMGrowBuf *src, const DMCompParams *cfg)
{
    if (src != NULL && (cfg->flags & DM_RLE_BACKWARDS_INPUT))
    {
        src->offs = src->len;
        src->backwards = TRUE;
    }

    if (dst != NULL && (cfg->flags & DM_RLE_BACKWARDS_OUTPUT))
    {
        dst->backwards = TRUE;
        dst->offs = dst->size;
    }

#ifdef RLE_DEBUG
fprintf(stderr, "dmSetupRLEBuffers:\n");
if (src != NULL)
fprintf(stderr, "  src.len=%" DM_PRIx_SIZE_T ", src.size=%" DM_PRIx_SIZE_T ", src.offs=%" DM_PRIx_SIZE_T "\n", src->len, src->size, src->offs);
if (dst != NULL)
fprintf(stderr, "  dst.len=%" DM_PRIx_SIZE_T ", dst.size=%" DM_PRIx_SIZE_T ", dst.offs=%" DM_PRIx_SIZE_T "\n", dst->len, dst->size, dst->offs);
fprintf(stderr, "------------------\n");
#endif
}


void dmFinishRLEBuffers(DMGrowBuf *dst, DMGrowBuf *src, const DMCompParams *cfg)
{
    (void) src;

#ifdef RLE_DEBUG
fprintf(stderr, "------------------\n");
fprintf(stderr, "dmFinishRLEBuffers:\n");
if (src != NULL)
fprintf(stderr, "  src.len=%" DM_PRIx_SIZE_T ", src.size=%" DM_PRIx_SIZE_T ", src.offs=%" DM_PRIx_SIZE_T "\n", src->len, src->size, src->offs);
if (dst != NULL)
fprintf(stderr, "  dst.len=%" DM_PRIx_SIZE_T ", dst.size=%" DM_PRIx_SIZE_T ", dst.offs=%" DM_PRIx_SIZE_T "\n", dst->len, dst->size, dst->offs);
#endif

    if (dst != NULL)
    {
        if (cfg->flags & DM_RLE_BACKWARDS_OUTPUT)
        {
            memmove(dst->data, dst->data + dst->offs, dst->len);
            dst->offs = 0;
        }

        switch (cfg->flags & DM_OUT_CROP_MASK)
        {
            case DM_OUT_CROP_END:
                if (cfg->cropOutLen < dst->len)
                {
                    memmove(dst->data, dst->data + dst->len - cfg->cropOutLen, cfg->cropOutLen);
                    dst->len = cfg->cropOutLen;
                }
                break;

            case DM_OUT_CROP_START:
                if (cfg->cropOutLen <= dst->len)
                    dst->len = cfg->cropOutLen;
                break;
        }
    }

#ifdef RLE_DEBUG
fprintf(stderr, "ADJUSTED:\n");
if (src != NULL)
fprintf(stderr, "  src.len=%" DM_PRIx_SIZE_T ", src.size=%" DM_PRIx_SIZE_T ", src.offs=%" DM_PRIx_SIZE_T "\n", src->len, src->size, src->offs);
if (dst != NULL)
fprintf(stderr, "  dst.len=%" DM_PRIx_SIZE_T ", dst.size=%" DM_PRIx_SIZE_T ", dst.offs=%" DM_PRIx_SIZE_T "\n", dst->len, dst->size, dst->offs);
#endif
}


int dmGenericRLEOutputRun(DMGrowBuf *dst, const DMCompParams *cfg, const Uint8 data, const unsigned int count)
{
    for (unsigned int scount = count; scount; scount--)
    {
        if (!dmGrowBufPutU8(dst, data))
        {
            return dmError(DMERR_MALLOC,
                "%s: RLE: Could not output RLE run %d x 0x%02x @ "
                "offs=0x%" DM_PRIx_SIZE_T ", size=0x%" DM_PRIx_SIZE_T ".\n",
                cfg->func, count, data, dst->offs, dst->size);
        }
    }
    return DMERR_OK;
}


int dmDecodeGenericRLE(DMGrowBuf *dst, const DMGrowBuf *psrc, const DMCompParams *cfg)
{
    int res;
    Uint8 tmp1, tmp2, tmp3, data;
    DMGrowBuf src;

    // As we need to modify the offs, etc. but not the data,
    // we will just make a shallow copy of the DMGrowBuf struct
    dmGrowBufConstCopy(&src, psrc);
    dmSetupRLEBuffers(dst, &src, cfg);

    while (dmGrowBufGetU8(&src, &data))
    {
        unsigned int count = 1;

        if (cfg->type == DM_COMP_RLE_MARKER)
        {
            // A simple marker byte RLE variant: [Marker] [count] [data]
            if ((cfg->flags & DM_RLE_BYTE_RUNS) && data == cfg->rleMarkerB)
            {
                if (!dmGrowBufGetU8(&src, &tmp1))
                {
#ifdef RLE_DEBUG
fprintf(stderr, "  marker=$%02x\n", cfg->rleMarkerB);
fprintf(stderr, "  src.len=%" DM_PRIx_SIZE_T ", src.size=%" DM_PRIx_SIZE_T ", src.offs=%" DM_PRIx_SIZE_T "\n", src.len, src.size, src.offs);
fprintf(stderr, "  dst.len=%" DM_PRIx_SIZE_T ", dst.size=%" DM_PRIx_SIZE_T ", dst.offs=%" DM_PRIx_SIZE_T "\n", dst->len, dst->size, dst->offs);
#endif
                    res = dmError(DMERR_INVALID_DATA,
                        "%s: RLE: Invalid data/out of data for byte length run sequence (1).\n",
                        cfg->func);
                    goto out;
                }
                if (!dmGrowBufGetU8(&src, &tmp2))
                {
#ifdef RLE_DEBUG
fprintf(stderr, "  marker=$%02x, data=$%02x\n", cfg->rleMarkerB, tmp1);
fprintf(stderr, "  src.len=%" DM_PRIx_SIZE_T ", src.size=%" DM_PRIx_SIZE_T ", src.offs=%" DM_PRIx_SIZE_T "\n", src.len, src.size, src.offs);
fprintf(stderr, "  dst.len=%" DM_PRIx_SIZE_T ", dst.size=%" DM_PRIx_SIZE_T ", dst.offs=%" DM_PRIx_SIZE_T "\n", dst->len, dst->size, dst->offs);
#endif
                    res = dmError(DMERR_INVALID_DATA,
                        "%s: RLE: Invalid data/out of data for byte length run sequence (2).\n",
                        cfg->func);
                    goto out;
                }
                switch (cfg->flags & DM_RLE_ORDER_MASK)
                {
                    case DM_RLE_ORDER_1:
                        count = tmp1;
                        data  = tmp2;
                        break;

                    case DM_RLE_ORDER_2:
                        data  = tmp1;
                        count = tmp2;
                        break;
                }

                if (count == 0 && (cfg->flags & DM_RLE_ZERO_COUNT_MAX))
                    count = 256;
            }
            else
            if ((cfg->flags & DM_RLE_WORD_RUNS) && data == cfg->rleMarkerW)
            {
                if (!dmGrowBufGetU8(&src, &tmp1) ||
                    !dmGrowBufGetU8(&src, &tmp2) ||
                    !dmGrowBufGetU8(&src, &tmp3))
                {
                    res = dmError(DMERR_INVALID_DATA,
                        "%s: RLE: Invalid data/out of data for word length run sequence.\n",
                        cfg->func);
                    goto out;
                }
                switch (cfg->flags & DM_RLE_ORDER_MASK)
                {
                    case DM_RLE_ORDER_1:
                        count = (tmp2 << 8) | tmp1;
                        data = tmp3;
                        break;

                    case DM_RLE_ORDER_2:
                        data = tmp1;
                        count = (tmp3 << 8) | tmp2;
                        break;
                }

                if (count == 0 && (cfg->flags & DM_RLE_ZERO_COUNT_MAX))
                    count = 65536;
            }
        }
        else
        if (cfg->type == DM_COMP_RLE_MASK)
        {
            // Mask marker RLE: usually high bit(s) of byte mark RLE sequence
            // and the lower bits contain the count: [Mask + count] [data]
            if ((data & cfg->rleMarkerMask) == cfg->rleMarkerBits)
            {
                if (!dmGrowBufGetU8(&src, &tmp1))
                {
                    res = dmError(DMERR_INVALID_DATA,
                        "%s: RLE: Invalid data/out of data for byte length mask/run sequence.\n",
                        cfg->func);
                    goto out;
                }

                count = data & cfg->rleCountMask;
                data = tmp1;
            }
        }

        if ((res = dmGenericRLEOutputRun(dst, cfg, data, count)) != DMERR_OK)
            goto out;
    }

    dmFinishRLEBuffers(dst, &src, cfg);
    res = DMERR_OK;

out:
    return res;
}


int dmDecodeGenericRLEAlloc(DMGrowBuf *dst, const DMGrowBuf *src, const DMCompParams *cfg)
{
    int res;
    if ((res = dmGrowBufAlloc(dst, BUF_SIZE_INITIAL, BUF_SIZE_GROW)) != DMERR_OK)
        return res;

    return dmDecodeGenericRLE(dst, src, cfg);
}


int dmEncodeGenericRLESequence(DMGrowBuf *dst, const Uint8 data, unsigned int count, const DMCompParams *cfg)
{
    BOOL copyOnly = FALSE;
    int res;

    switch (cfg->type)
    {
        case DM_COMP_RLE_MARKER:
            if ((cfg->flags & DM_RLE_WORD_RUNS) &&
                (count >= cfg->rleMinCountW || data == cfg->rleMarkerW))
            {
                if (count == 65536 && (cfg->flags & DM_RLE_ZERO_COUNT_MAX))
                    count = 0;

                if (!dmGrowBufPutU8(dst, cfg->rleMarkerW))
                    goto err;

                switch (cfg->flags & DM_RLE_ORDER_MASK)
                {
                    case DM_RLE_ORDER_1:
                        if (!dmGrowBufPutU16LE(dst, count) ||
                            !dmGrowBufPutU8(dst, data))
                            goto err;
                        break;

                    case DM_RLE_ORDER_2:
                        if (!dmGrowBufPutU8(dst, data) ||
                            !dmGrowBufPutU16LE(dst, count))
                            goto err;
                        break;
                }
            }
            else
            if ((cfg->flags & DM_RLE_BYTE_RUNS) &&
                (count >= cfg->rleMinCountB || data == cfg->rleMarkerB))
            {
                if (count == 256 && (cfg->flags & DM_RLE_ZERO_COUNT_MAX))
                    count = 0;

                if (!dmGrowBufPutU8(dst, cfg->rleMarkerB))
                    goto err;

                switch (cfg->flags & DM_RLE_ORDER_MASK)
                {
                    case DM_RLE_ORDER_1:
                        if (!dmGrowBufPutU8(dst, count) ||
                            !dmGrowBufPutU8(dst, data))
                            goto err;
                        break;

                    case DM_RLE_ORDER_2:
                        if (!dmGrowBufPutU8(dst, data) ||
                            !dmGrowBufPutU8(dst, count))
                            goto err;
                        break;
                }
            }
            else
                copyOnly = TRUE;
            break;

        case DM_COMP_RLE_MASK:
            if (count >= cfg->rleMinCountB || (data & cfg->rleMarkerMask) == cfg->rleMarkerBits)
            {
                // Mask marker RLE: usually high bit(s) of byte mark RLE sequence
                // and the lower bits contain the count: [Mask + count] [data]
                if (!dmGrowBufPutU8(dst, cfg->rleMarkerBits | count) ||
                    !dmGrowBufPutU8(dst, data))
                    goto err;
            }
            else
                copyOnly = TRUE;
            break;
    }

    if (copyOnly && (res = dmGenericRLEOutputRun(dst, cfg, data, count)) != DMERR_OK)
        return res;

    return DMERR_OK;

err:
    return dmError(DMERR_MALLOC,
        "%s: RLE: Could not output RLE sequence %d x 0x%02x.\n",
        cfg->func, count, data);
}


int dmEncodeGenericRLE(DMGrowBuf *dst, const DMGrowBuf *psrc, const DMCompParams *cfg)
{
    DMGrowBuf src;
    unsigned int count = 0;
    int prev = -1, res = DMERR_OK;
    Uint8 data;

    // As we need to modify the offs, etc. but not the data,
    // we will just make a shallow copy of the DMGrowBuf struct
    dmGrowBufConstCopy(&src, psrc);
    dmSetupRLEBuffers(dst, &src, cfg);

    while (dmGrowBufGetU8(&src, &data))
    {
        // If new data byte is different, or we exceed the rleMaxCount
        // for the active runs mode(s) .. then encode the run.
        if ((data != prev && prev != -1) ||
            ((cfg->flags & DM_RLE_WORD_RUNS) && count >= cfg->rleMaxCountW) ||
            (((cfg->flags & DM_RLE_RUNS_MASK) == DM_RLE_BYTE_RUNS) && count >= cfg->rleMaxCountB))
        {
            if ((res = dmEncodeGenericRLESequence(dst, prev, count, cfg)) != DMERR_OK)
                goto err;

            count = 1;
        }
        else
            count++;

        prev = data;
    }

    // If there is anything left in the output queue ..
    if ((res = dmEncodeGenericRLESequence(dst, prev, count, cfg)) != DMERR_OK)
        goto err;

    dmFinishRLEBuffers(dst, &src, cfg);

err:
    return res;
}


int dmEncodeGenericRLEAlloc(DMGrowBuf *dst, const DMGrowBuf *src, const DMCompParams *cfg)
{
    int res;
    if ((res = dmGrowBufAlloc(dst, BUF_SIZE_INITIAL, BUF_SIZE_GROW)) != DMERR_OK)
        return res;

    return dmEncodeGenericRLE(dst, src, cfg);
}


int dmC64SanityCheckEncDecOp(const int i, const DMC64EncDecOp *op, const DMC64Image *img)
{
    if (op->flags == 0)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid operation flags value %d in generic encode/decode operator %d @ #%d.\n",
            op->flags, op->type, i);
    }

    switch (op->type)
    {
        case DO_COPY:
        case DO_SET_MEM:
        case DO_SET_MEM_HI:
        case DO_SET_MEM_LO:
        case DO_SET_OP:
            switch (op->subject)
            {
                case DS_COLOR_RAM:
                case DS_BITMAP_RAM:
                case DS_SCREEN_RAM:
                case DS_CHAR_DATA:
                    if (op->bank < 0 || op->bank > img->nblocks)
                    {
                        return dmError(DMERR_INTERNAL,
                            "Invalid bank %d / %d definition in generic encode/decode operator %d @ #%d.\n",
                            op->bank, img->nblocks, op->type, i);
                    }
                    break;

                case DS_EXTRA_DATA:
                    if (op->bank < 0 || op->bank >= D64_MAX_EXTRA_DATA)
                    {
                        return dmError(DMERR_INTERNAL,
                            "Invalid bank %d definition in generic encode/decode operator %d @ #%d.\n",
                            op->bank, op->type, i);
                    }
                    break;
            }
            break;

        // Just list the allowed ops here
        case DO_FUNC:
        case DO_CHAR_CFG:
        case DO_LAST:
            break;

        default:
            return dmError(DMERR_INTERNAL,
                "Invalid op type %d in generic encode/decode operator @ #%d.\n",
                op->type, i);
            break;
    }

    return DMERR_OK;
}


size_t dmC64GetSubjectSize(const int subject, const DMC64ImageCommonFormat *fmt)
{
    switch (subject)
    {
        case DS_SCREEN_RAM:
        case DS_COLOR_RAM:
            return fmt->chHeight * fmt->chWidth;

        case DS_BITMAP_RAM:
            return fmt->chHeight * fmt->chWidth * 8;

        case DS_CHAR_DATA:
            return D64_MAX_CHARS * D64_CHR_SIZE;

        case DS_D020:
        case DS_BGCOL:
        case DS_D021:
        case DS_D022:
        case DS_D023:
        case DS_D024:
            return 1;

        default:
            // Default to size of 0
            return 0;
    }
}


size_t dmC64GetOpSubjectSize(const DMC64EncDecOp *op, const DMC64ImageCommonFormat *fmt)
{
    size_t size = dmC64GetSubjectSize(op->subject, fmt);

    // If the operator specified size is larger, use it.
    if (op->size > size)
        size = op->size;

    return size;
}


const char *dmC64GetOpSubjectName(const int subject)
{
    static const char *subjectNames[DS_LAST] =
    {
        "Color RAM",
        "Bitmap RAM",
        "Screen RAM",
        "Extra data",
        "Character data",

        "d020 / border",
        "d021 / background",
        "d022",
        "d023",
        "d024",
    };
    if (subject >= 0 && subject < DS_LAST)
        return subjectNames[subject];
    else
        return NULL;
}


const char *dmC64GetOpType(const int type)
{
    static const char *typeNames[DO_LAST] =
    {
        "COPY",
        "SET_MEM",
        "SET_OP",
        "SET_MEM_HI",
        "SET_MEM_LO",

        "FUNC",
        "CHAR_CFG",
    };
    if (type >= 0 && type < DO_LAST)
        return typeNames[type];
    else
        return "ERROR";
}


const DMC64MemBlock * dmC64GetOpMemBlock(const DMC64Image *img, const int subject, const int bank)
{
    if (bank >= 0 && bank < img->nblocks)
    {
        switch (subject)
        {
            case DS_COLOR_RAM  : return &img->color[bank];
            case DS_SCREEN_RAM : return &img->screen[bank];
            case DS_BITMAP_RAM : return &img->bitmap[bank];
            case DS_CHAR_DATA  : return &img->charData[bank];
            case DS_EXTRA_DATA : return &img->extraData[bank];
        }
    }

    return NULL;
}


int dmC64DecodeGenericBMP(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;

    if (buf == NULL || buf->data == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    dmC64SetupImageData(img, fmt);

    // Perform decoding
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = fmtGetEncDecOp(fmt, i);
        const Uint8 *src;
        DMC64MemBlock *blk = NULL;
        const char *subjname = dmC64GetOpSubjectName(op->subject);
        size_t size;
        Uint8 value;

        // Check for last operator
        if (op->type == DO_LAST)
            break;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op, img)) != DMERR_OK)
            return res;

        // Check flags
        if ((op->flags & DF_DECODE) == 0)
            continue;

        // Is the operation inside the bounds?
        size = dmC64GetOpSubjectSize(op, fmt->format);
        if (op->type == DO_COPY && op->offs + size > buf->len + 1)
        {
            return dmError(DMERR_INVALID_DATA,
                "Decode SRC out of bounds, op #%d type=%s, subj=%s, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                i, dmC64GetOpType(op->type), subjname, op->offs, op->offs, op->bank,
                size, size, buf->len, buf->len);
        }

        src = buf->data + op->offs;

        // Perform operation
        switch (op->type)
        {
            case DO_COPY:
            case DO_SET_MEM:
            case DO_SET_MEM_HI:
            case DO_SET_MEM_LO:
            case DO_SET_OP:
                switch (op->subject)
                {
                    case DS_COLOR_RAM:
                    case DS_SCREEN_RAM:
                    case DS_BITMAP_RAM:
                    case DS_CHAR_DATA:
                    case DS_EXTRA_DATA:
                        // XXX BZZZT .. a nasty cast here
                        blk = (DMC64MemBlock *) dmC64GetOpMemBlock(img, op->subject, op->bank);

                        if ((dmC64MemBlockReAlloc(blk, op->offs2 + size)) != DMERR_OK)
                        {
                            return dmError(DMERR_MALLOC,
                                "Could not allocate '%s' block! "
                                "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                subjname, i, op->offs, op->offs, op->bank, op->offs2 + size, op->offs2 + size, buf->len, buf->len);
                        }
                        switch (op->type)
                        {
                            case DO_COPY:
                                memcpy(blk->data + op->offs2, src, size);
                                break;

                            case DO_SET_MEM:
                                dmMemset(blk->data + op->offs2, *src, size);
                                break;

                            case DO_SET_MEM_HI:
                                dmMemset(blk->data + op->offs2, (*src >> 4) & 0x0f, size);
                                break;

                            case DO_SET_MEM_LO:
                                dmMemset(blk->data + op->offs2, *src & 0x0f, size);
                                break;

                            case DO_SET_OP:
                                dmMemset(blk->data + op->offs2, op->offs, size);
                                break;

                            default:
                                return dmError(DMERR_INTERNAL,
                                    "Unhandled op type %s in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    dmC64GetOpType(op->type), i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                        }
                        break;

                    case DS_D020:
                    case DS_BGCOL:
                    case DS_D021:
                    case DS_D022:
                    case DS_D023:
                    case DS_D024:
                    case DS_EXTRA_INFO:
                        switch (op->type)
                        {
                            case DO_COPY:
                            case DO_SET_MEM:
                                value = *src;
                                break;

                            case DO_SET_OP:
                                value = op->offs;
                                break;

                            case DO_SET_MEM_HI:
                                value = (*src >> 4) & 0x0f;
                                break;

                            case DO_SET_MEM_LO:
                                value = *src & 0x0f;
                                break;

                            default:
                                return dmError(DMERR_INTERNAL,
                                    "Unhandled op type %s in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    dmC64GetOpType(op->type), i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                        }
                        switch (op->subject)
                        {
                            case DS_D020: img->d020 = value; break;
                            case DS_BGCOL:
                            case DS_D021: img->bgcolor = value; break;
                            case DS_D022: img->d022 = value; break;
                            case DS_D023: img->d023 = value; break;
                            case DS_D024: img->d024 = value; break;
                            case DS_EXTRA_INFO: img->extraInfo[op->offs2] = value; break;
                        }
                        break;

                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled subject %d in "
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->subject, i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                }
                break;

            case DO_CHAR_CFG:
                switch (op->subject)
                {
                    case D64_CHCFG_SCREEN:
                        break;

                    case D64_CHCFG_LINEAR:
                        for (int bank = 0; bank < img->nblocks; bank++)
                        {
                            for (int offs = 0; offs < fmt->format->chHeight * fmt->format->chWidth; offs++)
                                img->screen[bank].data[offs] = offs & 0xff;
                        }
                        break;

                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DO_CHAR_CFG mode %d in ",
                            "op #%d, bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->subject, i, op->bank, size, size, buf->len, buf->len);
                }
                break;

            case DO_FUNC:
                if (op->decFunction != NULL &&
                    (res = op->decFunction(op, img, buf, fmt->format)) != DMERR_OK)
                {
                    return dmError(res,
                        "Decode op custom function failed: op #%d, "
                        "offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                }
                break;
        }
    }

    // Sanity check certain things ..
    if ((img->extraInfo[D64_EI_MODE] & D64_FMT_ILACE) &&
        img->extraInfo[D64_EI_ILACE_TYPE] == D64_ILACE_NONE)
    {
        return dmError(DMERR_INTERNAL,
            "Format '%s' (%s) has interlace flag set, but interlace type is not set.\n",
            fmt->name, fmt->fext);
    }

    return DMERR_OK;
}


int dmC64EncodeGenericBMP(const BOOL allocate, DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;

    if (img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    // Allocate the output buffer if requested
    if (allocate && (res = dmGrowBufAlloc(buf, BUF_SIZE_INITIAL, BUF_SIZE_GROW)) != DMERR_OK)
    {
        dmError(res,
            "Could not allocate %d bytes of memory for C64 image encoding buffer.\n",
            fmt->size);
        goto err;
    }

    if (buf->backwards)
    {
        dmError(DMERR_INVALID_DATA,
            "Buffer specified for dmC64EncodeGenericBMP() is in backwards mode, which is not supported.\n");
        goto err;
    }

    // Perform encoding
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = fmtGetEncDecOp(fmt, i);
        size_t size, chksize;
        const DMC64MemBlock *blk = NULL;
        const char *subjname = dmC64GetOpSubjectName(op->subject);
        Uint8 value;

        // Check for last operator
        if (op->type == DO_LAST)
            break;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op, img)) != DMERR_OK)
            goto err;

        // Check flags
        if ((op->flags & DF_ENCODE) == 0)
            continue;

        // Do we need to reallocate some more space?
        size = dmC64GetOpSubjectSize(op, fmt->format);
        chksize = buf->offs + op->offs + size;
        if (!dmGrowBufCheckGrow(buf, chksize))
        {
            res = dmError(DMERR_MALLOC,
                "Could not re-allocate %d bytes of memory for C64 image encoding buffer.\n",
                chksize);
            goto err;
        }

        // Perform operation
        Uint8 *dst = buf->data + buf->offs + op->offs;
        switch (op->type)
        {
            case DO_COPY:
            case DO_SET_MEM:
            case DO_SET_MEM_HI:
            case DO_SET_MEM_LO:
            case DO_SET_OP:
                switch (op->subject)
                {
                    case DS_COLOR_RAM:
                    case DS_SCREEN_RAM:
                    case DS_BITMAP_RAM:
                    case DS_CHAR_DATA:
                    case DS_EXTRA_DATA:
                        blk = dmC64GetOpMemBlock(img, op->subject, op->bank);
                        switch (op->type)
                        {
                            case DO_COPY:
                                if (blk->data == NULL)
                                {
                                    res = dmError(DMERR_NULLPTR,
                                        "'%s' block is NULL in "
                                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                        subjname, i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                                    goto err;
                                }
                                if (op->offs2 + size > blk->size)
                                {
                                    res = dmError(DMERR_INTERNAL,
                                        "'%s' size mismatch %d <> %d in "
                                        "op #%d, offs=%d ($%04x), bank=%d, offs2=%d ($%02x), size=%d ($%04x)\n",
                                        subjname, op->offs2 + size, blk->size, i, op->offs, op->offs, op->bank, op->offs2, op->offs2, size, size);
                                    goto err;
                                }
                                memcpy(dst, blk->data + op->offs2, size);
                                break;

                            case DO_SET_MEM:
                            case DO_SET_MEM_HI:
                            case DO_SET_MEM_LO:
                            case DO_SET_OP:
                                // This operation makes no sense in encoding, so do nothing
                                break;

                            default:
                                return dmError(DMERR_INTERNAL,
                                    "Unhandled op type %s in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    dmC64GetOpType(op->type), i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                        }
                        break;

                    case DS_D020:
                    case DS_BGCOL:
                    case DS_D021:
                    case DS_D022:
                    case DS_D023:
                    case DS_D024:
                    case DS_EXTRA_INFO:
                        switch (op->subject)
                        {
                            case DS_D020: value = img->d020; break;
                            case DS_BGCOL:
                            case DS_D021: value = img->bgcolor; break;
                            case DS_D022: value = img->d022; break;
                            case DS_D023: value = img->d023; break;
                            case DS_D024: value = img->d024; break;
                            case DS_EXTRA_INFO: value = img->extraInfo[op->offs2]; break;
                        }
                        switch (op->type)
                        {
                            case DO_COPY:
                            case DO_SET_MEM:
                                *dst = value;
                                break;

                            case DO_SET_MEM_HI:
                                *dst |= (value & 0x0f) << 4;
                                break;

                            case DO_SET_MEM_LO:
                                *dst |= value & 0x0f;
                                break;

                            case DO_SET_OP:
                                // Do nothing in this case
                                // XXX TODO: what about DS_EXTRA_INFO?
                                break;

                            default:
                                return dmError(DMERR_INTERNAL,
                                    "Unhandled op type %s in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    dmC64GetOpType(op->type), i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                        }
                        break;

                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled subject '%s' in "
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            subjname, i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                }
                break;

            case DO_FUNC:
                if (op->encFunction != NULL &&
                    (res = op->encFunction(op, buf, img, fmt->format)) != DMERR_OK)
                {
                    dmErrorMsg(
                        "Encode op custom function failed: op #%d, "
                        "offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, buf->len, buf->len);
                    goto err;
                }
                break;
        }
    }

    res = DMERR_OK;

err:
    return res;
}


static int fmtGetGenericSCPixel(Uint8 *col, const DMC64Image *img,
    const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)

    return dmC64GetGenericSCPixel(
        col, img,
        bmoffs, scroffs,
        vshift, 0, 0);
}


static int fmtGetGenericMCPixel(Uint8 *col, const DMC64Image *img,
    const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)

    return dmC64GetGenericMCPixel(
        col, img,
        bmoffs, scroffs,
        vshift, 0,
        0, 0, img->bgcolor);
}


static int fmtGetGenericCharPixel(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_CHAR_PIXEL(img)
    int chr = img->screen[0].data[scroffs];

    if (!img->extraInfo[D64_EI_CHAR_CUSTOM] &&
        img->extraInfo[D64_EI_CHAR_CASE])
        chr += 256; // lower case, so add 256 to char ROM offset

    switch (img->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK)
    {
        case D64_FMT_HIRES:
            return dmC64GetGenericCharSCPixel(
                col, img,
                scroffs, rasterX,
                0, (chr * D64_CHR_SIZE) + (rasterY & 7), chr,
                0, img->bgcolor);

        case D64_FMT_MC:
            return dmC64GetGenericCharMCPixel(
                col, img,
                scroffs, rasterX,
                0, (chr * D64_CHR_SIZE) + (rasterY & 7), chr,
                0, img->bgcolor, img->d022, img->d023);

        case D64_FMT_ECM:
            return dmC64GetGenericCharECMPixel(
                col, img,
                scroffs, rasterX,
                0, ((chr & 0x3f) * D64_CHR_SIZE) + (rasterY & 7), chr,
                0, img->bgcolor, img->d022, img->d023, img->d024);

        default:
            return dmError(DMERR_INVALID_DATA,
                "Invalid character map image mode=0x%x.\n",
                img->extraInfo[D64_EI_MODE]);
    }
}


// Convert a generic "C64" format bitmap in DMC64Image struct to
// a indexed/paletted bitmap image.
int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src, const DMC64ImageConvSpec *spec)
{
    DMC64GetPixelFunc getPixel;

    // Sanity check arguments
    if (dst == NULL || src == NULL || spec == NULL)
        return DMERR_NULLPTR;

    if (dst->width != src->fmt->width || dst->height != src->fmt->height)
    {
        return dmError(DMERR_INVALID_DATA,
            "Invalid src vs. dst width/height %d x %d <-> %d x %d\n",
            src->fmt->width, src->fmt->height, dst->width, dst->height);
    }

    dmMemset(dst->data, 0, dst->size);

    // Check pixel getter function
    if (src->fmt->getPixel != NULL)
        getPixel = src->fmt->getPixel;
    else
    if (src->extraInfo[D64_EI_MODE] & D64_FMT_CHAR)
        getPixel = fmtGetGenericCharPixel;
    else
    switch (src->extraInfo[D64_EI_MODE] & D64_FMT_MODE_MASK)
    {
        case D64_FMT_MC    : getPixel = fmtGetGenericMCPixel; break;
        case D64_FMT_HIRES : getPixel = fmtGetGenericSCPixel; break;
        default:
            return dmError(DMERR_INVALID_DATA,
                "Invalid bitmap image type/fmt=0x%x.\n",
                src->extraInfo[D64_EI_MODE]);
    }

    // Perform conversion
    for (int yc = 0; yc < dst->height; yc++)
    {
        Uint8 *dp = dst->data + (yc * dst->pitch);
        for (int xc = 0; xc < dst->width; xc++, dp++)
        {
            int res;
            if ((res = getPixel(dp, src, xc, yc)) != DMERR_OK)
                return res;
        }
    }

    return DMERR_OK;
}


int dmC64ConvertBMP2Image(DMImage **pdst, const DMC64Image *src, const DMC64ImageConvSpec *spec)
{
    DMImage *dst;
    BOOL mixed;
    int res;

    if (pdst == NULL || src == NULL || spec == NULL)
        return DMERR_NULLPTR;

    // Allocate image structure
    if ((*pdst = dst = dmImageAlloc(
        src->fmt->width, src->fmt->height, DM_PIXFMT_PALETTE, -1)) == NULL)
        return DMERR_MALLOC;

    // Set palette information
    mixed = (src->extraInfo[D64_EI_MODE] & D64_FMT_ILACE) &&
        src->extraInfo[D64_EI_ILACE_TYPE] == D64_ILACE_COLOR;

    if ((res = dmC64SetImagePalette(dst, spec, mixed)) != DMERR_OK)
        return res;

    // Convert
    if (src->fmt->convertFrom != NULL)
        res = src->fmt->convertFrom(dst, src, spec);
    else
        res = dmC64ConvertGenericBMP2Image(dst, src, spec);

    return res;
}


int dmC64DecodeBMP(DMC64Image **img, const DMGrowBuf *buf,
    const size_t probeOffs, const size_t loadOffs,
    const DMC64ImageFormat **fmt, const DMC64ImageFormat *forced)
{
    DMGrowBuf tmp;

    if (img == NULL || buf == NULL)
        return DMERR_NULLPTR;

    // Check for forced format
    if (forced != NULL)
        *fmt = forced;
    else
    {
        // Nope, perform a generic probe
        if (probeOffs >= buf->len)
            return DMERR_OUT_OF_DATA;

        dmGrowBufConstCopyOffs(&tmp, buf, probeOffs);
        if (dmC64ProbeBMP(&tmp, fmt) == DM_PROBE_SCORE_FALSE)
            return DMERR_NOT_SUPPORTED;
    }

    if (loadOffs >= buf->len)
        return DMERR_INVALID_ARGS;

    if (*fmt == NULL)
        return DMERR_NOT_SUPPORTED;

    // Format supports only reading?
    if (((*fmt)->flags & DM_FMT_RD) == 0)
        return DMERR_NOT_SUPPORTED;

    // Allocate memory
    if ((*img = dmC64ImageAlloc(*fmt)) == NULL)
        return DMERR_MALLOC;

    dmGrowBufConstCopyOffs(&tmp, buf, loadOffs);

    // Decode the bitmap to memory layout
    if ((*fmt)->decode != NULL)
        return (*fmt)->decode(*img, &tmp, *fmt);
    else
        return dmC64DecodeGenericBMP(*img, &tmp, *fmt);
}


int dmC64MemBlockAllocSubj(DMC64Image *img, const int subject, const int bank)
{
    const char *subjname = dmC64GetOpSubjectName(subject);
    size_t size = dmC64GetSubjectSize(subject, img->fmt);
    DMC64MemBlock *blk = (DMC64MemBlock *) dmC64GetOpMemBlock(img, subject, bank);
    int res;

    if ((res = dmC64MemBlockAlloc(blk, size)) != DMERR_OK)
    {
        return dmError(res,
            "Could not allocate '%s' block with size %d bytes.\n",
            subjname, size);
    }

    return DMERR_OK;
}


// Convert a generic bitmap image to DMC64Image
int dmC64ConvertGenericImage2BMP(DMC64Image *dst, const DMImage *src,
    const DMC64ImageFormat *fmt, const DMC64ImageConvSpec *spec)
{
    if (dst == NULL || src == NULL || fmt == NULL || spec == NULL)
        return DMERR_NULLPTR;

    return DMERR_NOT_SUPPORTED;
}


int dmC64ConvertImage2BMP(DMC64Image **pdst, const DMImage *src,
    const DMC64ImageFormat *fmt, const DMC64ImageConvSpec *spec)
{
    int res;
    DMC64Image *dst;

    if (pdst == NULL || src == NULL)
        return DMERR_NULLPTR;

    // Allocate the basic C64 bitmap image structure
    if ((*pdst = dst = dmC64ImageAlloc(fmt)) == NULL)
        return DMERR_MALLOC;

    // Convert
    if (fmt->format->convertTo != NULL)
        res = fmt->format->convertTo(dst, src, fmt, spec);
    else
        res = dmC64ConvertGenericImage2BMP(dst, src, fmt, spec);

    return res;
}


int dmC64EncodeBMP(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;

    if (buf == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    if ((fmt->flags & DM_FMT_WR) == 0)
        return DMERR_NOT_SUPPORTED;

    // Allocate a buffer
    if ((res = dmGrowBufAlloc(buf, BUF_SIZE_INITIAL, BUF_SIZE_GROW)) != DMERR_OK)
        goto err;

    // Add the loading address
    if (!dmGrowBufPutU16LE(buf, fmt->addr))
        goto err;

    // Attempt to encode the image to a buffer
    if (fmt->encode != NULL)
        res = fmt->encode(buf, img, fmt);
    else
        res = dmC64EncodeGenericBMP(FALSE, buf, img, fmt);

    if (res != DMERR_OK)
        goto err;

    // Finally, if the format has a set size and our buffer is smaller
    // than that size, we grow the buffer to match (with zeroed data).
    // This accounts for format variants that are otherwise identical.
    if (fmt->size > 0 && buf->len < fmt->size &&
        !dmGrowBufCheckGrow(buf, fmt->size))
    {
        res = DMERR_MALLOC;
        goto err;
    }

    return DMERR_OK;

err:
    // In error case, free the buffer
    dmGrowBufFree(buf);
    return res;
}


// Perform probing of the given data buffer, trying to determine
// if it contains a supported "C64" image format. Returns the
// "probe score", see libgfx.h for list of values. If a match
// is found, pointer to format description is set to *pfmt.
int dmC64ProbeBMP(const DMGrowBuf *buf, const DMC64ImageFormat **pfmt)
{
    int scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;

    for (int i = 0; i < ndmC64ImageFormats; i++)
    {
        const DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        int score = DM_PROBE_SCORE_FALSE;
        if (fmt->probe == NULL && fmt->size > 0 && fmt->addr > 0)
        {
            // Generic probe just checks matching size and load address
            if (buf->len == fmt->size && dmCompareAddr16(buf, 0, fmt->addr))
                score = DM_PROBE_SCORE_GOOD;
        }
        else
        if (fmt->probe != NULL)
            score = fmt->probe(buf, fmt);

        if (score > scoreMax)
        {
            scoreMax = score;
            scoreIndex = i;
        }
    }

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


BOOL             dmLib64GFXInitialized = FALSE;
DMC64ImageFormat **dmC64ImageFormatsSorted = NULL;


int dmC64ImageFormatCompare(const void *va, const void *vb)
{
    const DMC64ImageFormat
        *fmta = *(DMC64ImageFormat **) va,
        *fmtb = *(DMC64ImageFormat **) vb;

    int res = fmta->format->mode - fmtb->format->mode;
    if (res == 0)
        return strcmp(fmta->name, fmtb->name);
    else
        return res;
}


int dmLib64GFXInit(void)
{
    // Safety check
    if (dmLib64GFXInitialized)
        dmLib64GFXClose();

    dmLib64GFXInitialized = TRUE;

    if ((dmC64ImageFormatsSorted = dmCalloc(ndmC64ImageFormats, sizeof(dmC64ImageFormatsSorted[0]))) == NULL)
        return DMERR_MALLOC;

    for (int i = 0; i < ndmC64ImageFormats; i++)
    {
        DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        if (fmt->format == NULL)
            fmt->format = &fmt->formatDef;

        dmC64ImageFormatsSorted[i] = fmt;
    }

    qsort(dmC64ImageFormatsSorted, ndmC64ImageFormats,
        sizeof(DMC64ImageFormat *), dmC64ImageFormatCompare);

    return DMERR_OK;
}


void dmLib64GFXClose(void)
{
    dmFreeR(&dmC64ImageFormatsSorted);
    dmLib64GFXInitialized = FALSE;
}