Mercurial > hg > dmlib
diff tools/libgfx.c @ 1307:43b13dbbdcd1
Moved libgfx to tools/ as it's not really a very generic piece of code that
belongs to the engine ..
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 19 Aug 2017 15:18:29 +0300 |
parents | src/libgfx.c@e968b259605b |
children | 8f71ca1900ea |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/libgfx.c Sat Aug 19 15:18:29 2017 +0300 @@ -0,0 +1,1998 @@ +/* + * Functions for reading and converting various restricted + * C64/etc and/or indexed/paletted graphics formats. + * Programmed and designed by Matti 'ccr' Hamalainen + * (C) Copyright 2012 Tecnic Software productions (TNSP) + * + * Please read file 'COPYING' for information on license and distribution. + */ +#include "libgfx.h" +#include "dmfile.h" +#include "dmbstr.h" + +#ifdef DM_USE_LIBPNG +#include <png.h> +#endif + + +int dmGFXErrorMode = DM_ERRMODE_FAIL; + + + +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_IFMT_PALETTE : return 1; + case DM_IFMT_RGB : return 3; + case DM_IFMT_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; + + 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) +{ + int i; + + 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 (i = 0; i < ncolors; i++) + { + (*ppal)[i].a = (i == ctransp) ? 0 : 255; + } + + return TRUE; +} + + +BOOL dmImageAllocPalette(DMImage *img, int ncolors, int ctransp) +{ + if (img == NULL) + return FALSE; + + img->ncolors = ncolors; + img->ctransp = ctransp; + return dmPaletteAlloc(&(img->pal), ncolors, ctransp); +} + + +static BOOL dmReadPaletteData(FILE *fp, DMColor *pal, int ncolors) +{ + int i; + + for (i = 0; i < ncolors; i++) + { + Uint8 colR, colG, colB; + if (!dm_fread_byte(fp, &colR) || + !dm_fread_byte(fp, &colG) || + !dm_fread_byte(fp, &colB)) + return FALSE; + + pal[i].r = colR; + pal[i].g = colG; + pal[i].b = colB; + } + + return TRUE; +} + + +int dmWriteImageData(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_IFMT_PALETTE: + for (xscale = 0; xscale < spec->scaleX; xscale++) + *ptr1++ = c; + break; + + case DM_IFMT_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_IFMT_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 dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors, + const char *indent, const char *type) +{ + int i; + + for (i = 0; i < ncolors; i++) + { + int 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; + + fprintf(fp, "%s%s $%04X\n", + indent != NULL ? indent : "\t", + type != NULL ? type : "dc.w", + color); + } + + return DMERR_OK; +} + + +int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec) +{ + int xc, yc, plane, res; + DMBitStreamContext bs; + + if ((res = dmInitBitStreamFILE(&bs, fp)) != DMERR_OK) + return res; + + if (spec->planar) + { + // Output bitplanes in planerd format (each plane of line sequentially) + for (yc = 0; yc < img->height; yc++) + { + for (plane = 0; plane < spec->nplanes; plane++) + { + Uint8 *sp = img->data + yc * img->pitch; + for (xc = 0; xc < img->width; xc++) + { + if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1)) + return DMERR_FWRITE; + } + } + } + } + else + { + // Output each bitplane in sequence + for (plane = 0; plane < spec->nplanes; plane++) + { + for (yc = 0; yc < img->height; yc++) + { + Uint8 *sp = img->data + yc * img->pitch; + for (xc = 0; xc < img->width; xc++) + { + if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1)) + return DMERR_FWRITE; + } + } + } + } + + return dmFlushBitStream(&bs); +} + + +int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec) +{ + FILE *fp; + int res; + + if ((fp = fopen(filename, "wb")) == NULL) + { + return dmError(DMERR_FOPEN, + "RAW: Could not open file '%s' for writing.\n", + filename); + } + + res = dmWriteRAWImageFILE(fp, img, spec); + + fclose(fp); + return res; +} + + +static int dmWritePPMRow(void *cbdata, const Uint8 *row, const size_t len) +{ + if (fwrite(row, sizeof(Uint8), len, (FILE *) cbdata) == len) + return DMERR_OK; + else + return DMERR_FWRITE; +} + + +int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec) +{ + DMImageConvSpec tmpSpec; + + // Write PPM header + fprintf(fp, + "P6\n%d %d\n255\n", + img->width * spec->scaleX, + img->height * spec->scaleY); + + // Write image data + memcpy(&tmpSpec, spec, sizeof(DMImageConvSpec)); + tmpSpec.format = DM_IFMT_RGB; + return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec); +} + + +int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec) +{ + FILE *fp; + int res; + + // Create output file + if ((fp = fopen(filename, "wb")) == NULL) + { + return dmError(DMERR_FOPEN, + "PPM: could not open file '%s' for writing.\n", + filename); + } + + res = dmWritePPMImageFILE(fp, img, spec); + + fclose(fp); + return res; +} + + +#ifdef DM_USE_LIBPNG +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; +} + + +int dmWritePNGImageFILE(FILE *fp, 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_init_io(png_ptr, fp); + + // Write PNG header info + switch (spec->format) + { + case DM_IFMT_PALETTE: fmt = PNG_COLOR_TYPE_PALETTE; break; + case DM_IFMT_RGB : fmt = PNG_COLOR_TYPE_RGB; break; + case DM_IFMT_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_IFMT_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; +} + + +int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec) +{ + int res; + FILE *fp; + + if ((fp = fopen(filename, "wb")) == NULL) + { + return dmError(DMERR_FOPEN, + "PNG: could not open file '%s' for writing.\n", + filename); + } + + res = dmWritePNGImageFILE(fp, img, spec); + + fclose(fp); + return res; +} + + +int dmReadPNGImageFILE(FILE *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; + int i, bit_depth, color_type, ncolors, ntrans; + 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_init_io(png_ptr, fp); + + // 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_IFMT_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; + } + + // ... + 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 (!dmImageAllocPalette(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 (!dmImageAllocPalette(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; +} + + +int dmReadPNGImage(const char *filename, DMImage **img) +{ + int res; + FILE *fp; + + if ((fp = fopen(filename, "rb")) == NULL) + { + return dmError(DMERR_FOPEN, + "PNG: Could not open file '%s' for reading.\n", + filename); + } + + res = dmReadPNGImageFILE(fp, img); + + fclose(fp); + return res; +} +#endif + + +#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; + FILE *fp; +} DMPCXData; + + +// 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 = fwrite(pcx->buf, sizeof(Uint8), pcx->bufOffs, pcx->fp) == 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 dmWritePCXImageFILE(FILE *fp, 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; + + // Create output file + pcx.buf = NULL; + pcx.header = &hdr; + pcx.fp = fp; + + // Create PCX header + dmMemset(&hdr, 0, sizeof(hdr)); + if (spec.paletted && img->pal != NULL) + { + 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 (!dm_fwrite_byte(pcx.fp, hdr.manufacturer) || + !dm_fwrite_byte(pcx.fp, hdr.version) || + !dm_fwrite_byte(pcx.fp, hdr.encoding) || + !dm_fwrite_byte(pcx.fp, hdr.bitsPerPlane)) + { + res = dmError(DMERR_FWRITE, + "PCX: Could not write basic header data.\n"); + goto error; + } + + if (!dm_fwrite_le16(pcx.fp, hdr.xmin) || + !dm_fwrite_le16(pcx.fp, hdr.ymin) || + !dm_fwrite_le16(pcx.fp, hdr.xmax) || + !dm_fwrite_le16(pcx.fp, hdr.ymax) || + !dm_fwrite_le16(pcx.fp, hdr.hres) || + !dm_fwrite_le16(pcx.fp, hdr.vres)) + { + res = dmError(DMERR_FWRITE, + "PCX: Could not write image dimensions.\n"); + goto error; + } + + if (!dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap))) + { + res = dmError(DMERR_FWRITE, + "PCX: Could not write colormap.\n"); + goto error; + } + + if (!dm_fwrite_byte(pcx.fp, hdr.reserved) || + !dm_fwrite_byte(pcx.fp, hdr.nplanes) || + !dm_fwrite_le16(pcx.fp, hdr.bpl) || + !dm_fwrite_le16(pcx.fp, hdr.palInfo) || + !dm_fwrite_le16(pcx.fp, hdr.hScreenSize) || + !dm_fwrite_le16(pcx.fp, hdr.vScreenSize) || + !dm_fwrite_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); + + dm_fwrite_byte(pcx.fp, 0x0C); + + for (i = 0; i < img->ncolors; i++) + { + if (!dm_fwrite_byte(pcx.fp, img->pal[i].r) || + !dm_fwrite_byte(pcx.fp, img->pal[i].g) || + !dm_fwrite_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 (!dm_fwrite_byte(pcx.fp, 0) || + !dm_fwrite_byte(pcx.fp, 0) || + !dm_fwrite_byte(pcx.fp, 0)) + { + res = dmError(DMERR_FWRITE, + "PCX: Could not write palette data.\n"); + goto error; + } + } + } + +error: + dmFree(pcx.buf); + return res; +} + + +int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec) +{ + FILE *fp; + int res; + + if ((fp = fopen(filename, "wb")) == NULL) + { + return dmError(DMERR_FOPEN, + "PCX: Could not open file '%s' for writing.\n", + filename); + } + + res = dmWritePCXImageFILE(fp, img, spec); + + fclose(fp); + return res; +} + + +static BOOL dmPCXDecodeRLERow(FILE *fp, Uint8 *buf, const size_t bufLen) +{ + size_t offs = 0; + do + { + int count; + Uint8 data; + + if (!dm_fread_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 && !dm_fread_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 dmReadPCXImageFILE(FILE *fp, DMImage **pimg) +{ + DMImage *img; + DMPCXData pcx; + DMPCXHeader hdr; + int res = 0; + BOOL isPaletted; + pcx.buf = NULL; + + // Read PCX header + if (!dm_fread_byte(fp, &hdr.manufacturer) || + !dm_fread_byte(fp, &hdr.version) || + !dm_fread_byte(fp, &hdr.encoding) || + !dm_fread_byte(fp, &hdr.bitsPerPlane) || + !dm_fread_le16(fp, &hdr.xmin) || + !dm_fread_le16(fp, &hdr.ymin) || + !dm_fread_le16(fp, &hdr.xmax) || + !dm_fread_le16(fp, &hdr.ymax) || + !dm_fread_le16(fp, &hdr.hres) || + !dm_fread_le16(fp, &hdr.vres) || + !dm_fread_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) || + !dm_fread_byte(fp, &hdr.reserved) || + !dm_fread_byte(fp, &hdr.nplanes) || + !dm_fread_le16(fp, &hdr.bpl) || + !dm_fread_le16(fp, &hdr.palInfo) || + !dm_fread_le16(fp, &hdr.hScreenSize) || + !dm_fread_le16(fp, &hdr.vScreenSize) || + !dm_fread_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_IFMT_PALETTE : DM_IFMT_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; + } + + // 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: + memset(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 (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C) + { + read = FALSE; + ncolors = DMPCX_PAL_COLORS; + } + else + { + read = TRUE; + ncolors = 256; + } + + if (!dmImageAllocPalette(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 (!dmReadPaletteData(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; +} + + +int dmReadPCXImage(const char *filename, DMImage **pimg) +{ + FILE *fp; + int res; + + if ((fp = fopen(filename, "rb")) == NULL) + { + return dmError(DMERR_FOPEN, + "PCX: Could not open file '%s' for reading.\n", + filename); + } + + res = dmReadPCXImageFILE(fp, pimg); + + fclose(fp); + return res; +} + + +#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 str[6]; +} 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 chBMHD, chCMAP, chBODY; + DMIFFBMHD bmhd; + Uint32 camg; + int ncolors; + DMColor *pal; + BOOL planar; +} DMIFF; + + +static BOOL dmReadIFFChunk(FILE *fp, DMIFFChunk *chunk) +{ + if (!dm_fread_be32(fp, &chunk->id) || + !dm_fread_be32(fp, &chunk->size)) + { + dmError(DMERR_FREAD, + "ILBM: Could not read IFF chunk header.\n"); + return FALSE; + } + else + return TRUE; +} + +static char * dmGetIFFChunkID(DMIFFChunk *chunk) +{ + chunk->str[0] = (chunk->id >> 24) & 0xff; + chunk->str[1] = (chunk->id >> 16) & 0xff; + chunk->str[2] = (chunk->id >> 8) & 0xff; + chunk->str[3] = (chunk->id) & 0xff; + chunk->str[4] = 0; + return chunk->str; +} + +static int dmSkipIFFChunkRest(FILE *fp, const DMIFFChunk *chunk, const Uint32 used) +{ + if (chunk->size > used) + { + dmMsg(4, "ILBM: Skipping %d bytes (%d of %d consumed)\n", + chunk->size - used, used, chunk->size); + + if (fseeko(fp, chunk->size - used, SEEK_CUR) != 0) + { + return dmError(DMERR_FSEEK, + "ILBM: 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, + "ILBM: Multiple instances of chunk %s found.\n", + dmGetIFFChunkID(chunk)); + } + + dest->count++; + + if (chunk->size < minSize) + { + return dmError(DMERR_OUT_OF_DATA, + "ILBM: Chunk is too small.\n"); + } + + return DMERR_OK; +} + + +static BOOL dmIFFDecodeByteRun1Row(FILE *fp, Uint8 *buf, const size_t bufLen) +{ + size_t offs = 0; + do + { + Sint8 dcount; + Uint8 data; + + if (!dm_fread_byte(fp, (Uint8 *) &dcount)) + return FALSE; + + if (dcount == -128) + { + if (!dm_fread_byte(fp, &data)) + return FALSE; + } + else + if (dcount < 0) + { + int count = (-dcount) + 1; + if (!dm_fread_byte(fp, &data)) + return FALSE; + + while (count-- && offs < bufLen) + buf[offs++] = data; + } + else + { + int count = dcount + 1; + while (count-- && offs < bufLen) + { + if (!dm_fread_byte(fp, &data)) + return FALSE; + + buf[offs++] = data; + } + } + } while (offs < bufLen); + + return TRUE; +} + + +static BOOL dmIFFReadOneRow(FILE *fp, DMIFF *iff, Uint8 *buf, const size_t bufLen) +{ + if (iff->bmhd.compression == IFF_COMP_BYTERUN1) + return dmIFFDecodeByteRun1Row(fp, buf, bufLen); + else + return dm_fread_str(fp, buf, bufLen); +} + + +void dmDecodeBitPlane(Uint8 *dp, Uint8 *src, const int width, const int nplane) +{ + int xc; + for (xc = 0; xc < width; xc++) + { + const Uint8 data = (src[xc / 8] >> (7 - (xc & 7))) & 1; + dp[xc] |= (data << nplane); + } +} + + +int dmDecodeILBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read) +{ + DMImage *img; + Uint8 *buf; + size_t bufLen; + int yc, res = DMERR_OK; + + *read = 0; + + // Allocate image + if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h, + iff->bmhd.nplanes <= 8 ? DM_IFMT_PALETTE : DM_IFMT_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; + + // Allocate planar decoding buffer + bufLen = ((img->width + 15) / 16) * 2; + if ((buf = dmMalloc(bufLen)) == NULL) + return DMERR_MALLOC; + + dmMsg(2, "ILBM: plane row size %d bytes.\n", bufLen); + + // Decode the chunk + for (yc = 0; yc < img->height; yc++) + { + int plane; + const int nplanes = iff->bmhd.nplanes; + Uint8 *dp = img->data + (yc * img->pitch); + + dmMemset(dp, 0, img->pitch); + + for (plane = 0; plane < nplanes; plane++) + { + // Decompress or read data + if (!dmIFFReadOneRow(fp, iff, buf, bufLen)) + { + res = dmError(DMERR_FREAD, + "ILBM: Error in reading image plane #%d @ %d.\n", + plane, yc); + goto error; + } + + // Decode bitplane + dmDecodeBitPlane(dp, buf, img->width, plane); + + *read += bufLen; + } + + // Read mask data + if (iff->bmhd.masking == IFF_MASK_HAS_MASK) + { + int xc; + + // Decompress or read data + if (!dmIFFReadOneRow(fp, iff, buf, bufLen)) + { + res = dmError(DMERR_FREAD, + "ILBM: Error in reading mask plane.\n"); + goto error; + } + + // Decode mask + for (xc = 0; xc < img->width; xc++) + { + const Uint8 data = (buf[xc / 8] >> (7 - (xc & 7))) & 1; + + // Black out any pixels with mask bit 0 + if (!data) + dp[xc] = 0; + } + + *read += bufLen; + } + } + +error: + dmFree(buf); + return res; +} + + +int dmDecodePBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read) +{ + DMImage *img; + int yc, res = DMERR_OK; + + *read = 0; + + // Allocate image + if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h, + iff->bmhd.nplanes <= 8 ? DM_IFMT_PALETTE : DM_IFMT_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; + + // Decode the chunk + for (yc = 0; yc < img->height; yc++) + { + Uint8 *dp = img->data + (yc * img->pitch); + + if (!dmIFFReadOneRow(fp, iff, dp, img->width)) + { + res = dmError(DMERR_FREAD, + "ILBM: Error in reading image row #%d.\n", yc); + goto error; + } + + *read += img->width; + } + +error: + return res; +} + + +int dmReadILBMImageFILE(FILE *fp, DMImage **pimg) +{ + Uint32 idILBM; + DMIFFChunk chunk; + DMIFF iff; + Uint32 read; + BOOL parsed = FALSE; + int i, res = DMERR_OK; + + dmMemset(&iff, 0, sizeof(iff)); + + // Read IFF FORM header + if (!dmReadIFFChunk(fp, &chunk) || + chunk.id != IFF_ID_FORM || + chunk.size < 32) + { + return dmError(DMERR_NOT_SUPPORTED, + "ILBM: Not a IFF file (%08X vs %08X / %d).\n", + chunk.id, IFF_ID_FORM, chunk.size); + } + + // Check IFF ILBM signature + if (!dm_fread_be32(fp, &idILBM) || + (idILBM != IFF_ID_ILBM && idILBM != IFF_ID_PBM)) + { + return dmError(DMERR_INVALID_DATA, + "ILBM: Not a ILBM file.\n"); + } + + iff.planar = (idILBM == IFF_ID_ILBM); + + while (!parsed && !feof(fp)) + { + if (!dmReadIFFChunk(fp, &chunk)) + { + return dmError(DMERR_FREAD, + "ILBM: Error reading IFF ILBM data.\n"); + } + + 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 (!dm_fread_be16(fp, &iff.bmhd.w) || + !dm_fread_be16(fp, &iff.bmhd.h) || + !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.x) || + !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.y) || + !dm_fread_byte(fp, &iff.bmhd.nplanes) || + !dm_fread_byte(fp, &iff.bmhd.masking) || + !dm_fread_byte(fp, &iff.bmhd.compression) || + !dm_fread_byte(fp, &iff.bmhd.pad1) || + !dm_fread_be16(fp, &iff.bmhd.transp) || + !dm_fread_byte(fp, &iff.bmhd.xasp) || + !dm_fread_byte(fp, &iff.bmhd.yasp) || + !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pagew) || + !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pageh)) + { + return dmError(DMERR_FREAD, + "ILBM: Error reading BMHD chunk.\n"); + } + + dmMsg(2, "ILBM: BMHD %d x %d @ %d, %d : nplanes=%d, comp=%d, mask=%d\n", + iff.bmhd.w, iff.bmhd.h, iff.bmhd.x, iff.bmhd.y, + iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking); + + // 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, + "ILBM: Unsupported features, refusing to load.\n"); + } + + if ((res = dmSkipIFFChunkRest(fp, &chunk, sizeof(iff.bmhd))) != DMERR_OK) + return res; + 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, + "ILBM: CMAP chunk size not divisible by 3, possibly broken file.\n"); + } + + iff.ncolors = chunk.size / 3; + dmMsg(2, "ILBM: CMAP %d entries (%d bytes)\n", + iff.ncolors, chunk.size, 1 << iff.bmhd.nplanes); + + if (iff.bmhd.nplanes > 0 && iff.ncolors != 1 << iff.bmhd.nplanes) + dmMsg(2, "ILBM: 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, + "ILBM: Could not allocate palette data.\n"); + } + if (!dmReadPaletteData(fp, iff.pal, iff.ncolors)) + { + return dmError(DMERR_FREAD, + "ILBM: Error reading CMAP.\n"); + } + } + + if (iff.chBMHD.count && iff.chBODY.count) + parsed = TRUE; + break; + + case IFF_ID_BODY: + // Check for multiple occurences of CMAP + if ((res = dmCheckIFFChunk(&iff.chBODY, &chunk, FALSE, 1)) != DMERR_OK) + return res; + + // Check for sanity + if (!iff.chBMHD.count) + { + return dmError(DMERR_INVALID_DATA, + "ILBM: BODY chunk before BMHD?\n"); + } + + dmMsg(2, "ILBM: BODY chunk size %d bytes\n", chunk.size); + + // Decode the body + if (iff.planar) + { + if ((res = dmDecodeILBMBody(fp, &iff, pimg, &read)) != DMERR_OK) + return res; + } + else + { + if ((res = dmDecodePBMBody(fp, &iff, pimg, &read)) != DMERR_OK) + return res; + } + + if ((res = dmSkipIFFChunkRest(fp, &chunk, read)) != DMERR_OK) + return res; + + if (iff.chCMAP.count) + parsed = TRUE; + break; + + + case IFF_ID_CAMG: + if (!dm_fread_be32(fp, &iff.camg)) + { + return dmError(DMERR_FREAD, + "ILBM: Error reading CAMG chunk.\n"); + } + + dmMsg(2, "ILBM: CAMG value 0x%08x\n", iff.camg); + + if ((iff.camg & IFF_CAMG_HAM)) + { + return dmError(DMERR_NOT_SUPPORTED, + "ILBM: HAM files are not supported.\n"); + } + + if ((res = dmSkipIFFChunkRest(fp, &chunk, sizeof(Uint32))) != DMERR_OK) + return res; + break; + + + default: + { + dmMsg(4, "Unknown chunk ID '%s', size %d\n", + dmGetIFFChunkID(&chunk), chunk.size); + + if (fseeko(fp, chunk.size, SEEK_CUR) != 0) + { + return dmError(DMERR_FSEEK, + "ILBM: Error skipping in file."); + } + } + break; + } + + if (chunk.size & 1) + fgetc(fp); + } + + // 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 (iff.ncolors > 128) + { + return dmError(DMERR_NOT_SUPPORTED, + "ILBM: 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 (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; +} + + +int dmReadILBMImage(const char *filename, DMImage **pimg) +{ + FILE *fp; + int res; + + if ((fp = fopen(filename, "rb")) == NULL) + { + return dmError(DMERR_FOPEN, + "ILBM: Could not open file '%s' for reading.\n", + filename); + } + + res = dmReadILBMImageFILE(fp, pimg); + + fclose(fp); + return res; +} + + + + +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 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; +} + + +static int fmtProbeILBM(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; +} + + +DMImageFormat dmImageFormatList[IMGFMT_LAST] = +{ + { + "PNG", "Portable Network Graphics", + fmtProbePNG, +#ifdef DM_USE_LIBPNG + dmReadPNGImage, dmReadPNGImageFILE, + dmWritePNGImage, dmWritePNGImageFILE, +#else + NULL, NULL, + NULL, NULL, +#endif + }, + { + "PPM", "Portable PixMap", + NULL, + NULL, NULL, + dmWritePPMImage, dmWritePPMImageFILE, + }, + { + "PCX", "Z-Soft Paintbrush", + fmtProbePCX, + dmReadPCXImage, dmReadPCXImageFILE, + dmWritePCXImage, dmWritePCXImageFILE, + }, + { + "ILBM", "IFF ILBM", + fmtProbeILBM, + dmReadILBMImage, dmReadILBMImageFILE, + NULL, NULL, + }, + { + "RAW", "Plain bitplaned (planar or non-planar) RAW", + NULL, + NULL, NULL, + dmWriteRAWImage, dmWriteRAWImageFILE, + }, + { + "ARAW", "IFFMaster Amiga RAW", + NULL, + NULL, NULL, + dmWriteRAWImage, dmWriteRAWImageFILE, + } +}; + + +int dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **pfmt, int *index) +{ + int i, scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1; + + for (i = 0; i < IMGFMT_LAST; i++) + { + 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; +}