Mercurial > hg > dmlib
view libgfx.c @ 442:a67600e186d0
Fix probing to handle NULL probe functions.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 04 Nov 2012 06:41:10 +0200 |
parents | b4ed5292d7bf |
children | f7c9d1619c74 |
line wrap: on
line source
/* * Functions for reading and converting various restricted * C64/etc and/or indexed/paletted graphics formats. * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "libgfx.h" #include "dmfile.h" #include "dmbstr.h" #ifdef DM_USE_LIBPNG #include <png.h> #endif DMImage * dmImageAlloc(int width, int height) { DMImage *img = dmCalloc(1, sizeof(DMImage)); if (img == NULL) return NULL; img->width = img->pitch = width; img->height = height; img->data = dmMalloc(width * height * sizeof(Uint8)); if (img->data == 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); } } int dmImageGetBytesPerPixel(int format) { switch (format) { case DM_IFMT_PALETTE : return 1; case DM_IFMT_RGB_PLANE : case DM_IFMT_RGB : return 3; case DM_IFMT_RGBA : return 4; default: return 0; } } int dmWriteImageData(DMImage *img, void *cbdata, BOOL (*writeRowCB)(void *, Uint8 *, size_t), const DMImageSpec *spec) { int x, y, yscale, xscale, res = 0, rowSize, rowWidth; Uint8 *row = NULL; // Allocate memory for row buffer rowWidth = img->width * spec->scale; 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; for (x = 0; x < img->width; x++) { Uint8 c = img->data[(y * img->pitch) + x], qr, qg, qb, qa; switch (spec->format) { case DM_IFMT_PALETTE: for (xscale = 0; xscale < spec->scale; xscale++) *ptr1++ = c; break; case DM_IFMT_RGBA: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; qa = (c == img->ctrans) ? 0 : 255; for (xscale = 0; xscale < spec->scale; 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; for (xscale = 0; xscale < spec->scale; xscale++) { *ptr1++ = qr; *ptr1++ = qg; *ptr1++ = qb; } break; case DM_IFMT_RGB_PLANE: qr = img->pal[c].r; qg = img->pal[c].g; qb = img->pal[c].b; for (xscale = 0; xscale < spec->scale; xscale++) { *ptr1++ = qr; *ptr2++ = qg; *ptr3++ = qb; } break; } } for (yscale = 0; yscale < spec->scale; yscale++) { if (!writeRowCB(cbdata, row, rowSize)) { res = DMERR_FWRITE; goto done; } } } done: dmFree(row); return res; } #define DMCOL(x) (((x) >> 4) & 0xf) int dmWriteIFFMasterRAWPalette(const char *filename, DMImage *img, int ncolors) { FILE *fp; int i; if ((fp = fopen(filename, "w")) == NULL) { dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename); return -15; } 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, "\tdc.w $%04X\n", color); } fclose(fp); return 0; } int dmWriteIFFMasterRAWImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec) { int xc, yc, plane, res; DMBitStream bs; if ((res = dmInitBitStream(&bs, fp)) != DMERR_OK) return res; if (spec->interleave) { // Output bitplanes in interleaved 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 dmWriteIFFMasterRAWImage(const char *filename, DMImage *img, DMImageSpec *spec) { FILE *fp; int res; if ((fp = fopen(filename, "wb")) == NULL) { dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWriteIFFMasterRAWImageFILE(fp, img, spec); fclose(fp); return res; } static BOOL dmWritePPMRow(void *cbdata, Uint8 *row, size_t len) { return fwrite(row, sizeof(Uint8), len, (FILE *) cbdata) == len; } int dmWritePPMImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec) { // Write PPM header fprintf(fp, "P6\n%d %d\n255\n", img->width * spec->scale, img->height * spec->scale); // Write image data spec->format = DM_IFMT_RGB; return dmWriteImageData(img, (void *) fp, dmWritePPMRow, spec); } int dmWritePPMImage(const char *filename, DMImage *img, DMImageSpec *spec) { FILE *fp; int res; // Create output file if ((fp = fopen(filename, "wb")) == NULL) { dmError("PPM: could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWritePPMImageFILE(fp, img, spec); fclose(fp); return res; } #ifdef DM_USE_LIBPNG static BOOL dmWritePNGRow(void *cbdata, Uint8 *row, size_t len) { png_structp png_ptr = cbdata; (void) len; if (setjmp(png_jmpbuf(png_ptr))) return FALSE; png_write_row(png_ptr, row); return TRUE; } int dmWritePNGImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_colorp palette = NULL; int fmt, res = DMERR_OK; // Create PNG structures png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { dmError("PNG: png_create_write_struct() failed.\n"); res = DMERR_MALLOC; goto error; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { dmError("PNG: png_create_info_struct(%p) failed.\n", png_ptr); res = DMERR_INIT_FAIL; goto error; } if (setjmp(png_jmpbuf(png_ptr))) { dmError("PNG: Error during image writing..\n"); res = DMERR_INIT_FAIL; goto error; } 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: dmError("PNG: Internal error, unsupported image format %d.\n", spec->format); goto error; } png_set_IHDR(png_ptr, info_ptr, img->width * spec->scale, img->height * spec->scale, 8, /* bits per component */ fmt, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Palette if (spec->format == DM_IFMT_PALETTE) { int i; palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (palette == NULL) { dmError("PNG: Could not allocate palette structure."); goto error; } memset(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, PNG_MAX_PALETTE_LENGTH); } // 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: png_free(png_ptr, palette); palette = NULL; if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); return res; } int dmWritePNGImage(const char *filename, DMImage *img, DMImageSpec *spec) { int res; FILE *fp; if ((fp = fopen(filename, "wb")) == NULL) { dmError("PNG: could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } res = dmWritePNGImageFILE(fp, img, spec); fclose(fp); return res; } #endif typedef struct { Uint8 r,g,b; } DMPCXColor; typedef struct { Uint8 manufacturer, version, encoding, bpp; Uint16 xmin, ymin, xmax, ymax; Uint16 hres, vres; DMPCXColor colormap[16]; Uint8 reserved; Uint8 nplanes; Uint16 bpl; Uint16 palinfo; Uint8 filler[58]; } DMPCXHeader; typedef struct { DMPCXHeader *header; Uint8 *buf; size_t bufLen, bufOffs; int format; FILE *fp; } DMPCXData; static inline Uint8 dmPCXGetByte(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 BOOL dmWritePCXRow(void *cbdata, Uint8 *row, size_t len) { DMPCXData *pcx = (DMPCXData *) cbdata; int plane; size_t soffs = 0; // fprintf(stderr, "%d, %d * %d = %d\n", len, pcx->header->bpl, pcx->header->nplanes, pcx->header->nplanes * pcx->header->bpl); pcx->bufOffs = 0; for (plane = 0; plane < pcx->header->nplanes; plane++) { Uint8 data = dmPCXGetByte(row, len, soffs++), count = 1; // size_t blen = pcx->header->bpl * pcx->header->nplanes; size_t blen = pcx->header->bpl; while (soffs < blen) { if (data == dmPCXGetByte(row, len, soffs) && count < 63) { count++; soffs++; } else { if (count == 1 && (data & 0xC0) != 0xC0) { if (!dmPCXPutByte(pcx, data)) return FALSE; } else { if (!dmPCXPutByte(pcx, 0xC0 | count) || !dmPCXPutByte(pcx, data)) return FALSE; } data = dmPCXGetByte(row, len, soffs++); count = 1; } } if (count > 1) { if (!dmPCXPutByte(pcx, 0xC0 | count) || !dmPCXPutByte(pcx, data)) return FALSE; } if (!dmPCXFlush(pcx)) return FALSE; } return TRUE; } int dmWritePCXImageFILE(FILE *fp, DMImage *img, DMImageSpec *spec) { DMPCXData pcx; DMPCXHeader hdr; int res; // Create output file pcx.buf = NULL; pcx.format = spec->paletted ? DM_IFMT_PALETTE : DM_IFMT_RGB_PLANE; pcx.header = &hdr; pcx.fp = fp; // Create PCX header memset(&hdr, 0, sizeof(hdr)); if (spec->paletted) { int i; for (i = 0; i < (img->ncolors > 16 ? 16 : 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.bpp = 8; hdr.hres = img->width * spec->scale; hdr.vres = img->height * spec->scale; hdr.xmin = hdr.ymin = 0; hdr.xmax = hdr.hres - 1; hdr.ymax = hdr.vres - 1; hdr.nplanes = dmImageGetBytesPerPixel(pcx.format); hdr.palinfo = 1; res = (img->width * spec->scale); hdr.bpl = res / 2; if (res % 2) hdr.bpl++; hdr.bpl *= 2; dmMsg(2, "PCX: paletted=%d, nplanes=%d, bpp=%d, bpl=%d\n", spec->paletted, hdr.nplanes, hdr.bpp, hdr.bpl); pcx.bufLen = hdr.bpl * 4; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { dmError("PCX: Could not allocate %d bytes for RLE compression buffer.\n", pcx.bufLen); res = DMERR_MALLOC; 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.bpp)) { dmError("PCX: Could not write basic header data.\n"); res = DMERR_FWRITE; 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)) { dmError("PCX: Could not write image dimensions.\n"); res = DMERR_FWRITE; goto error; } if (!dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.colormap, sizeof(hdr.colormap))) { dmError("PCX: Could not write colormap.\n"); res = DMERR_FWRITE; 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_str(pcx.fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { dmError("PCX: Could not write header remainder.\n"); res = DMERR_FWRITE; goto error; } // Write image data res = dmWriteImageData(img, (void *) &pcx, dmWritePCXRow, spec); // Write VGA palette if (spec->paletted) { int i; dm_fwrite_byte(pcx.fp, 0x0C); dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors); for (i = 0; i < img->ncolors; i++) { 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); } // Pad the palette, if necessary for (; i < 256; i++) { dm_fwrite_byte(pcx.fp, 0); dm_fwrite_byte(pcx.fp, 0); dm_fwrite_byte(pcx.fp, 0); } } error: dmFree(pcx.buf); return res; } int dmWritePCXImage(const char *filename, DMImage *img, DMImageSpec *spec) { FILE *fp; int res; if ((fp = fopen(filename, "wb")) == NULL) { dmError("PCX: Could not open file '%s' for writing.\n", filename); return DMERR_FOPEN; } 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) { count = data & 0x3F; if (!dm_fread_byte(fp, &data)) return FALSE; } else count = 1; while (count-- && offs < bufLen) buf[offs++] = data; } while (offs < bufLen); return TRUE; } int dmReadPCXImageFILE(FILE *fp, DMImage **pimg) { DMImage *img; DMPCXData pcx; DMPCXHeader hdr; BOOL paletted; int res = 0, yc, xc; Uint8 *dp; 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.bpp)) { dmError("PCX: Could not read basic header data.\n"); res = DMERR_FREAD; goto error; } if (hdr.manufacturer != 10 || hdr.version != 5 || hdr.encoding != 1 || hdr.bpp != 8) { dmError("PCX: Not a PCX file, or unsupported variant.\n"); res = DMERR_FREAD; goto error; } if (!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)) { dmError("PCX: Could not read image dimensions.\n"); res = DMERR_FREAD; goto error; } if (!dm_fread_str(fp, (Uint8 *) &hdr.colormap, sizeof(hdr.colormap))) { dmError("PCX: Could not read colormap.\n"); res = DMERR_FREAD; goto error; } if (!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_str(fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler))) { dmError("PCX: Could not read header remainder.\n"); res = DMERR_FREAD; goto error; } if (hdr.nplanes != 3 && hdr.nplanes != 1) { dmError("PCX: Unsupported number of bitplanes %d.\n", hdr.nplanes); res = DMERR_FREAD; goto error; } // Allocate image if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1)) == NULL) { dmError("PCX: Could not allocate image structure.\n"); res = DMERR_MALLOC; goto error; } paletted = hdr.nplanes == 1; pcx.bufLen = hdr.nplanes * hdr.bpl; if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL) { dmError("PCX: Could not allocate RLE buffer.\n"); res = DMERR_MALLOC; goto error; } // Read image data dp = img->data; for (yc = 0; yc < img->height; yc++) { // Decode row of RLE'd data if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen)) { dmError("PCX: Error decoding RLE data.\n"); res = DMERR_INVALID_DATA; goto error; } // Decode bitplanes switch (hdr.nplanes) { case 1: memcpy(dp, pcx.buf, img->width); break; case 3: { Uint8 *dptr = dp, *sptr1 = pcx.buf, *sptr2 = sptr1 + hdr.bpl, *sptr3 = sptr2 + hdr.bpl; for (xc = 0; xc < img->width; xc++) { *dptr++ = *sptr1++; *dptr++ = *sptr2++; *dptr++ = *sptr3++; } } break; } dp += img->pitch; } // Read VGA palette if (paletted) { int i; Uint8 tmpb; BOOL read; if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C) { read = FALSE; img->ncolors = 16; } else { read = TRUE; img->ncolors = 256; } if ((img->pal = dmCalloc(img->ncolors, sizeof(DMColor))) == NULL) { dmError("PCX: Could not allocate palette data!\n"); res = DMERR_MALLOC; goto error; } if (read) { for (i = 0; i < img->ncolors; i++) { Uint8 tmpR, tmpG, tmpB; if (!dm_fread_byte(fp, &tmpR) || !dm_fread_byte(fp, &tmpG) || !dm_fread_byte(fp, &tmpB)) goto error; img->pal[i].r = tmpR; img->pal[i].g = tmpG; img->pal[i].b = tmpB; } } else { for (i = 0; i < img->ncolors; i++) { if (i < 16) { 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) { dmError("PCX: Could not open file '%s' for reading.\n", filename); return -15; } res = dmReadPCXImageFILE(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) return DM_PROBE_SCORE_GOOD; return DM_PROBE_SCORE_FALSE; } static int fmtProbePCX(const Uint8 *buf, const size_t len) { if (len > 128 + 64 && buf[0] == 10 && buf[1] == 5 && buf[2] == 1 && buf[3] == 8) return DM_PROBE_SCORE_GOOD; return DM_PROBE_SCORE_FALSE; } DMImageFormat dmImageFormatList[IMGFMT_LAST] = { { "PNG", "Portable Network Graphics", fmtProbePNG, NULL, NULL, #ifdef DM_USE_LIBPNG dmWritePNGImage, dmWritePNGImageFILE, #else NULL, NULL, #endif }, { "PPM", "Portable PixMap", NULL, NULL, NULL, dmWritePPMImage, dmWritePPMImageFILE, }, { "PCX", "Z-Soft Paintbrush", fmtProbePCX, dmReadPCXImage, dmReadPCXImageFILE, dmWritePCXImage, dmWritePCXImageFILE, }, { "ARAW", "IFFMaster Amiga RAW", NULL, NULL, NULL, dmWriteIFFMasterRAWImage, dmWriteIFFMasterRAWImageFILE, } }; 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; }