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