diff options
author | Karl Berry <karl@freefriends.org> | 2021-02-25 19:22:25 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-02-25 19:22:25 +0000 |
commit | ad547a6b5986815fda458221149728d9d9ab1d87 (patch) | |
tree | 16296910eb3eca724371474ea9aea3994dc69614 /Build/source/libs/gd/libgd-src/src/gd_png.c | |
parent | 947b43de3dd21d58ccc2ffadefc4441ea1c2a813 (diff) |
restore Build,TODO from r57911
git-svn-id: svn://tug.org/texlive/trunk@57915 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/libs/gd/libgd-src/src/gd_png.c')
-rw-r--r-- | Build/source/libs/gd/libgd-src/src/gd_png.c | 1145 |
1 files changed, 1145 insertions, 0 deletions
diff --git a/Build/source/libs/gd/libgd-src/src/gd_png.c b/Build/source/libs/gd/libgd-src/src/gd_png.c new file mode 100644 index 00000000000..8119cf39c05 --- /dev/null +++ b/Build/source/libs/gd/libgd-src/src/gd_png.c @@ -0,0 +1,1145 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include "gd.h" +#include "gd_errors.h" + +/* JCE: Arrange HAVE_LIBPNG so that it can be set in gd.h */ +#ifdef HAVE_LIBPNG + +#include "gdhelpers.h" +#include "png.h" /* includes zlib.h and setjmp.h */ + +#define TRUE 1 +#define FALSE 0 + +/*--------------------------------------------------------------------------- + + gd_png.c Copyright 1999 Greg Roelofs and Thomas Boutell + + The routines in this file, gdImagePng*() and gdImageCreateFromPng*(), + are drop-in replacements for gdImageGif*() and gdImageCreateFromGif*(), + except that these functions are noisier in the case of errors (comment + out all fprintf() statements to disable that). + + GD 2.0 supports RGBA truecolor and will read and write truecolor PNGs. + GD 2.0 supports 8 bits of color resolution per channel and + 7 bits of alpha channel resolution. Images with more than 8 bits + per channel are reduced to 8 bits. Images with an alpha channel are + only able to resolve down to '1/128th opaque' instead of '1/256th', + and this conversion is also automatic. I very much doubt you can see it. + Both tRNS and true alpha are supported. + + Gamma is ignored, and there is no support for text annotations. + + Last updated: 9 February 2001 + + ---------------------------------------------------------------------------*/ + +/** + * File: PNG IO + * + * Read and write PNG images. + */ + +#ifdef PNG_SETJMP_SUPPORTED +typedef struct _jmpbuf_wrapper { + jmp_buf jmpbuf; +} +jmpbuf_wrapper; + +static void +gdPngErrorHandler (png_structp png_ptr, png_const_charp msg) +{ + jmpbuf_wrapper *jmpbuf_ptr; + + /* This function, aside from the extra step of retrieving the "error + * pointer" (below) and the fact that it exists within the application + * rather than within libpng, is essentially identical to libpng's + * default error handler. The second point is critical: since both + * setjmp() and longjmp() are called from the same code, they are + * guaranteed to have compatible notions of how big a jmp_buf is, + * regardless of whether _BSD_SOURCE or anything else has (or has not) + * been defined. */ + + gd_error_ex(GD_WARNING, "gd-png: fatal libpng error: %s\n", msg); + + jmpbuf_ptr = png_get_error_ptr (png_ptr); + if (jmpbuf_ptr == NULL) { /* we are completely hosed now */ + gd_error_ex(GD_ERROR, "gd-png: EXTREMELY fatal error: jmpbuf unrecoverable; terminating.\n"); + exit (99); + } + + longjmp (jmpbuf_ptr->jmpbuf, 1); +} +#endif + +static void +gdPngReadData (png_structp png_ptr, png_bytep data, png_size_t length) +{ + int check; + check = gdGetBuf (data, length, (gdIOCtx *) png_get_io_ptr (png_ptr)); + if (check != (int)length) { + png_error(png_ptr, "Read Error: truncated data"); + } +} + +static void +gdPngWriteData (png_structp png_ptr, png_bytep data, png_size_t length) +{ + gdPutBuf (data, length, (gdIOCtx *) png_get_io_ptr (png_ptr)); +} + +static void +gdPngFlushData (png_structp png_ptr) +{ + (void)png_ptr; +} + +/* + Function: gdImageCreateFromPng + + <gdImageCreateFromPng> is called to load images from PNG format + files. Invoke <gdImageCreateFromPng> with an already opened + pointer to a FILE containing the desired + image. <gdImageCreateFromPng> returns a <gdImagePtr> to the new + image, or NULL if unable to load the image (most often because the + file is corrupt or does not contain a PNG + image). <gdImageCreateFromPng> does not close the file. You can + inspect the sx and sy members of the image to determine its + size. The image must eventually be destroyed using + gdImageDestroy(). + + If the PNG image being loaded is a truecolor image, the resulting + gdImagePtr will refer to a truecolor image. If the PNG image being + loaded is a palette or grayscale image, the resulting gdImagePtr + will refer to a palette image. gd retains only 8 bits of + resolution for each of the red, green and blue channels, and only + 7 bits of resolution for the alpha channel. The former restriction + affects only a handful of very rare 48-bit color and 16-bit + grayscale PNG images. The second restriction affects all + semitransparent PNG images, but the difference is essentially + invisible to the eye. 7 bits of alpha channel resolution is, in + practice, quite a lot. + + Variants: + + <gdImageCreateFromPngPtr> creates an image from PNG data (i.e. the + contents of a PNG file) already in memory. + + <gdImageCreateFromPngCtx> reads in an image using the functions in + a <gdIOCtx> struct. + + <gdImageCreateFromPngSource> is similar to + <gdImageCreateFromPngCtx> but uses the old <gdSource> interface. + It is *obsolete*. + + Parameters: + + infile - The input FILE pointer. + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + ... inside a function ... + FILE *in; + in = fopen("mypng.png", "rb"); + im = gdImageCreateFromPng(in); + fclose(in); + // ... Use the image ... + gdImageDestroy(im); + + (end code) + */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPng (FILE * inFile) +{ + gdImagePtr im; + gdIOCtx *in = gdNewFileCtx (inFile); + if (in == NULL) return NULL; + im = gdImageCreateFromPngCtx (in); + in->gd_free (in); + return im; +} + +/* + Function: gdImageCreateFromPngPtr + + See <gdImageCreateFromPng>. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngPtr (int size, void *data) +{ + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx (size, data, 0); + if(!in) + return 0; + im = gdImageCreateFromPngCtx (in); + in->gd_free (in); + return im; +} + + + +/* This routine is based in part on the Chapter 13 demo code in + * "PNG: The Definitive Guide" (http://www.libpng.org/pub/png/book/). + */ + +/* + Function: gdImageCreateFromPngCtx + + See <gdImageCreateFromPng>. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngCtx (gdIOCtx * infile) +{ + png_byte sig[8]; +#ifdef PNG_SETJMP_SUPPORTED + jmpbuf_wrapper jbw; +#endif + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height, rowbytes, w, h, res_x, res_y; + int bit_depth, color_type, interlace_type, unit_type; + int num_palette = 0, num_trans; + png_colorp palette; + png_color_16p trans_gray_rgb; + png_color_16p trans_color_rgb; + png_bytep trans; + png_bytep image_data = NULL; + png_bytepp row_pointers = NULL; + gdImagePtr im = NULL; + int i, j, *open = NULL; + volatile int transparent = -1; + volatile int palette_allocated = FALSE; + + /* Make sure the signature can't match by dumb luck -- TBB */ + /* GRR: isn't sizeof(infile) equal to the size of the pointer? */ + memset (sig, 0, sizeof (sig)); + + /* first do a quick check that the file really is a PNG image; could + * have used slightly more general png_sig_cmp() function instead */ + if (gdGetBuf (sig, 8, infile) < 8) { + return NULL; + } + + if (png_sig_cmp(sig, 0, 8) != 0) { /* bad signature */ + return NULL; /* bad signature */ + } + +#ifdef PNG_SETJMP_SUPPORTED + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, &jbw, gdPngErrorHandler, NULL); +#else + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +#endif + if (png_ptr == NULL) { + gd_error("gd-png error: cannot allocate libpng main struct\n"); + return NULL; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) { + gd_error("gd-png error: cannot allocate libpng info struct\n"); + png_destroy_read_struct (&png_ptr, NULL, NULL); + + return NULL; + } + + /* we could create a second info struct here (end_info), but it's only + * useful if we want to keep pre- and post-IDAT chunk info separated + * (mainly for PNG-aware image editors and converters) + */ + + /* setjmp() must be called in every non-callback function that calls a + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. + */ +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(jbw.jmpbuf)) { + gd_error("gd-png error: setjmp returns error condition 1\n"); + png_destroy_read_struct (&png_ptr, &info_ptr, NULL); + + return NULL; + } +#endif + + png_set_sig_bytes (png_ptr, 8); /* we already read the 8 signature bytes */ + + png_set_read_fn (png_ptr, (void *) infile, gdPngReadData); + png_read_info (png_ptr, info_ptr); /* read all PNG info up to image data */ + + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); + if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA) + || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + im = gdImageCreateTrueColor ((int) width, (int) height); + } else { + im = gdImageCreate ((int) width, (int) height); + } + if (im == NULL) { + gd_error("gd-png error: cannot allocate gdImage struct\n"); + goto error; + } + + if (bit_depth == 16) { + png_set_strip_16 (png_ptr); + } else if (bit_depth < 8) { + png_set_packing (png_ptr); /* expand to 1 byte per pixel */ + } + + /* setjmp() must be called in every non-callback function that calls a + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. + */ +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(jbw.jmpbuf)) { + gd_error("gd-png error: setjmp returns error condition 2\n"); + goto error; + } +#endif + +#ifdef PNG_pHYs_SUPPORTED + /* check if the resolution is specified */ + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) { + if (png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type)) { + switch (unit_type) { + case PNG_RESOLUTION_METER: + im->res_x = DPM2DPI(res_x); + im->res_y = DPM2DPI(res_y); + break; + } + } + } +#endif + + switch (color_type) { + case PNG_COLOR_TYPE_PALETTE: + png_get_PLTE (png_ptr, info_ptr, &palette, &num_palette); +#ifdef DEBUG + gd_error("gd-png color_type is palette, colors: %d\n", num_palette); +#endif /* DEBUG */ + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { + /* gd 2.0: we support this rather thoroughly now. Grab the + * first fully transparent entry, if any, as the value of + * the simple-transparency index, mostly for backwards + * binary compatibility. The alpha channel is where it's + * really at these days. + */ + int firstZero = 1; + png_get_tRNS (png_ptr, info_ptr, &trans, &num_trans, NULL); + for (i = 0; i < num_trans; ++i) { + im->alpha[i] = gdAlphaMax - (trans[i] >> 1); + if ((trans[i] == 0) && (firstZero)) { + /* 2.0.5: long-forgotten patch from Wez Furlong */ + transparent = i; + firstZero = 0; + } + } + } + break; + + case PNG_COLOR_TYPE_GRAY: + /* create a fake palette and check for single-shade transparency */ + if ((palette = (png_colorp) gdMalloc (256 * sizeof (png_color))) == NULL) { + gd_error("gd-png error: cannot allocate gray palette\n"); + goto error; + } + palette_allocated = TRUE; + if (bit_depth < 8) { + num_palette = 1 << bit_depth; + for (i = 0; i < 256; ++i) { + j = (255 * i) / (num_palette - 1); + palette[i].red = palette[i].green = palette[i].blue = j; + } + } else { + num_palette = 256; + for (i = 0; i < 256; ++i) { + palette[i].red = palette[i].green = palette[i].blue = i; + } + } + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_get_tRNS (png_ptr, info_ptr, NULL, NULL, &trans_gray_rgb); + if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ + transparent = trans_gray_rgb->gray >> 8; + } else { + transparent = trans_gray_rgb->gray; + } + /* Note slight error in 16-bit case: up to 256 16-bit shades + * may get mapped to a single 8-bit shade, and only one of them + * is supposed to be transparent. IOW, both opaque pixels and + * transparent pixels will be mapped into the transparent entry. + * There is no particularly good way around this in the case + * that all 256 8-bit shades are used, but one could write some + * custom 16-bit code to handle the case where there are gdFree + * palette entries. This error will be extremely rare in + * general, though. (Quite possibly there is only one such + * image in existence.) */ + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + png_set_gray_to_rgb(png_ptr); + + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + /* gd 2.0: we now support truecolor. See the comment above + for a rare situation in which the transparent pixel may not + work properly with 16-bit channels. */ + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_get_tRNS (png_ptr, info_ptr, NULL, NULL, &trans_color_rgb); + if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ + transparent = gdTrueColor (trans_color_rgb->red >> 8, + trans_color_rgb->green >> 8, + trans_color_rgb->blue >> 8); + } else { + transparent = gdTrueColor (trans_color_rgb->red, + trans_color_rgb->green, + trans_color_rgb->blue); + } + } + break; + default: + gd_error("gd-png color_type is unknown: %d\n", color_type); + goto error; + } + + png_read_update_info (png_ptr, info_ptr); + + /* allocate space for the PNG image data */ + rowbytes = png_get_rowbytes (png_ptr, info_ptr); + if (overflow2(rowbytes, height)) + goto error; + image_data = (png_bytep) gdMalloc (rowbytes * height); + if (!image_data) { + gd_error("gd-png error: cannot allocate image data\n"); + goto error; + } + if (overflow2(height, sizeof (png_bytep))) + goto error; + + row_pointers = (png_bytepp) gdMalloc (height * sizeof (png_bytep)); + if (!row_pointers) { + gd_error("gd-png error: cannot allocate row pointers\n"); + goto error; + } + + /* setjmp() must be called in every non-callback function that calls a + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. + */ +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(jbw.jmpbuf)) { + gd_error("gd-png error: setjmp returns error condition 3\n"); + goto error; + } +#endif + + /* set the individual row_pointers to point at the correct offsets */ + for (h = 0; h < height; ++h) { + row_pointers[h] = image_data + h * rowbytes; + } + + png_read_image (png_ptr, row_pointers); /* read whole image... */ + png_read_end (png_ptr, NULL); /* ...done! */ + + if (!im->trueColor) { + im->colorsTotal = num_palette; + /* load the palette and mark all entries "open" (unused) for now */ + open = im->open; + for (i = 0; i < num_palette; ++i) { + im->red[i] = palette[i].red; + im->green[i] = palette[i].green; + im->blue[i] = palette[i].blue; + open[i] = 1; + } + for (i = num_palette; i < gdMaxColors; ++i) { + open[i] = 1; + } + } + /* 2.0.12: Slaven Rezic: palette images are not the only images + with a simple transparent color setting */ + im->transparent = transparent; + im->interlace = (interlace_type == PNG_INTERLACE_ADAM7); + + /* can't nuke structs until done with palette */ + png_destroy_read_struct (&png_ptr, &info_ptr, NULL); + switch (color_type) { + case PNG_COLOR_TYPE_RGB: + for (h = 0; h < height; h++) { + int boffset = 0; + for (w = 0; w < width; w++) { + register png_byte r = row_pointers[h][boffset++]; + register png_byte g = row_pointers[h][boffset++]; + register png_byte b = row_pointers[h][boffset++]; + im->tpixels[h][w] = gdTrueColor (r, g, b); + } + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_RGB_ALPHA: + for (h = 0; h < height; h++) { + int boffset = 0; + for (w = 0; w < width; w++) { + register png_byte r = row_pointers[h][boffset++]; + register png_byte g = row_pointers[h][boffset++]; + register png_byte b = row_pointers[h][boffset++]; + + /* gd has only 7 bits of alpha channel resolution, and + * 127 is transparent, 0 opaque. A moment of convenience, + * a lifetime of compatibility. + */ + + register png_byte a = gdAlphaMax - (row_pointers[h][boffset++] >> 1); + im->tpixels[h][w] = gdTrueColorAlpha(r, g, b, a); + } + } + break; + default: + if (!im->trueColor) { + /* Palette image, or something coerced to be one */ + for (h = 0; h < height; ++h) { + for (w = 0; w < width; ++w) { + register png_byte idx = row_pointers[h][w]; + im->pixels[h][w] = idx; + open[idx] = 0; + } + } + } + } +#ifdef DEBUG + if (!im->trueColor) { + for (i = num_palette; i < gdMaxColors; ++i) { + if (!open[i]) { + fprintf (stderr, + "gd-png warning: image data references out-of-range" + " color index (%d)\n", i); + } + } + } +#endif + + done: + if (palette_allocated) { + gdFree (palette); + } + if (image_data) + gdFree(image_data); + if (row_pointers) + gdFree(row_pointers); + + return im; + + error: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (im) { + gdImageDestroy(im); + im = NULL; + } + goto done; +} + + +/* + Function: gdImagePngEx + + <gdImagePngEx> outputs the specified image to the specified file in + PNG format. The file must be open for writing. Under MSDOS and all + versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. <gdImagePngEx> does not close the file; + your code must do so. + + In addition, <gdImagePngEx> allows the level of compression to be + specified. A compression level of 0 means "no compression." A + compression level of 1 means "compressed, but as quickly as + possible." A compression level of 9 means "compressed as much as + possible to produce the smallest possible file." A compression level + of -1 will use the default compression level at the time zlib was + compiled on your system. + + Variants: + + <gdImagePng> is equivalent to calling <gdImagePngEx> with + compression of -1. + + <gdImagePngCtx> and <gdImagePngCtxEx> write via a <gdIOCtx> + instead of a file handle. + + <gdImagePngPtr> and <gdImagePngPtrEx> store the image file to + memory. + + Parameters: + + im - the image to write + outFile - the output FILE* object. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + Nothing. + + Example: + (start code) + + gdImagePtr im; + int black, white; + FILE *out; + + im = gdImageCreate(100, 100); // Create the image + white = gdImageColorAllocate(im, 255, 255, 255); // Alloc background + black = gdImageColorAllocate(im, 0, 0, 0); // Allocate drawing color + gdImageRectangle(im, 0, 0, 99, 99, black); // Draw rectangle + out = fopen("rect.png", "wb"); // Open output file (binary) + gdImagePngEx(im, out, 9); // Write PNG, max compression + fclose(out); // Close file + gdImageDestroy(im); // Destroy image + + (end code) +*/ +BGD_DECLARE(void) gdImagePngEx (gdImagePtr im, FILE * outFile, int level) +{ + gdIOCtx *out = gdNewFileCtx (outFile); + if (out == NULL) return; + gdImagePngCtxEx (im, out, level); + out->gd_free (out); +} + +/* + Function: gdImagePng + + Equivalent to calling <gdImagePngEx> with compression of -1. + + Parameters: + + im - the image to save. + outFile - the output FILE*. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImagePng (gdImagePtr im, FILE * outFile) +{ + gdIOCtx *out = gdNewFileCtx (outFile); + if (out == NULL) return; + gdImagePngCtxEx (im, out, -1); + out->gd_free (out); +} + +static int _gdImagePngCtxEx(gdImagePtr im, gdIOCtx * outfile, int level); + +/* + Function: gdImagePngPtr + + Equivalent to calling <gdImagePngPtrEx> with compression of -1. + + See <gdImagePngEx> for more information. + + Parameters: + + im - the image to save. + size - Output: size in bytes of the result. + + Returns: + + A pointer to memory containing the image data or NULL on error. + +*/ +BGD_DECLARE(void *) gdImagePngPtr (gdImagePtr im, int *size) +{ + void *rv; + gdIOCtx *out = gdNewDynamicCtx (2048, NULL); + if (out == NULL) return NULL; + if (!_gdImagePngCtxEx (im, out, -1)) { + rv = gdDPExtractData (out, size); + } else { + rv = NULL; + } + out->gd_free (out); + return rv; +} + +/* + Function: gdImagePngPtrEx + + Identical to <gdImagePngEx> except that it returns a pointer to a + memory area with the PNG data. This memory must be freed by the + caller when it is no longer needed. **The caller must invoke + gdFree(), not free()** + + The 'size' parameter receives the total size of the block of + memory. + + See <gdImagePngEx> for more information. + + Parameters: + + im - the image to save. + size - Output: size in bytes of the result. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + A pointer to memory containing the image data or NULL on error. + +*/ +BGD_DECLARE(void *) gdImagePngPtrEx (gdImagePtr im, int *size, int level) +{ + void *rv; + gdIOCtx *out = gdNewDynamicCtx (2048, NULL); + if (out == NULL) return NULL; + if (!_gdImagePngCtxEx (im, out, level)) { + rv = gdDPExtractData (out, size); + } else { + rv = NULL; + } + out->gd_free (out); + return rv; +} + + + +/* + Function: gdImagePngCtx + + Equivalent to calling <gdImagePngCtxEx> with compression of -1. + See <gdImagePngEx> for more information. + + Parameters: + + im - the image to save. + outfile - the <gdIOCtx> to write to. + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile) +{ + /* 2.0.13: 'return' here was an error, thanks to Kevin Smith */ + gdImagePngCtxEx (im, outfile, -1); +} + + + + +/* + Function: gdImagePngCtxEx + + Outputs the given image as PNG data, but using a <gdIOCtx> instead + of a file. See <gdIamgePnEx>. + + Parameters: + + im - the image to save. + outfile - the <gdIOCtx> to write to. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level) +{ + _gdImagePngCtxEx(im, outfile, level); +} + +/* This routine is based in part on code from Dale Lutz (Safe Software Inc.) + * and in part on demo code from Chapter 15 of "PNG: The Definitive Guide" + * (http://www.libpng.org/pub/png/book/). + */ +/* returns 0 on success, 1 on failure */ +static int _gdImagePngCtxEx(gdImagePtr im, gdIOCtx * outfile, int level) +{ + int i, j, bit_depth = 0, interlace_type; + int width = im->sx; + int height = im->sy; + int colors = im->colorsTotal; + int *open = im->open; + int mapping[gdMaxColors]; /* mapping[gd_index] == png_index */ + png_byte trans_values[256]; + png_color_16 trans_rgb_value; + png_color palette[gdMaxColors]; + png_structp png_ptr; + png_infop info_ptr; + png_bytep *row_pointers = NULL; + volatile int transparent = im->transparent; + volatile int remap = FALSE; +#ifdef PNG_SETJMP_SUPPORTED + jmpbuf_wrapper jbw; +#endif + int ret = 0; + + /* width or height of value 0 is invalid in IHDR; + see http://www.w3.org/TR/PNG-Chunks.html */ + if (width == 0 || height ==0) return 1; + +#ifdef PNG_SETJMP_SUPPORTED + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, + &jbw, gdPngErrorHandler, + NULL); +#else + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +#endif + if (png_ptr == NULL) { + gd_error("gd-png error: cannot allocate libpng main struct\n"); + return 1; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) { + gd_error("gd-png error: cannot allocate libpng info struct\n"); + png_destroy_write_struct (&png_ptr, (png_infopp) NULL); + return 1; + } + +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(jbw.jmpbuf)) { + gd_error("gd-png error: setjmp returns error condition\n"); + png_destroy_write_struct (&png_ptr, &info_ptr); + + if (row_pointers) { + for (i = 0; i < height; ++i) + gdFree(row_pointers[i]); + gdFree(row_pointers); + } + + return 1; + } +#endif + + png_set_write_fn (png_ptr, (void *) outfile, gdPngWriteData, + gdPngFlushData); + + /* This is best for palette images, and libpng defaults to it for + palette images anyway, so we don't need to do it explicitly. + What to ideally do for truecolor images depends, alas, on the image. + gd is intentionally imperfect and doesn't spend a lot of time + fussing with such things. */ + + /* Faster if this is uncommented, but may produce larger truecolor files. + Wait for gdImagePngCtxEx. */ +#if 0 + png_set_filter (png_ptr, 0, PNG_FILTER_NONE); +#endif + + /* 2.0.12: this is finally a parameter */ + png_set_compression_level (png_ptr, level); + +#ifdef PNG_pHYs_SUPPORTED + /* 2.1.0: specify the resolution */ + png_set_pHYs(png_ptr, info_ptr, DPI2DPM(im->res_x), DPI2DPM(im->res_y), + PNG_RESOLUTION_METER); +#endif + + /* can set this to a smaller value without compromising compression if all + * image data is 16K or less; will save some decoder memory [min == 8] */ + /* png_set_compression_window_bits(png_ptr, 15); */ + + if (!im->trueColor) { + if (transparent >= im->colorsTotal || + (transparent >= 0 && open[transparent])) + transparent = -1; + } + if (!im->trueColor) { + for (i = 0; i < gdMaxColors; ++i) + mapping[i] = -1; + } + if (!im->trueColor) { + /* count actual number of colors used (colorsTotal == high-water mark) */ + colors = 0; + for (i = 0; i < im->colorsTotal; ++i) { + if (!open[i]) { + mapping[i] = colors; + ++colors; + } + } + if (colors == 0) { + gd_error("gd-png error: no colors in palette\n"); + ret = 1; + goto bail; + } + if (colors < im->colorsTotal) { + remap = TRUE; + } + if (colors <= 2) + bit_depth = 1; + else if (colors <= 4) + bit_depth = 2; + else if (colors <= 16) + bit_depth = 4; + else + bit_depth = 8; + } + interlace_type = im->interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; + + if (im->trueColor) { + if (im->saveAlphaFlag) { + png_set_IHDR (png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + } else { + png_set_IHDR (png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + } + } else { + png_set_IHDR (png_ptr, info_ptr, width, height, bit_depth, + PNG_COLOR_TYPE_PALETTE, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + } + if (im->trueColor && (!im->saveAlphaFlag) && (transparent >= 0)) { + /* 2.0.9: fixed by Thomas Winzig */ + trans_rgb_value.red = gdTrueColorGetRed (im->transparent); + trans_rgb_value.green = gdTrueColorGetGreen (im->transparent); + trans_rgb_value.blue = gdTrueColorGetBlue (im->transparent); + png_set_tRNS (png_ptr, info_ptr, 0, 0, &trans_rgb_value); + } + if (!im->trueColor) { + /* Oy veh. Remap the PNG palette to put the + entries with interesting alpha channel + values first. This minimizes the size + of the tRNS chunk and thus the size + of the PNG file as a whole. */ + int tc = 0; + int i; + int j; + int k; + for (i = 0; (i < im->colorsTotal); i++) { + if ((!im->open[i]) && (im->alpha[i] != gdAlphaOpaque)) { + tc++; + } + } + if (tc) { +#if 0 + for (i = 0; (i < im->colorsTotal); i++) { + trans_values[i] = 255 - + ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); + } + png_set_tRNS (png_ptr, info_ptr, trans_values, 256, NULL); +#endif + if (!remap) { + remap = TRUE; + } + /* (Semi-)transparent indexes come up from the bottom + of the list of real colors; opaque + indexes come down from the top */ + j = 0; + k = colors - 1; + for (i = 0; (i < im->colorsTotal); i++) { + if (!im->open[i]) { + if (im->alpha[i] != gdAlphaOpaque) { + /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ + trans_values[j] = 255 - + ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); + mapping[i] = j++; + } else { + mapping[i] = k--; + } + } + } + png_set_tRNS (png_ptr, info_ptr, trans_values, tc, NULL); + } + } + + /* convert palette to libpng layout */ + if (!im->trueColor) { + if (remap) + for (i = 0; i < im->colorsTotal; ++i) { + if (mapping[i] < 0) + continue; + palette[mapping[i]].red = im->red[i]; + palette[mapping[i]].green = im->green[i]; + palette[mapping[i]].blue = im->blue[i]; + } + else + for (i = 0; i < colors; ++i) { + palette[i].red = im->red[i]; + palette[i].green = im->green[i]; + palette[i].blue = im->blue[i]; + } + png_set_PLTE (png_ptr, info_ptr, palette, colors); + } + + /* write out the PNG header info (everything up to first IDAT) */ + png_write_info (png_ptr, info_ptr); + + /* make sure < 8-bit images are packed into pixels as tightly as possible */ + png_set_packing (png_ptr); + + /* This code allocates a set of row buffers and copies the gd image data + * into them only in the case that remapping is necessary; in gd 1.3 and + * later, the im->pixels array is laid out identically to libpng's row + * pointers and can be passed to png_write_image() function directly. + * The remapping case could be accomplished with less memory for non- + * interlaced images, but interlacing causes some serious complications. */ + if (im->trueColor) { + /* performance optimizations by Phong Tran */ + int channels = im->saveAlphaFlag ? 4 : 3; + /* Our little 7-bit alpha channel trick costs us a bit here. */ + unsigned char *pOutputRow; + int **ptpixels = im->tpixels; + int *pThisRow; + unsigned char a; + int thisPixel; + png_bytep *prow_pointers; + int saveAlphaFlag = im->saveAlphaFlag; + if (overflow2(sizeof (png_bytep), height)) { + ret = 1; + goto bail; + } + /* Need to use calloc so we can clean it up sanely in the error handler. */ + row_pointers = gdCalloc(height, sizeof (png_bytep)); + if (row_pointers == NULL) { + gd_error("gd-png error: unable to allocate row_pointers\n"); + ret = 1; + goto bail; + } + prow_pointers = row_pointers; + for (j = 0; j < height; ++j) { + if (overflow2(width, channels) || ((*prow_pointers = + (png_bytep) gdMalloc (width * channels)) == NULL)) { + gd_error("gd-png error: unable to allocate rows\n"); + for (i = 0; i < j; ++i) + gdFree (row_pointers[i]); + /* 2.0.29: memory leak TBB */ + gdFree(row_pointers); + ret = 1; + goto bail; + } + pOutputRow = *prow_pointers++; + pThisRow = *ptpixels++; + for (i = 0; i < width; ++i) { + thisPixel = *pThisRow++; + *pOutputRow++ = gdTrueColorGetRed (thisPixel); + *pOutputRow++ = gdTrueColorGetGreen (thisPixel); + *pOutputRow++ = gdTrueColorGetBlue (thisPixel); + + if (saveAlphaFlag) { + /* convert the 7-bit alpha channel to an 8-bit alpha channel. + We do a little bit-flipping magic, repeating the MSB + as the LSB, to ensure that 0 maps to 0 and + 127 maps to 255. We also have to invert to match + PNG's convention in which 255 is opaque. */ + a = gdTrueColorGetAlpha (thisPixel); + /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ + *pOutputRow++ = 255 - ((a << 1) + (a >> 6)); + } + } + } + + png_write_image (png_ptr, row_pointers); + png_write_end (png_ptr, info_ptr); + + for (j = 0; j < height; ++j) + gdFree (row_pointers[j]); + gdFree (row_pointers); + } else { + if (remap) { + png_bytep *row_pointers; + if (overflow2(sizeof (png_bytep), height)) { + ret = 1; + goto bail; + } + row_pointers = gdMalloc (sizeof (png_bytep) * height); + if (row_pointers == NULL) { + gd_error("gd-png error: unable to allocate row_pointers\n"); + ret = 1; + goto bail; + } + for (j = 0; j < height; ++j) { + if ((row_pointers[j] = (png_bytep) gdMalloc (width)) == NULL) { + gd_error("gd-png error: unable to allocate rows\n"); + for (i = 0; i < j; ++i) + gdFree (row_pointers[i]); + /* TBB: memory leak */ + gdFree (row_pointers); + ret = 1; + goto bail; + } + for (i = 0; i < width; ++i) + row_pointers[j][i] = mapping[im->pixels[j][i]]; + } + + png_write_image (png_ptr, row_pointers); + png_write_end (png_ptr, info_ptr); + + for (j = 0; j < height; ++j) + gdFree (row_pointers[j]); + gdFree (row_pointers); + } else { + png_write_image (png_ptr, im->pixels); + png_write_end (png_ptr, info_ptr); + } + } + /* 1.6.3: maybe we should give that memory BACK! TBB */ +bail: + png_destroy_write_struct (&png_ptr, &info_ptr); + return ret; +} + +#else /* !HAVE_LIBPNG */ + +static void _noPngError(void) +{ + gd_error("PNG image support has been disabled\n"); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPng (FILE * inFile) +{ + _noPngError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngPtr (int size, void *data) +{ + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngCtx (gdIOCtx * infile) +{ + return NULL; +} + +BGD_DECLARE(void) gdImagePngEx (gdImagePtr im, FILE * outFile, int level) +{ + _noPngError(); +} + +BGD_DECLARE(void) gdImagePng (gdImagePtr im, FILE * outFile) +{ + _noPngError(); +} + +BGD_DECLARE(void *) gdImagePngPtr (gdImagePtr im, int *size) +{ + return NULL; +} + +BGD_DECLARE(void *) gdImagePngPtrEx (gdImagePtr im, int *size, int level) +{ + return NULL; +} + +BGD_DECLARE(void) gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile) +{ + _noPngError(); +} + +BGD_DECLARE(void) gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level) +{ + _noPngError(); +} + +#endif /* HAVE_LIBPNG */ |