view tools/lib64gfx.c @ 1931:410679d2fe8a

"Enable" the image->c64 bitmap conversion path in gfxconv. It does not work without the necessary bits elsewhere, though. Also add DMC64ImageConvSpec structure for delivering conversion parameters, though it is not yet used either.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 28 Jun 2018 17:26:30 +0300
parents 1b55bcf548de
children c5a46cb4cce5
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-2018 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)


// Based on Pepto's palette, stolen from VICE
DMColor dmDefaultC64Palette[C64_NCOLORS] =
{
    { 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 },
};


char * dmC64GetImageTypeString(char *buf, const size_t len, const int type, const BOOL lng)
{
    snprintf(buf, len,
        "%s%s%s%s",
        (type & D64_FMT_MC)    ? (lng ? "MultiColor " : "MC ") : "HiRes ",
        (type & D64_FMT_ILACE) ? (lng ? "Interlaced " : "ILace ") : "",
        (type & D64_FMT_FLI)   ? "FLI " : "",
        (type & D64_FMT_CHAR)  ? "CHAR" : ""
        );

    return buf;
}


void dmC64ImageDump(FILE *fh, const DMC64Image *img, const DMC64ImageFormat *fmt, const char *indent)
{
    char typeStr[64], typeStr2[64];

    dmC64GetImageTypeString(typeStr, sizeof(typeStr), fmt->format->type, TRUE);

    if (fmt != NULL)
    {
        fprintf(fh,
            "%sFormat              : %s [%s]\n",
            indent, fmt->name, fmt->fext);
    }

    if (img != NULL)
    {
        dmC64GetImageTypeString(typeStr2, sizeof(typeStr2), img->type, TRUE);

        fprintf(fh,
            "%sType                : %s [%s]\n"
            "%sBanks               : %d\n",
            indent, typeStr, typeStr2,
            indent, img->nbanks);

        if (img->type & D64_FMT_ILACE)
        {
            char *tmps;
            switch (img->laceType)
            {
                case D64_ILACE_COLOR: tmps = "color"; break;
                case D64_ILACE_RES: tmps = "resolution"; break;
                default: tmps = "ERROR"; break;
            }
            fprintf(fh,
                "%sInterlace type      : %s\n",
                indent, tmps);
        }

        fprintf(fh,
            "%sWidth x Height      : %d x %d [%d x %d]\n"
            "%sCHwidth x CHheight  : %d x %d [%d x %d]\n",
            indent, img->width, img->height,
            fmt->format->width, fmt->format->height,
            indent, img->chWidth, img->chHeight,
            fmt->format->chWidth, fmt->format->chHeight);
    }
    else
    {
        fprintf(fh,
            "%sType                : %s\n"
            "%sWidth x Height      : %d x %d\n"
            "%sCHwidth x CHheight  : %d x %d\n",
            indent, typeStr,
            indent, fmt->format->width, fmt->format->height,
            indent, fmt->format->chWidth, fmt->format->chHeight);
    }
}


void dmSetDefaultC64Palette(DMImage *img)
{
    img->constpal = TRUE;
    img->pal      = dmDefaultC64Palette;
    img->ncolors  = C64_NCOLORS;
    img->ctransp  = 255;
}


BOOL dmSetMixedColorC64Palette(DMImage *img)
{
    if (!dmImagePaletteAlloc(img, C64_NCOLORS * C64_NCOLORS, -1))
        return FALSE;

    int n = 0;
    for (int n1 = 0; n1 < C64_NCOLORS; n1++)
    {
        const DMColor *col1 = &dmDefaultC64Palette[n1];
        for (int n2 = 0; n2 < C64_NCOLORS; n2++)
        {
            const DMColor *col2 = &dmDefaultC64Palette[n2];
            img->pal[n].r = (col1->r + col2->r) / 2;
            img->pal[n].g = (col1->g + col2->g) / 2;
            img->pal[n].b = (col1->b + col2->b) / 2;
            n++;
        }
    }

    return TRUE;
}


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 dmC64ImageGetNumBanks(const DMC64ImageFormat *fmt)
{
    int nbanks = 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 > nbanks)
            nbanks = op->bank;
    }

    return nbanks + 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->type        = fmt->format->type;
    img->width       = fmt->format->width;
    img->height      = fmt->format->height;
    img->chWidth     = fmt->format->chWidth;
    img->chHeight    = fmt->format->chHeight;
    img->nbanks      = dmC64ImageGetNumBanks(fmt);

    // Allocate banks
    if ((img->color = dmCalloc(img->nbanks, sizeof(DMC64MemBlock))) == NULL ||
        (img->bitmap = dmCalloc(img->nbanks, sizeof(DMC64MemBlock))) == NULL ||
        (img->screen = dmCalloc(img->nbanks, sizeof(DMC64MemBlock))) == NULL ||
        (img->charData = dmCalloc(img->nbanks, 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->nbanks; 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 < C64_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);
}


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

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


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

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


int dmGenericRLEOutputRun(DMGrowBuf *dst, const DMCompParams *cfg, const Uint8 data, const unsigned int count)
{
    unsigned int scount;
    for (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))
                {
                    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))
                {
                    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)
{
    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->nbanks)
                    {
                        return dmError(DMERR_INTERNAL,
                            "Invalid bank %d / %d definition in generic encode/decode operator %d @ #%d.\n",
                            op->bank, img->nbanks, op->type, i);
                    }
                    break;

                case DS_EXTRA_DATA:
                    if (op->bank < 0 || op->bank >= C64_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 DMC64ImageFormat *fmt)
{
    switch (subject)
    {
        case DS_SCREEN_RAM:
        case DS_COLOR_RAM:
            return fmt->format->chHeight * fmt->format->chWidth;
            break;

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

        case DS_CHAR_DATA:
            return C64_MAX_CHARS * C64_CHR_SIZE;
            break;

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

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


size_t dmC64GetOpSubjectSize(const DMC64EncDecOp *op, const DMC64ImageFormat *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;
}


void dmC64GetOpMemBlock(const DMC64Image *img, const int subject, const int bank, const DMC64MemBlock **blk)
{
    *blk = NULL;

    if (bank >= 0 && bank < img->nbanks)
    {
        switch (subject)
        {
            case DS_COLOR_RAM  : *blk = &img->color[bank]; break;
            case DS_SCREEN_RAM : *blk = &img->screen[bank]; break;
            case DS_BITMAP_RAM : *blk = &img->bitmap[bank]; break;
            case DS_CHAR_DATA  : *blk = &img->charData[bank]; break;
            case DS_EXTRA_DATA : *blk = &img->extraData[bank]; break;
        }
    }
}


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->type      = fmt->format->type;
    img->width     = fmt->format->width;
    img->height    = fmt->format->height;
    img->chWidth   = fmt->format->chWidth;
    img->chHeight  = fmt->format->chHeight;
    img->nbanks    = dmC64ImageGetNumBanks(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;

        // Is the operation inside the bounds?
        size = dmC64GetOpSubjectSize(op, fmt);
        if (op->type == DO_COPY && op->offs + size > buf->len + 1)
        {
            return dmError(DMERR_INVALID_DATA,
                "Decode SRC out of bounds, op #%d type=%d, subj=%s, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                i, 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 --v
                        dmC64GetOpMemBlock(img, op->subject, op->bank, (const DMC64MemBlock **) &blk);

                        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 %d in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    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 %d in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    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->nbanks; 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 &&
                    !op->decFunction(img, op, buf, fmt))
                {
                    return dmError(DMERR_INTERNAL,
                        "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;

        // Do we need to reallocate some more space?
        size = dmC64GetOpSubjectSize(op, fmt);
        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:
                        dmC64GetOpMemBlock(img, op->subject, op->bank, &blk);
                        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 %d in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    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 %d in "
                                    "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                                    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 &&
                    !op->encFunction(op, buf, img, fmt))
                {
                    res = dmError(DMERR_INTERNAL,
                        "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;
}


// 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->width || dst->height < src->height)
    {
        return dmError(DMERR_INVALID_DATA,
            "Invalid src vs. dst width/height %d x %d <-> %d x %d\n",
            src->width, src->height, dst->width, dst->height);
    }

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

    // Check pixel getter function
    if (fmt->format->getPixel != NULL)
        getPixel = fmt->format->getPixel;
    else
        getPixel = (fmt->format->type & D64_FMT_MC) ? fmtGetGenericMCPixel : fmtGetGenericSCPixel;

    // Resolution interlaced pics need to halve the source width
    int rwidth = src->width;
    if ((src->type & D64_FMT_ILACE) && src->laceType == D64_ILACE_RES)
        rwidth /= 2;

    // Perform conversion
    for (int yc = 0; yc < src->height; yc++)
    {
        Uint8 *dp = dst->data + (yc * dst->pitch);
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * src->chWidth;
        const int bmoffsy = y * src->chWidth * 8 + yb;

        for (int xc = 0; xc < rwidth; xc++)
        if (src->type & D64_FMT_CHAR)
        {
            // Charmode conversion
            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            {
                // Hi-res charmap
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0].data[scroffs];
                const int vshift = 7 - (xc & 7);

                if ((src->charData[0].data[chr * C64_CHR_SIZE + yb] >> vshift) & 1)
                    *dp++ = src->color[0].data[scroffs];
                else
                    *dp++ = src->bgcolor;
            }
            else
            {
                // Multicolor variants
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0].data[scroffs];
                const int col = src->color[0].data[scroffs] & 15;

                if (col & 8)
                {
                    const int vshift = 6 - ((xc * 2) & 6);
                    switch ((src->charData[0].data[chr * C64_CHR_SIZE + yb] >> vshift) & 3)
                    {
                        case 0: *dp++ = src->bgcolor; break;
                        case 1: *dp++ = src->d022; break;
                        case 2: *dp++ = src->d023; break;
                        case 3: *dp++ = col;
                    }
                }
                else
                {
                    const int vshift = 7 - (xc & 7);
                    if ((src->charData[0].data[chr * C64_CHR_SIZE + yb] >> vshift) & 1)
                        *dp++ = src->color[0].data[scroffs];
                    else
                        *dp++ = src->bgcolor;
                }
            }
        }
        else
        {
            // Perform generic BITMAP conversion
            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            {
                // Hi-res bitmap
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int vshift = 7 - (xc & 7);

                *dp++ = getPixel(src, bmoffs, scroffs, vshift, 0, xc, yc);
            }
            else
            {
                // Multicolor bitmap and variants
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int vshift = 6 - ((xc * 2) & 6);

                if (src->type & D64_FMT_ILACE)
                {
                    switch (src->laceType)
                    {
                        case D64_ILACE_RES:
                            *dp++ = getPixel(src, bmoffs, scroffs, vshift, 0, xc, yc);
                            *dp++ = getPixel(src, bmoffs, scroffs, vshift, 1, xc, yc);
                            break;

                        default:
                            return DMERR_NOT_SUPPORTED;
                    }
                }
                else
                {
                    *dp++ = getPixel(src, bmoffs, scroffs, vshift, 0, xc, yc);
                }
            }
        }
    }

    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->width, src->height, DM_COLFMT_PALETTE, -1)) == NULL)
        return DMERR_MALLOC;

    // Set partial palette information
    dst->ncolors  = C64_NCOLORS;
    dst->constpal = TRUE;
    dst->pal      = dmDefaultC64Palette;

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


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


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


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