#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "gd.h"
#ifdef HAVE_LIBPNG
#include "gdhelpers.h"
#include "png.h"
#define TRUE 1
#define FALSE 0
#ifndef PNG_SETJMP_NOT_SUPPORTED
typedef struct _jmpbuf_wrapper
{
jmp_buf jmpbuf;
}
jmpbuf_wrapper;
static jmpbuf_wrapper gdPngJmpbufStruct;
static void
gdPngErrorHandler (png_structp png_ptr, png_const_charp msg)
{
jmpbuf_wrapper *jmpbuf_ptr;
fprintf (stderr, "gd-png: fatal libpng error: %s\n", msg);
fflush (stderr);
jmpbuf_ptr = png_get_error_ptr (png_ptr);
if (jmpbuf_ptr == NULL)
{
fprintf (stderr,
"gd-png: EXTREMELY fatal error: jmpbuf unrecoverable; terminating.\n");
fflush (stderr);
exit (99);
}
longjmp (jmpbuf_ptr->jmpbuf, 1);
}
#endif
static void
gdPngReadData (png_structp png_ptr, png_bytep data, png_size_t length)
{
gdGetBuf (data, length, (gdIOCtx *) png_get_io_ptr (png_ptr));
}
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)
{
}
gdImagePtr
gdImageCreateFromPng (FILE * inFile)
{
gdImagePtr im;
gdIOCtx *in = gdNewFileCtx (inFile);
im = gdImageCreateFromPngCtx (in);
in->gd_free (in);
return im;
}
gdImagePtr
gdImageCreateFromPngCtx (gdIOCtx * infile)
{
png_byte sig[8];
png_structp png_ptr;
png_infop info_ptr;
png_uint_32 width, height, rowbytes;
int bit_depth, color_type, interlace_type;
int num_palette, 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;
memset (infile, 0, sizeof (infile));
gdGetBuf (sig, 8, infile);
if (!png_check_sig (sig, 8))
return NULL;
#ifndef PNG_SETJMP_NOT_SUPPORTED
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, &gdPngJmpbufStruct,
gdPngErrorHandler, NULL);
#else
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
if (png_ptr == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate libpng main struct\n");
return NULL;
}
info_ptr = png_create_info_struct (png_ptr);
if (info_ptr == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate libpng info struct\n");
png_destroy_read_struct (&png_ptr, NULL, NULL);
return NULL;
}
#ifndef PNG_SETJMP_NOT_SUPPORTED
if (setjmp (gdPngJmpbufStruct.jmpbuf))
{
fprintf (stderr, "gd-png error: setjmp returns error condition\n");
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
return NULL;
}
#endif
png_set_sig_bytes (png_ptr, 8);
png_set_read_fn (png_ptr, (void *) infile, gdPngReadData);
png_read_info (png_ptr, info_ptr);
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))
{
im = gdImageCreateTrueColor ((int) width, (int) height);
}
else
{
im = gdImageCreate ((int) width, (int) height);
}
if (im == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate gdImage struct\n");
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
gdFree (image_data);
gdFree (row_pointers);
return NULL;
}
if (bit_depth == 16)
png_set_strip_16 (png_ptr);
else if (bit_depth < 8)
png_set_packing (png_ptr);
switch (color_type)
{
case PNG_COLOR_TYPE_PALETTE:
png_get_PLTE (png_ptr, info_ptr, &palette, &num_palette);
#ifdef DEBUG
fprintf (stderr, "gd-png color_type is palette, colors: %d\n",
num_palette);
#endif
if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
{
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))
{
transparent = i;
firstZero = 0;
}
}
}
break;
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if ((palette =
(png_colorp) gdMalloc (256 * sizeof (png_color))) == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate gray palette\n");
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
return NULL;
}
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)
transparent = trans_gray_rgb->gray >> 8;
else
transparent = trans_gray_rgb->gray;
}
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
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)
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;
}
png_read_update_info (png_ptr, info_ptr);
rowbytes = png_get_rowbytes (png_ptr, info_ptr);
if ((image_data = (png_bytep) gdMalloc (rowbytes * height)) == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate image data\n");
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
return NULL;
}
if ((row_pointers =
(png_bytepp) gdMalloc (height * sizeof (png_bytep))) == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate row pointers\n");
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
gdFree (image_data);
return NULL;
}
for (j = 0; j < height; ++j)
{
row_pointers[j] = image_data + j * rowbytes;
}
png_read_image (png_ptr, row_pointers);
png_read_end (png_ptr, NULL);
if (!im->trueColor)
{
im->colorsTotal = num_palette;
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;
}
}
im->transparent = transparent;
im->interlace = (interlace_type == PNG_INTERLACE_ADAM7);
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
switch (color_type)
{
case PNG_COLOR_TYPE_RGB:
for (j = 0; j < height; j++)
{
int boffset = 0;
for (i = 0; i < width; i++)
{
register png_byte r = row_pointers[j][boffset++];
register png_byte g = row_pointers[j][boffset++];
register png_byte b = row_pointers[j][boffset++];
im->tpixels[j][i] = gdTrueColor (r, g, b);
}
}
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
for (j = 0; j < height; j++)
{
int boffset = 0;
for (i = 0; i < width; i++)
{
register png_byte r = row_pointers[j][boffset++];
register png_byte g = row_pointers[j][boffset++];
register png_byte b = row_pointers[j][boffset++];
register png_byte a = gdAlphaMax -
(row_pointers[j][boffset++] >> 1);
im->tpixels[j][i] = gdTrueColorAlpha (r, g, b, a);
}
}
break;
default:
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
register png_byte idx = row_pointers[j][i];
im->pixels[j][i] = 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
if (palette_allocated)
gdFree (palette);
gdFree (image_data);
gdFree (row_pointers);
return im;
}
void
gdImagePngEx (gdImagePtr im, FILE * outFile, int level)
{
gdIOCtx *out = gdNewFileCtx (outFile);
gdImagePngCtxEx (im, out, level);
out->gd_free (out);
}
void
gdImagePng (gdImagePtr im, FILE * outFile)
{
gdIOCtx *out = gdNewFileCtx (outFile);
gdImagePngCtxEx (im, out, -1);
out->gd_free (out);
}
void *
gdImagePngPtr (gdImagePtr im, int *size)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
gdImagePngCtxEx (im, out, -1);
rv = gdDPExtractData (out, size);
out->gd_free (out);
return rv;
}
void *
gdImagePngPtrEx (gdImagePtr im, int *size, int level)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
gdImagePngCtxEx (im, out, level);
rv = gdDPExtractData (out, size);
out->gd_free (out);
return rv;
}
void
gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile)
{
gdImagePngCtxEx (im, outfile, -1);
}
void
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];
png_byte trans_values[256];
png_color_16 trans_rgb_value;
png_color palette[gdMaxColors];
png_structp png_ptr;
png_infop info_ptr;
volatile int transparent = im->transparent;
volatile int remap = FALSE;
#ifndef PNG_SETJMP_NOT_SUPPORTED
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
&gdPngJmpbufStruct, gdPngErrorHandler,
NULL);
#else
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
if (png_ptr == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate libpng main struct\n");
return;
}
info_ptr = png_create_info_struct (png_ptr);
if (info_ptr == NULL)
{
fprintf (stderr, "gd-png error: cannot allocate libpng info struct\n");
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
return;
}
#ifndef PNG_SETJMP_NOT_SUPPORTED
if (setjmp (gdPngJmpbufStruct.jmpbuf))
{
fprintf (stderr, "gd-png error: setjmp returns error condition\n");
png_destroy_write_struct (&png_ptr, &info_ptr);
return;
}
#endif
png_set_write_fn (png_ptr, (void *) outfile, gdPngWriteData,
gdPngFlushData);
png_set_compression_level (png_ptr, level);
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)
{
colors = 0;
for (i = 0; i < im->colorsTotal; ++i)
{
if (!open[i])
{
mapping[i] = colors;
++colors;
}
}
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))
{
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)
{
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;
}
j = 0;
k = colors - 1;
for (i = 0; (i < im->colorsTotal); i++)
{
if (!im->open[i])
{
if (im->alpha[i] != gdAlphaOpaque)
{
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);
}
}
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);
}
png_write_info (png_ptr, info_ptr);
png_set_packing (png_ptr);
if (im->trueColor)
{
int channels = im->saveAlphaFlag ? 4 : 3;
png_bytep *row_pointers;
row_pointers = gdMalloc (sizeof (png_bytep) * height);
if (row_pointers == NULL)
{
fprintf (stderr, "gd-png error: unable to allocate row_pointers\n");
}
for (j = 0; j < height; ++j)
{
int bo = 0;
if ((row_pointers[j] =
(png_bytep) gdMalloc (width * channels)) == NULL)
{
fprintf (stderr, "gd-png error: unable to allocate rows\n");
for (i = 0; i < j; ++i)
gdFree (row_pointers[i]);
return;
}
for (i = 0; i < width; ++i)
{
unsigned char a;
row_pointers[j][bo++] = gdTrueColorGetRed (im->tpixels[j][i]);
row_pointers[j][bo++] = gdTrueColorGetGreen (im->tpixels[j][i]);
row_pointers[j][bo++] = gdTrueColorGetBlue (im->tpixels[j][i]);
if (im->saveAlphaFlag)
{
a = gdTrueColorGetAlpha (im->tpixels[j][i]);
row_pointers[j][bo++] = 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;
row_pointers = gdMalloc (sizeof (png_bytep) * height);
if (row_pointers == NULL)
{
fprintf (stderr,
"gd-png error: unable to allocate row_pointers\n");
}
for (j = 0; j < height; ++j)
{
if ((row_pointers[j] = (png_bytep) gdMalloc (width)) == NULL)
{
fprintf (stderr, "gd-png error: unable to allocate rows\n");
for (i = 0; i < j; ++i)
gdFree (row_pointers[i]);
return;
}
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);
}
}
png_destroy_write_struct (&png_ptr, &info_ptr);
}
#endif