Mercurial > hg > dmlib
view tools/lib64gfx.c @ 2291:4f3bd5710585
Merge.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 03 Jul 2019 01:52:19 +0300 |
parents | 48b48251610a |
children | cd266022e4a8 |
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; } } static void dmC64SetupImageData(DMC64Image *img, const DMC64ImageFormat *fmt) { img->fmt = fmt->format; img->nblocks = dmC64ImageGetNumBlocks(fmt); memset(img->extraInfo, 0, sizeof(img->extraInfo)); img->extraInfo[D64_EI_MODE] = fmt->format->mode; } DMC64Image *dmC64ImageAlloc(const DMC64ImageFormat *fmt) { DMC64Image *img = dmMalloc0(sizeof(DMC64Image)); if (img == NULL) return NULL; // Initialize image information dmC64SetupImageData(img, 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 / border", "d021 / background", "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; dmC64SetupImageData(img, 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: case DS_EXTRA_INFO: 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; case DS_EXTRA_INFO: img->extraInfo[op->offs2] = 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 ((img->extraInfo[D64_EI_MODE] & D64_FMT_ILACE) && img->extraInfo[D64_EI_ILACE_TYPE] == 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: case DS_EXTRA_INFO: 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; case DS_EXTRA_INFO: value = img->extraInfo[op->offs2]; 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 // XXX TODO: what about DS_EXTRA_INFO? 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_MODE] & D64_FMT_MODE_MASK) { 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 mode=0x%x.\n", img->extraInfo[D64_EI_MODE]); } } // Convert a generic "C64" format bitmap in DMC64Image struct to // a indexed/paletted bitmap image. int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src, const DMC64ImageConvSpec *spec) { DMC64GetPixelFunc getPixel; // Sanity check arguments if (dst == NULL || src == 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->extraInfo[D64_EI_MODE] & D64_FMT_CHAR) getPixel = fmtGetGenericCharPixel; else switch (src->extraInfo[D64_EI_MODE] & 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->extraInfo[D64_EI_MODE]); } // 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 DMC64ImageConvSpec *spec) { DMImage *dst; BOOL mixed; int res; if (pdst == NULL || src == 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 mixed = (src->extraInfo[D64_EI_MODE] & D64_FMT_ILACE) && src->extraInfo[D64_EI_ILACE_TYPE] == D64_ILACE_COLOR; if ((res = dmC64SetImagePalette(dst, spec, mixed)) != DMERR_OK) return res; // Convert if (src->fmt->convertFrom != NULL) res = src->fmt->convertFrom(dst, src, spec); else res = dmC64ConvertGenericBMP2Image(dst, src, 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->mode - fmtb->format->mode; 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; }