view tools/lib64gfx.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents 7694b5c8edc1
children 5477e792def3
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;
    }
}


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

    if (img == NULL)
        return NULL;

    // Initialize image information
    img->fmt         = fmt->format;
    img->nblocks     = dmC64ImageGetNumBlocks(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",
        "d021/bgcol",
        "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;

    // Clear the image structure, set basics
    img->nblocks    = dmC64ImageGetNumBlocks(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:
                        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;
                        }
                        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 ((fmt->format->type & D64_FMT_ILACE) && img->laceType == 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:
                        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;
                        }
                        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
                                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_CHAR_MODE])
    {
        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 type/fmt=0x%x.\n",
                img->fmt->type);
    }
}


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

    // Sanity check arguments
    if (dst == NULL || src == NULL || fmt == 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->fmt->type & D64_FMT_CHAR)
        getPixel = fmtGetGenericCharPixel;
    else
    switch (src->fmt->type & 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->fmt->type);
    }

    // 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 DMC64ImageFormat *fmt, const DMC64ImageConvSpec *spec)
{
    int res;
    DMImage *dst;

    if (pdst == NULL || src == NULL || fmt == 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
    if ((res = dmC64SetImagePalette(dst, spec, FALSE)) != DMERR_OK)
        return res;

    // Convert
    if (fmt->format->convertFrom != NULL)
        res = fmt->format->convertFrom(dst, src, fmt, spec);
    else
        res = dmC64ConvertGenericBMP2Image(dst, src, fmt, 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->type - fmtb->format->type;
    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;
}