Mercurial > hg > dmlib
view tools/libgfx.c @ 1896:f80b2dc77c30
Work begins on IFF ILBM/PBM image writer. It is pretty broken, some things
will not work and some things are hardcoded. The ByteRun1 compression
implementation is somewhat inefficient. Interleaved files do not work yet.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 26 Jun 2018 03:13:38 +0300 |
parents | eb03869a10d3 |
children | 50dbfc10f49f |
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 = 8; } BOOL dmPutBits(DMBitStreamContext *ctx, const int val, const int n) { unsigned int mask = 1 << (n - 1); for (int i = 0; i < n; i++) { ctx->outBuf <<= 1; if (val & mask) ctx->outBuf |= 1; mask >>= 1; ctx->outBitCount--; if (ctx->outBitCount == 0) { if (!ctx->putByte(ctx, ctx->outBuf & 0xff)) return FALSE; ctx->outBitCount = 8; ctx->outByteCount++; } } return TRUE; } int dmFlushBitStream(DMBitStreamContext *ctx) { if (ctx == NULL) return DMERR_NULLPTR; if (ctx->outBitCount != 8) dmPutBits(ctx, 0, ctx->outBitCount); return 0; } static BOOL dmPutByteFILE(DMBitStreamContext *ctx, const Uint8 val) { return dmf_write_byte((DMResource *) ctx->handle, val); } int dmInitBitStreamFILE(DMBitStreamContext *ctx, DMResource *fp) { if (ctx == NULL || fp == NULL) return DMERR_NULLPTR; ctx->putByte = dmPutByteFILE; ctx->handle = (void *) fp; dmInitBitStreamContext(ctx); return DMERR_OK; } BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, 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_COLFMT_PALETTE : return 1; case DM_COLFMT_RGB : return 3; case DM_COLFMT_RGBA : return 4; default : return -1; } } 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->format = format; img->bpp = (bpp <= 0) ? dmImageGetBytesPerPixel(format) * 8 : bpp; img->pitch = width * img->bpp; img->size = img->pitch * img->height; img->ctransp = -1; 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) { dmFree(img->pal); } dmFree(img->data); dmFree(img); } } BOOL dmPaletteAlloc(DMColor **ppal, int ncolors, int ctransp) { if (ppal == NULL) return FALSE; // Allocate desired amount of palette if ((*ppal = dmCalloc(ncolors, sizeof(DMColor))) == NULL) return FALSE; // Set alpha values to max, except for transparent color for (int i = 0; i < ncolors; i++) { (*ppal)[i].a = (i == ctransp) ? 0x00 : 0xff; } return TRUE; } BOOL dmImagePaletteAlloc(DMImage *img, int ncolors, int ctransp) { if (img == NULL) return FALSE; img->ncolors = ncolors; img->ctransp = ctransp; img->constpal = FALSE; return dmPaletteAlloc(&(img->pal), ncolors, ctransp); } static BOOL dmPaletteReadData(DMResource *fp, DMColor *pal, int ncolors) { 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 FALSE; pal[i].r = colR; pal[i].g = colG; pal[i].b = colB; } return TRUE; } int dmWriteImageData(const DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *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->format); 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; for (x = 0; x < img->width; x++) { Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8], qr, qg, qb, qa; switch (spec->format) { case DM_COLFMT_PALETTE: for (xscale = 0; xscale < spec->scaleX; xscale++) *ptr1++ = c; break; case DM_COLFMT_RGBA: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; qa = img->pal[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_COLFMT_RGB: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[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; } } 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 DMImageConvSpec *spec, const int fmtid) { 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); if (fmtid == DM_IMGFMT_ARAW) { dmfprintf(fp, "%s_ncolors: dw.w %d\n" "%s_palette:\n", prefix, img->ncolors, prefix); for (int i = 0; i < (1 << spec->nplanes); i++) { Uint32 color; if (i < img->ncolors) { color = (DMCOL(img->pal[i].r) << 8) | (DMCOL(img->pal[i].g) << 4) | (DMCOL(img->pal[i].b)); } else color = 0; dmfprintf(fp, "\tdc.w $%04X\n", color); } dmfprintf(fp, "%s: incbin \"%s\"\n", prefix, filename); } return DMERR_OK; } static BOOL dmWriteRAWRow(DMBitStreamContext *bs, const DMImage *img, const DMImageConvSpec *spec, const int yc, const int plane) { const Uint8 *sp = img->data + (yc * img->pitch); for (int xc = 0; xc < img->width; xc++) { for (int xscale = 0; xscale < spec->scaleX; xscale++) if (!dmPutBits(bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1)) return FALSE; } return TRUE; } int dmWriteRAWImage(DMResource *fp, const DMImage *img, const DMImageConvSpec *spec) { int res; DMBitStreamContext bs; if ((res = dmInitBitStreamFILE(&bs, fp)) != DMERR_OK) return res; 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 (!dmWriteRAWRow(&bs, img, spec, yc, plane)) return DMERR_FWRITE; } } 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 (!dmWriteRAWRow(&bs, img, spec, yc, plane)) return DMERR_FWRITE; } } return dmFlushBitStream(&bs); } 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 DMImageConvSpec *spec) { DMImageConvSpec tmpSpec; // Write PPM header char *tmp = dm_strdup_printf( "P6\n%d %d\n255\n", img->width * spec->scaleX, img->height * spec->scaleY); if (tmp == NULL) return DMERR_MALLOC; dmfputs(tmp, fp); dmFree(tmp); // Write image data memcpy(&tmpSpec, spec, sizeof(DMImageConvSpec)); tmpSpec.format = DM_COLFMT_RGB; return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec); } #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 DMImageConvSpec *spec) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; int fmt, res; // 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->format) { case DM_COLFMT_PALETTE: fmt = PNG_COLOR_TYPE_PALETTE; break; case DM_COLFMT_RGB : fmt = PNG_COLOR_TYPE_RGB; break; case DM_COLFMT_RGBA : fmt = PNG_COLOR_TYPE_RGB_ALPHA; break; default: res = dmError(DMERR_NOT_SUPPORTED, "PNG: Unsupported image format %d.\n", spec->format); goto error; } png_set_IHDR(png_ptr, info_ptr, img->width * spec->scaleX, img->height * spec->scaleY, 8, /* bits per component */ 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->format == DM_COLFMT_PALETTE) { int i; 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 (i = 0; i < img->ncolors; i++) { palette[i].red = img->pal[i].r; palette[i].green = img->pal[i].g; palette[i].blue = img->pal[i].b; } png_set_PLTE(png_ptr, info_ptr, palette, img->ncolors); } // 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, res_y; int i, bit_depth, color_type, ncolors, ntrans, unit_type; int res = DMERR_OK; 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) png_set_expand_gray_1_2_4_to_8(png_ptr); if (bit_depth > 8) { res = dmError(DMERR_NOT_SUPPORTED, "PNG: Unsupported bit depth for grayscale image: %d\n", bit_depth); goto error; } break; case PNG_COLOR_TYPE_PALETTE: png_set_packing(png_ptr); 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, DM_COLFMT_PALETTE, // 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; // ... row_pointers = png_malloc(png_ptr, height * sizeof(png_bytep)); for (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); // Create palette switch (color_type) { case PNG_COLOR_TYPE_GRAY: ncolors = 256; dmMsg(2, "PNG: Generating %d color grayscale palette.\n", ncolors); if (!dmImagePaletteAlloc(img, ncolors, -1)) { res = DMERR_MALLOC; goto error; } for (i = 0; i < img->ncolors; i++) { img->pal[i].r = img->pal[i].g = img->pal[i].b = i; } break; case PNG_COLOR_TYPE_PALETTE: png_get_PLTE(png_ptr, info_ptr, &palette, &ncolors); dmMsg(2, "PNG: Palette of %d colors found.\n", ncolors); if (ncolors > 0 && palette != NULL) { if (!dmImagePaletteAlloc(img, ncolors, -1)) { res = DMERR_MALLOC; goto error; } for (i = 0; i < img->ncolors; i++) { img->pal[i].r = palette[i].red; img->pal[i].g = palette[i].green; img->pal[i].b = palette[i].blue; } } break; } if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { png_get_tRNS(png_ptr, info_ptr, &trans, &ntrans, NULL); if (trans != NULL && ntrans > 0) { for (i = 0; i < img->ncolors && i < ntrans; i++) { img->pal[i].a = trans[i]; if (img->ctransp < 0 && trans[i] == 0) img->ctransp = i; } } } error: // png_free(png_ptr, palette); if (png_ptr && info_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return res; } #endif // // Z-Soft PCX format // #define DMPCX_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[DMPCX_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 DMImageConvSpec *pspec) { DMPCXData pcx; DMPCXHeader hdr; DMImageConvSpec spec; int res; // Always force planar for PCX memcpy(&spec, pspec, sizeof(DMImageConvSpec)); spec.planar = TRUE; // XXX: 24bit PCX does not work yet .. if (!spec.paletted) { return dmError(DMERR_NOT_SUPPORTED, "24bit PCX not supported yet.\n"); } if (spec.paletted && 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.paletted) { for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : img->ncolors); i++) { hdr.colorMap[i].r = img->pal[i].r; hdr.colorMap[i].g = img->pal[i].g; hdr.colorMap[i].b = img->pal[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.format); 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, isPaletted=%s, planar=%s\n", hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, spec.paletted ? "yes" : "no", 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.paletted) { int i; dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors); dmf_write_byte(pcx.fp, 0x0C); for (i = 0; i < img->ncolors; i++) { if (!dmf_write_byte(pcx.fp, img->pal[i].r) || !dmf_write_byte(pcx.fp, img->pal[i].g) || !dmf_write_byte(pcx.fp, img->pal[i].b)) { res = dmError(DMERR_FWRITE, "PCX: Could not write palette data.\n"); goto error; } } // Pad the palette, if necessary for (; i < 256; i++) { if (!dmf_write_byte(pcx.fp, 0) || !dmf_write_byte(pcx.fp, 0) || !dmf_write_byte(pcx.fp, 0)) { 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 and bpp, attempting to fix ..\n"); 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_COLFMT_PALETTE : DM_COLFMT_RGBA, // 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 32: case 24: case 16: 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 = DMPCX_PAL_COLORS; } else { read = TRUE; ncolors = 256; } if (!dmImagePaletteAlloc(img, ncolors, -1)) { res = dmError(DMERR_MALLOC, "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 (!dmPaletteReadData(fp, img->pal, ncolors)) { res = dmError(DMERR_FREAD, "PCX: Error reading palette.\n"); goto error; } } else { // 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\n", ncolors); for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : img->ncolors); i++) { img->pal[i].r = hdr.colorMap[i].r; img->pal[i].g = hdr.colorMap[i].g; img->pal[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_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; DMColor *pal; } DMIFF; static int fmtProbeIFF(const Uint8 *buf, const size_t len) { if (len > 32 && buf[ 0] == 'F' && buf[ 1] == 'O' && buf[ 2] == 'R' && buf[ 3] == 'M' && ( (buf[ 8] == 'I' && buf[ 9] == 'L' && buf[10] == 'B' && buf[11] == 'M') || (buf[ 8] == 'P' && buf[ 9] == 'B' && buf[10] == 'M' && buf[11] == 0x20) )) { if (buf[12] == 'B' && buf[13] == 'M' && buf[14] == 'H' && buf[15] == 'D') return DM_PROBE_SCORE_MAX; else return DM_PROBE_SCORE_GOOD; } return DM_PROBE_SCORE_FALSE; } 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; if (chunk->size > read) { dmMsg(4, "IFF: Skipping %d bytes (%d of %d consumed)\n", chunk->size - read, read, chunk->size); if (dmfseek(fp, chunk->size - read, SEEK_CUR) != 0) { return dmError(DMERR_FSEEK, "IFF: Failed to skip chunk end.\n"); } else return DMERR_OK; } else return DMERR_OK; } static int dmCheckIFFChunk(DMIFFChunk *dest, 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 is too small.\n"); } return DMERR_OK; } static BOOL dmIFFDecodeByteRun1Row(DMResource *fp, Uint8 *buf, const size_t bufLen) { size_t offs = 0; do { Sint8 dcount; Uint8 data; if (!dmf_read_byte(fp, (Uint8 *) &dcount)) return FALSE; if (dcount == -128) { if (!dmf_read_byte(fp, &data)) return FALSE; } else if (dcount < 0) { int count = (-dcount) + 1; if (!dmf_read_byte(fp, &data)) return FALSE; while (count-- && offs < bufLen) buf[offs++] = data; } else { int count = dcount + 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 void dmDecodeBitPlane(Uint8 *dst, const Uint8 *src, const int width, const int nplane) { for (int xc = 0; xc < width; xc++) dst[xc] |= dmDecodeBit(src, xc) << nplane; } static int dmDecodeILBMBody(DMResource *fp, DMIFF *iff, DMImage *img) { Uint8 *buf; size_t bufLen; int res = DMERR_OK; const int nplanes = iff->bmhd.nplanes; // Allocate planar decoding buffer bufLen = ((img->width + 15) / 16) * 2; if ((buf = dmMalloc(bufLen)) == NULL) return DMERR_MALLOC; dmMsg(2, "IFF: plane row size %d bytes.\n", bufLen); // Decode the chunk for (int yc = 0; yc < img->height; yc++) { Uint8 *dp = img->data + (yc * img->pitch); 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 image plane #%d @ %d.\n", plane, yc); goto error; } // Decode bitplane dmDecodeBitPlane(dp, buf, img->width, 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 mask plane.\n"); goto error; } // 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->ctransp < 0 ? 0 : img->ctransp; } } } error: dmFree(buf); return res; } static int dmDecodePBMBody(DMResource *fp, DMIFF *iff, DMImage *img) { for (int yc = 0; yc < img->height; yc++) { if (!dmIFFReadOneRow(fp, iff, img->data + (yc * img->pitch), img->width)) { return dmError(DMERR_FREAD, "IFF: Error reading PBM image row #%d.\n", yc); } } return DMERR_OK; } int dmReadIFFImage(DMResource *fp, DMImage **pimg) { DMIFFChunk chunk; DMIFF iff; Uint32 idsig; BOOL parsed = FALSE, planar; 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, &idsig) || (idsig != IFF_ID_ILBM && idsig != IFF_ID_PBM)) { return dmError(DMERR_INVALID_DATA, "IFF: Not a IFF ILBM/PBM file.\n"); } planar = (idsig == IFF_ID_ILBM); 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"); } 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 (!dmPaletteAlloc(&iff.pal, iff.ncolors, (iff.bmhd.masking == IFF_MASK_TRANSP) ? iff.bmhd.transp : -1)) { return dmError(DMERR_MALLOC, "IFF: Could not allocate palette data.\n"); } if (!dmPaletteReadData(fp, iff.pal, iff.ncolors)) { return dmError(DMERR_FREAD, "IFF: Error reading CMAP.\n"); } } if (iff.chBMHD.count && iff.chBODY.count) parsed = TRUE; break; case IFF_ID_BODY: // 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: BODY chunk before BMHD?\n"); } dmMsg(2, "IFF: BODY chunk size %d bytes\n", chunk.size); // Allocate image if ((*pimg = dmImageAlloc(iff.bmhd.w, iff.bmhd.h, iff.bmhd.nplanes <= 8 ? DM_COLFMT_PALETTE : DM_COLFMT_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 (planar) { if ((res = dmDecodeILBMBody(fp, &iff, *pimg)) != DMERR_OK) return res; } else { if ((res = dmDecodePBMBody(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(4, "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; } // Set colormap after finishing if (iff.pal != NULL && iff.ncolors > 0 && *pimg != NULL) { // If halfbrite is used, duplicate the palette if (iff.camg & IFF_CAMG_HALFBRITE) { void *ptmp; if (!planar) { 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 ((ptmp = dmRealloc(iff.pal, sizeof(DMColor) * iff.ncolors * 2)) == NULL) { dmFree(iff.pal); iff.pal = NULL; return DMERR_MALLOC; } else iff.pal = ptmp; for (int i = 0; i < iff.ncolors; i++) { int i2 = iff.ncolors + i; iff.pal[i2].r = iff.pal[i].r / 2; iff.pal[i2].g = iff.pal[i].g / 2; iff.pal[i2].b = iff.pal[i].b / 2; } } (*pimg)->ncolors = iff.ncolors; (*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, chunk->size)) { 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) && !dmf_write_byte(fp, 0)) return dmferror(fp); 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, }; BOOL dmIFFEncodeByteRun1Flush( DMResource *fp, const int mode, const BOOL flush, size_t *l_offs, const size_t offs, const Uint8 *buf, const Uint8 data, unsigned int *r_count) { if (mode == DMODE_LIT) { if (offs - *l_offs > *r_count || flush) { size_t count = (offs - *l_offs) - *r_count; Sint8 tmp = count - 1; if (!dmf_write_byte(fp, tmp) || !dmf_write_str(fp, buf + *l_offs, count)) return FALSE; } (*r_count)++; } else { unsigned int count = *r_count; Sint8 tmp = -(count - 1); if (!dmf_write_byte(fp, tmp) || !dmf_write_byte(fp, data)) return FALSE; *r_count = 0; *l_offs = offs; } return TRUE; } BOOL dmIFFEncodeByteRun1Row(DMResource *fp, const Uint8 *buf, const size_t bufLen) { size_t l_offs = 0, offs = 0; unsigned int r_count = 0; int prev = -1, mode = DMODE_LIT; for (offs = 0; offs < bufLen; offs++) { Uint8 data = buf[offs]; int next_mode; if (data == prev) { r_count++; next_mode = DMODE_RLE; } else { next_mode = DMODE_LIT; } BOOL flush = offs - l_offs >= 126 || r_count >= 126; if ((next_mode != mode || flush) && !dmIFFEncodeByteRun1Flush(fp, mode, flush, &l_offs, offs, buf, prev, &r_count)) return FALSE; mode = next_mode; prev = data; } if (!dmIFFEncodeByteRun1Flush(fp, mode, TRUE, &l_offs, offs, buf, prev, &r_count)) return FALSE; return TRUE; } 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); } static inline Uint8 dmEncodeBit(const Uint8 *buf, const int xc) { return (buf[xc] >> (7 - (xc & 7))) & 1; } void dmEncodeBitPlane(Uint8 *dp, const Uint8 *src, const int width, const int nplane) { for (int xc = 0; xc < width; xc++) dp[xc / 8] |= dmEncodeBit(src, xc) << nplane; } int dmEncodeILBMBody(DMResource *fp, DMIFF *iff, const DMImage *img) { Uint8 *buf; size_t bufLen; int res = DMERR_OK; const int nplanes = iff->bmhd.nplanes; // Allocate planar encoding buffer bufLen = ((img->width + 15) / 16) * 2; if ((buf = dmMalloc(bufLen)) == NULL) return DMERR_MALLOC; dmMsg(2, "IFF: plane row size %d bytes.\n", bufLen); // Encode the chunk for (int yc = 0; yc < img->height; yc++) { const Uint8 *sp = img->data + (yc * img->pitch); dmMemset(buf, 0, bufLen); for (int plane = 0; plane < nplanes; plane++) { // Encode bitplane dmEncodeBitPlane(buf, sp, img->width, plane); // Compress / write data if (!dmIFFWriteOneRow(fp, iff, buf, bufLen)) { res = dmError(DMERR_FWRITE, "IFF: Error in writing image plane #%d @ %d.\n", plane, yc); goto error; } } } error: dmFree(buf); return res; } int dmEncodePBMBody(DMResource *fp, DMIFF *iff, const DMImage *img) { for (int yc = 0; yc < img->height; yc++) { if (!dmIFFWriteOneRow(fp, iff, img->data + (yc * img->pitch), img->width)) { return dmError(DMERR_FWRITE, "IFF: Error writing PBM image row #%d.\n", yc); } } return DMERR_OK; } int dmWriteIFFImage(DMResource *fp, const DMImage *img, const DMImageConvSpec *spec) { Uint32 idsig; DMIFF iff; int res = DMERR_OK; // XXX: Non-paletted ILBM not supported! if (!spec->paletted) { return dmError(DMERR_NOT_SUPPORTED, "Non-paletted IFF is not supported.\n"); } // Setup headers iff.bmhd.x = 0; iff.bmhd.y = 0; iff.bmhd.w = img->width; iff.bmhd.h = img->height; iff.bmhd.pagew = img->width; iff.bmhd.pageh = img->height; 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->ctransp < 0) ? IFF_MASK_NONE : spec->mask; iff.bmhd.compression = spec->compression ? IFF_COMP_BYTERUN1 : IFF_COMP_NONE; iff.bmhd.transp = (img->ctransp >= 0 && spec->mask == IFF_MASK_TRANSP) ? img->ctransp : 0xffff; iff.bmhd.nplanes = spec->nplanes; idsig = spec->planar ? IFF_ID_ILBM : IFF_ID_PBM; 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, idsig)) { res = dmError(DMERR_FWRITE, "IFF: Error writing %s signature.\n", spec->planar ? "ILBM" : "PBM"); 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 (img->ncolors > 0 && spec->paletted) { if ((res = dmWriteIFFChunkHdr(fp, &iff.chCMAP, IFF_ID_CMAP)) != DMERR_OK) goto out; for (int i = 0; i < img->ncolors; i++) { DMColor *col = &img->pal[i]; if (!dmf_write_byte(fp, col->r) || !dmf_write_byte(fp, col->g) || !dmf_write_byte(fp, col->b)) { res = dmError(DMERR_FWRITE, "IFF: Could not write CMAP palette entry #%d.\n", i); goto out; } } if ((res = dmWriteIFFChunkFinish(fp, &iff.chCMAP)) != DMERR_OK) goto out; dmMsg(2, "IFF: CMAP %d entries (%d bytes)\n", img->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_ID_BODY)) != DMERR_OK) goto out; if (spec->planar) { if ((res = dmEncodeILBMBody(fp, &iff, img)) != DMERR_OK) goto out; } else { if ((res = dmEncodePBMBody(fp, &iff, img)) != DMERR_OK) 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: return res; } // // List of formats // const DMImageFormat dmImageFormatList[] = { #ifdef DM_USE_LIBPNG { "png", "Portable Network Graphics", DM_IMGFMT_PNG, DM_FMT_RDWR, fmtProbePNG, dmReadPNGImage, dmWritePNGImage, }, #endif { "ppm", "Portable PixMap", DM_IMGFMT_PPM, DM_FMT_WR, NULL, NULL, dmWritePPMImage, }, { "pcx", "Z-Soft Paintbrush", DM_IMGFMT_PCX, DM_FMT_RDWR, fmtProbePCX, dmReadPCXImage, dmWritePCXImage, }, { "iff", "IFF ILBM / PBM", DM_IMGFMT_IFF, DM_FMT_RDWR, fmtProbeIFF, dmReadIFFImage, dmWriteIFFImage, }, { "raw", "Plain bitplaned (planar or non-planar) RAW", DM_IMGFMT_RAW, DM_FMT_WR, NULL, NULL, dmWriteRAWImage, }, { "araw", "IFFMaster Amiga RAW", DM_IMGFMT_ARAW, DM_FMT_WR, NULL, NULL, dmWriteRAWImage, } }; 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; }