# HG changeset patch # User Matti Hamalainen # Date 1503145109 -10800 # Node ID 43b13dbbdcd1a17979103c82fd8480737db94b74 # Parent 696c58784635518efff829c37320bc8b1b2a57be Moved libgfx to tools/ as it's not really a very generic piece of code that belongs to the engine .. diff -r 696c58784635 -r 43b13dbbdcd1 Makefile.gen --- a/Makefile.gen Sat Aug 19 13:02:34 2017 +0300 +++ b/Makefile.gen Sat Aug 19 15:18:29 2017 +0300 @@ -249,7 +249,6 @@ ### Dependancies ifeq ($(DM_BUILD_TOOLS),yes) -DMLIB_OBJS += libgfx.o ifeq ($(DM_USE_STDIO),yes) TOOL_BINARIES+= objlink data2inc gfxconv gentab ifeq ($(SUP_MODLOAD),yes) @@ -414,7 +413,7 @@ @echo " CC $+" @$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS) -$(OBJPATH)libgfx.o: $(DMLIBSRC)libgfx.c $(DMLIBSRC)libgfx.h +$(OBJPATH)libgfx.o: $(DMLIB)tools/libgfx.c $(DMLIB)tools/libgfx.h @echo " CC $+" @$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS) $(LIBPNG_CFLAGS) @@ -513,11 +512,11 @@ @echo " LINK $+" @$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(TOOL_LDFLAGS) -$(TOOL_BINPATH)gfxconv$(EXEEXT): $(OBJPATH)gfxconv.o $(OBJPATH)lib64gfx.o $(DMLIB_A) +$(TOOL_BINPATH)gfxconv$(EXEEXT): $(OBJPATH)gfxconv.o $(OBJPATH)lib64gfx.o $(OBJPATH)libgfx.o $(DMLIB_A) @echo " LINK $+" @$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(TOOL_LDFLAGS) $(LIBPNG_LDFLAGS) $(DM_ZLIB_LDFLAGS) -$(TOOL_BINPATH)view64$(EXEEXT): $(OBJPATH)view64.o $(OBJPATH)lib64gfx.o $(DMLIB_A) +$(TOOL_BINPATH)view64$(EXEEXT): $(OBJPATH)view64.o $(OBJPATH)lib64gfx.o $(OBJPATH)libgfx.o $(DMLIB_A) @echo " LINK $+" @$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(LIBPNG_LDFLAGS) $(DM_ZLIB_LDFLAGS) $(SDL_LDFLAGS) diff -r 696c58784635 -r 43b13dbbdcd1 src/libgfx.c --- a/src/libgfx.c Sat Aug 19 13:02:34 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1998 +0,0 @@ -/* - * 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 -#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; -} diff -r 696c58784635 -r 43b13dbbdcd1 src/libgfx.h --- a/src/libgfx.h Sat Aug 19 13:02:34 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/* - * Functions for loading and saving bitmap images - * Programmed and designed by Matti 'ccr' Hamalainen - * (C) Copyright 2012 Tecnic Software productions (TNSP) - * - * Please read file 'COPYING' for information on license and distribution. - */ -#ifndef LIBMGFX_H -#define LIBMGFX_H 1 - -#include "dmlib.h" - - -#ifdef __cplusplus -extern "C" { -#endif - - -enum -{ - IMGFMT_PNG, - IMGFMT_PPM, - IMGFMT_PCX, - IMGFMT_ILBM, - IMGFMT_RAW, - IMGFMT_ARAW, - - IMGFMT_LAST -}; - - -enum -{ - DM_IFMT_PALETTE, - DM_IFMT_RGB, - DM_IFMT_RGBA, -}; - - -enum -{ - DM_ERRMODE_FAIL = 0, - DM_ERRMODE_RECOV_1, - DM_ERRMODE_RECOV_2, -}; - - -// Bitmapped image struct -typedef struct -{ - int format; // one of types specified by DM_IFMT_* - int width, height; - int pitch; // bytes per scanline - int bpp; // bits per pixel - - int ncolors; // number of colors in palette, if any - int ctransp; // transparency color index - BOOL constpal; // is the palette a const? - DMColor *pal; // pointer to palette struct, NULL if no palette - - size_t size; - Uint8 *data; -} DMImage; - - -typedef struct -{ - int format; - int scaleX, scaleY; - int nplanes, bpp; - BOOL planar, paletted; -} DMImageConvSpec; - - -typedef struct -{ - char *fext; - char *desc; - int (*probe)(const Uint8 *buf, const size_t len); - int (*read)(const char *filename, DMImage **pimg); - int (*readFILE)(FILE *fp, DMImage **pimg); - int (*write)(const char *filename, DMImage *pimg, const DMImageConvSpec *spec); - int (*writeFILE)(FILE *fp, DMImage *pimg, const DMImageConvSpec *spec); -} DMImageFormat; - - -// Probe scores -#define DM_PROBE_SCORE_MAX 1000 -#define DM_PROBE_SCORE_GOOD 750 -#define DM_PROBE_SCORE_AVG 500 -#define DM_PROBE_SCORE_MAYBE 250 -#define DM_PROBE_SCORE_FALSE 0 - - -extern DMImageFormat dmImageFormatList[IMGFMT_LAST]; -extern int dmGFXErrorMode; - - -DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp); -void dmImageFree(DMImage *img); -int dmImageGetBytesPerPixel(const int format); -int dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **fmt, int *index); - -BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha); - - -int dmWriteImageData(DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *spec); - -int dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors, const char *indent, const char *type); -int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); -int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); - -int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); -int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); - -#ifdef DM_USE_LIBPNG -int dmWritePNGImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); -int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); -#endif - -int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); -int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); -int dmReadPCXImageFILE(FILE *fp, DMImage **pimg); -int dmReadPCXImage(const char *filename, DMImage **pimg); - - -#ifdef __cplusplus -} -#endif - -#endif // LIBMGFX_H diff -r 696c58784635 -r 43b13dbbdcd1 tools/libgfx.c --- /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 +#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; +} diff -r 696c58784635 -r 43b13dbbdcd1 tools/libgfx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/libgfx.h Sat Aug 19 15:18:29 2017 +0300 @@ -0,0 +1,131 @@ +/* + * Functions for loading and saving bitmap images + * Programmed and designed by Matti 'ccr' Hamalainen + * (C) Copyright 2012 Tecnic Software productions (TNSP) + * + * Please read file 'COPYING' for information on license and distribution. + */ +#ifndef LIBMGFX_H +#define LIBMGFX_H 1 + +#include "dmlib.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +enum +{ + IMGFMT_PNG, + IMGFMT_PPM, + IMGFMT_PCX, + IMGFMT_ILBM, + IMGFMT_RAW, + IMGFMT_ARAW, + + IMGFMT_LAST +}; + + +enum +{ + DM_IFMT_PALETTE, + DM_IFMT_RGB, + DM_IFMT_RGBA, +}; + + +enum +{ + DM_ERRMODE_FAIL = 0, + DM_ERRMODE_RECOV_1, + DM_ERRMODE_RECOV_2, +}; + + +// Bitmapped image struct +typedef struct +{ + int format; // one of types specified by DM_IFMT_* + int width, height; + int pitch; // bytes per scanline + int bpp; // bits per pixel + + int ncolors; // number of colors in palette, if any + int ctransp; // transparency color index + BOOL constpal; // is the palette a const? + DMColor *pal; // pointer to palette struct, NULL if no palette + + size_t size; + Uint8 *data; +} DMImage; + + +typedef struct +{ + int format; + int scaleX, scaleY; + int nplanes, bpp; + BOOL planar, paletted; +} DMImageConvSpec; + + +typedef struct +{ + char *fext; + char *desc; + int (*probe)(const Uint8 *buf, const size_t len); + int (*read)(const char *filename, DMImage **pimg); + int (*readFILE)(FILE *fp, DMImage **pimg); + int (*write)(const char *filename, DMImage *pimg, const DMImageConvSpec *spec); + int (*writeFILE)(FILE *fp, DMImage *pimg, const DMImageConvSpec *spec); +} DMImageFormat; + + +// Probe scores +#define DM_PROBE_SCORE_MAX 1000 +#define DM_PROBE_SCORE_GOOD 750 +#define DM_PROBE_SCORE_AVG 500 +#define DM_PROBE_SCORE_MAYBE 250 +#define DM_PROBE_SCORE_FALSE 0 + + +extern DMImageFormat dmImageFormatList[IMGFMT_LAST]; +extern int dmGFXErrorMode; + + +DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp); +void dmImageFree(DMImage *img); +int dmImageGetBytesPerPixel(const int format); +int dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **fmt, int *index); + +BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha); + + +int dmWriteImageData(DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *spec); + +int dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors, const char *indent, const char *type); +int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); +int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); + +int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); +int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); + +#ifdef DM_USE_LIBPNG +int dmWritePNGImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); +int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); +#endif + +int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec); +int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec); +int dmReadPCXImageFILE(FILE *fp, DMImage **pimg); +int dmReadPCXImage(const char *filename, DMImage **pimg); + + +#ifdef __cplusplus +} +#endif + +#endif // LIBMGFX_H