Mercurial > hg > dmlib
view tools/libgfx.c @ 2208:90ec1ec89c56
Revamp the palette handling in lib64gfx somewhat, add helper functions to
lib64util for handling external palette file options and add support for
specifying one of the "internal" palettes or external (.act) palette file to
gfxconv and 64vw.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 14 Jun 2019 05:01:12 +0300 |
parents | 1ea48084055e |
children | 7a0af15fbe97 |
line wrap: on
line source
/* * Functions for reading and converting various graphics file formats * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2018 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "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 fmtProbeACT(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; } 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"); } memcpy(&tmpSpec, spec, sizeof(DMImageWriteSpec)); switch (spec->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); 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; } 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); if (width < 1 || height < 1) { res = dmError(DMERR_INVALID_DATA, "PNG: Invalid width or height (%d x %d)\n", width, height); 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.palInfo = 1; hdr.hScreenSize = hdr.hres; hdr.vScreenSize = hdr.vres; // TODO XXX .. maybe actually compute these asdf hdr.bitsPerPlane = 8; hdr.nplanes = dmImageGetBytesPerPixel(spec.pixfmt); 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 %d 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)) { res = dmError(DMERR_FWRITE, "PCX: Could not write basic header data.\n"); goto error; } if (!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)) { res = dmError(DMERR_FWRITE, "PCX: Could not write image dimensions.\n"); goto error; } if (!dmf_write_str(pcx.fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap))) { res = dmError(DMERR_FWRITE, "PCX: Could not write colormap.\n"); goto error; } if (!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 remainder.\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"); 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=%d\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 %d is uneven, adjusting to %d.\n", size, size + 1); size++; } if (size > read) { dmMsg(3, "IFF: Skipping %d bytes (%d of %d 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 %d bytes.\n", bufLen); } else if (iff->idsig == IFF_ID_ACBM) { bufLen = (img->width * img->height + 7) / 8; dmMsg(2, "IFF: Plane buffer size %d 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); // 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 %d 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", "ACT Palette", DM_PALFMT_ACT, DM_FMT_RDWR, fmtProbeACT, dmReadACTPalette, dmWriteACTPalette, }, }; 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; }