/*---------------------------------------------------------------------------* | PDFlib - A library for generating PDF on the fly | +---------------------------------------------------------------------------+ | Copyright (c) 1997-2006 Thomas Merz and PDFlib GmbH. All rights reserved. | +---------------------------------------------------------------------------+ | | | This software is subject to the PDFlib license. It is NOT in the | | public domain. Extended versions and commercial licenses are | | available, please check http://www.pdflib.com. | | | *---------------------------------------------------------------------------*/ /* $Id: p_png.c,v 1.1 2008/10/17 06:11:49 scuri Exp $ * * PNG processing for PDFlib * */ #include "p_intern.h" #include "p_color.h" #include "p_image.h" #if defined(__ia64__) && defined (__linux__) #define PDF_ALIGN16 #endif /* SPNG - Simple PNG ** ** The items below, prefixed with spng_, or SPNG_, establish a replacement ** for LIBPNG that works very fast, but processes simple PNG images only: ** - bit_depth <= 8 (no 16-bit) ** - interlace_type 0 (no interlacing) ** - color_type 0, 2, or 3 (no alpha-channel); color type 3 requires ** libpng for palette processing */ #define SPNG_SIGNATURE "\211\120\116\107\015\012\032\012" #define SPNG_CHUNK_IHDR 0x49484452 #define SPNG_CHUNK_PLTE 0x504C5445 #define SPNG_CHUNK_tRNS 0x74524E53 #define SPNG_CHUNK_IDAT 0x49444154 #define SPNG_CHUNK_IEND 0x49454E44 /* spng_init() return codes */ #define SPNG_ERR_OK 0 /* no error */ #define SPNG_ERR_NOPNG 1 /* bad PNG signature */ #define SPNG_ERR_FMT 2 /* bad PNG file format */ typedef struct { /* from IHDR: */ int width; int height; pdc_byte bit_depth; pdc_byte color_type; pdc_byte compr_type; pdc_byte filter_type; pdc_byte interlace_type; } spng_info; static int spng_getint(pdc_file *fp) { unsigned char buf[4]; if (!PDC_OK_FREAD(fp, buf, 4)) return -1; return (int) pdc_get_be_long(buf); } /* spng_getint */ static int spng_init(PDF *p, pdf_image *image, spng_info *spi) { pdc_file *fp = image->fp; char buf[8]; (void) p; /* check signature */ if (!PDC_OK_FREAD(fp, buf, 8) || strncmp(buf, SPNG_SIGNATURE, 8) != 0) return SPNG_ERR_NOPNG; /* read IHDR */ if (spng_getint(fp) != 13 || spng_getint(fp) != SPNG_CHUNK_IHDR) return SPNG_ERR_FMT; spi->width = spng_getint(fp); spi->height = spng_getint(fp); if (!PDC_OK_FREAD(fp, buf, 5)) return SPNG_ERR_FMT; spi->bit_depth = (pdc_byte) buf[0]; spi->color_type = (pdc_byte) buf[1]; spi->compr_type = (pdc_byte) buf[2]; spi->filter_type = (pdc_byte) buf[3]; spi->interlace_type = (pdc_byte) buf[4]; (void) spng_getint(fp); /* CRC */ /* decide whether this image is "simple". */ #ifdef HAVE_LIBPNG if (spi->bit_depth > 8 || spi->color_type > 3 || spi->interlace_type != 0) #else if (spi->bit_depth > 8 || spi->color_type > 2 || spi->interlace_type != 0) #endif /* !HAVE_LIBPNG */ { image->use_raw = pdc_false; return SPNG_ERR_OK; } else image->use_raw = pdc_true; /* read (or skip) all chunks up to the first IDAT. */ for (/* */ ; /* */ ; /* */) { int len = spng_getint(fp); int type = spng_getint(fp); switch (type) { case SPNG_CHUNK_IDAT: /* prepare data xfer */ image->info.png.nbytes = (size_t) len; return SPNG_ERR_OK; case -1: return SPNG_ERR_FMT; /* if we decide to live without LIBPNG, ** we should handle these cases, too. */ case SPNG_CHUNK_tRNS: /* transparency chunk */ case SPNG_CHUNK_PLTE: /* read in palette */ default: pdc_fseek(fp, len + 4, SEEK_CUR); /* skip data & CRC */ break; } /* switch */ } return SPNG_ERR_OK; } /* spng_init */ #define PDF_PNG_BUFFERSIZE 8192 static void pdf_data_source_PNG_init(PDF *p, PDF_data_source *src) { static const char fn[] = "pdf_data_source_PNG_init"; pdf_image *image = (pdf_image *) src->private_data; #ifdef HAVE_LIBPNG if (image->use_raw) { #endif src->buffer_length = PDF_PNG_BUFFERSIZE; src->buffer_start = (pdc_byte *) pdc_malloc(p->pdc, src->buffer_length, fn); src->bytes_available = 0; src->next_byte = src->buffer_start; #ifdef HAVE_LIBPNG } else { image->info.png.cur_line = 0; src->buffer_length = image->info.png.rowbytes; } #endif } #undef min #define min(a, b) (((a) < (b)) ? (a) : (b)) static void spng_error(PDF *p, PDF_data_source *src) { pdf_image *image = (pdf_image *) src->private_data; pdc_error(p->pdc, PDF_E_IMAGE_CORRUPT, "PNG", pdf_get_image_filename(p, image), 0, 0); } /* spng_error */ static pdc_bool pdf_data_source_PNG_fill(PDF *p, PDF_data_source *src) { pdf_image *image = (pdf_image *) src->private_data; PDC_TRY(p->pdc) { #ifdef HAVE_LIBPNG if (image->use_raw) { #endif pdc_file * fp = image->fp; size_t buf_avail = src->buffer_length; pdc_byte *scan = src->buffer_start; src->bytes_available = 0; if (image->info.png.nbytes == 0) { PDC_EXIT_TRY(p->pdc); return pdc_false; } do { size_t nbytes = min(image->info.png.nbytes, buf_avail); if (!PDC_OK_FREAD(fp, scan, nbytes)) spng_error(p, src); src->bytes_available += nbytes; scan += nbytes; buf_avail -= nbytes; if ((image->info.png.nbytes -= nbytes) == 0) { /* proceed to next IDAT chunk */ (void) spng_getint(fp); /* CRC */ image->info.png.nbytes = (size_t) spng_getint(fp); /* length */ if (spng_getint(fp) != SPNG_CHUNK_IDAT) { image->info.png.nbytes = 0; PDC_EXIT_TRY(p->pdc); return pdc_true; } } } while (buf_avail); #ifdef HAVE_LIBPNG } else { if (image->info.png.cur_line == image->height) { PDC_EXIT_TRY(p->pdc); return pdc_false; } src->next_byte = image->info.png.raster + image->info.png.cur_line * image->info.png.rowbytes; src->bytes_available = src->buffer_length; image->info.png.cur_line++; } #endif /* HAVE_LIBPNG */ } PDC_CATCH(p->pdc) { image->corrupt = pdc_true; } return !image->corrupt; } static void pdf_data_source_PNG_terminate(PDF *p, PDF_data_source *src) { pdf_image *image = (pdf_image *) src->private_data; #ifdef HAVE_LIBPNG if (image->use_raw) { #endif pdc_free(p->pdc, (void *) src->buffer_start); #ifdef HAVE_LIBPNG } #endif } #ifdef HAVE_LIBPNG /* * We suppress libpng's warning message by supplying * our own error and warning handlers */ static void pdf_libpng_warning_handler(png_structp png_ptr, png_const_charp message) { (void) png_ptr; /* avoid compiler warning "unreferenced parameter" */ (void) message; /* avoid compiler warning "unreferenced parameter" */ /* do nothing */ return; } /* * use the libpng portability aid in an attempt to overcome version differences */ #ifndef png_jmpbuf #define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) #endif static void pdf_libpng_error_handler(png_structp png_ptr, png_const_charp message) { #ifdef PDF_ALIGN16 jmp_buf jbuf; #endif (void) message; /* avoid compiler warning "unreferenced parameter" */ #ifdef PDF_ALIGN16 memcpy(jbuf, png_jmpbuf(png_ptr), sizeof (jmp_buf)); longjmp(jbuf, 1); #else longjmp(png_jmpbuf(png_ptr), 1); #endif } static void * pdf_libpng_malloc(png_structp png_ptr, size_t size) { PDF *p = (PDF *)png_ptr->mem_ptr; return pdc_malloc(p->pdc, size, "libpng"); } static void pdf_libpng_free(png_structp png_ptr, void *mem) { PDF *p = (PDF *)png_ptr->mem_ptr; pdc_free(p->pdc, mem); } pdc_bool pdf_is_PNG_file(PDF *p, pdc_file *fp) { pdc_byte sig[8]; pdc_logg_cond(p->pdc, 1, trc_image, "\tChecking image type PNG...\n"); if (!PDC_OK_FREAD(fp, sig, 8) || !png_check_sig(sig, 8)) { pdc_fseek(fp, 0L, SEEK_SET); return pdc_false; } return pdc_true; } static void /* CDPDF - moved to inside the HAVE_LIBPNG definition */ pdf_png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { pdc_file *fp = (pdc_file *) png_ptr->io_ptr; char *filename = (char *) pdc_file_name(fp); if (!PDC_OK_FREAD(fp, data, length)) { pdc_core *pdc = pdc_file_getpdc(fp); pdc_error(pdc, PDF_E_IMAGE_CORRUPT, "PNG", filename, 0, 0); } } int pdf_process_PNG_data( PDF *p, int imageslot) { static const char *fn = "pdf_process_PNG_data"; pdc_file *save_fp; spng_info s_info; #ifdef PDF_ALIGN16 jmp_buf jbuf; #endif png_uint_32 width, height, ui; png_bytep *row_pointers = NULL, trans; png_color_8p sig_bit; png_color_16p trans_values; int bit_depth, color_type, i, num_trans; pdc_scalar dpi_x, dpi_y; pdf_image *image; volatile int errcode = 0; pdf_colorspace cs; pdf_colormap * volatile colormap = NULL; volatile int slot; image = &p->images[imageslot]; /* * We can install our own memory handlers in libpng since * our PNG library is specially extended to support this. * A custom version of libpng without support for * png_create_read_struct_2() is no longer supported. */ image->info.png.png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, (png_voidp) NULL, (png_error_ptr)pdf_libpng_error_handler, (png_error_ptr)pdf_libpng_warning_handler, /* CDPDF - added type cast */ p, (png_malloc_ptr) pdf_libpng_malloc, (png_free_ptr) pdf_libpng_free); if (!image->info.png.png_ptr) { pdc_error(p->pdc, PDC_E_MEM_OUT, fn, 0, 0, 0); } image->info.png.info_ptr = png_create_info_struct(image->info.png.png_ptr); if (image->info.png.info_ptr == NULL) { png_destroy_read_struct(&image->info.png.png_ptr, (png_infopp) NULL, (png_infopp) NULL); pdc_error(p->pdc, PDC_E_MEM_OUT, fn, 0, 0, 0); } /* due to alignment bug on itanium machines: ** use well aligned local jbuf instead of sometimes ** bad aligned (allocated) jmp_buf. */ #ifdef PDF_ALIGN16 if (setjmp(jbuf)) #else if (setjmp(png_jmpbuf(image->info.png.png_ptr))) #endif { errcode = PDF_E_IMAGE_CORRUPT; goto PDF_PNG_ERROR; } #ifdef PDF_ALIGN16 memcpy(png_jmpbuf(image->info.png.png_ptr), jbuf, sizeof (jmp_buf)); #endif if (pdf_is_PNG_file(p, image->fp) == pdc_false) { errcode = PDC_E_IO_BADFORMAT; goto PDF_PNG_ERROR; } /* from file or from memory */ png_set_read_fn(image->info.png.png_ptr, image->fp, (png_rw_ptr)pdf_png_read_data); /* CDPDF - added type cast */ png_set_sig_bytes(image->info.png.png_ptr, 8); png_read_info(image->info.png.png_ptr, image->info.png.info_ptr); png_get_IHDR(image->info.png.png_ptr, image->info.png.info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); image->width = (pdc_scalar) width; image->height = (pdc_scalar) height; /* reduce 16-bit images to 8 bit since PDF < 1.5 stops at 8 bit */ if (p->compatibility < PDC_1_5 && bit_depth == 16) { png_set_strip_16(image->info.png.png_ptr); bit_depth = 8; } image->bpc = bit_depth; /* * We currently don't support a real alpha channel but only binary * tranparency ("poor man's alpha"). For palette images we do our best and * treat alpha values of up to 50% as transparent, and values above 50% * as opaque. Gray and RGB images with an associated alpha channel will * be pre-multiplied by libpng (against white background). */ #define ALPHA_THRESHOLD 128 switch (color_type) { case PNG_COLOR_TYPE_GRAY_ALPHA: /* LATER: construct mask from alpha channel */ /* png_set_IHDR(image->info.png.png_ptr, image->info.png.info_ptr, width, height, bit_depth, PNG_COLOR_MASK_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); */ /* * We strip the alpha channel, and let libpng pre-multiply * the opacity values to the image data. */ png_set_strip_alpha(image->info.png.png_ptr); /* fall through */ case PNG_COLOR_TYPE_GRAY: if (png_get_sBIT(image->info.png.png_ptr, image->info.png.info_ptr, &sig_bit)) { png_set_shift(image->info.png.png_ptr, sig_bit); } image->colorspace = DeviceGray; image->components = 1; break; case PNG_COLOR_TYPE_RGB_ALPHA: /* LATER: construct mask from alpha channel */ /* * We strip the alpha channel, and let libpng pre-multiply * the opacity values to the image data. */ png_set_strip_alpha(image->info.png.png_ptr); /* fall through */ case PNG_COLOR_TYPE_RGB: if (image->colorspace == pdc_undef) image->colorspace = DeviceRGB; image->components = 3; break; case PNG_COLOR_TYPE_PALETTE: { png_colorp pcm; png_get_PLTE(image->info.png.png_ptr, image->info.png.info_ptr, &pcm, &cs.val.indexed.palette_size); colormap = (pdf_colormap *) pdc_malloc(p->pdc, sizeof(pdf_colormap), fn); /* This seems redundant, but the png_colorp structure may not * be packed on some platforms. */ for (i = 0; i < cs.val.indexed.palette_size; i++) { (*colormap)[i][0] = (pdc_byte) pcm[i].red; (*colormap)[i][1] = (pdc_byte) pcm[i].green; (*colormap)[i][2] = (pdc_byte) pcm[i].blue; } image->components = 1; /* This case should arguably be prohibited. However, we allow * it and take the palette indices 0 and 1 as the mask, * disregarding any color values in the palette. */ if (image->imagemask) { image->colorspace = DeviceGray; break; } cs.type = Indexed; cs.val.indexed.base = DeviceRGB; cs.val.indexed.colormap = colormap; cs.val.indexed.colormap_id = PDC_BAD_ID; slot = pdf_add_colorspace(p, &cs, pdc_false); image->colorspace = slot; } break; } if (colormap) pdc_free(p->pdc, colormap); if (image->imagemask) { if (image->components != 1) { errcode = PDF_E_IMAGE_BADMASK; goto PDF_PNG_ERROR; } if (p->compatibility <= PDC_1_3) { if (image->components != 1 || image->bpc != 1) { errcode = PDF_E_IMAGE_MASK1BIT13; goto PDF_PNG_ERROR; } } else if (image->bpc > 1) { /* images with more than one bit will be written as /SMask, * and don't require an /ImageMask entry. */ image->imagemask = pdc_false; } image->colorspace = DeviceGray; } /* we invert this flag later */ if (image->ignoremask) image->transparent = pdc_true; /* let libpng expand interlaced images */ (void) png_set_interlace_handling(image->info.png.png_ptr); /* read the physical dimensions chunk to find the resolution values */ dpi_x = (pdc_scalar) png_get_x_pixels_per_meter(image->info.png.png_ptr, image->info.png.info_ptr); dpi_y = (pdc_scalar) png_get_y_pixels_per_meter(image->info.png.png_ptr, image->info.png.info_ptr); if (dpi_x != 0 && dpi_y != 0) { /* absolute values */ image->dpi_x = dpi_x * PDC_INCH2METER; image->dpi_y = dpi_y * PDC_INCH2METER; } else { /* aspect ratio */ image->dpi_y = -png_get_pixel_aspect_ratio(image->info.png.png_ptr, image->info.png.info_ptr); if (image->dpi_y == 0) /* unknown */ image->dpi_x = 0; else image->dpi_x = -1.0; } /* read the transparency chunk */ if (png_get_valid(image->info.png.png_ptr, image->info.png.info_ptr, PNG_INFO_tRNS)) { png_get_tRNS(image->info.png.png_ptr, image->info.png.info_ptr, &trans, &num_trans, &trans_values); if (num_trans > 0) { if (color_type == PNG_COLOR_TYPE_GRAY) { image->transparent = !image->transparent; /* LATER: scale down 16-bit transparency values ? */ image->transval[0] = (pdc_byte) trans_values[0].gray; } else if (color_type == PNG_COLOR_TYPE_RGB) { image->transparent = !image->transparent; /* LATER: scale down 16-bit transparency values ? */ image->transval[0] = (pdc_byte) trans_values[0].red; image->transval[1] = (pdc_byte) trans_values[0].green; image->transval[2] = (pdc_byte) trans_values[0].blue; } else if (color_type == PNG_COLOR_TYPE_PALETTE) { /* we use the first transparent entry in the tRNS palette */ for (i = 0; i < num_trans; i++) { if ((pdc_byte) trans[i] < ALPHA_THRESHOLD) { image->transparent = !image->transparent; image->transval[0] = (pdc_byte) i; break; } } } } } png_read_update_info(image->info.png.png_ptr, image->info.png.info_ptr); image->info.png.rowbytes = png_get_rowbytes(image->info.png.png_ptr, image->info.png.info_ptr); image->info.png.raster = (pdc_byte *) pdc_calloc(p->pdc,image->info.png.rowbytes * height, fn); row_pointers = (png_bytep *) pdc_malloc(p->pdc, height * sizeof(png_bytep), fn); for (ui = 0; ui < height; ui++) { row_pointers[ui] = image->info.png.raster + ui * image->info.png.rowbytes; } /* try the simple way: */ save_fp = image->fp; image->src.init = pdf_data_source_PNG_init; image->src.fill = pdf_data_source_PNG_fill; image->src.terminate = pdf_data_source_PNG_terminate; image->src.private_data = (void *) image; image->fp = pdc_fsearch_fopen(p->pdc, image->filename, NULL, NULL, PDC_FILE_BINARY); if (image->fp != NULL && spng_init(p, image, &s_info) == SPNG_ERR_OK && image->use_raw) { pdc_fclose(save_fp); image->predictor = pred_png; image->compression = pdf_comp_flate; } else { if (image->fp != (pdc_file *) 0) pdc_fclose(image->fp); image->fp = save_fp; /* Provide a suitable background for images with alpha channel */ if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { png_color_16p image_background; if (png_get_bKGD(image->info.png.png_ptr, image->info.png.info_ptr, &image_background)) { png_set_background(image->info.png.png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); } else { png_color_16 my_white; if (bit_depth == 8) { my_white.red = 0xFF; my_white.green = 0xFF; my_white.blue = 0xFF; my_white.gray = 0xFF; } else { my_white.red = 0xFFFF; my_white.green = 0xFFFF; my_white.blue = 0xFFFF; my_white.gray = 0xFFFF; } png_set_background(image->info.png.png_ptr, &my_white, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); } } /* fetch the actual image data */ png_read_image(image->info.png.png_ptr, row_pointers); } image->in_use = pdc_true; /* mark slot as used */ pdf_put_image(p, imageslot, pdc_true, pdc_true); pdc_free(p->pdc, image->info.png.raster); if (row_pointers != NULL) pdc_free(p->pdc, row_pointers); png_destroy_read_struct(&image->info.png.png_ptr, &image->info.png.info_ptr, NULL); if (!image->corrupt) return imageslot; PDF_PNG_ERROR: { const char *stemp = NULL; if (errcode) { png_destroy_read_struct(&image->info.png.png_ptr, &image->info.png.info_ptr, NULL); stemp = pdf_get_image_filename(p, image); } switch (errcode) { case PDF_E_IMAGE_ICC: case PDF_E_IMAGE_ICC2: case PDF_E_IMAGE_MASK1BIT13: case PDF_E_IMAGE_BADMASK: pdc_set_errmsg(p->pdc, errcode, stemp, 0, 0, 0); break; case PDC_E_IO_BADFORMAT: pdc_set_errmsg(p->pdc, errcode, stemp, "PNG", 0, 0); break; case PDF_E_IMAGE_CORRUPT: pdc_set_errmsg(p->pdc, errcode, "PNG", stemp, 0, 0); break; case 0: /* error code and message already set */ break; } } return -1; } #else /* !HAVE_LIBPNG */ pdc_bool pdf_is_PNG_file(PDF *p, pdc_file *fp) { return pdc_false; } /* simple built-in PNG reader without libpng */ int pdf_process_PNG_data( PDF *p, int imageslot) { static const char fn[] = "pdf_process_PNG_data"; spng_info s_info; pdf_image *image; image = &p->images[imageslot]; image->src.init = pdf_data_source_PNG_init; image->src.fill = pdf_data_source_PNG_fill; image->src.terminate = pdf_data_source_PNG_terminate; image->src.private_data = (void *) image; if (spng_init(p, image, &s_info) == SPNG_ERR_OK && image->use_raw) { image->predictor = pred_png; image->compression = pdf_comp_flate; /* CDPDF - fixed function name */ image->width = (pdc_scalar) s_info.width; image->height = (pdc_scalar) s_info.height; image->bpc = s_info.bit_depth; image->components = 3; /* other types are rejected in spng_init() */ image->colorspace = (s_info.color_type == 0 ? DeviceGray : DeviceRGB); } else { pdc_set_errmsg(p->pdc, PDF_E_UNSUPP_IMAGE, "PNG", 0, 0, 0); return -1; } image->in_use = pdc_true; /* mark slot as used */ pdf_put_image(p, imageslot, pdc_true, pdc_true); return image->corrupt ? -1 : imageslot; } #endif /* !HAVE_LIBPNG */