#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "gd.h"
#ifdef HAVE_LIBPNG
#include "png.h"
#include "gdhelpers.h"
#define TRUE 1
#define FALSE 0
const char * gdPngGetVersionString()
{
return PNG_LIBPNG_VER_STRING;
}
#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;
php_gd_error_ex(E_WARNING, "gd-png: fatal libpng error: %s", msg);
jmpbuf_ptr = png_get_error_ptr (png_ptr);
if (jmpbuf_ptr == NULL) {
php_gd_error_ex(E_ERROR, "gd-png: EXTREMELY fatal error: jmpbuf unrecoverable; terminating.");
}
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 != 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)
{
}
gdImagePtr gdImageCreateFromPng (FILE * inFile)
{
gdImagePtr im;
gdIOCtx *in = gdNewFileCtx(inFile);
im = gdImageCreateFromPngCtx(in);
in->gd_free(in);
return im;
}
gdImagePtr gdImageCreateFromPngPtr (int size, void *data)
{
gdImagePtr im;
gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0);
im = gdImageCreateFromPngCtx(in);
in->gd_free(in);
return im;
}
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;
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;
volatile png_bytep image_data = NULL;
volatile png_bytepp row_pointers = NULL;
gdImagePtr im = NULL;
int i, j, *open = NULL;
volatile int transparent = -1;
volatile int palette_allocated = FALSE;
memset (sig, 0, sizeof(sig));
if (gdGetBuf(sig, 8, infile) < 8) {
return NULL;
}
if (png_sig_cmp(sig, 0, 8) != 0) {
return NULL;
}
#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) {
php_gd_error("gd-png error: cannot allocate libpng main struct");
return NULL;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
php_gd_error("gd-png error: cannot allocate libpng info struct");
png_destroy_read_struct (&png_ptr, NULL, NULL);
return NULL;
}
#ifdef PNG_SETJMP_SUPPORTED
if (setjmp(jbw.jmpbuf)) {
php_gd_error("gd-png error: setjmp returns error condition");
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)
|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
im = gdImageCreateTrueColor((int) width, (int) height);
} else {
im = gdImageCreate((int) width, (int) height);
}
if (im == NULL) {
php_gd_error("gd-png error: cannot allocate gdImage struct");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return NULL;
}
if (bit_depth == 16) {
png_set_strip_16(png_ptr);
} else if (bit_depth < 8) {
png_set_packing (png_ptr);
}
#ifdef PNG_SETJMP_SUPPORTED
if (setjmp(jbw.jmpbuf)) {
php_gd_error("gd-png error: setjmp returns error condition");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
gdFree(image_data);
gdFree(row_pointers);
if (im) {
gdImageDestroy(im);
}
return NULL;
}
#endif
switch (color_type) {
case PNG_COLOR_TYPE_PALETTE:
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
#ifdef DEBUG
php_gd_error("gd-png color_type is palette, colors: %d", 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:
if ((palette = (png_colorp) gdMalloc (256 * sizeof (png_color))) == NULL) {
php_gd_error("gd-png error: cannot allocate gray palette");
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_GRAY_ALPHA:
png_set_gray_to_rgb(png_ptr);
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);
image_data = (png_bytep) safe_emalloc(rowbytes, height, 0);
row_pointers = (png_bytepp) safe_emalloc(height, sizeof(png_bytep), 0);
for (h = 0; h < height; ++h) {
row_pointers[h] = image_data + h * 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 (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++];
register png_byte a = gdAlphaMax - (row_pointers[h][boffset++] >> 1);
im->tpixels[h][w] = gdTrueColorAlpha(r, g, b, a);
}
}
break;
default:
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]) {
php_gd_error("gd-png warning: image data references out-of-range color index (%d)", i);
}
}
}
#endif
if (palette_allocated) {
gdFree(palette);
}
gdFree(image_data);
gdFree(row_pointers);
return im;
}
void gdImagePngEx (gdImagePtr im, FILE * outFile, int level, int basefilter)
{
gdIOCtx *out = gdNewFileCtx(outFile);
gdImagePngCtxEx(im, out, level, basefilter);
out->gd_free(out);
}
void gdImagePng (gdImagePtr im, FILE * outFile)
{
gdIOCtx *out = gdNewFileCtx(outFile);
gdImagePngCtxEx(im, out, -1, -1);
out->gd_free(out);
}
void * gdImagePngPtr (gdImagePtr im, int *size)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
gdImagePngCtxEx(im, out, -1, -1);
rv = gdDPExtractData(out, size);
out->gd_free(out);
return rv;
}
void * gdImagePngPtrEx (gdImagePtr im, int *size, int level, int basefilter)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
gdImagePngCtxEx(im, out, level, basefilter);
rv = gdDPExtractData(out, size);
out->gd_free(out);
return rv;
}
void gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile)
{
gdImagePngCtxEx(im, outfile, -1, -1);
}
void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilter)
{
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;
#ifdef PNG_SETJMP_SUPPORTED
jmpbuf_wrapper jbw;
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) {
php_gd_error("gd-png error: cannot allocate libpng main struct");
return;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
php_gd_error("gd-png error: cannot allocate libpng info struct");
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
return;
}
#ifdef PNG_SETJMP_SUPPORTED
if (setjmp(jbw.jmpbuf)) {
php_gd_error("gd-png error: setjmp returns error condition");
png_destroy_write_struct (&png_ptr, &info_ptr);
return;
}
#endif
png_set_write_fn(png_ptr, (void *) outfile, gdPngWriteData, gdPngFlushData);
if (level != -1 && (level < 0 || level > 9)) {
php_gd_error("gd-png error: compression level must be 0 through 9");
return;
}
png_set_compression_level(png_ptr, level);
if (basefilter >= 0) {
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, basefilter);
}
if (!im->trueColor) {
if (transparent >= im->colorsTotal || (transparent >= 0 && open[transparent])) {
transparent = -1;
}
for (i = 0; i < gdMaxColors; ++i) {
mapping[i] = -1;
}
colors = 0;
for (i = 0; i < im->colorsTotal; ++i) {
if (!open[i]) {
mapping[i] = colors;
++colors;
}
}
if (colors == 0) {
php_gd_error("gd-png error: no colors in palette");
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)) {
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;
unsigned char* pOutputRow;
int **ptpixels = im->tpixels;
int *pThisRow;
unsigned char a;
int thisPixel;
png_bytep *prow_pointers;
int saveAlphaFlag = im->saveAlphaFlag;
row_pointers = safe_emalloc(sizeof(png_bytep), height, 0);
prow_pointers = row_pointers;
for (j = 0; j < height; ++j) {
*prow_pointers = (png_bytep) safe_emalloc(width, channels, 0);
pOutputRow = *prow_pointers++;
pThisRow = *ptpixels++;
for (i = 0; i < width; ++i) {
thisPixel = *pThisRow++;
*pOutputRow++ = gdTrueColorGetRed(thisPixel);
*pOutputRow++ = gdTrueColorGetGreen(thisPixel);
*pOutputRow++ = gdTrueColorGetBlue(thisPixel);
if (saveAlphaFlag) {
a = gdTrueColorGetAlpha(thisPixel);
if (a == 127) {
*pOutputRow++ = 0;
} else {
*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;
row_pointers = safe_emalloc(height, sizeof(png_bytep), 0);
for (j = 0; j < height; ++j) {
row_pointers[j] = (png_bytep) gdMalloc(width);
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);
}
}
bail:
png_destroy_write_struct(&png_ptr, &info_ptr);
}
#endif