/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
static void
image_png_read_buf(png_structp png_ptr, png_bytep data, png_size_t len)
{
image *im = (image *)png_get_io_ptr(png_ptr);
DEBUG_TRACE("PNG read_buf wants %ld bytes, %d in buffer\n", len, buffer_len(im->buf));
if (im->fh != NULL) {
if ( !_check_buf(im->fh, im->buf, len, MAX(len, BUFFER_SIZE)) ) {
goto eof;
}
}
else {
if (len > buffer_len(im->buf)) {
// read from SV into buffer
int sv_readlen = len - buffer_len(im->buf);
if (sv_readlen > sv_len(im->sv_data) - im->sv_offset)
goto eof;
DEBUG_TRACE(" Reading %d bytes of SV data @ %d\n", sv_readlen, im->sv_offset);
buffer_append(im->buf, SvPVX(im->sv_data) + im->sv_offset, sv_readlen);
im->sv_offset += sv_readlen;
}
}
png_memcpy(data, buffer_ptr(im->buf), len);
buffer_consume(im->buf, len);
goto ok;
eof:
png_error(png_ptr, "Not enough PNG data");
ok:
return;
}
static void
image_png_error(png_structp png_ptr, png_const_charp error_msg)
{
image *im = (image *)png_get_error_ptr(png_ptr);
warn("Image::Scale libpng error: %s (%s)\n", error_msg, SvPVX(im->path));
longjmp(png_jmpbuf(png_ptr), 1);
}
static void
image_png_warning(png_structp png_ptr, png_const_charp warning_msg)
{
image *im = (image *)png_get_error_ptr(png_ptr);
warn("Image::Scale libpng warning: %s (%s)\n", warning_msg, SvPVX(im->path));
}
int
image_png_read_header(image *im)
{
im->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)im, image_png_error, image_png_warning);
if ( !im->png_ptr )
croak("Image::Scale could not initialize libpng\n");
im->info_ptr = png_create_info_struct(im->png_ptr);
if ( !im->info_ptr ) {
png_destroy_read_struct(&im->png_ptr, (png_infopp)NULL, (png_infopp)NULL);
croak("Image::Scale could not initialize libpng\n");
}
if ( setjmp( png_jmpbuf(im->png_ptr) ) ) {
image_png_finish(im);
return 0;
}
png_set_read_fn(im->png_ptr, im, image_png_read_buf);
png_read_info(im->png_ptr, im->info_ptr);
im->width = png_get_image_width(im->png_ptr, im->info_ptr);
im->height = png_get_image_height(im->png_ptr, im->info_ptr);
im->channels = png_get_channels(im->png_ptr, im->info_ptr);
im->has_alpha = 1;
return 1;
}
static void
image_png_interlace_pass_gray(image *im, unsigned char *ptr, int start_y, int stride_y, int start_x, int stride_x)
{
int x, y;
for (y = 0; y < im->height; y++) {
png_read_row(im->png_ptr, ptr, NULL);
if (start_y == 0) {
start_y = stride_y;
for (x = start_x; x < im->width; x += stride_x) {
im->pixbuf[y * im->width + x] = COL_FULL(
ptr[x * 2], ptr[x * 2], ptr[x * 2], ptr[x * 2 + 1]
);
}
}
start_y--;
}
}
static void
image_png_interlace_pass(image *im, unsigned char *ptr, int start_y, int stride_y, int start_x, int stride_x)
{
int x, y;
for (y = 0; y < im->height; y++) {
png_read_row(im->png_ptr, ptr, NULL);
if (start_y == 0) {
start_y = stride_y;
for (x = start_x; x < im->width; x += stride_x) {
im->pixbuf[y * im->width + x] = COL_FULL(
ptr[x * 4], ptr[x * 4 + 1], ptr[x * 4 + 2], ptr[x * 4 + 3]
);
}
}
start_y--;
}
}
int
image_png_load(image *im)
{
int bit_depth, color_type, num_passes, x, y;
int ofs;
volatile unsigned char *ptr = NULL; // volatile = won't be rolled back if longjmp is called
if ( setjmp( png_jmpbuf(im->png_ptr) ) ) {
if (ptr != NULL)
Safefree(ptr);
image_png_finish(im);
return 0;
}
// If reusing the object a second time, we need to completely create a new png struct
if (im->used) {
DEBUG_TRACE("Recreating libpng objects\n");
image_png_finish(im);
if (im->fh != NULL) {
// reset file to begining of image
PerlIO_seek(im->fh, im->image_offset, SEEK_SET);
}
else {
// reset SV read
im->sv_offset = im->image_offset;
}
buffer_clear(im->buf);
image_png_read_header(im);
}
bit_depth = png_get_bit_depth(im->png_ptr, im->info_ptr);
color_type = png_get_color_type(im->png_ptr, im->info_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_expand(im->png_ptr); // png_set_palette_to_rgb(im->png_ptr);
im->channels = 4;
}
else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand(im->png_ptr); // png_set_expand_gray_1_2_4_to_8(im->png_ptr);
else if (png_get_valid(im->png_ptr, im->info_ptr, PNG_INFO_tRNS))
png_set_expand(im->png_ptr); // png_set_tRNS_to_alpha(im->png_ptr);
if (bit_depth == 16)
png_set_strip_16(im->png_ptr);
else if (bit_depth < 8)
png_set_packing(im->png_ptr);
// Make non-alpha RGB/Palette 32-bit and Gray 16-bit for easier handling
if ( !(color_type & PNG_COLOR_MASK_ALPHA) ) {
png_set_add_alpha(im->png_ptr, 0xFF, PNG_FILLER_AFTER);
}
num_passes = png_set_interlace_handling(im->png_ptr);
DEBUG_TRACE("png bit_depth %d, color_type %d, channels %d, num_passes %d\n", bit_depth, color_type, im->channels, num_passes);
png_read_update_info(im->png_ptr, im->info_ptr);
image_alloc(im, im->width, im->height);
New(0, ptr, png_get_rowbytes(im->png_ptr, im->info_ptr), unsigned char);
ofs = 0;
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { // Grayscale (Alpha)
if (num_passes == 1) { // Non-interlaced
for (y = 0; y < im->height; y++) {
png_read_row(im->png_ptr, (unsigned char *)ptr, NULL);
for (x = 0; x < im->width; x++) {
im->pixbuf[ofs++] = COL_FULL(ptr[x * 2], ptr[x * 2], ptr[x * 2], ptr[x * 2 + 1]);
}
}
}
else if (num_passes == 7) { // Interlaced
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 0, 8, 0, 8);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 0, 8, 4, 8);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 4, 8, 0, 4);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 0, 4, 2, 4);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 2, 4, 0, 2);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 0, 2, 1, 2);
image_png_interlace_pass_gray(im, (unsigned char *)ptr, 1, 2, 0, 1);
}
}
else { // RGB(A)
if (num_passes == 1) { // Non-interlaced
for (y = 0; y < im->height; y++) {
png_read_row(im->png_ptr, (unsigned char *)ptr, NULL);
for (x = 0; x < im->width; x++) {
im->pixbuf[ofs++] = COL_FULL(ptr[x * 4], ptr[x * 4 + 1], ptr[x * 4 + 2], ptr[x * 4 + 3]);
}
}
}
else if (num_passes == 7) { // Interlaced
// The first pass will return an image 1/8 as wide as the entire image
// (every 8th column starting in column 0)
// and 1/8 as high as the original (every 8th row starting in row 0)
image_png_interlace_pass(im, (unsigned char *)ptr, 0, 8, 0, 8);
// The second will be 1/8 as wide (starting in column 4)
// and 1/8 as high (also starting in row 0)
image_png_interlace_pass(im, (unsigned char *)ptr, 0, 8, 4, 8);
// The third pass will be 1/4 as wide (every 4th pixel starting in column 0)
// and 1/8 as high (every 8th row starting in row 4)
image_png_interlace_pass(im, (unsigned char *)ptr, 4, 8, 0, 4);
// The fourth pass will be 1/4 as wide and 1/4 as high
// (every 4th column starting in column 2, and every 4th row starting in row 0)
image_png_interlace_pass(im, (unsigned char *)ptr, 0, 4, 2, 4);
// The fifth pass will return an image 1/2 as wide,
// and 1/4 as high (starting at column 0 and row 2)
image_png_interlace_pass(im, (unsigned char *)ptr, 2, 4, 0, 2);
// The sixth pass will be 1/2 as wide and 1/2 as high as the original
// (starting in column 1 and row 0)
image_png_interlace_pass(im, (unsigned char *)ptr, 0, 2, 1, 2);
// The seventh pass will be as wide as the original, and 1/2 as high,
// containing all of the odd numbered scanlines.
image_png_interlace_pass(im, (unsigned char *)ptr, 1, 2, 0, 1);
}
else {
croak("Image::Scale unsupported PNG interlace type (%d passes)\n", num_passes);
}
}
Safefree(ptr);
// This is not required, so we can save some time by not reading post-image chunks
//png_read_end(im->png_ptr, im->info_ptr);
return 1;
}
static void
image_png_compress(image *im, png_structp png_ptr, png_infop info_ptr)
{
int i, x, y;
int color_space = PNG_COLOR_TYPE_RGB_ALPHA;
volatile unsigned char *ptr = NULL;
if (setjmp( png_jmpbuf(png_ptr) )) {
if (ptr != NULL)
Safefree(ptr);
return;
}
// Match output color space with input file
switch (im->channels) {
case 4:
case 3:
DEBUG_TRACE("PNG output color space set to RGBA\n");
color_space = PNG_COLOR_TYPE_RGB_ALPHA;
break;
case 2:
case 1:
DEBUG_TRACE("PNG output color space set to gray alpha\n");
color_space = PNG_COLOR_TYPE_GRAY_ALPHA;
break;
}
png_set_IHDR(png_ptr, info_ptr, im->target_width, im->target_height, 8, color_space,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
New(0, ptr, png_get_rowbytes(png_ptr, info_ptr), unsigned char);
i = 0;
if (color_space == PNG_COLOR_TYPE_GRAY_ALPHA) {
for (y = 0; y < im->target_height; y++) {
for (x = 0; x < im->target_width; x++) {
ptr[x * 2] = COL_BLUE(im->outbuf[i]);
ptr[x * 2 + 1] = COL_ALPHA(im->outbuf[i]);
i++;
}
png_write_row(png_ptr, (png_bytep)ptr);
}
}
else { // RGB
for (y = 0; y < im->target_height; y++) {
for (x = 0; x < im->target_width; x++) {
ptr[x * 4] = COL_RED(im->outbuf[i]);
ptr[x * 4 + 1] = COL_GREEN(im->outbuf[i]);
ptr[x * 4 + 2] = COL_BLUE(im->outbuf[i]);
ptr[x * 4 + 3] = COL_ALPHA(im->outbuf[i]);
i++;
}
png_write_row(png_ptr, (png_bytep)ptr);
}
}
Safefree(ptr);
png_write_end(png_ptr, info_ptr);
}
void
image_png_save(image *im, const char *path)
{
png_structp png_ptr;
png_infop info_ptr;
FILE *out;
if (im->outbuf == NULL)
croak("Image::Scale cannot write PNG with no output data\n");
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
croak("Image::Scale could not initialize libpng\n");
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, NULL);
croak("Image::Scale could not initialize libpng\n");
}
if ((out = fopen(path, "wb")) == NULL) {
png_destroy_write_struct(&png_ptr, &info_ptr);
croak("Image::Scale cannot open %s for writing\n", path);
}
png_init_io(png_ptr, out);
image_png_compress(im, png_ptr, info_ptr);
fclose(out);
png_destroy_write_struct(&png_ptr, &info_ptr);
}
static void
image_png_write_sv(png_structp png_ptr, png_bytep data, png_size_t len)
{
SV *sv_buf = (SV *)png_get_io_ptr(png_ptr);
// Copy buffer to SV
sv_catpvn(sv_buf, (char *)data, len);
DEBUG_TRACE("image_png_write_sv copied %ld bytes\n", len);
}
static void
image_png_flush_sv(png_structp png_ptr)
{
// Nothing
}
void
image_png_to_sv(image *im, SV *sv_buf)
{
png_structp png_ptr;
png_infop info_ptr;
if (im->outbuf == NULL)
croak("Image::Scale cannot write PNG with no output data\n");
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
croak("Image::Scale could not initialize libpng\n");
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, NULL);
croak("Image::Scale could not initialize libpng\n");
}
png_set_write_fn(png_ptr, sv_buf, image_png_write_sv, image_png_flush_sv);
image_png_compress(im, png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
}
void
image_png_finish(image *im)
{
if (im->png_ptr != NULL) {
png_destroy_read_struct(&im->png_ptr, &im->info_ptr, NULL);
im->png_ptr = NULL;
DEBUG_TRACE("libpng destroy\n");
}
}