/* png.c * PNG decoding * (c) 2002 Karel 'Clock' Kulhavy * This is a part of the Links program, released under GPL. */ #include "cfg.h" #ifdef G #include "links.h" #ifdef REPACK_16 #undef REPACK_16 #endif /* #ifdef REPACK_16 */ #if SIZEOF_UNSIGNED_SHORT != 2 #define REPACK_16 #endif /* #if SIZEOF_UNSIGNED_SHORT != 2 */ #ifndef REPACK_16 #ifndef C_LITTLE_ENDIAN #ifndef C_BIG_ENDIAN #define REPACK_16 #endif /* #ifndef C_BIG_ENDIAN */ #endif /* #ifndef C_LITTLE_ENDIAN */ #endif /* #ifndef REPACK_16 */ /* Decoder structs */ struct png_decoder{ png_structp png_ptr; png_infop info_ptr; }; /* Warning for from-web PNG images */ static void img_my_png_warning(png_structp a, png_const_charp b) { } /* Error for from-web PNG images. */ static void img_my_png_error(png_structp png_ptr, png_const_charp error_string) { #if (PNG_LIBPNG_VER < 10500) longjmp(png_ptr->jmpbuf,1); #else png_longjmp(png_ptr,1); #endif } static void png_info_callback(png_structp png_ptr, png_infop info_ptr) { int bit_depth, color_type, intent; double gamma; unsigned char bytes_per_pixel=3; struct cached_image *cimg; cimg=global_cimg; bit_depth=png_get_bit_depth(png_ptr, info_ptr); color_type=png_get_color_type(png_ptr, info_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)){ png_set_expand(png_ptr); /* Legacy version of png_set_tRNS_to_alpha(png_ptr); */ bytes_per_pixel++; } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); if (bit_depth==16){ #ifndef REPACK_16 #ifdef C_LITTLE_ENDIAN /* We use native endianity only if unsigned short is 2-byte * because otherwise we have to reassemble the buffer so we * will leave in the libpng-native big endian. */ png_set_swap(png_ptr); #endif /* #ifdef C_LITTLE_ENDIAN */ #endif /* #ifndef REPACK_16 */ bytes_per_pixel*=(int)sizeof(unsigned short); } png_set_interlace_handling(png_ptr); if (color_type==PNG_COLOR_TYPE_RGB_ALPHA ||color_type==PNG_COLOR_TYPE_GRAY_ALPHA){ if (bytes_per_pixel==3 ||bytes_per_pixel==3*sizeof(unsigned short)) bytes_per_pixel=4*bytes_per_pixel/3; } cimg->width=(int)png_get_image_width(png_ptr,info_ptr); cimg->height=(int)png_get_image_height(png_ptr,info_ptr); cimg->buffer_bytes_per_pixel=bytes_per_pixel; if (png_get_sRGB(png_ptr, info_ptr, &intent)){ gamma=sRGB_gamma; } else { if (!png_get_gAMA(png_ptr, info_ptr, &gamma)){ gamma=sRGB_gamma; } } if (gamma < 0.01 || gamma > 100) gamma = sRGB_gamma; cimg->red_gamma=(float)gamma; cimg->green_gamma=(float)gamma; cimg->blue_gamma=(float)gamma; png_read_update_info(png_ptr,info_ptr); cimg->strip_optimized=0; if (header_dimensions_known(cimg)) img_my_png_error(png_ptr, "bad image size"); } #ifdef REPACK_16 /* Converts unsigned shorts to doublechars (in big endian) */ static void a2char_from_unsigned_short(unsigned char *chr, unsigned short *shrt, int len) { unsigned short s; for (;len;len--,shrt++,chr+=2){ s=*shrt; *chr=s>>8; chr[1]=s; } } /* Converts doublechars (in big endian) to unsigned shorts */ static void unsigned_short_from_2char(unsigned short *shrt, unsigned char *chr, int len) { unsigned short s; for (;len;len--,shrt++,chr+=2){ s=((*chr)<<8)|chr[1]; *shrt=s; } } #endif static void png_row_callback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) { struct cached_image *cimg; #ifdef REPACK_16 unsigned char *tmp; int channels; #endif /* #ifdef REPACK_16 */ cimg=global_cimg; #ifdef REPACK_16 if (cimg->buffer_bytes_per_pixel>4) { channels=cimg->buffer_bytes_per_pixel/sizeof(unsigned short); if (PNG_INTERLACE_NONE==png_get_interlace_type(png_ptr, ((struct png_decoder *)cimg->decoder)->info_ptr)) { unsigned_short_from_2char((unsigned short *)(cimg->buffer+cimg ->buffer_bytes_per_pixel *cimg->width *row_num), new_row, cimg->width *channels); }else{ if ((unsigned)cimg->width > (unsigned)MAXINT / 2 / channels) overalloc(); tmp=mem_alloc(cimg->width*2*channels); a2char_from_unsigned_short(tmp, (unsigned short *)(cimg->buffer +cimg->buffer_bytes_per_pixel *cimg->width*row_num), cimg->width*channels); png_progressive_combine_row(png_ptr, tmp, new_row); unsigned_short_from_2char((unsigned short *)(cimg->buffer +cimg->buffer_bytes_per_pixel *cimg->width*row_num), tmp, cimg->width*channels); mem_free(tmp); } }else #endif /* #ifdef REPACK_16 */ { png_progressive_combine_row(png_ptr, cimg->buffer+cimg->buffer_bytes_per_pixel *cimg->width*row_num, new_row); } cimg->rows_added=1; } static void png_end_callback(png_structp png_ptr, png_infop info) { end_callback_hit=1; } /* Decoder structs */ void png_start(struct cached_image *cimg) { png_structp png_ptr; png_infop info_ptr; struct png_decoder *decoder; retry1: #ifdef PNG_USER_MEM_SUPPORTED png_ptr=png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, img_my_png_error, img_my_png_warning, NULL, my_png_alloc, my_png_free); #else png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, img_my_png_error, img_my_png_warning); #endif if (!png_ptr) { if (out_of_memory(0, NULL, 0)) goto retry1; fatal_exit("png_create_read_struct failed"); } retry2: info_ptr=png_create_info_struct(png_ptr); if (!info_ptr) { if (out_of_memory(0, NULL, 0)) goto retry2; fatal_exit("png_create_info_struct failed"); } if (setjmp(png_jmpbuf(png_ptr))){ error: png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); img_end(cimg); return; } png_set_progressive_read_fn(png_ptr, NULL, png_info_callback, &png_row_callback, png_end_callback); if (setjmp(png_jmpbuf(png_ptr))) goto error; decoder=mem_alloc(sizeof(*decoder)); decoder->png_ptr=png_ptr; decoder->info_ptr=info_ptr; cimg->decoder=decoder; } void png_restart(struct cached_image *cimg, unsigned char *data, int length) { png_structp png_ptr; png_infop info_ptr; volatile int h; #ifdef DEBUG if (!cimg->decoder) internal_error("decoder NULL in png_restart\n"); #endif /* #ifdef DEBUG */ h = close_std_handle(2); png_ptr=((struct png_decoder *)(cimg->decoder))->png_ptr; info_ptr=((struct png_decoder *)(cimg->decoder))->info_ptr; end_callback_hit=0; if (setjmp(png_jmpbuf(png_ptr))) { restore_std_handle(2, h); img_end(cimg); return; } png_process_data(png_ptr, info_ptr, data, length); restore_std_handle(2, h); if (end_callback_hit) img_end(cimg); } void png_destroy_decoder(struct cached_image *cimg) { struct png_decoder *decoder = (struct png_decoder *)cimg->decoder; png_destroy_read_struct(&decoder->png_ptr, &decoder->info_ptr, NULL); } void add_png_version(unsigned char **s, int *l) { add_to_str(s, l, cast_uchar "PNG ("); #ifdef HAVE_PNG_GET_LIBPNG_VER add_to_str(s, l, cast_uchar png_get_libpng_ver(NULL)); #else add_to_str(s, l, cast_uchar PNG_LIBPNG_VER_STRING); #endif add_chr_to_str(s, l, ')'); } #endif /* #ifdef G */