/* Convert 1 to 4-band 8 or 16-bit VIPS images to/from PNG. * * 28/11/03 JC * - better no-overshoot on tile loop * 22/2/05 * - read non-interlaced PNG with a line buffer (thanks Michel Brabants) * 11/1/06 * - read RGBA palette-ized images more robustly (thanks Tom) * 20/4/06 * - auto convert to sRGB/mono (with optional alpha) for save * 1/5/06 * - from vips_png.c * 2/11/07 * - use im_wbuffer() API for BG writes */ /* This file is part of VIPS. VIPS is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser 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 */ /* These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk */ /* #define DEBUG */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #ifndef HAVE_PNG #include int im_vips2png( IMAGE *in, const char *filename ) { im_error( "im_vips2png", _( "PNG support disabled" ) ); return( -1 ); } #else /*HAVE_PNG*/ #include #include #include #include #include #include #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ #if PNG_LIBPNG_VER < 10003 #error "PNG library too old." #endif static void user_error_function( png_structp png_ptr, png_const_charp error_msg ) { im_error( "im_vips2png", _( "PNG error: \"%s\"" ), error_msg ); } static void user_warning_function( png_structp png_ptr, png_const_charp warning_msg ) { im_error( "im_vips2png", _( "PNG warning: \"%s\"" ), warning_msg ); } /* What we track during a PNG write. */ typedef struct { IMAGE *in; REGION *reg; im_threadgroup_t *tg; FILE *fp; png_structp pPng; png_infop pInfo; png_bytep *row_pointer; } Write; static void write_destroy( Write *write ) { IM_FREEF( im_region_free, write->reg ); IM_FREEF( im_threadgroup_free, write->tg ); IM_FREEF( im_close, write->in ); IM_FREEF( fclose, write->fp ); if( write->pPng ) png_destroy_write_struct( &write->pPng, &write->pInfo ); IM_FREE( write->row_pointer ); im_free( write ); } static Write * write_new( IMAGE *in ) { Write *write; if( !(write = IM_NEW( NULL, Write )) ) return( NULL ); memset( write, 0, sizeof( Write ) ); if( !(write->in = im__convert_saveable( in, TRUE )) ) { im_error( "im_vips2png", _( "unable to convert to RGB for save" ) ); write_destroy( write ); return( NULL ); } write->reg = im_region_create( write->in ); write->tg = im_threadgroup_create( write->in ); write->row_pointer = IM_ARRAY( NULL, write->tg->nlines, png_bytep ); write->fp = NULL; write->pPng = NULL; write->pInfo = NULL; if( !write->reg || !write->tg || !write->row_pointer ) { write_destroy( write ); return( NULL ); } if( !(write->pPng = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, user_error_function, user_warning_function )) ) { write_destroy( write ); return( NULL ); } /* Catch PNG errors from png_create_info_struct(). */ if( setjmp( write->pPng->jmpbuf ) ) { write_destroy( write ); return( NULL ); } if( !(write->pInfo = png_create_info_struct( write->pPng )) ) { write_destroy( write ); return( NULL ); } return( write ); } static int write_png_block( REGION *region, Rect *area, void *a, void *b ) { Write *write = (Write *) a; int i; /* Catch PNG errors. Yuk. */ if( setjmp( write->pPng->jmpbuf ) ) return( -1 ); for( i = 0; i < area->height; i++ ) write->row_pointer[i] = (png_bytep) IM_REGION_ADDR( region, 0, area->top + i ); png_write_rows( write->pPng, write->row_pointer, area->height ); return( 0 ); } /* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace ) { IMAGE *in = write->in; int i, nb_passes; assert( in->BandFmt == IM_BANDFMT_UCHAR ); assert( in->Coding == IM_CODING_NONE ); assert( in->Bands > 0 && in->Bands < 5 ); /* Catch PNG errors. */ if( setjmp( write->pPng->jmpbuf ) ) return( -1 ); /* Check input image. */ if( im_pincheck( in ) ) return( -1 ); if( compress < 0 || compress > 9 ) { im_error( "im_vips2png", _( "compress should be in [0,9]" ) ); return( -1 ); } /* Set compression parameters. */ png_set_compression_level( write->pPng, compress ); write->pInfo->width = in->Xsize; write->pInfo->height = in->Ysize; write->pInfo->bit_depth = (in->BandFmt == IM_BANDFMT_UCHAR ? 8 : 16); write->pInfo->gamma = (float) 1.0; switch( in->Bands ) { case 1: write->pInfo->color_type = PNG_COLOR_TYPE_GRAY; break; case 2: write->pInfo->color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 3: write->pInfo->color_type = PNG_COLOR_TYPE_RGB; break; case 4: write->pInfo->color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; default: assert( 0 ); } png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ if( write->pInfo->bit_depth > 8 && !im_amiMSBfirst() ) png_set_swap( write->pPng ); if( interlace ) nb_passes = png_set_interlace_handling( write->pPng ); else nb_passes = 1; /* Write data. */ for( i = 0; i < nb_passes; i++ ) if( im_wbuffer( write->tg, write_png_block, write, NULL ) ) return( -1 ); /* The setjmp() was held by our background writer: reset it. */ if( setjmp( write->pPng->jmpbuf ) ) return( -1 ); png_write_end( write->pPng, write->pInfo ); return( 0 ); } /* Write a VIPS image to a file as PNG. */ int im_vips2png( IMAGE *in, const char *filename ) { Write *write; int compress; int interlace; char *p, *q; char name[FILENAME_MAX]; char mode[FILENAME_MAX]; char buf[FILENAME_MAX]; if( !(write = write_new( in )) ) return( -1 ); /* Extract write mode from filename and parse. */ im_filename_split( filename, name, mode ); strcpy( buf, mode ); p = &buf[0]; compress = 6; interlace = 0; if( (q = im_getnextoption( &p )) ) compress = atoi( q ); if( (q = im_getnextoption( &p )) ) interlace = atoi( q ); /* Make output. */ #ifdef BINARY_OPEN if( !(write->fp = fopen( name, "wb" )) ) { #else /*BINARY_OPEN*/ if( !(write->fp = fopen( name, "w" )) ) { #endif /*BINARY_OPEN*/ write_destroy( write ); im_error( "im_vips2png", _( "unable to open \"%s\"" ), name ); return( -1 ); } png_init_io( write->pPng, write->fp ); /* Convert it! */ if( write_vips( write, compress, interlace ) ) { write_destroy( write ); im_error( "im_vips2png", _( "unable to write \"%s\"" ), name ); return( -1 ); } write_destroy( write ); return( 0 ); } #endif /*HAVE_PNG*/