view lib64gfx.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents 1ed5025c2538
children 4cdcaeb68b54
line wrap: on
line source

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


const char *dmC64ImageTypeNames[DM_C64IFMT_LAST_TYPE] =
{
    "hires",
    "multicolor",
    "hires interlace",
    "mc interlace",
    "hires fli",
    "mc fli",
};


// Based on Pepto's palette, stolen from VICE
DMColor dmC64Palette[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 },
};


const size_t dmC64DefaultSizes[DT_LAST] =
{
    C64_SCR_COLOR_SIZE,
    C64_SCR_BITMAP_SIZE,
    C64_SCR_SCREEN_SIZE,
    1,
    C64_SCR_EXTRADATA,
};



int dmC64ConvertCSData(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 -1;
    if (xoffs < 0 || yoffs < 0)
        return -2;
    if (xoffs > img->width - widthpx ||
        yoffs > img->height - height)
        return -3;

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

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

            for (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;
            int xc;
            Uint8 *d = dp;

            for (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 0;
}


static int fmtProbeDrazPaint(const Uint8 *buf, const size_t len)
{
    if (len == 10051 && buf[0] == 0x00 && buf[1] == 0x58)
        return DM_PROBE_SCORE_GOOD;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeDrazPaint20Packed(const Uint8 *buf, const size_t len)
{
    const char *ident = (const char *) buf + 2;
    if (len > 22 && buf[0] == 0x00 && buf[1] == 0x58 &&
            strncmp(ident, "DRAZPAINT ", 10) == 0 &&
            ident[11] == '.' && (
            (ident[10] == '1' && ident[12] == '4') ||
            (ident[10] == '2' && ident[12] == '0')
            ))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeDrazPaintPacked(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    Uint8 rleMarker;
    Uint8 *mem, *dst, *dstEnd;
    const Uint8 *src, *srcEnd;

    if ((mem = dmMalloc(C64_RAM_SIZE)) == NULL)
        return -1;
    
    rleMarker = *(buf + 0x0d);
    src       = buf + 0x0e;
    srcEnd    = buf + len;
    dst       = mem;
    dstEnd    = mem + C64_RAM_SIZE;

    while (src <= srcEnd && dst <= dstEnd)
    {
        int c = *src++;
        if (c == rleMarker && src + 2 <= srcEnd)
        {
            int cnt = *src++;
            c = *src++;
            while (cnt-- && dst <= dstEnd)
                *dst++ = c;
        }
        else
            *dst++ = c;
    }
    
    res = dmC64DecodeGenericBMP(img, mem, dst - mem + 1, fmt);
    
    dmFree(mem);

    return res;
}


static int fmtProbeDrazLace10(const Uint8 *buf, const size_t len)
{
    if (len == 18242 && buf[0] == 0x00 && buf[1] == 0x58)
        return DM_PROBE_SCORE_GOOD;
    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeDrazLace10Packed(const Uint8 *buf, const size_t len)
{
    const char *ident = (const char *) buf + 2;
    if (len > 22 && buf[0] == 0x00 && buf[1] == 0x58 &&
            strncmp(ident, "DRAZLACE! 1.0", 13) == 0)
        return DM_PROBE_SCORE_MAX;
    
    return DM_PROBE_SCORE_FALSE;
}


static BOOL fmtDrazLaceSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) len;

    img->laceType = buf[op->offs] ? DM_C64ILACE_RES : DM_C64ILACE_COLOR;
    img->laceBank2 = 0;
    return TRUE;
}


#define AMICA_DM_PROBE_SIZE 1024
static int fmtProbeAmicaPaintPacked(const Uint8 *buf, const size_t len)
{
    int i, n;
    if (len < AMICA_DM_PROBE_SIZE || buf[0] != 0x00 || buf[1] != 0x40)
        return DM_PROBE_SCORE_FALSE;
    
    // Interpaint Hi-Res gives a false positive
    if (len == 9002)
        return DM_PROBE_SCORE_FALSE;
    
    for (n = 0, i = 2; i < AMICA_DM_PROBE_SIZE; i++)
        if (buf[i] == 0xC2) n++;
    
    if (n > 5)
        return DM_PROBE_SCORE_GOOD;
    if (n > 3)
        return DM_PROBE_SCORE_AVG;

    return DM_PROBE_SCORE_MAYBE;
}


static int fmtDecodeAmicaPaintPacked(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    Uint8 *mem, *dst, *dstEnd;
    const Uint8 *src, *srcEnd;

    if ((mem = dmMalloc(C64_RAM_SIZE)) == NULL)
        return -1;
    
    src    = buf;
    srcEnd = buf + len;
    dst    = mem;
    dstEnd = mem + C64_RAM_SIZE;

    while (src <= srcEnd && dst <= dstEnd)
    {
        int c = *src++;
        if (c == 0xC2 && src + 2 <= srcEnd)
        {
            int cnt = *src++;
            c = *src++;
            while (cnt-- && dst <= dstEnd)
                *dst++ = c;
        }
        else
            *dst++ = c;
    }
    
    res = dmC64DecodeGenericBMP(img, mem, dst - mem + 1, fmt);
    
    dmFree(mem);

    return res;
}


static int fmtProbeKoalaPaint(const Uint8 *buf, const size_t len)
{
    if (len == 10003 && buf[0] == 0x00 && buf[1] == 0x60)
        return DM_PROBE_SCORE_AVG;
    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeTruePaint(const Uint8 *buf, const size_t len)
{
    if (len == 19434 && buf[0] == 0x00 && buf[1] == 0x9c)
        return DM_PROBE_SCORE_GOOD;
    return DM_PROBE_SCORE_FALSE;
}


static BOOL fmtTruePaintSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) op;
    (void) buf;
    (void) len;
    img->laceType = DM_C64ILACE_RES;
    img->laceBank2 = 1;
    return TRUE;
}


DMC64ImageFormat dmC64ImageFormats[] =
{
    {
        DM_C64IFMT_MC, ".drp", "DrazPaint 2.0 (packed)",
        fmtProbeDrazPaint20Packed, fmtDecodeDrazPaintPacked,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2740, 0,  0, NULL, NULL },
        }
    },

    {
        DM_C64IFMT_MC_ILACE, ".dlp", "DrazLace 1.0 (packed)",
        fmtProbeDrazLace10Packed, fmtDecodeDrazPaintPacked,
        NULL, NULL, NULL,
        6,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2740, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
        }
    },
    
    {
        DM_C64IFMT_MC, ".drp", "DrazPaint (unpacked)",
        fmtProbeDrazPaint, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2740, 0,  0, NULL, NULL },
        }
    },

    {
        DM_C64IFMT_MC_ILACE, ".drl", "DrazLace 1.0 (unpacked)",
        fmtProbeDrazLace10, NULL,
        NULL, NULL, NULL,
        6,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2740, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
        }
    },
    
    {
        DM_C64IFMT_MC_ILACE, ".mci", "Truepaint (unpacked)",
        fmtProbeTruePaint, NULL,
        NULL, NULL, NULL,
        6,
        {
            { DT_SCREEN_RAM,   0x0000, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x03e8, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0400, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x2400, 1,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x4400, 1,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x4800, 0,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x0000, 0,  0, fmtTruePaintSetLaceType, NULL },
        }
    },
    
    {
        DM_C64IFMT_MC, ".kla", "Koala Paint (unpacked)",
        fmtProbeKoalaPaint, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2710, 0,  0, NULL, NULL },
        }
    },

    {
        DM_C64IFMT_MC, ".ami", "Amica Paint (packed)",
        fmtProbeAmicaPaintPacked, fmtDecodeAmicaPaintPacked,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_BGCOLOR,      0x2710, 0,  0, NULL, NULL },
        }
    },
    
};

const int ndmC64ImageFormats = sizeof(dmC64ImageFormats) / sizeof(dmC64ImageFormats[0]);


int dmC64ProbeGeneric(const Uint8 *buf, const size_t len, DMC64ImageFormat **pfmt)
{
    int i, scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;

    for (i = 0; i < ndmC64ImageFormats; i++)
    {
        DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        int score = fmt->probe(buf, len);
        if (score > scoreMax)
        {
            scoreMax = score;
            scoreIndex = i;
        }
    }

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


static int dmC64SanityCheckEncDecOp(const int i, const DMC64EncDecOp *op)
{
    if (op->bank < 0 || op->bank >= C64_SCR_MAX_BANK)
    {
        dmError("Invalid bank %d definition in generic encode/decode operator %d @ #%d.\n",
            op->bank, op->type, i);
        return DMERR_INTERNAL;
    }

    if (op->type < 0 || op->type >= DT_LAST)
    {
        dmError("Invalid encode/decode operator type %d @ #%d.\n",
            op->type, i);
        return DMERR_INTERNAL;
    }

    return DMERR_OK;
}


int dmC64DecodeGenericBMP(DMC64Image *img, const Uint8 *buf,
    const size_t len, const DMC64ImageFormat *fmt)
{
    int i;

    memset(img, 0, sizeof(*img));
    img->type = fmt->type;

    for (i = 0; i < fmt->ndecencOps; i++)
    {
        const DMC64EncDecOp *op = &fmt->decencOps[i];
        const Uint8 *src;
        size_t size;
        int res;

        if ((res = dmC64SanityCheckEncDecOp(i, op)) != DMERR_OK)
            return res;
        
        size = (op->size == 0) ? dmC64DefaultSizes[op->type] : op->size;
        
        if (op->offs + size > len)
        {
            dmError("Decode out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, len, len);
            return DMERR_INVALID_DATA;
        }
        
        src = buf + op->offs;
        
        switch (op->type)
        {
            case DT_COLOR_RAM:   memcpy(img->color[op->bank], src, size); break;
            case DT_BITMAP:      memcpy(img->bitmap[op->bank], src, size); break;
            case DT_SCREEN_RAM:  memcpy(img->screen[op->bank], src, size); break;
            case DT_BGCOLOR:     img->bgcolor = *src; break;
            case DT_EXTRADATA:   memcpy(img->extradata, src, size); break;
            case DT_DEC_FUNCTION:
                if (op->decfunction == NULL)
                {
                    dmError("Decode op is a function, but function ptr is NULL: "
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                    return DMERR_INTERNAL;
                }
                if (!op->decfunction(img, op, buf, len))
                {
                    dmError("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, len, len);
                    return DMERR_INTERNAL;
                }
                break;
        }
    }
    
    return DMERR_OK;
}


static int dmC64ConvertHiResBMP(DMImage *screen, const DMC64Image *img)
{
    int yc;
    Uint8 *dp = screen->data;
    
    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
    {
        Uint8 *d = dp;
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * C64_SCR_CH_WIDTH;
        const int bmoffsy = y * C64_SCR_WIDTH;
        int xc;

        for (xc = 0; xc < C64_SCR_WIDTH; xc++)
        {
            const int x = xc / 8;
            const int scroffs = scroffsy + x;
            const int b = img->bitmap[0][bmoffsy + (x * 8) + yb];
            const int v = 7 - (xc & 7);
            Uint8 c;

            if ((b >> v) & 1)
                c = img->screen[0][scroffs] & 15;
            else
                c = img->screen[0][scroffs] >> 4;
            
            *d++ = c;
        }

        dp += screen->pitch;
    }

    return DMERR_OK;
}


static int dmC64ConvertMultiColorBMP(DMImage *screen, const DMC64Image *img)
{
    int yc;
    Uint8 *dp = screen->data;
    
    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
    {
        Uint8 *d = dp;
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * C64_SCR_CH_WIDTH;
        const int bmoffsy = y * C64_SCR_WIDTH;
        int xc;

        for (xc = 0; xc < C64_SCR_WIDTH / 2; xc++)
        {
            const int x = xc / 4;
            const int scroffs = scroffsy + x;
            const int b = img->bitmap[0][bmoffsy + (x * 8) + yb];
            const int v = 6 - ((xc * 2) & 6);
            Uint8 c;

            switch ((b >> v) & 3)
            {
                case 0: c = img->bgcolor; break;
                case 1: c = img->screen[0][scroffs] >> 4; break;
                case 2: c = img->screen[0][scroffs] & 15; break;
                case 3: c = img->color[0][scroffs] & 15; break;
            }
            
            *d++ = c;
            *d++ = c;
        }

        dp += screen->pitch;
    }

    return DMERR_OK;
}


static int dmC64ConvertLaceMultiColorBMP(DMImage *screen, const DMC64Image *img)
{
    int yc;
    Uint8 *dp = screen->data;
    
    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
    {
        Uint8 *d = dp;
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * C64_SCR_CH_WIDTH;
        const int bmoffsy = y * C64_SCR_WIDTH;
        int xc;

        for (xc = 0; xc < C64_SCR_WIDTH / 2; xc++)
        {
            const int x = xc / 4;
            const int scroffs = scroffsy + x;
            const int bmoffs = bmoffsy + (x * 8) + yb;
            const int v = 6 - ((xc * 2) & 6);
            const int b1 = (img->bitmap[0][bmoffs] >> v) & 3;
            const int b2 = (img->bitmap[1][bmoffs] >> v) & 3;
            Uint8 c1, c2;

            switch (b1)
            {
                case 0: c1 = img->bgcolor; break;
                case 1: c1 = img->screen[0][scroffs] >> 4; break;
                case 2: c1 = img->screen[0][scroffs] & 15; break;
                case 3: c1 = img->color[0][scroffs] & 15; break;
            }

            switch (b2)
            {
                case 0: c2 = img->bgcolor; break;
                case 1: c2 = img->screen[img->laceBank2][scroffs] >> 4; break;
                case 2: c2 = img->screen[img->laceBank2][scroffs] & 15; break;
                case 3: c2 = img->color[img->laceBank2][scroffs] & 15; break;
            }
            
            *d++ = c1;
            *d++ = c2;
        }

        dp += screen->pitch;
    }

    return DMERR_OK;
}


int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src)
{
    switch (src->type)
    {
        case DM_C64IFMT_HIRES:
            return dmC64ConvertHiResBMP(dst, src);
        
        case DM_C64IFMT_MC:
            return dmC64ConvertMultiColorBMP(dst, src);

        case DM_C64IFMT_MC_ILACE:
            return dmC64ConvertLaceMultiColorBMP(dst, src);

        default:
            return -1;
    }
}


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

int dmReadDataFile(FILE *inFile, const char *filename, Uint8 **pbuf, size_t *pbufSize)
{
    FILE *f;
    int res = DMERR_OK;
    Uint8 *dataBuf = NULL, *dataPtr;
    size_t readSize, dataSize, dataRead;
    
    if (inFile != NULL)
        f = inFile;
    else
    if (filename != NULL)
    {
        if ((f = fopen(filename, "rb")) == NULL)
        {
            dmError("Could not open '%s' for reading.\n", filename);
            return DMERR_FOPEN;
        }
    }
    else
    {
        dmError("NULL filename and stream pointers.\n");
        return DMERR_NULLPTR;
    }

    // Allocate initial data buffer
    readSize = dataSize = BUF_SIZE_INITIAL;
    if ((dataBuf = dmMalloc(dataSize)) == NULL)
    {
        dmError("Error allocating memory for data, %d bytes.\n", dataSize);
        res = DMERR_MALLOC;
        goto error;
    }

    dataPtr = dataBuf;
    dataRead = 0;

    while (!feof(f) && !ferror(f))
    {
        size_t read = fread(dataPtr, 1, readSize, f);
        dataPtr += read;
        dataRead += read;

        if (dataRead >= dataSize)
        {
            readSize = BUF_SIZE_GROW;
            dataSize += BUF_SIZE_GROW;
            if ((dataBuf = dmRealloc(dataBuf, dataSize)) == NULL)
            {
                dmError("Error reallocating memory for data, %d bytes.\n", dataSize);
                res = DMERR_MALLOC;
                goto error;
            }
        }
        else
            break;
    }

    *pbufSize = dataRead;
    *pbuf = dataBuf;

error:
    if (f != inFile)
        fclose(f);
    
    return res;
}


int dmC64DecodeBMP(DMC64Image *img, const Uint8 *buf, const size_t len,
    const size_t probeOffs, const size_t loadOffs,
    DMC64ImageFormat **fmt, DMC64ImageFormat *forced)
{
    // Check for forced format
    if (forced != NULL)
        *fmt = forced;
    else
    {
        // Nope, perform a generic probe
        if (probeOffs >= len)
            return -200;

        if (dmC64ProbeGeneric(buf + probeOffs, len - probeOffs, fmt) == DM_PROBE_SCORE_FALSE)
            return -201;
    }

    if (loadOffs >= len)
        return -203;

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