Mercurial > hg > dmlib
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; }