Mercurial > hg > dmlib
view tools/libgfx.c @ 2408:60e119262c67
Option index re-ordering cleanup work.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 13 Jan 2020 21:21:25 +0200 |
parents | 85700c9b7dc8 |
children | 69a5af2eb1ea |
line wrap: on
line source
/* * Functions for reading and converting various graphics file formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2020 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "libgfx.h" #ifdef DM_USE_LIBPNG #include <png.h> #endif int dmGFXErrorMode = DM_ERRMODE_FAIL; void dmInitBitStreamContext(DMBitStreamContext *ctx) { ctx->outBuf = 0; ctx->outByteCount = 0; ctx->outBitCount = 0; ctx->inBuf = 0; ctx->inByteCount = 0; ctx->inBitCount = 0; } int dmPutBits(DMBitStreamContext *ctx, const unsigned int val, const unsigned int n) { for (unsigned int i = 0; i < n; i++) { ctx->outBuf <<= 1; if (val & (1 << (n - 1 - i))) ctx->outBuf |= 1; ctx->outBitCount++; if (ctx->outBitCount == 8) { int ret; if ((ret = ctx->putByte(ctx)) != DMERR_OK) return ret; ctx->outBitCount = 0; ctx->outByteCount++; } } return DMERR_OK; } int dmGetBits(DMBitStreamContext *ctx, unsigned int *val, const unsigned int n) { *val = 0; for (unsigned int i = 0; i < n; i++) { if (ctx->inBitCount == 0) { int ret; if ((ret = ctx->getByte(ctx)) != DMERR_OK) return ret; ctx->inBitCount = 8; ctx->inByteCount++; } *val <<= 1; *val |= ctx->inBuf >> 7 & 1; ctx->inBuf <<= 1; ctx->inBitCount--; } return DMERR_OK; } int dmFlushBitStream(DMBitStreamContext *ctx) { if (ctx == NULL) return DMERR_NULLPTR; if (ctx->outBitCount > 0) return dmPutBits(ctx, 0, 8 - ctx->outBitCount); return DMERR_OK; } int dmGetNPlanesFromNColors(const int ncolors) { if (ncolors <= 0) return -1; for (int n = 8; n >= 0;) { if ((ncolors - 1) & (1 << n)) return n + 1; else n--; } return -2; } BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, const BOOL alpha) { if (c1->r == c2->r && c1->g == c2->g && c1->b == c2->b) return alpha ? (c1->a == c2->a) : TRUE; else return FALSE; } int dmImageGetBytesPerPixel(const int format) { switch (format) { case DM_PIXFMT_GRAYSCALE : return 1; case DM_PIXFMT_PALETTE : return 1; case DM_PIXFMT_RGB : return 3; case DM_PIXFMT_RGBA : return 4; default : return -1; } } int dmImageGetBitsPerPixel(const int format) { return dmImageGetBytesPerPixel(format) * 8; } DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp) { DMImage *img = dmMalloc0(sizeof(DMImage)); if (img == NULL) return NULL; img->width = width; img->height = height; img->pixfmt = format; img->bpp = (bpp <= 0) ? dmImageGetBitsPerPixel(format) : bpp; img->pitch = (width * img->bpp) / 8; img->size = img->pitch * img->height; img->aspect = -1; if ((img->data = dmMalloc(img->size)) == NULL) { dmFree(img); return NULL; } return img; } void dmImageFree(DMImage *img) { if (img != NULL) { if (!img->constpal) dmPaletteFree(img->pal); dmFree(img->data); dmFree(img); } } int dmPaletteAlloc(DMPalette **ppal, const int ncolors, const int ctransp) { DMPalette *pal; if (ppal == NULL) return DMERR_NULLPTR; *ppal = NULL; // Allocate palette struct if ((pal = dmMalloc0(sizeof(DMPalette))) == NULL) return DMERR_MALLOC; pal->ncolors = ncolors; pal->ctransp = ctransp; // Allocate desired amount of palette if ((pal->colors = dmCalloc(pal->ncolors, sizeof(DMColor))) == NULL) { dmFree(pal); return DMERR_MALLOC; } // Set alpha values to max, except for transparent color for (int i = 0; i < pal->ncolors; i++) { pal->colors[i].a = (i == pal->ctransp) ? 0x00 : 0xff; } *ppal = pal; return DMERR_OK; } int dmPaletteResize(DMPalette **ppal, const int ncolors) { DMPalette *pal; if (ppal == NULL) return DMERR_NULLPTR; if (ncolors <= 0 || ncolors > 256) return DMERR_INVALID_ARGS; if (*ppal == NULL) return dmPaletteAlloc(ppal, ncolors, -1); pal = *ppal; if (pal->ncolors == ncolors) return DMERR_OK; if ((pal->colors = dmRealloc(pal->colors, sizeof(DMColor) * ncolors)) == NULL) return DMERR_MALLOC; if (ncolors - pal->ncolors > 0) memset(&(pal->colors[pal->ncolors]), 0, sizeof(DMColor) * (ncolors - pal->ncolors)); pal->ncolors = ncolors; return DMERR_OK; } int dmPaletteCopy(DMPalette **pdst, const DMPalette *src) { DMPalette *pal; if (pdst == NULL) return DMERR_NULLPTR; *pdst = NULL; // Allocate palette struct if ((pal = dmMalloc(sizeof(DMPalette))) == NULL) return DMERR_MALLOC; memcpy(pal, src, sizeof(DMPalette)); // Allocate desired amount of palette if ((pal->colors = dmCalloc(pal->ncolors, sizeof(DMColor))) == NULL) { dmFree(pal); return DMERR_MALLOC; } memcpy(pal->colors, src->colors, sizeof(DMColor) * pal->ncolors); *pdst = pal; return DMERR_OK; } void dmPaletteFree(DMPalette *pal) { if (pal != NULL) { dmFree(pal->colors); dmFree(pal); } } static int dmPaletteReadData(DMResource *fp, DMPalette *pal, const int ncolors) { if (pal->ncolors < ncolors) return DMERR_INVALID_ARGS; for (int i = 0; i < ncolors; i++) { Uint8 colR, colG, colB; if (!dmf_read_byte(fp, &colR) || !dmf_read_byte(fp, &colG) || !dmf_read_byte(fp, &colB)) return DMERR_FREAD; pal->colors[i].r = colR; pal->colors[i].g = colG; pal->colors[i].b = colB; } return DMERR_OK; } static int dmPaletteWriteData(DMResource *fp, const DMPalette *pal, const int ncolors, const int npad) { int i; if (pal == NULL || fp == NULL) return DMERR_NULLPTR; if (pal->ncolors < ncolors) return DMERR_INVALID_ARGS; for (i = 0; i < ncolors; i++) { DMColor *col = &pal->colors[i]; if (!dmf_write_byte(fp, col->r) || !dmf_write_byte(fp, col->g) || !dmf_write_byte(fp, col->b)) return DMERR_FWRITE; } for (; i < npad; i++) { if (!dmf_write_byte(fp, 0) || !dmf_write_byte(fp, 0) || !dmf_write_byte(fp, 0)) return DMERR_FWRITE; } return DMERR_OK; } static int fmtProbeACTPalette(const Uint8 *buf, const size_t len) { if (len == 0x304 && buf[0x300] == 0) return DM_PROBE_SCORE_MAX; return DM_PROBE_SCORE_FALSE; } int dmReadACTPalette(DMResource *fp, DMPalette **pdst) { int res; Uint16 tmp1, tmp2; if ((res = dmPaletteAlloc(pdst, 256, -1)) != DMERR_OK) return res; if ((res = dmPaletteReadData(fp, *pdst, 256)) != DMERR_OK) goto error; if (!dmf_read_be16(fp, &tmp1) || !dmf_read_be16(fp, &tmp2)) { res = DMERR_FREAD; goto error; } if (tmp1 == 0 || tmp1 > 256) { res = DMERR_INVALID_DATA; goto error; } if ((res = dmPaletteResize(pdst, tmp1)) != DMERR_OK) goto error; (*pdst)->ctransp = tmp2 < 256 ? tmp2 : -1; return res; error: dmPaletteFree(*pdst); *pdst = NULL; return res; } int dmWriteACTPalette(DMResource *fp, const DMPalette *ppal) { int res; if ((res = dmPaletteWriteData(fp, ppal, ppal->ncolors, 256)) != DMERR_OK) return res; if (!dmf_write_be16(fp, ppal->ncolors) || !dmf_write_be16(fp, ppal->ctransp >= 0 ? ppal->ctransp : 0xffff)) return DMERR_FWRITE; return DMERR_OK; } static int fmtProbeRAWPalette(const Uint8 *buf, const size_t len) { (void) buf; if (len == 0x300) return DM_PROBE_SCORE_MAX; return DM_PROBE_SCORE_FALSE; } int dmReadRAWPalette(DMResource *fp, DMPalette **pdst) { int res; if ((res = dmPaletteAlloc(pdst, 256, -1)) != DMERR_OK) return res; if ((res = dmPaletteReadData(fp, *pdst, 256)) != DMERR_OK) goto error; return res; error: dmPaletteFree(*pdst); *pdst = NULL; return res; } int dmWriteRAWPalette(DMResource *fp, const DMPalette *ppal) { int res; if ((res = dmPaletteWriteData(fp, ppal, ppal->ncolors, 256)) != DMERR_OK) return res; return DMERR_OK; } int dmWriteImageData(const DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageWriteSpec *spec) { int x, y, yscale, xscale, res = 0, rowSize, rowWidth; Uint8 *row = NULL; // Allocate memory for row buffer rowWidth = img->width * spec->scaleX; rowSize = rowWidth * dmImageGetBytesPerPixel(spec->pixfmt); if ((row = dmMalloc(rowSize + 16)) == NULL) { res = DMERR_MALLOC; goto done; } // Generate the image for (y = 0; y < img->height; y++) { Uint8 *ptr1 = row, *ptr2 = ptr1 + rowWidth, *ptr3 = ptr2 + rowWidth, *ptr4 = ptr3 + rowWidth; if (img->pixfmt == DM_PIXFMT_GRAYSCALE) { for (x = 0; x < img->width; x++) { Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8]; switch (spec->pixfmt) { case DM_PIXFMT_PALETTE: case DM_PIXFMT_GRAYSCALE: for (xscale = 0; xscale < spec->scaleX; xscale++) *ptr1++ = c; break; case DM_PIXFMT_RGBA: if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = c; *ptr2++ = c; *ptr3++ = c; *ptr4++ = 0xff; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = c; *ptr1++ = c; *ptr1++ = c; *ptr1++ = 0xff; } } break; case DM_PIXFMT_RGB: if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = c; *ptr2++ = c; *ptr3++ = c; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = c; *ptr1++ = c; *ptr1++ = c; } } break; default: res = DMERR_NOT_SUPPORTED; goto done; } } } else if (img->pixfmt == DM_PIXFMT_PALETTE) { for (x = 0; x < img->width; x++) { Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8], qr, qg, qb, qa; if (c >= img->pal->ncolors) { res = DMERR_INVALID_DATA; goto done; } switch (spec->pixfmt) { case DM_PIXFMT_PALETTE: case DM_PIXFMT_GRAYSCALE: for (xscale = 0; xscale < spec->scaleX; xscale++) *ptr1++ = c; break; case DM_PIXFMT_RGBA: qr = img->pal->colors[c].r; qg = img->pal->colors[c].g; qb = img->pal->colors[c].b; qa = img->pal->colors[c].a; if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = qr; *ptr2++ = qg; *ptr3++ = qb; *ptr4++ = qa; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = qr; *ptr1++ = qg; *ptr1++ = qb; *ptr1++ = qa; } } break; case DM_PIXFMT_RGB: qr = img->pal->colors[c].r; qg = img->pal->colors[c].g; qb = img->pal->colors[c].b; if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = qr; *ptr2++ = qg; *ptr3++ = qb; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = qr; *ptr1++ = qg; *ptr1++ = qb; } } break; default: res = DMERR_NOT_SUPPORTED; goto done; } } } else if (img->pixfmt == DM_PIXFMT_RGB || img->pixfmt == DM_PIXFMT_RGBA) { Uint8 *sp = img->data + (y * img->pitch); for (x = 0; x < img->width; x++, sp += img->bpp / 8) switch (spec->pixfmt) { case DM_PIXFMT_RGBA: if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = sp[0]; *ptr2++ = sp[1]; *ptr3++ = sp[2]; *ptr4++ = sp[3]; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = sp[0]; *ptr1++ = sp[1]; *ptr1++ = sp[2]; *ptr1++ = sp[3]; } } break; case DM_PIXFMT_RGB: if (spec->planar) { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = sp[0]; *ptr2++ = sp[1]; *ptr3++ = sp[2]; } } else { for (xscale = 0; xscale < spec->scaleX; xscale++) { *ptr1++ = sp[0]; *ptr1++ = sp[1]; *ptr1++ = sp[2]; } } break; default: res = DMERR_NOT_SUPPORTED; goto done; } } for (yscale = 0; yscale < spec->scaleY; yscale++) { if ((res = writeRowCB(cbdata, row, rowSize)) != DMERR_OK) goto done; } } done: dmFree(row); return res; } #define DMCOL(x) (((x) >> 4) & 0xf) int dmWriteIFFMasterRAWHeader( DMResource *fp, const char *filename, const char *prefix, const DMImage *img, const DMImageWriteSpec *spec) { if (dmfprintf(fp, "%s_width: dw.w %d\n" "%s_height: dw.w %d\n" "%s_nplanes: dw.w %d\n", prefix, img->width * spec->scaleX, prefix, img->height * spec->scaleY, prefix, spec->nplanes) < 0) return dmferror(fp); if (spec->fmtid == DM_IMGFMT_ARAW && img->pixfmt == DM_PIXFMT_PALETTE && img->pal != NULL) { if (dmfprintf(fp, "%s_ncolors: dw.w %d\n" "%s_palette:\n", prefix, img->pal->ncolors, prefix) < 0) return dmferror(fp); for (int i = 0; i < (1 << spec->nplanes); i++) { Uint32 color; if (i < img->pal->ncolors) { color = (DMCOL(img->pal->colors[i].r) << 8) | (DMCOL(img->pal->colors[i].g) << 4) | (DMCOL(img->pal->colors[i].b)); } else color = 0; if (dmfprintf(fp, "\tdc.w $%04X\n", color) < 0) return dmferror(fp); } if (dmfprintf(fp, "%s: incbin \"%s\"\n", prefix, filename) < 0) return dmferror(fp); } return DMERR_OK; } static int dmWriteRAWRow(DMBitStreamContext *bs, const DMImage *img, const DMImageWriteSpec *spec, const int yc, const int plane) { const Uint8 *sp = img->data + (yc * img->pitch); for (int xc = 0; xc < img->width; xc++) { int res; for (int xscale = 0; xscale < spec->scaleX; xscale++) if ((res = dmPutBits(bs, (sp[xc] >> plane) & 1, 1)) != DMERR_OK) return res; } return DMERR_OK; } static int dmPutByteRes(DMBitStreamContext *ctx) { DMResource *fp = (DMResource *) ctx->handle; if (!dmf_write_byte(fp, ctx->outBuf & 0xff)) return dmferror(fp); else return DMERR_OK; } int dmWriteRAWImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *spec) { int res; DMBitStreamContext ctx; dmInitBitStreamContext(&ctx); ctx.putByte = dmPutByteRes; ctx.handle = (void *) fp; if (spec->planar) { // Output bitplanes in planar format // (each plane of line sequentially) for (int yc = 0; yc < img->height; yc++) for (int yscale = 0; yscale < spec->scaleY; yscale++) for (int plane = 0; plane < spec->nplanes; plane++) { if ((res = dmWriteRAWRow(&ctx, img, spec, yc, plane)) != DMERR_OK) return res; } } else { // Output each bitplane in sequence for (int plane = 0; plane < spec->nplanes; plane++) for (int yc = 0; yc < img->height; yc++) for (int yscale = 0; yscale < spec->scaleY; yscale++) { if ((res = dmWriteRAWRow(&ctx, img, spec, yc, plane)) != DMERR_OK) return res; } } return dmFlushBitStream(&ctx); } static int dmPutByteCDump(DMBitStreamContext *ctx) { DMResource *fp = (DMResource *) ctx->handle; if (dmfprintf(fp, "0x%02x, ", ctx->outBuf & 0xff) < 2) return dmferror(fp); else return DMERR_OK; } int dmWriteCDumpImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *spec) { int res; DMBitStreamContext ctx; if (dmfprintf(fp, "#define SET_WIDTH %d\n" "#define SET_HEIGHT %d\n" "\n" "Uint8 img_data[] = {\n", img->width * spec->scaleX, img->height * spec->scaleY) < 0) return dmferror(fp); dmInitBitStreamContext(&ctx); ctx.putByte = dmPutByteCDump; ctx.handle = (void *) fp; if (spec->planar) { // Output bitplanes in planar format // (each plane of line sequentially) for (int yc = 0; yc < img->height; yc++) for (int yscale = 0; yscale < spec->scaleY; yscale++) for (int plane = 0; plane < spec->nplanes; plane++) { if ((res = dmWriteRAWRow(&ctx, img, spec, yc, plane)) != DMERR_OK) return res; if (dmfprintf(fp, "\n") < 0) return dmferror(fp); } } else { // Output each bitplane in sequence for (int plane = 0; plane < spec->nplanes; plane++) for (int yc = 0; yc < img->height; yc++) for (int yscale = 0; yscale < spec->scaleY; yscale++) { if ((res = dmWriteRAWRow(&ctx, img, spec, yc, plane)) != DMERR_OK) return res; if (dmfprintf(fp, "\n") < 0) return dmferror(fp); } } res = dmFlushBitStream(&ctx); if (res == DMERR_OK && dmfprintf(fp, "};\n") < 0) res = dmferror(fp); return res; } static int dmWritePPMRow(void *cbdata, const Uint8 *row, const size_t len) { if (dmf_write_str((DMResource *) cbdata, row, len)) return DMERR_OK; else return DMERR_FWRITE; } int dmWritePPMImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *spec) { DMImageWriteSpec tmpSpec; char *tmpFmt; // Check if we can do this if ((spec->pixfmt == DM_PIXFMT_PALETTE || spec->pixfmt == DM_PIXFMT_GRAYSCALE) && img->pixfmt != spec->pixfmt) { return dmError(DMERR_NOT_SUPPORTED, "Conversion of non-indexed image to indexed not supported yet.\n"); } // Force non-planar etc memcpy(&tmpSpec, spec, sizeof(DMImageWriteSpec)); tmpSpec.planar = FALSE; switch (tmpSpec.pixfmt) { case DM_PIXFMT_RGB: case DM_PIXFMT_RGBA: case DM_PIXFMT_PALETTE: tmpSpec.pixfmt = DM_PIXFMT_RGB; tmpFmt = "6"; break; case DM_PIXFMT_GRAYSCALE: tmpFmt = "5"; break; default: return dmError(DMERR_NOT_SUPPORTED, "PPM: Not a supported color format for PPM/PGM format image.\n"); } // Write PPM header char *tmp = dm_strdup_printf( "P%s\n%d %d\n255\n", tmpFmt, img->width * tmpSpec.scaleX, img->height * tmpSpec.scaleY); if (tmp == NULL) return DMERR_MALLOC; dmfputs(tmp, fp); dmFree(tmp); // Write image data return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec); } // Read a PPM/PGM/PNM header line, skipping comments static BOOL dmReadPPMHeader(DMResource *fp, char *buf, const size_t bufLen) { BOOL end = FALSE, comment = FALSE; size_t offs = 0; do { int ch = dmfgetc(fp); if (ch == EOF) return FALSE; else if (ch == '#') comment = TRUE; else if (ch == '\n') { if (!comment) end = TRUE; else comment = FALSE; } else if (!comment) { if (offs < bufLen - 1) buf[offs++] = ch; } } while (!end); buf[offs] = 0; return TRUE; } int dmReadPPMImage(DMResource *fp, DMImage **pimg) { DMImage *img = NULL; unsigned int width, height; int itype, res = DMERR_OK; char hdr1[8], hdr2[32], hdr3[16]; // Read PPM header if (!dmReadPPMHeader(fp, hdr1, sizeof(hdr1)) || !dmReadPPMHeader(fp, hdr2, sizeof(hdr2)) || !dmReadPPMHeader(fp, hdr3, sizeof(hdr3))) { res = dmError(DMERR_FREAD, "PPM: Could not read image header data.\n"); goto error; } if (hdr1[0] != 'P' || !isdigit(hdr1[1]) || !isdigit(hdr2[0]) || !isdigit(hdr3[0])) { res = dmError(DMERR_NOT_SUPPORTED, "PPM: Not a supported PPM/PGM format image.\n"); goto error; } switch (hdr1[1]) { case '6': itype = DM_PIXFMT_RGB; break; case '5': itype = DM_PIXFMT_GRAYSCALE; break; default: res = dmError(DMERR_NOT_SUPPORTED, "PPM: Unsupported PPM/PNM/PGM subtype.\n"); goto error; } if (sscanf(hdr2, "%u %u", &width, &height) != 2) { res = dmError(DMERR_INVALID_DATA, "PPM: Invalid PPM/PGM image dimensions.\n"); goto error; } dmMsg(2, "PPM: %d x %d, type=%d\n", width, height, itype); // Check image dimensions if (width == 0 || height == 0) { return dmError(DMERR_INVALID_DATA, "PPM: Invalid image dimensions.\n"); } if ((*pimg = img = dmImageAlloc(width, height, itype, -1)) == NULL) { res = dmError(DMERR_MALLOC, "PPM: Could not allocate image data.\n"); goto error; } if (!dmf_read_str(fp, img->data, img->size)) { res = dmError(DMERR_FREAD, "PPM: Could not read image data.\n"); goto error; } error: return res; } static int fmtProbePPM(const Uint8 *buf, const size_t len) { if (len > 32 && buf[0] == 'P' && (buf[1] == '5' || buf[1] == '6')) return DM_PROBE_SCORE_MAX; return DM_PROBE_SCORE_FALSE; } #ifdef DM_USE_LIBPNG static int fmtProbePNG(const Uint8 *buf, const size_t len) { if (len > 64 && buf[0] == 0x89 && buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G' && buf[4] == 0x0d && buf[5] == 0x0a) { if (buf[12] == 'I' && buf[13] == 'H' && buf[14] == 'D' && buf[15] == 'R') return DM_PROBE_SCORE_MAX; else return DM_PROBE_SCORE_GOOD; } return DM_PROBE_SCORE_FALSE; } static int dmWritePNGRow(void *cbdata, const Uint8 *row, const size_t len) { png_structp png_ptr = cbdata; (void) len; if (setjmp(png_jmpbuf(png_ptr))) return DMERR_INTERNAL; png_write_row(png_ptr, row); return DMERR_OK; } static void dmPNGWriteData(png_structp png_ptr, png_bytep data, png_size_t length) { DMResource *res = (DMResource *) png_get_io_ptr(png_ptr); // XXX TODO: How the fuck does one do error handling here? dmf_write_str(res, data, length); } static void dmPNGWriteFlush(png_structp png_ptr) { (void) png_ptr; } int dmWritePNGImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *spec) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; int fmt, res; // Check if we can do this if ((spec->pixfmt == DM_PIXFMT_PALETTE || spec->pixfmt == DM_PIXFMT_GRAYSCALE) && img->pixfmt != spec->pixfmt) { return dmError(DMERR_NOT_SUPPORTED, "Conversion of non-indexed image to indexed not supported yet.\n"); } // Create PNG structures png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { res = dmError(DMERR_MALLOC, "PNG: png_create_write_struct() failed.\n"); goto error; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { res = dmError(DMERR_INIT_FAIL, "PNG: png_create_info_struct(%p) failed.\n", png_ptr); goto error; } if (setjmp(png_jmpbuf(png_ptr))) { res = dmError(DMERR_INIT_FAIL, "PNG: Error during image writing..\n"); goto error; } res = DMERR_OK; png_set_write_fn(png_ptr, fp, dmPNGWriteData, dmPNGWriteFlush); // Write PNG header info switch (spec->pixfmt) { case DM_PIXFMT_PALETTE : fmt = PNG_COLOR_TYPE_PALETTE; break; case DM_PIXFMT_GRAYSCALE : fmt = PNG_COLOR_TYPE_GRAY; break; case DM_PIXFMT_RGB : fmt = PNG_COLOR_TYPE_RGB; break; case DM_PIXFMT_RGBA : fmt = PNG_COLOR_TYPE_RGB_ALPHA; break; default: res = dmError(DMERR_NOT_SUPPORTED, "PNG: Unsupported image format %d.\n", spec->pixfmt); goto error; } png_set_compression_level(png_ptr, spec->compression); png_set_IHDR(png_ptr, info_ptr, img->width * spec->scaleX, img->height * spec->scaleY, 8, // bits per component: TODO: FIXME: calculate and use fmt, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); dmMsg(2, "PNG: %d x %d, depth=%d, type=%d\n", img->width * spec->scaleX, img->height * spec->scaleY, 8, fmt); // Palette if (spec->pixfmt == DM_PIXFMT_PALETTE) { png_colorp palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (palette == NULL) { res = dmError(DMERR_MALLOC, "PNG: Could not allocate palette structure."); goto error; } dmMemset(palette, 0, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); for (int i = 0; i < img->pal->ncolors; i++) { palette[i].red = img->pal->colors[i].r; palette[i].green = img->pal->colors[i].g; palette[i].blue = img->pal->colors[i].b; } png_set_PLTE(png_ptr, info_ptr, palette, img->pal->ncolors); png_free(png_ptr, palette); } // png_set_gAMA(png_ptr, info_ptr, 2.2); png_write_info(png_ptr, info_ptr); // Write compressed image data dmWriteImageData(img, (void *) png_ptr, dmWritePNGRow, spec); // Write footer png_write_end(png_ptr, NULL); error: if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); return res; } void dmPNGReadData(png_structp png_ptr, png_bytep data, png_size_t length) { DMResource *res = (DMResource *) png_get_io_ptr(png_ptr); // XXX TODO: How the fuck does one do error handling here? dmf_read_str(res, data, length); } int dmReadPNGImage(DMResource *fp, DMImage **pimg) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_colorp palette = NULL; png_bytep *row_pointers = NULL; png_bytep trans = NULL; png_uint_32 width, height, res_x = 0, res_y = 0; int res, itype, bit_depth, color_type, ncolors, ntrans, unit_type; DMImage *img; // Create PNG structures png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { res = dmError(DMERR_MALLOC, "PNG: png_create_write_struct() failed.\n"); goto error; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { res = dmError(DMERR_INIT_FAIL, "PNG: png_create_info_struct(%p) failed.\n", png_ptr); goto error; } if (setjmp(png_jmpbuf(png_ptr))) { res = dmError(DMERR_INIT_FAIL, "PNG: Error during image reading.\n"); goto error; } res = DMERR_OK; png_set_read_fn(png_ptr, fp, dmPNGReadData); // Read image information png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); dmMsg(2, "PNG: %d x %d, bit_depth=%d, color_type=%d\n", width, height, bit_depth, color_type); if (width < 1 || height < 1) { res = dmError(DMERR_INVALID_DATA, "PNG: Invalid image dimensions.\n"); goto error; } switch (color_type) { case PNG_COLOR_TYPE_GRAY: if (bit_depth > 8) { res = dmError(DMERR_NOT_SUPPORTED, "PNG: Unsupported bit depth for grayscale image: %d\n", bit_depth); goto error; } if (bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); itype = DM_PIXFMT_GRAYSCALE; break; case PNG_COLOR_TYPE_PALETTE: png_set_packing(png_ptr); itype = DM_PIXFMT_PALETTE; break; case PNG_COLOR_TYPE_RGB: itype = DM_PIXFMT_RGB; break; case PNG_COLOR_TYPE_RGBA: itype = DM_PIXFMT_RGBA; break; default: res = dmError(DMERR_NOT_SUPPORTED, "PNG: RGB/RGBA images not supported for loading.\n"); goto error; } // Allocate image dmMsg(2, "PNG: %d x %d, depth=%d, type=%d\n", width, height, bit_depth, color_type); if ((*pimg = img = dmImageAlloc(width, height, itype, // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp -1 /* bit_depth */)) == NULL) { res = dmError(DMERR_MALLOC, "PNG: Could not allocate image data.\n"); goto error; } // Set image aspect ratio png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type); if (res_x > 0 && res_y > 0 && res_y / res_x != (unsigned) (img->height / img->width)) img->aspect = (float) res_y / (float) res_x; // ... if ((row_pointers = png_malloc(png_ptr, height * sizeof(png_bytep))) == NULL) goto error; for (int i = 0; i < img->height; i++) row_pointers[i] = img->data + (i * img->pitch); png_read_image(png_ptr, row_pointers); png_read_end(png_ptr, NULL); png_free(png_ptr, row_pointers); // Create palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &ncolors); if (ncolors > 0 && palette != NULL) { dmMsg(2, "PNG: Palette of %d colors found.\n", ncolors); if ((res = dmPaletteAlloc(&(img->pal), ncolors, -1)) != DMERR_OK) goto error; for (int i = 0; i < img->pal->ncolors; i++) { img->pal->colors[i].r = palette[i].red; img->pal->colors[i].g = palette[i].green; img->pal->colors[i].b = palette[i].blue; } } png_get_tRNS(png_ptr, info_ptr, &trans, &ntrans, NULL); if (trans != NULL && ntrans > 0) { dmMsg(2, "PNG: %d transparent colors.\n", ntrans); for (int i = 0; i < img->pal->ncolors && i < ntrans; i++) { img->pal->colors[i].a = trans[i]; if (img->pal->ctransp < 0 && trans[i] == 0) img->pal->ctransp = i; } } } res = DMERR_OK; error: if (png_ptr != NULL && info_ptr != NULL) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return res; } #endif // // Z-Soft PCX format // #define PCX_PAL_COLORS 16 // Number of internal palette colors typedef struct { Uint8 r,g,b; } DMPCXColor; typedef struct { Uint8 manufacturer, // always 0x0a version, // Z-Soft PC Paintbrush version: // 0 = v2.5 // 2 = v2.8 with palette, // 3 = v2.8 without palette // 4 = PC Paintbrush for Windows // 5 = v3.0 or better encoding, // usually 0x01 = RLE, 0x00 = uncompressed bitsPerPlane; // bits per pixel per plane Uint16 xmin, ymin, xmax, ymax; Uint16 hres, vres; // resolution in DPI, can be image dimensions as well. DMPCXColor colorMap[PCX_PAL_COLORS]; Uint8 reserved; // should be set to 0 Uint8 nplanes; // number of planes Uint16 bpl; // bytes per plane LINE Uint16 palInfo; // 1 = color/BW, 2 = grayscale Uint16 hScreenSize, vScreenSize; Uint8 filler[54]; } DMPCXHeader; typedef struct { DMPCXHeader *header; Uint8 *buf; size_t bufLen, bufOffs; DMResource *fp; } DMPCXData; static int fmtProbePCX(const Uint8 *buf, const size_t len) { if (len > 128 + 32 && (buf[1] == 5 || buf[1] == 2 || buf[1] == 3) && buf[2] == 1 && (buf[3] == 8 || buf[3] == 4 || buf[3] == 3 || buf[3] == 1) && buf[65] >= 1 && buf[65] <= 4) return DM_PROBE_SCORE_GOOD; return DM_PROBE_SCORE_FALSE; } // Returns one byte from row buffer (of length len) at offset soffs, // OR zero if the offset is outside buffer. static inline Uint8 dmPCXGetByte(const Uint8 *row, const size_t len, const size_t soffs) { return (soffs < len) ? row[soffs] : 0; } static BOOL dmPCXFlush(DMPCXData *pcx) { BOOL ret = TRUE; if (pcx->bufOffs > 0) ret = dmf_write_str(pcx->fp, pcx->buf, pcx->bufOffs); pcx->bufOffs = 0; return ret; } static inline BOOL dmPCXPutByte(DMPCXData *pcx, const Uint8 val) { if (pcx->bufOffs < pcx->bufLen) { pcx->buf[pcx->bufOffs++] = val; return TRUE; } else return dmPCXFlush(pcx); } static int dmPCXPutData(DMPCXData *pcx, const Uint8 data, const int count) { if (count == 1 && (data & 0xC0) != 0xC0) { if (!dmPCXPutByte(pcx, data)) return DMERR_FWRITE; } else { if (!dmPCXPutByte(pcx, 0xC0 | count) || !dmPCXPutByte(pcx, data)) return DMERR_FWRITE; } return DMERR_OK; } static int dmWritePCXRow(void *cbdata, const Uint8 *row, const size_t len) { DMPCXData *pcx = (DMPCXData *) cbdata; int err; size_t soffs = 0; pcx->bufOffs = 0; for (int plane = 0; plane < pcx->header->nplanes; plane++) { Uint8 data = dmPCXGetByte(row, len, soffs++), count = 1; size_t blen = pcx->header->bpl * pcx->header->nplanes; while (soffs < blen) { if (data == dmPCXGetByte(row, len, soffs) && count < 0x3F) { count++; soffs++; } else { if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK) return err; data = dmPCXGetByte(row, len, soffs++); count = 1; } } if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK) return err; if (!dmPCXFlush(pcx)) return DMERR_FWRITE; } return DMERR_OK; } int dmWritePCXImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *pspec) { DMPCXData pcx; DMPCXHeader hdr; DMImageWriteSpec spec; int res; // Always force planar for PCX memcpy(&spec, pspec, sizeof(DMImageWriteSpec)); spec.planar = TRUE; // XXX: 24bit PCX does not work yet .. if ((img->pixfmt != DM_PIXFMT_PALETTE && img->pixfmt != DM_PIXFMT_GRAYSCALE) || (spec.pixfmt != DM_PIXFMT_PALETTE && spec.pixfmt != DM_PIXFMT_GRAYSCALE)) { return dmError(DMERR_NOT_SUPPORTED, "24bit PCX not supported yet.\n"); } if (spec.pixfmt == DM_PIXFMT_PALETTE && img->pal == NULL) { return dmError(DMERR_NULLPTR, "Image spec says paletted/indexed image, but palette pointer is NULL.\n"); } // Create output file pcx.buf = NULL; pcx.header = &hdr; pcx.fp = fp; // Create PCX header dmMemset(&hdr, 0, sizeof(hdr)); if (spec.pixfmt == DM_PIXFMT_PALETTE || spec.pixfmt == DM_PIXFMT_GRAYSCALE) { const int ncolors = img->pal->ncolors > PCX_PAL_COLORS ? PCX_PAL_COLORS : img->pal->ncolors; for (int i = 0; i < ncolors; i++) { hdr.colorMap[i].r = img->pal->colors[i].r; hdr.colorMap[i].g = img->pal->colors[i].g; hdr.colorMap[i].b = img->pal->colors[i].b; } } hdr.manufacturer = 10; hdr.version = 5; hdr.encoding = 1; hdr.hres = img->width * spec.scaleX; hdr.vres = img->height * spec.scaleY; hdr.xmin = hdr.ymin = 0; hdr.xmax = (img->width * spec.scaleX) - 1; hdr.ymax = (img->height * spec.scaleY) - 1; hdr.hScreenSize = hdr.hres; hdr.vScreenSize = hdr.vres; switch (spec.pixfmt) { case DM_PIXFMT_GRAYSCALE: hdr.palInfo = 2; break; default: hdr.palInfo = 1; break; } // TODO XXX .. maybe actually compute these asdf hdr.bitsPerPlane = 8; hdr.nplanes = dmImageGetBytesPerPixel(spec.pixfmt); // Compute bytes per line res = img->width * spec.scaleX; hdr.bpl = res / 2; if (res % 2) hdr.bpl++; hdr.bpl *= 2; dmMsg(2, "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n", hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax, hdr.hres, hdr.vres, hdr.hScreenSize, hdr.vScreenSize); dmMsg(2, "PCX: nplanes=%d, bpp=%d, bpl=%d, colfmt=%d, planar=%s\n", hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, spec.pixfmt, spec.planar ? "yes" : "no" ); // TODO XXX this is also bogus pcx.bufLen = hdr.bpl * 4; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { res = dmError(DMERR_MALLOC, "PCX: Could not allocate %" DM_PRIu_SIZE_T " bytes for RLE compression buffer.\n", pcx.bufLen); goto error; } // Write PCX header if (!dmf_write_byte(pcx.fp, hdr.manufacturer) || !dmf_write_byte(pcx.fp, hdr.version) || !dmf_write_byte(pcx.fp, hdr.encoding) || !dmf_write_byte(pcx.fp, hdr.bitsPerPlane) || !dmf_write_le16(pcx.fp, hdr.xmin) || !dmf_write_le16(pcx.fp, hdr.ymin) || !dmf_write_le16(pcx.fp, hdr.xmax) || !dmf_write_le16(pcx.fp, hdr.ymax) || !dmf_write_le16(pcx.fp, hdr.hres) || !dmf_write_le16(pcx.fp, hdr.vres) || !dmf_write_str(pcx.fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) || !dmf_write_byte(pcx.fp, hdr.reserved) || !dmf_write_byte(pcx.fp, hdr.nplanes) || !dmf_write_le16(pcx.fp, hdr.bpl) || !dmf_write_le16(pcx.fp, hdr.palInfo) || !dmf_write_le16(pcx.fp, hdr.hScreenSize) || !dmf_write_le16(pcx.fp, hdr.vScreenSize) || !dmf_write_str(pcx.fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { res = dmError(DMERR_FWRITE, "PCX: Could not write header data.\n"); goto error; } // Write image data res = dmWriteImageData(img, (void *) &pcx, dmWritePCXRow, &spec); // Write VGA palette if (spec.pixfmt == DM_PIXFMT_PALETTE || spec.pixfmt == DM_PIXFMT_GRAYSCALE) { dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->pal->ncolors); dmf_write_byte(pcx.fp, 0x0C); if ((res = dmPaletteWriteData(fp, img->pal, img->pal->ncolors, 256)) != DMERR_OK) { res = dmError(DMERR_FWRITE, "PCX: Could not write palette data.\n"); goto error; } } error: dmFree(pcx.buf); return res; } static BOOL dmPCXDecodeRLERow(DMResource *fp, Uint8 *buf, const size_t bufLen) { size_t offs = 0; do { int count; Uint8 data; if (!dmf_read_byte(fp, &data)) return FALSE; if ((data & 0xC0) == 0xC0) { BOOL skip = FALSE; count = data & 0x3F; if (count == 0) { switch (dmGFXErrorMode) { case DM_ERRMODE_RECOV_1: // Use as literal skip = TRUE; count = 1; break; case DM_ERRMODE_RECOV_2: // Ignore completely skip = TRUE; break; case DM_ERRMODE_FAIL: default: // Error out on "invalid" data return FALSE; } } if (!skip && !dmf_read_byte(fp, &data)) return FALSE; } else count = 1; while (count-- && offs < bufLen) buf[offs++] = data; // Check for remaining output count, error out if we wish to if (count > 0 && dmGFXErrorMode == DM_ERRMODE_FAIL) return FALSE; } while (offs < bufLen); return TRUE; } int dmReadPCXImage(DMResource *fp, DMImage **pimg) { DMImage *img; DMPCXData pcx; DMPCXHeader hdr; int res = 0; BOOL isPaletted; pcx.buf = NULL; // Read PCX header if (!dmf_read_byte(fp, &hdr.manufacturer) || !dmf_read_byte(fp, &hdr.version) || !dmf_read_byte(fp, &hdr.encoding) || !dmf_read_byte(fp, &hdr.bitsPerPlane) || !dmf_read_le16(fp, &hdr.xmin) || !dmf_read_le16(fp, &hdr.ymin) || !dmf_read_le16(fp, &hdr.xmax) || !dmf_read_le16(fp, &hdr.ymax) || !dmf_read_le16(fp, &hdr.hres) || !dmf_read_le16(fp, &hdr.vres) || !dmf_read_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) || !dmf_read_byte(fp, &hdr.reserved) || !dmf_read_byte(fp, &hdr.nplanes) || !dmf_read_le16(fp, &hdr.bpl) || !dmf_read_le16(fp, &hdr.palInfo) || !dmf_read_le16(fp, &hdr.hScreenSize) || !dmf_read_le16(fp, &hdr.vScreenSize) || !dmf_read_str(fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { res = dmError(DMERR_FREAD, "PCX: Could not read image header data.\n"); goto error; } if (hdr.manufacturer != 10 || hdr.version > 5 || hdr.encoding != 1) { res = dmError(DMERR_NOT_SUPPORTED, "PCX: Not a PCX file, or unsupported variant.\n"); goto error; } if (hdr.nplanes == 4 && hdr.bitsPerPlane == 4) { dmMsg(2, "PCX: Probably invalid combination of nplanes=%d and bpp=%d, attempting to fix ..\n", hdr.nplanes, hdr.bitsPerPlane); hdr.bitsPerPlane = 1; } isPaletted = (hdr.bitsPerPlane * hdr.nplanes) <= 8; dmMsg(2, "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n", hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax, hdr.hres, hdr.vres, hdr.hScreenSize, hdr.vScreenSize); dmMsg(2, "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s\n", hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, isPaletted ? "yes" : "no"); // Check image dimensions if (hdr.xmin > hdr.xmax || hdr.ymin > hdr.ymax) { res = dmError(DMERR_INVALID_DATA, "PCX: Invalid image dimensions.\n"); goto error; } if (hdr.nplanes < 1 || hdr.nplanes > 8) { res = dmError(DMERR_NOT_SUPPORTED, "PCX: Unsupported number of bitplanes %d.\n", hdr.nplanes); goto error; } if (!isPaletted) { res = dmError(DMERR_NOT_SUPPORTED, "PCX: Non-indexed (truecolour) PCX images not supported for loading.\n"); goto error; } // Allocate image if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1, isPaletted ? DM_PIXFMT_PALETTE : DM_PIXFMT_RGB, // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp // isPaletted ? (hdr.bitsPerPlane * hdr.nplanes) : -1 -1 )) == NULL) { res = dmError(DMERR_MALLOC, "PCX: Could not allocate image structure.\n"); goto error; } // Set image aspect ratio if (hdr.hScreenSize > 0 && hdr.vScreenSize > 0 && hdr.vScreenSize / hdr.hScreenSize != img->height / img->width) img->aspect = (float) hdr.vScreenSize / (float) hdr.hScreenSize; // Sanity check bytes per line value if (hdr.bpl < (img->width * hdr.bitsPerPlane) / 8) { res = dmError(DMERR_MALLOC, "PCX: The bytes per plane line value %d is smaller than width*bpp/8 = %d!\n", hdr.bpl, (img->width * hdr.bitsPerPlane) / 8); goto error; } pcx.bufLen = hdr.nplanes * hdr.bpl; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { res = dmError(DMERR_MALLOC, "PCX: Could not allocate RLE buffer.\n"); goto error; } dmMsg(2, "PCX: bufLen=%" DM_PRIu_SIZE_T "\n", pcx.bufLen); // Read image data Uint8 *dp = img->data; for (int yc = 0; yc < img->height; yc++) { // Decode row of RLE'd data if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen)) { res = dmError(DMERR_INVALID_DATA, "PCX: Error decoding RLE compressed data.\n"); goto error; } // Decode bitplanes switch (hdr.bitsPerPlane) { case 24: case 8: { // Actually bytes and bits per plane per pixel .. const int bytesPerPlane = hdr.bitsPerPlane / 8; for (int nplane = 0; nplane < hdr.nplanes; nplane++) { Uint8 *dptr = dp + (nplane * bytesPerPlane), *sptr = pcx.buf + (hdr.bpl * nplane); memcpy(dptr, sptr, img->width * bytesPerPlane); } } break; case 1: dmMemset(dp, 0, img->width); for (int nplane = 0; nplane < hdr.nplanes; nplane++) { Uint8 *sptr = pcx.buf + (hdr.bpl * nplane); for (int xc = 0; xc < img->width; xc++) { const int px = 7 - (xc & 7); dp[xc] |= ((sptr[xc / 8] & (1 << px)) >> px) << nplane; } } break; default: res = dmError(DMERR_NOT_SUPPORTED, "PCX: Unsupported number of bits per plane %d.\n", hdr.bitsPerPlane); goto error; } dp += img->pitch; } // Read additional VGA palette, if available if (isPaletted) { int ncolors; Uint8 tmpb; BOOL read; if (!dmf_read_byte(fp, &tmpb) || tmpb != 0x0C) { read = FALSE; ncolors = PCX_PAL_COLORS; } else { read = TRUE; ncolors = 256; } if ((res = dmPaletteAlloc(&(img->pal), ncolors, -1)) != DMERR_OK) { res = dmError(res, "PCX: Could not allocate palette data!\n"); goto error; } if (read) { // Okay, attempt to read the palette data dmMsg(2, "PCX: Reading palette of %d colors\n", ncolors); if ((res = dmPaletteReadData(fp, img->pal, ncolors)) != DMERR_OK) { dmErrorMsg("PCX: Error reading palette.\n"); goto error; } } else { const int nmax = img->pal->ncolors > PCX_PAL_COLORS ? PCX_PAL_COLORS : img->pal->ncolors; // If the extra palette is not available, copy the colors from // the header palette to our internal palette structure. dmMsg(2, "PCX: Initializing palette from header of %d colors (%d)\n", ncolors, nmax); for (int i = 0; i < nmax; i++) { img->pal->colors[i].r = hdr.colorMap[i].r; img->pal->colors[i].g = hdr.colorMap[i].g; img->pal->colors[i].b = hdr.colorMap[i].b; } } } error: dmFree(pcx.buf); return res; } // // IFF ILBM / PBM format // #define IFF_ID_FORM 0x464F524D // "FORM" #define IFF_ID_ILBM 0x494C424D // "ILBM" #define IFF_ID_PBM 0x50424D20 // "PBM " #define IFF_ID_BMHD 0x424D4844 // "BMHD" #define IFF_ID_CMAP 0x434D4150 // "CMAP" #define IFF_ID_BODY 0x424F4459 // "BODY" #define IFF_ID_CAMG 0x43414D47 // "CAMG" #define IFF_ID_ACBM 0x4143424D // "ACBM" #define IFF_ID_ABIT 0x41424954 // "ABIT" #define IFF_MASK_NONE 0 #define IFF_MASK_HAS_MASK 1 #define IFF_MASK_TRANSP 2 #define IFF_MASK_LASSO 3 #define IFF_COMP_NONE 0 #define IFF_COMP_BYTERUN1 1 #define IFF_CAMG_LACE 0x00000004 #define IFF_CAMG_HALFBRITE 0x00000080 #define IFF_CAMG_HAM 0x00000800 typedef struct { Uint32 id; Uint32 size; int count; char idStr[6]; off_t offs; } DMIFFChunk; typedef struct { Uint16 w, h; Sint16 x, y; Uint8 nplanes; Uint8 masking; Uint8 compression; Uint8 pad1; Uint16 transp; Uint8 xasp, yasp; Sint16 pagew, pageh; } DMIFFBMHD; typedef struct { DMIFFChunk chFORM, chBMHD, chCMAP, chBODY; DMIFFBMHD bmhd; Uint32 camg; int ncolors; DMPalette *pal; Uint32 idsig; char *idstr; } DMIFF; static int fmtProbeIFF(const Uint8 *buf, const size_t len, const Uint32 id) { if (len > 32 && DM_BE32_TO_NATIVE(*(Uint32 *) (buf + 0)) == IFF_ID_FORM && DM_BE32_TO_NATIVE(*(Uint32 *) (buf + 8)) == id) { if (DM_BE32_TO_NATIVE(*(Uint32 *) (buf + 12)) == IFF_ID_BMHD) return DM_PROBE_SCORE_MAX; else return DM_PROBE_SCORE_GOOD; } return DM_PROBE_SCORE_FALSE; } static int fmtProbeIFF_ILBM(const Uint8 *buf, const size_t len) { return fmtProbeIFF(buf, len, IFF_ID_ILBM); } static int fmtProbeIFF_PBM(const Uint8 *buf, const size_t len) { return fmtProbeIFF(buf, len, IFF_ID_PBM); } static int fmtProbeIFF_ACBM(const Uint8 *buf, const size_t len) { return fmtProbeIFF(buf, len, IFF_ID_ACBM); } static void dmMakeIFFChunkIDStr(DMIFFChunk *chunk) { chunk->idStr[0] = (chunk->id >> 24) & 0xff; chunk->idStr[1] = (chunk->id >> 16) & 0xff; chunk->idStr[2] = (chunk->id >> 8) & 0xff; chunk->idStr[3] = (chunk->id) & 0xff; chunk->idStr[4] = 0; } static int dmReadIFFChunkHdr(DMResource *fp, DMIFFChunk *chunk) { if (!dmf_read_be32(fp, &chunk->id) || !dmf_read_be32(fp, &chunk->size)) { return dmError(DMERR_FREAD, "IFF: Could not read IFF chunk header.\n"); } else { chunk->offs = dmftell(fp); dmMakeIFFChunkIDStr(chunk); return DMERR_OK; } } static int dmSkipIFFChunkRest(DMResource *fp, const DMIFFChunk *chunk) { off_t read = dmftell(fp) - chunk->offs, size = chunk->size; if (size & 1) { dmMsg(3, "IFF: Chunk size %" DM_PRId_OFF_T " is uneven, adjusting to %" DM_PRId_OFF_T ".\n", size, size + 1); size++; } if (size > read) { dmMsg(3, "IFF: Skipping %" DM_PRId_OFF_T " bytes (%" DM_PRId_OFF_T " of %" DM_PRId_OFF_T " consumed)\n", size - read, read, size); if (dmfseek(fp, size - read, SEEK_CUR) != 0) { return dmError(DMERR_FSEEK, "IFF: Failed to skip chunk end.\n"); } } return DMERR_OK; } static int dmCheckIFFChunk(DMIFFChunk *dest, const DMIFFChunk *chunk, const BOOL multi, const Uint32 minSize) { if (dest->count > 0 && !multi) { return dmError(DMERR_INVALID_DATA, "IFF: Multiple instances of chunk %s found.\n", chunk->idStr); } dest->count++; if (chunk->size < minSize) { return dmError(DMERR_OUT_OF_DATA, "IFF: Chunk %s is too small (%d < %d).\n", chunk->idStr, chunk->size, minSize); } return DMERR_OK; } BOOL dmIFFDecodeByteRun1Row(DMResource *fp, Uint8 *buf, const size_t bufLen) { size_t offs = 0; do { Uint8 data, ucount; if (!dmf_read_byte(fp, &ucount)) return FALSE; if (ucount == 0x80) { if (!dmf_read_byte(fp, &data)) return FALSE; } else if (ucount & 0x80) { Uint8 count = (ucount ^ 0xff) + 2; if (!dmf_read_byte(fp, &data)) return FALSE; while (count-- && offs < bufLen) buf[offs++] = data; } else { Uint8 count = ucount + 1; while (count-- && offs < bufLen) { if (!dmf_read_byte(fp, &data)) return FALSE; buf[offs++] = data; } } } while (offs < bufLen); return TRUE; } static BOOL dmIFFReadOneRow(DMResource *fp, DMIFF *iff, Uint8 *buf, const size_t bufLen) { if (iff->bmhd.compression == IFF_COMP_BYTERUN1) return dmIFFDecodeByteRun1Row(fp, buf, bufLen); else return dmf_read_str(fp, buf, bufLen); } static inline Uint8 dmDecodeBit(const Uint8 *buf, const int xc) { return (buf[xc / 8] >> (7 - (xc & 7))) & 1; } static int dmDecodeIFFBody(DMResource *fp, DMIFF *iff, DMImage *img) { Uint8 *buf = NULL; size_t bufLen = 0; int res = DMERR_OK; const int nplanes = iff->bmhd.nplanes; if (iff->idsig == IFF_ID_ILBM) { bufLen = ((img->width + 15) / 16) * 2; dmMsg(2, "IFF: Line / plane row size %" DM_PRIu_SIZE_T " bytes.\n", bufLen); } else if (iff->idsig == IFF_ID_ACBM) { bufLen = (img->width * img->height + 7) / 8; dmMsg(2, "IFF: Plane buffer size %" DM_PRIu_SIZE_T " bytes.\n", bufLen); } if (bufLen > 0 && (buf = dmMalloc(bufLen)) == NULL) return DMERR_MALLOC; // Decode the chunk if (iff->idsig == IFF_ID_ACBM) { // Initialize destination image data dmMemset(img->data, 0, img->size); for (int plane = 0; plane < nplanes; plane++) { // Decompress or read data if (!dmIFFReadOneRow(fp, iff, buf, bufLen)) { res = dmError(DMERR_FREAD, "IFF: Error in reading ACBM image plane #%d.\n", plane); goto out; } for (int yc = 0; yc < img->height; yc++) { Uint8 *dp = img->data + (yc * img->pitch); Uint8 *sp = buf + (yc * img->width) / 8; for (int xc = 0; xc < img->width; xc++) { dp[xc] |= dmDecodeBit(sp, xc) << plane; } } } } else for (int yc = 0; yc < img->height; yc++) { Uint8 *dp = img->data + (yc * img->pitch); if (iff->idsig == IFF_ID_ILBM) { // Clear planar decoding buffer dmMemset(dp, 0, img->pitch); for (int plane = 0; plane < nplanes; plane++) { // Decompress or read data if (!dmIFFReadOneRow(fp, iff, buf, bufLen)) { res = dmError(DMERR_FREAD, "IFF: Error in reading ILBM image plane #%d @ %d.\n", plane, yc); goto out; } // Decode bitplane for (int xc = 0; xc < img->width; xc++) dp[xc] |= dmDecodeBit(buf, xc) << plane; } // Read mask data if (iff->bmhd.masking == IFF_MASK_HAS_MASK) { // Decompress or read data if (!dmIFFReadOneRow(fp, iff, buf, bufLen)) { res = dmError(DMERR_FREAD, "IFF: Error in reading ILBM mask plane.\n"); goto out; } // Decode mask for (int xc = 0; xc < img->width; xc++) { const Uint8 data = dmDecodeBit(buf, xc); // Black out any pixels with mask bit 0 if (!data) dp[xc] = img->pal->ctransp < 0 ? 0 : img->pal->ctransp; } } } else if (iff->idsig == IFF_ID_PBM) { if (!dmIFFReadOneRow(fp, iff, dp, img->width)) { res = dmError(DMERR_FREAD, "IFF: Error reading PBM image row #%d.\n", yc); goto out; } } } out: dmFree(buf); return res; } int dmReadIFFImage(DMResource *fp, DMImage **pimg) { DMIFFChunk chunk; DMIFF iff; BOOL parsed = FALSE; int res = DMERR_OK; dmMemset(&iff, 0, sizeof(iff)); // Read IFF FORM header if ((res = dmReadIFFChunkHdr(fp, &chunk)) != DMERR_OK) return res; if (chunk.id != IFF_ID_FORM || chunk.size < 32) { return dmError(DMERR_NOT_SUPPORTED, "IFF: Not a IFF file (%08X vs %08X / %d).\n", chunk.id, IFF_ID_FORM, chunk.size); } // Check IFF ILBM/PBM signature if (!dmf_read_be32(fp, &iff.idsig) || (iff.idsig != IFF_ID_ILBM && iff.idsig != IFF_ID_PBM && iff.idsig != IFF_ID_ACBM)) { return dmError(DMERR_INVALID_DATA, "IFF: Not a IFF ILBM/PBM/ACBM file.\n"); } dmMsg(3, "IFF: FORM is %s format image, with size %d bytes.\n", iff.idsig == IFF_ID_ILBM ? "ILBM" : (iff.idsig == IFF_ID_PBM ? "PBM" : "ACBM"), chunk.size); while (!parsed && !dmfeof(fp)) { // Read chunk header if ((res = dmReadIFFChunkHdr(fp, &chunk)) != DMERR_OK) return res; switch (chunk.id) { case IFF_ID_BMHD: // Check for multiple occurences of BMHD if ((res = dmCheckIFFChunk(&iff.chBMHD, &chunk, FALSE, sizeof(iff.bmhd))) != DMERR_OK) return res; // Read BMHD data if (!dmf_read_be16(fp, &iff.bmhd.w) || !dmf_read_be16(fp, &iff.bmhd.h) || !dmf_read_be16(fp, (Uint16 *) &iff.bmhd.x) || !dmf_read_be16(fp, (Uint16 *) &iff.bmhd.y) || !dmf_read_byte(fp, &iff.bmhd.nplanes) || !dmf_read_byte(fp, &iff.bmhd.masking) || !dmf_read_byte(fp, &iff.bmhd.compression) || !dmf_read_byte(fp, &iff.bmhd.pad1) || !dmf_read_be16(fp, &iff.bmhd.transp) || !dmf_read_byte(fp, &iff.bmhd.xasp) || !dmf_read_byte(fp, &iff.bmhd.yasp) || !dmf_read_be16(fp, (Uint16 *) &iff.bmhd.pagew) || !dmf_read_be16(fp, (Uint16 *) &iff.bmhd.pageh)) { return dmError(DMERR_FREAD, "IFF: Error reading BMHD chunk.\n"); } dmMsg(1, "IFF: BMHD %d x %d @ %d, %d : nplanes=%d, comp=%d, mask=%d, transp=%d\n", iff.bmhd.w, iff.bmhd.h, iff.bmhd.x, iff.bmhd.y, iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking, iff.bmhd.transp); // Sanity check if (iff.bmhd.nplanes < 1 || iff.bmhd.nplanes > 8 || (iff.bmhd.compression != IFF_COMP_NONE && iff.bmhd.compression != IFF_COMP_BYTERUN1) || (iff.bmhd.masking != IFF_MASK_NONE && iff.bmhd.masking != IFF_MASK_HAS_MASK && iff.bmhd.masking != IFF_MASK_TRANSP)) { return dmError(DMERR_NOT_SUPPORTED, "IFF: Unsupported features, refusing to load.\n"); } if (iff.idsig == IFF_ID_ACBM && iff.bmhd.compression != IFF_COMP_NONE) { dmMsg(1, "IFF: ACBM image with compression != none (%d). Ignoring.\n", iff.bmhd.compression); iff.bmhd.compression = IFF_COMP_NONE; } break; case IFF_ID_CMAP: // Check for multiple occurences of CMAP if ((res = dmCheckIFFChunk(&iff.chCMAP, &chunk, FALSE, 3)) != DMERR_OK) return res; // Check for sanity if (chunk.size % 3 != 0) { // Non-fatal dmError(DMERR_INVALID_DATA, "IFF: CMAP chunk size not divisible by 3, possibly broken file.\n"); } iff.ncolors = chunk.size / 3; dmMsg(2, "IFF: CMAP %d entries (%d bytes)\n", iff.ncolors, chunk.size); if (iff.bmhd.nplanes > 0 && iff.ncolors != 1 << iff.bmhd.nplanes) dmMsg(2, "IFF: Expected %d entries in CMAP.\n", 1 << iff.bmhd.nplanes); // Read palette if (iff.ncolors > 0) { if ((res = dmPaletteAlloc(&iff.pal, iff.ncolors, (iff.bmhd.masking == IFF_MASK_TRANSP) ? iff.bmhd.transp : -1)) != DMERR_OK) { dmErrorMsg("IFF: Could not allocate palette data.\n"); return res; } if ((res = dmPaletteReadData(fp, iff.pal, iff.ncolors)) != DMERR_OK) { dmErrorMsg("IFF: Error reading CMAP.\n"); return res; } } if (iff.chBMHD.count && iff.chBODY.count) parsed = TRUE; break; case IFF_ID_BODY: case IFF_ID_ABIT: // Check for multiple occurences of BODY if ((res = dmCheckIFFChunk(&iff.chBODY, &chunk, FALSE, 1)) != DMERR_OK) return res; // Check for sanity if (!iff.chBMHD.count) { return dmError(DMERR_INVALID_DATA, "IFF: %s chunk before BMHD?\n", chunk.idStr); } dmMsg(2, "IFF: %s chunk size %d bytes\n", chunk.idStr, chunk.size); // Check image dimensions if (iff.bmhd.w == 0 || iff.bmhd.h == 0) { return dmError(DMERR_INVALID_DATA, "IFF: Invalid image dimensions.\n"); } // Allocate image if ((*pimg = dmImageAlloc(iff.bmhd.w, iff.bmhd.h, iff.bmhd.nplanes <= 8 ? DM_PIXFMT_PALETTE : DM_PIXFMT_RGBA, // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1 -1 )) == NULL) return DMERR_MALLOC; // Set image aspect ratio if (iff.bmhd.xasp > 0 && iff.bmhd.yasp > 0) (*pimg)->aspect = (float) iff.bmhd.yasp / (float) iff.bmhd.xasp; // Decode the body if ((res = dmDecodeIFFBody(fp, &iff, *pimg)) != DMERR_OK) return res; if ((res = dmSkipIFFChunkRest(fp, &chunk)) != DMERR_OK) return res; if (iff.chCMAP.count) parsed = TRUE; break; case IFF_ID_CAMG: if (!dmf_read_be32(fp, &iff.camg)) { return dmError(DMERR_FREAD, "IFF: Error reading CAMG chunk.\n"); } dmMsg(2, "IFF: CAMG value 0x%08x\n", iff.camg); if ((iff.camg & IFF_CAMG_HAM)) { return dmError(DMERR_NOT_SUPPORTED, "IFF: HAM files are not supported.\n"); } break; default: dmMsg(3, "Unknown chunk ID '%s', size %d\n", chunk.idStr, chunk.size); if (dmfseek(fp, chunk.size, SEEK_CUR) != 0) { return dmError(DMERR_FSEEK, "IFF: Error skipping in file."); } break; } if ((res = dmSkipIFFChunkRest(fp, &chunk)) != DMERR_OK) return res; } // Check if we should have a palette if (*pimg != NULL && (*pimg)->pixfmt == DM_PIXFMT_PALETTE) { // Check that we DO have a palette .. if (iff.pal == NULL || iff.pal->ncolors == 0) { return dmError(DMERR_INVALID_DATA, "IFF: A paletted/indexed color image with no CMAP. Bailing out.\n"); } // If halfbrite is used, duplicate the palette if (iff.camg & IFF_CAMG_HALFBRITE) { int ncolors = iff.ncolors; if (iff.idsig != IFF_ID_ILBM) { dmErrorMsg("IFF: Non-planar PBM file with Halfbrite enabled! This might not work.\n"); } if (iff.ncolors > 128) { return dmError(DMERR_NOT_SUPPORTED, "IFF: Halfbrite enabled, but ncolors > 128.\n"); } if ((res = dmPaletteResize(&iff.pal, ncolors * 2)) != DMERR_OK) { dmPaletteFree(iff.pal); return res; } for (int i = 0; i < ncolors; i++) { int i2 = ncolors + i; iff.pal->colors[i2].r = iff.pal->colors[i].r / 2; iff.pal->colors[i2].g = iff.pal->colors[i].g / 2; iff.pal->colors[i2].b = iff.pal->colors[i].b / 2; } } (*pimg)->pal = iff.pal; } return res; } static int dmWriteIFFChunkHdr(DMResource *fp, DMIFFChunk *chunk, const Uint32 id) { chunk->offs = dmftell(fp); chunk->id = id; dmMakeIFFChunkIDStr(chunk); if (!dmf_write_be32(fp, chunk->id) || !dmf_write_be32(fp, 0)) { return dmError(DMERR_FREAD, "IFF: Could not write IFF '%s' chunk header.\n", chunk->idStr); } else return DMERR_OK; } static int dmWriteIFFChunkFinish(DMResource *fp, DMIFFChunk *chunk) { off_t curr = dmftell(fp); if (curr < 0) return dmferror(fp); chunk->size = curr - chunk->offs - (sizeof(Uint32) * 2); if (chunk->size & 1) { dmMsg(3, "Padding chunk ID '%s', size %d ++\n", chunk->idStr, chunk->size); if (!dmf_write_byte(fp, 0)) return dmferror(fp); curr++; } if (dmfseek(fp, chunk->offs, SEEK_SET) < 0) return dmferror(fp); if (!dmf_write_be32(fp, chunk->id) || !dmf_write_be32(fp, chunk->size)) { return dmError(DMERR_FREAD, "IFF: Could not write IFF '%s' chunk header.\n", chunk->idStr); } if (dmfseek(fp, curr, SEEK_SET) < 0) return dmferror(fp); return DMERR_OK; } enum { DMODE_LIT, DMODE_RLE, }; static BOOL dmIFFEncodeByteRun1LIT(DMResource *fp, const Uint8 *buf, const size_t offs, const size_t count) { if (count <= 0) return TRUE; Uint8 tmp = count - 1; return dmf_write_byte(fp, tmp) && dmf_write_str(fp, buf + offs, count); } static BOOL dmIFFEncodeByteRun1RLE(DMResource *fp, const Uint8 *buf, const size_t offs, const size_t count) { if (count <= 0) return TRUE; Uint8 tmp = ((Uint8) count - 2) ^ 0xff, data = buf[offs]; return dmf_write_byte(fp, tmp) && dmf_write_byte(fp, data); } BOOL dmIFFEncodeByteRun1Row(DMResource *fp, const Uint8 *buf, const size_t bufLen) { int prev = -1, mode = DMODE_LIT; size_t offs, l_offs, r_offs; BOOL ret = TRUE; for (offs = l_offs = r_offs = 0; offs < bufLen; offs++) { Uint8 data = buf[offs]; BOOL flush = FALSE; int pmode = mode; if (data == prev) { if (mode == DMODE_LIT && offs - r_offs >= 2) { ret = dmIFFEncodeByteRun1LIT(fp, buf, l_offs, r_offs - l_offs); mode = DMODE_RLE; } } else { if (mode != DMODE_LIT) { ret = dmIFFEncodeByteRun1RLE(fp, buf, r_offs, offs - r_offs); mode = DMODE_LIT; l_offs = offs; } r_offs = offs; } if (!ret) goto out; // NOTE! RLE and LIT max are both 128, checked against DP2e flush = (pmode == DMODE_RLE && offs - r_offs >= 128) || (pmode == DMODE_LIT && offs - l_offs >= 128); // Check for last byte of input if (offs == bufLen - 1) { offs++; flush = TRUE; pmode = mode; } if (flush) { if (pmode == DMODE_RLE) ret = dmIFFEncodeByteRun1RLE(fp, buf, r_offs, offs - r_offs); else ret = dmIFFEncodeByteRun1LIT(fp, buf, l_offs, offs - l_offs); r_offs = l_offs = offs; mode = DMODE_LIT; if (!ret) goto out; } prev = data; } out: return ret; } static BOOL dmIFFWriteOneRow(DMResource *fp, DMIFF *iff, const Uint8 *buf, const size_t bufLen) { if (iff->bmhd.compression == IFF_COMP_BYTERUN1) return dmIFFEncodeByteRun1Row(fp, buf, bufLen); else return dmf_write_str(fp, buf, bufLen); } int dmWriteIFFImage(DMResource *fp, const DMImage *img, const DMImageWriteSpec *spec) { DMIFF iff; Uint8 *buf = NULL; size_t bufLen; int res = DMERR_OK; // XXX: Non-paletted IFF not supported! if ((img->pixfmt != DM_PIXFMT_PALETTE && img->pixfmt != DM_PIXFMT_GRAYSCALE) || (spec->pixfmt != DM_PIXFMT_PALETTE && spec->pixfmt != DM_PIXFMT_GRAYSCALE)) { return dmError(DMERR_NOT_SUPPORTED, "Non-paletted IFF is not supported.\n"); } switch (spec->fmtid) { case DM_IMGFMT_IFF_ILBM: iff.idsig = IFF_ID_ILBM; iff.idstr = "ILBM"; break; case DM_IMGFMT_IFF_PBM : iff.idsig = IFF_ID_PBM; iff.idstr = "PBM"; break; case DM_IMGFMT_IFF_ACBM: iff.idsig = IFF_ID_ACBM; iff.idstr = "ACBM"; break; default: return dmError(DMERR_NOT_SUPPORTED, "Invalid IFF format.\n"); } // Setup headers iff.bmhd.x = 0; iff.bmhd.y = 0; iff.bmhd.w = img->width * spec->scaleX; iff.bmhd.h = img->height * spec->scaleY; iff.bmhd.pagew = img->width * spec->scaleX; iff.bmhd.pageh = img->height * spec->scaleY; iff.bmhd.pad1 = 0; iff.bmhd.xasp = 1; // XXX TODO: compute the xasp/yasp from the img->aspect iff.bmhd.yasp = 1; iff.camg = 0; // XXX TODO: when/if HAM support iff.bmhd.masking = (img->pal && img->pal->ctransp < 0) ? IFF_MASK_NONE : spec->mask; iff.bmhd.transp = (img->pal && img->pal->ctransp >= 0 && spec->mask == IFF_MASK_TRANSP) ? img->pal->ctransp : 0xffff; iff.bmhd.nplanes = (iff.idsig == IFF_ID_PBM && spec->nplanes < 8) ? 8 : spec->nplanes; // Apparently ACBM can't/should not use compression .. even though // some files in the wild have bmhd.compression != 0 (but are not // actually compressed.) To be more compliant with the spec, iff.bmhd.compression = (spec->compression && iff.idsig != IFF_ID_ACBM) ? IFF_COMP_BYTERUN1 : IFF_COMP_NONE; dmMsg(2, "IFF: nplanes=%d, comp=%d, mask=%d\n", iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking); // Write IFF FORM header if ((res = dmWriteIFFChunkHdr(fp, &iff.chFORM, IFF_ID_FORM)) != DMERR_OK) goto out; // Write IFF ILBM/PBM signature if (!dmf_write_be32(fp, iff.idsig)) { res = dmError(DMERR_FWRITE, "IFF: Error writing %s signature.\n", iff.idstr); goto out; } // Write BMHD chunk and data if ((res = dmWriteIFFChunkHdr(fp, &iff.chBMHD, IFF_ID_BMHD)) != DMERR_OK || !dmf_write_be16(fp, iff.bmhd.w) || !dmf_write_be16(fp, iff.bmhd.h) || !dmf_write_be16(fp, iff.bmhd.x) || !dmf_write_be16(fp, iff.bmhd.y) || !dmf_write_byte(fp, iff.bmhd.nplanes) || !dmf_write_byte(fp, iff.bmhd.masking) || !dmf_write_byte(fp, iff.bmhd.compression) || !dmf_write_byte(fp, iff.bmhd.pad1) || !dmf_write_be16(fp, iff.bmhd.transp) || !dmf_write_byte(fp, iff.bmhd.xasp) || !dmf_write_byte(fp, iff.bmhd.yasp) || !dmf_write_be16(fp, iff.bmhd.pagew) || !dmf_write_be16(fp, iff.bmhd.pageh)) { res = dmError(DMERR_FWRITE, "IFF: Error writing BMHD chunk.\n"); goto out; } if ((res = dmWriteIFFChunkFinish(fp, &iff.chBMHD)) != DMERR_OK) goto out; // // CMAP // if (spec->pixfmt == DM_PIXFMT_PALETTE && img->pal != NULL && img->pal->ncolors > 0) { if ((res = dmWriteIFFChunkHdr(fp, &iff.chCMAP, IFF_ID_CMAP)) != DMERR_OK) goto out; if ((res = dmPaletteWriteData(fp, img->pal, img->pal->ncolors, -1)) != DMERR_OK) { res = dmError(DMERR_FWRITE, "IFF: Could not write CMAP palette.\n"); goto out; } if ((res = dmWriteIFFChunkFinish(fp, &iff.chCMAP)) != DMERR_OK) goto out; dmMsg(2, "IFF: CMAP %d entries (%d bytes)\n", img->pal->ncolors, iff.chCMAP.size); } // // CAMG // if ((res = dmWriteIFFChunkHdr(fp, &iff.chCMAP, IFF_ID_CAMG)) != DMERR_OK) goto out; if (!dmf_write_be32(fp, iff.camg)) { return dmError(DMERR_FREAD, "IFF: Error writing CAMG chunk.\n"); } if ((res = dmWriteIFFChunkFinish(fp, &iff.chCMAP)) != DMERR_OK) goto out; // // Encode the body // if ((res = dmWriteIFFChunkHdr(fp, &iff.chBODY, (iff.idsig == IFF_ID_ACBM) ? IFF_ID_ABIT : IFF_ID_BODY)) != DMERR_OK) goto out; // Allocate encoding buffer if (iff.idsig == IFF_ID_ILBM) bufLen = ((img->width * spec->scaleX + 15) / 16) * 2; else if (iff.idsig == IFF_ID_ACBM) bufLen = (img->width * spec->scaleX * img->height * spec->scaleY + 7) / 8; else bufLen = img->width * spec->scaleX; dmMsg(2, "IFF: Line/plane row size %" DM_PRIu_SIZE_T " bytes.\n", bufLen); if ((buf = dmMalloc(bufLen)) == NULL) return DMERR_MALLOC; // Encode the body if (iff.idsig == IFF_ID_ACBM) { for (int plane = 0; plane < iff.bmhd.nplanes; plane++) { // Encode bitplane dmMemset(buf, 0, bufLen); for (int yc = 0; yc < img->height * spec->scaleY; yc++) { Uint8 *sp = img->data + (yc * img->pitch); Uint8 *dp = buf + (yc * img->width * spec->scaleX) / 8; for (int xc = 0; xc < img->width * spec->scaleX; xc++) dp[xc / 8] |= ((sp[xc / spec->scaleX] >> plane) & 1) << (7 - (xc & 7)); } if (!dmIFFWriteOneRow(fp, &iff, buf, bufLen)) { res = dmError(DMERR_FWRITE, "IFF: Error writing ACBM image plane %d.\n", plane); goto out; } } } else { for (int yc = 0; yc < img->height; yc++) for (int yscale = 0; yscale < spec->scaleY; yscale++) { const Uint8 *sp = img->data + (yc * img->pitch); if (iff.idsig == IFF_ID_ILBM) { for (int plane = 0; plane < spec->nplanes; plane++) { // Encode bitplane dmMemset(buf, 0, bufLen); for (int xc = 0; xc < img->width * spec->scaleX; xc++) buf[xc / 8] |= ((sp[xc / spec->scaleX] >> plane) & 1) << (7 - (xc & 7)); // Compress / write data if (!dmIFFWriteOneRow(fp, &iff, buf, bufLen)) { res = dmError(DMERR_FWRITE, "IFF: Error writing ILBM image plane #%d @ row %d.\n", plane, yc); goto out; } } // Write mask data, if any if (iff.bmhd.masking == IFF_MASK_HAS_MASK) { dmMemset(buf, 0, bufLen); for (int xc = 0; xc < img->width * spec->scaleX; xc++) buf[xc / 8] |= (sp[xc / spec->scaleX] == img->pal->ctransp) << (7 - (xc & 7)); if (!dmIFFWriteOneRow(fp, &iff, buf, bufLen)) { res = dmError(DMERR_FWRITE, "IFF: Error writing ILBM mask plane %d.\n", yc); goto out; } } } else { for (int xc = 0; xc < img->width * spec->scaleX; xc++) buf[xc] = sp[xc / spec->scaleX]; if (!dmIFFWriteOneRow(fp, &iff, buf, bufLen)) { res = dmError(DMERR_FWRITE, "IFF: Error writing PBM image row #%d.\n", yc); goto out; } } } } if ((res = dmWriteIFFChunkFinish(fp, &iff.chBODY)) != DMERR_OK) goto out; // Finish the FORM chunk if ((res = dmWriteIFFChunkFinish(fp, &iff.chFORM)) != DMERR_OK) goto out; out: dmFree(buf); return res; } // // List of formats // const DMImageFormat dmImageFormatList[] = { #ifdef DM_USE_LIBPNG { "png", "Portable Network Graphics", DM_IMGFMT_PNG, DM_FMT_RDWR | DM_PIXFMT_ANY, fmtProbePNG, dmReadPNGImage, dmWritePNGImage, }, #endif { "ppm", "Portable PixMap", DM_IMGFMT_PPM, DM_FMT_RDWR | DM_PIXFMT_GRAYSCALE | DM_PIXFMT_RGB, fmtProbePPM, dmReadPPMImage, dmWritePPMImage, }, { "pcx", "Z-Soft Paintbrush", DM_IMGFMT_PCX, DM_FMT_RDWR | DM_PIXFMT_PALETTE | DM_PIXFMT_RGB, fmtProbePCX, dmReadPCXImage, dmWritePCXImage, }, { "ilbm", "IFF ILBM (interleaved/old DP2)", DM_IMGFMT_IFF_ILBM, DM_FMT_RDWR | DM_PIXFMT_PALETTE, fmtProbeIFF_ILBM, dmReadIFFImage, dmWriteIFFImage, }, { "pbm", "IFF PBM (DP2e)", DM_IMGFMT_IFF_PBM, DM_FMT_RDWR | DM_PIXFMT_PALETTE, fmtProbeIFF_PBM, dmReadIFFImage, dmWriteIFFImage, }, { "acbm", "IFF ACBM (Amiga Basic)", DM_IMGFMT_IFF_ACBM, DM_FMT_RDWR | DM_PIXFMT_PALETTE, fmtProbeIFF_ACBM, dmReadIFFImage, dmWriteIFFImage, }, { "raw", "Plain bitplaned (planar or non-planar) RAW", DM_IMGFMT_RAW, DM_FMT_WR | DM_PIXFMT_PALETTE, NULL, NULL, dmWriteRAWImage, }, { "araw", "IFFMaster Amiga RAW", DM_IMGFMT_ARAW, DM_FMT_WR | DM_PIXFMT_PALETTE, NULL, NULL, dmWriteRAWImage, }, { "cdump", "'C' dump (image data only)", DM_IMGFMT_CDUMP, DM_FMT_WR | DM_PIXFMT_ANY, NULL, NULL, dmWriteCDumpImage, } }; const int ndmImageFormatList = sizeof(dmImageFormatList) / sizeof(dmImageFormatList[0]); int dmImageProbeGeneric(const Uint8 *buf, const size_t len, const DMImageFormat **pfmt, int *index) { int scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1; for (int i = 0; i < ndmImageFormatList; i++) { const DMImageFormat *fmt = &dmImageFormatList[i]; if (fmt->probe != NULL) { int score = fmt->probe(buf, len); if (score > scoreMax) { scoreMax = score; scoreIndex = i; } } } if (scoreIndex >= 0) { *pfmt = &dmImageFormatList[scoreIndex]; *index = scoreIndex; return scoreMax; } else return DM_PROBE_SCORE_FALSE; } // // List of formats // const DMPaletteFormat dmPaletteFormatList[] = { { "act", "Adobe Color Table palette", DM_PALFMT_ACT, DM_FMT_RDWR, fmtProbeACTPalette, dmReadACTPalette, dmWriteACTPalette, }, { "rpl", "RAW binary palette (RGB, 768 bytes)", DM_PALFMT_RAW, DM_FMT_RDWR, fmtProbeRAWPalette, dmReadRAWPalette, dmWriteRAWPalette, }, }; const int ndmPaletteFormatList = sizeof(dmPaletteFormatList) / sizeof(dmPaletteFormatList[0]); int dmPaletteProbeGeneric(const Uint8 *buf, const size_t len, const DMPaletteFormat **pfmt, int *index) { int scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1; for (int i = 0; i < ndmPaletteFormatList; i++) { const DMPaletteFormat *fmt = &dmPaletteFormatList[i]; if (fmt->probe != NULL) { int score = fmt->probe(buf, len); if (score > scoreMax) { scoreMax = score; scoreIndex = i; } } } if (scoreIndex >= 0) { *pfmt = &dmPaletteFormatList[scoreIndex]; *index = scoreIndex; return scoreMax; } else return DM_PROBE_SCORE_FALSE; }