/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. Copyright (C) 2002-2018 by Jin-Hwan Cho and Shunsaku Hirata, the dvipdfmx project team. Copyright (C) 1998, 1999 by Mark A. Wicks 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. */ /* * PNG SUPPORT * * All bitdepth supported. * Supported color types are: PALETTE, RGB, GRAY, RGB_ALPHA, GRAY_ALPHA. * Supported ancillary chunks: tRNS, cHRM, gAMA, (sRGB), (iCCP) * * cHRM support is not tested well. CalRGB/CalGray colorspace is used for * PNG images that have cHRM chunk. * */ #include "dpx-dpxconf.h" #include "dpx-error.h" #include "dpx-mem.h" #include "dpx-numbers.h" #include "dpx-pdfcolor.h" #include "dpx-pdfobj.h" #define PNG_DEBUG_STR "PNG" #define PNG_DEBUG 3 /* * Write, MNG, Progressive not required. */ #define PNG_NO_WRITE_SUPPORTED #define PNG_NO_MNG_FEATURES #define PNG_NO_PROGRESSIVE_READ #include "dpx-pngimage.h" #include #include #include #include #include #include #include "dpx-pdfximage.h" #define DPX_PNG_DEFAULT_GAMMA 2.2 #define PDF_TRANS_TYPE_NONE 0 #define PDF_TRANS_TYPE_BINARY 1 #define PDF_TRANS_TYPE_ALPHA 2 /* ColorSpace */ static pdf_obj *create_cspace_Indexed (png_structp png_ptr, png_infop info_ptr); /* CIE-Based: CalRGB/CalGray */ static pdf_obj *create_cspace_CalRGB (png_structp png_ptr, png_infop info_ptr); static pdf_obj *create_cspace_CalGray (png_structp png_ptr, png_infop info_ptr); static pdf_obj *make_param_Cal (png_byte color_type, double G, double xw, double yw, double xr, double yr, double xg, double yg, double xb, double yb); /* sRGB: * * We (and PDF) do not have direct sRGB support. The sRGB color space can be * precisely represented by ICC profile, but we use approximate CalRGB color * space. */ static pdf_obj *create_cspace_sRGB (png_structp png_ptr, png_infop info_ptr); static pdf_obj *get_rendering_intent (png_structp png_ptr, png_infop info_ptr); /* ICCBased: * * Not supported yet. * Must check if ICC profile is valid and can be imported to PDF. * There are few restrictions (should be applied to PNG too?) in ICC profile * support in PDF. Some information should be obtained from profile. */ static pdf_obj *create_cspace_ICCBased (png_structp png_ptr, png_infop info_ptr); /* Transparency */ static int check_transparency (png_structp png_ptr, png_infop info_ptr); /* Color-Key Mask */ static pdf_obj *create_ckey_mask (png_structp png_ptr, png_infop info_ptr); /* Soft Mask: * * create_soft_mask() is for PNG_COLOR_TYPE_PALLETE. * Images with alpha chunnel use strip_soft_mask(). * An object representing mask itself is returned. */ static pdf_obj *create_soft_mask (png_structp png_ptr, png_infop info_ptr, png_bytep image_data_ptr, png_uint_32 width, png_uint_32 height); static pdf_obj *strip_soft_mask (png_structp png_ptr, png_infop info_ptr, png_bytep image_data_ptr, png_uint_32p rowbytes_ptr, png_uint_32 width, png_uint_32 height); /* Read image body */ static void read_image_data (png_structp png_ptr, png_bytep dest_ptr, png_uint_32 height, png_uint_32 rowbytes); int check_for_png (rust_input_handle_t handle) { unsigned char sigbytes[8]; ttstub_input_seek(handle, 0, SEEK_SET); if (ttstub_input_read (handle, (char *) sigbytes, sizeof(sigbytes)) != sizeof(sigbytes) || (png_sig_cmp (sigbytes, 0, sizeof(sigbytes)))) return 0; else return 1; } static void _png_warning_callback(png_structp png_ptr, png_const_charp msg) { (void)png_ptr; (void)msg; /* Make compiler happy */ } static void _png_read (png_structp png_ptr, png_bytep outbytes, png_size_t n) { rust_input_handle_t handle = png_get_io_ptr (png_ptr); ssize_t r; r = ttstub_input_read (handle, (char *) outbytes, n); if (r < 0 || (size_t) r != n) _tt_abort ("error reading PNG"); } int png_include_image (pdf_ximage *ximage, rust_input_handle_t handle) { pdf_obj *stream; pdf_obj *stream_dict; pdf_obj *colorspace, *mask, *intent; png_bytep stream_data_ptr; int trans_type; ximage_info info; /* Libpng stuff */ png_structp png_ptr; png_infop png_info_ptr; png_byte bpc, color_type; png_uint_32 width, height, rowbytes; pdf_ximage_init_image_info(&info); stream = NULL; stream_dict = NULL; colorspace = mask = intent = NULL; ttstub_input_seek(handle, 0, SEEK_SET); png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, _png_warning_callback); if (png_ptr == NULL) { dpx_warning("%s: Creating Libpng read struct failed.", PNG_DEBUG_STR); return -1; } png_info_ptr = png_create_info_struct (png_ptr); if (png_info_ptr == NULL) { dpx_warning("%s: Creating Libpng info struct failed.", PNG_DEBUG_STR); png_destroy_read_struct(&png_ptr, NULL, NULL); return -1; } #if PNG_LIBPNG_VER >= 10603 /* ignore possibly incorrect CMF bytes */ png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); #endif /* Rust-backed IO */ png_set_read_fn (png_ptr, handle, _png_read); /* NOTE: could use png_set_sig_bytes() to tell libpng if we started at non-zero file offset */ /* Read PNG info-header and get some info. */ png_read_info(png_ptr, png_info_ptr); color_type = png_get_color_type (png_ptr, png_info_ptr); width = png_get_image_width (png_ptr, png_info_ptr); height = png_get_image_height(png_ptr, png_info_ptr); bpc = png_get_bit_depth (png_ptr, png_info_ptr); if (bpc > 8) { if (pdf_check_version(1, 5) < 0) { /* Ask libpng to convert down to 8-bpc. */ dpx_warning("%s: 16-bpc PNG requires PDF version 1.5.", PNG_DEBUG_STR); png_set_strip_16(png_ptr); bpc = 8; } } else if (bpc < 8) { /* Instruct libpng to scale each pixel color to a full byte while reading even though there's only 1/2/4 bits of color associated. */ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_expand_gray_1_2_4_to_8(png_ptr); else png_set_packing(png_ptr); bpc = 8; } /* Ask libpng to gamma-correct. * It is wrong to assume screen gamma value 2.2 but... * We do gamma correction here only when uncalibrated color space is used. */ if (!png_get_valid(png_ptr, png_info_ptr, PNG_INFO_iCCP) && !png_get_valid(png_ptr, png_info_ptr, PNG_INFO_sRGB) && !png_get_valid(png_ptr, png_info_ptr, PNG_INFO_cHRM) && png_get_valid(png_ptr, png_info_ptr, PNG_INFO_gAMA)) { double G = 1.0; png_get_gAMA (png_ptr, png_info_ptr, &G); png_set_gamma(png_ptr, 2.2, G); } trans_type = check_transparency(png_ptr, png_info_ptr); /* check_transparency() does not do updata_info() */ png_read_update_info(png_ptr, png_info_ptr); rowbytes = png_get_rowbytes(png_ptr, png_info_ptr); /* Values listed below will not be modified in the remaining process. */ info.width = width; info.height = height; info.bits_per_component = bpc; { png_uint_32 xppm = png_get_x_pixels_per_meter(png_ptr, png_info_ptr); png_uint_32 yppm = png_get_y_pixels_per_meter(png_ptr, png_info_ptr); if (xppm > 0) info.xdensity = 72.0 / 0.0254 / xppm; if (yppm > 0) info.ydensity = 72.0 / 0.0254 / yppm; } stream = pdf_new_stream (STREAM_COMPRESS); stream_dict = pdf_stream_dict(stream); stream_data_ptr = (png_bytep) NEW(rowbytes*height, png_byte); read_image_data(png_ptr, stream_data_ptr, height, rowbytes); /* Non-NULL intent means there is valid sRGB chunk. */ intent = get_rendering_intent(png_ptr, png_info_ptr); if (intent) pdf_add_dict(stream_dict, pdf_new_name("Intent"), intent); switch (color_type) { case PNG_COLOR_TYPE_PALETTE: colorspace = create_cspace_Indexed(png_ptr, png_info_ptr); switch (trans_type) { case PDF_TRANS_TYPE_BINARY: /* Color-key masking */ mask = create_ckey_mask(png_ptr, png_info_ptr); break; case PDF_TRANS_TYPE_ALPHA: /* Soft mask */ mask = create_soft_mask(png_ptr, png_info_ptr, stream_data_ptr, width, height); break; default: /* Nothing to be done here. * No tRNS chunk or image already composited with background color. */ break; } info.num_components = 1; break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_iCCP)) colorspace = create_cspace_ICCBased(png_ptr, png_info_ptr); else if (intent) { colorspace = create_cspace_sRGB(png_ptr, png_info_ptr); } else { colorspace = create_cspace_CalRGB(png_ptr, png_info_ptr); } if (!colorspace) colorspace = pdf_new_name("DeviceRGB"); switch (trans_type) { case PDF_TRANS_TYPE_BINARY: mask = create_ckey_mask(png_ptr, png_info_ptr); break; /* rowbytes changes 4 to 3 at here */ case PDF_TRANS_TYPE_ALPHA: mask = strip_soft_mask(png_ptr, png_info_ptr, stream_data_ptr, &rowbytes, width, height); break; default: mask = NULL; } info.num_components = 3; break; case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_iCCP)) colorspace = create_cspace_ICCBased(png_ptr, png_info_ptr); else if (intent) { colorspace = create_cspace_sRGB(png_ptr, png_info_ptr); } else { colorspace = create_cspace_CalGray(png_ptr, png_info_ptr); } if (!colorspace) colorspace = pdf_new_name("DeviceGray"); switch (trans_type) { case PDF_TRANS_TYPE_BINARY: mask = create_ckey_mask(png_ptr, png_info_ptr); break; case PDF_TRANS_TYPE_ALPHA: mask = strip_soft_mask(png_ptr, png_info_ptr, stream_data_ptr, &rowbytes, width, height); break; default: mask = NULL; } info.num_components = 1; break; default: dpx_warning("%s: Unknown PNG colortype %d.", PNG_DEBUG_STR, color_type); } pdf_add_dict(stream_dict, pdf_new_name("ColorSpace"), colorspace); pdf_add_stream(stream, stream_data_ptr, rowbytes*height); free(stream_data_ptr); if (mask) { if (trans_type == PDF_TRANS_TYPE_BINARY) pdf_add_dict(stream_dict, pdf_new_name("Mask"), mask); else if (trans_type == PDF_TRANS_TYPE_ALPHA) { if (info.bits_per_component >= 8 && info.width > 64) { pdf_stream_set_predictor(mask, 2, info.width, info.bits_per_component, 1); } pdf_add_dict(stream_dict, pdf_new_name("SMask"), pdf_ref_obj(mask)); pdf_release_obj(mask); } else { dpx_warning("%s: Unknown transparency type...???", PNG_DEBUG_STR); pdf_release_obj(mask); } } /* Finally read XMP Metadata * See, XMP Specification Part 3, Storage in Files * http://www.adobe.com/jp/devnet/xmp.html * * We require libpng version >= 1.6.14 since prior versions * of libpng had a bug that incorrectly treat the compression * flag of iTxt chunks. */ #if PNG_LIBPNG_VER >= 10614 if (pdf_check_version(1, 4) >= 0) { png_textp text_ptr; pdf_obj *XMP_stream, *XMP_stream_dict; int i, num_text; int have_XMP = 0; num_text = png_get_text(png_ptr, png_info_ptr, &text_ptr, NULL); for (i = 0; i < num_text; i++) { if (!memcmp(text_ptr[i].key, "XML:com.adobe.xmp", 17)) { /* XMP found */ if (text_ptr[i].compression != PNG_ITXT_COMPRESSION_NONE || text_ptr[i].itxt_length == 0) dpx_warning("%s: Invalid value(s) in iTXt chunk for XMP Metadata.", PNG_DEBUG_STR); else if (have_XMP) dpx_warning("%s: Multiple XMP Metadata. Don't know how to treat it.", PNG_DEBUG_STR); else { /* We compress XMP metadata for included images here. * It is not recommended to compress XMP metadata for PDF documents but * we compress XMP metadata for included images here to avoid confusing * application programs that only want PDF document global XMP metadata * and scan for that. */ XMP_stream = pdf_new_stream(STREAM_COMPRESS); XMP_stream_dict = pdf_stream_dict(XMP_stream); pdf_add_dict(XMP_stream_dict, pdf_new_name("Type"), pdf_new_name("Metadata")); pdf_add_dict(XMP_stream_dict, pdf_new_name("Subtype"), pdf_new_name("XML")); pdf_add_stream(XMP_stream, text_ptr[i].text, text_ptr[i].itxt_length); pdf_add_dict(stream_dict, pdf_new_name("Metadata"), pdf_ref_obj(XMP_stream)); pdf_release_obj(XMP_stream); have_XMP = 1; } } } } #endif /* PNG_LIBPNG_VER */ png_read_end(png_ptr, NULL); /* Cleanup */ if (png_info_ptr) png_destroy_info_struct(png_ptr, &png_info_ptr); if (png_ptr) png_destroy_read_struct(&png_ptr, NULL, NULL); if (color_type != PNG_COLOR_TYPE_PALETTE && info.bits_per_component >= 8 && info.height > 64) { pdf_stream_set_predictor(stream, 15, info.width, info.bits_per_component, info.num_components); } pdf_ximage_set_image(ximage, &info, stream); return 0; } /* * The returned value trans_type is the type of transparency to be used for * this image. Possible values are: * * PDF_TRANS_TYPE_NONE No Masking will be used/required. * PDF_TRANS_TYPE_BINARY Pixels are either fully opaque/fully transparent. * PDF_TRANS_TYPE_ALPHA Uses alpha channel, requies SMask.(PDF-1.4) * * check_transparency() must check the current setting of output PDF version * and must choose appropriate trans_type value according to PDF version of * current output PDF document. * * If the PDF version is less than 1.3, no transparency is supported for this * version of PDF, hence PDF_TRANS_TYPE_NONE must be returned. And when the PDF * version is equal to 1.3, possible retrun values are PDF_TRANS_TYPE_BINARY or * PDF_TRANS_TYPE_NONE. The latter case arises when PNG file uses alpha channel * explicitly (color type PNG_COLOR_TYPE_XXX_ALPHA), or the tRNS chunk for the * PNG_COLOR_TYPE_PALETTE image contains intermediate values of opacity. * * Finally, in the case of PDF version 1.4, all kind of translucent pixels can * be represented with Soft-Mask. */ static int check_transparency (png_structp png_ptr, png_infop info_ptr) { int trans_type; png_byte color_type; png_color_16p trans_values; png_bytep trans; int num_trans; color_type = png_get_color_type(png_ptr, info_ptr); /* * First we set trans_type to appropriate value for PNG image. */ if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { trans_type = PDF_TRANS_TYPE_ALPHA; } else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) && png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values)) { /* Have valid tRNS chunk. */ switch (color_type) { case PNG_COLOR_TYPE_PALETTE: /* Use color-key mask if possible. */ trans_type = PDF_TRANS_TYPE_BINARY; while (num_trans-- > 0) { if (trans[num_trans] != 0x00 && trans[num_trans] != 0xff) { /* This seems not binary transparency */ trans_type = PDF_TRANS_TYPE_ALPHA; break; } } break; case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_RGB: /* RGB or GRAY, single color specified by trans_values is transparent. */ trans_type = PDF_TRANS_TYPE_BINARY; break; default: /* Else tRNS silently ignored. */ trans_type = PDF_TRANS_TYPE_NONE; } } else { /* no transparency */ trans_type = PDF_TRANS_TYPE_NONE; } /* * Now we check PDF version. * We can convert alpha cahnnels to explicit mask via user supplied alpha- * threshold value. But I will not do that. */ if (( pdf_check_version(1, 3) < 0 && trans_type != PDF_TRANS_TYPE_NONE ) || ( pdf_check_version(1, 4) < 0 && trans_type == PDF_TRANS_TYPE_ALPHA )) { /* * No transparency supported but PNG uses transparency, or Soft-Mask * required but no support for it is available in this version of PDF. * We must do pre-composition of image with the background image here. But, * we cannot do that in general since dvipdfmx is not a rasterizer. What we * can do here is to composite image with a rectangle filled with the * background color. However, images are stored as an Image XObject which * can be referenced anywhere in the PDF document content. Hence, we cannot * know the correct background color at this time. So we will choose white * as background color, which is most probable color in our cases. * We ignore bKGD chunk. */ png_color_16 bg; bg.red = 255; bg.green = 255; bg.blue = 255; bg.gray = 255; bg.index = 0; png_set_background(png_ptr, &bg, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); dpx_warning("%s: Transparency will be ignored. (no support in PDF ver. < 1.3)", PNG_DEBUG_STR); if (pdf_check_version(1, 3) < 0) dpx_warning("%s: Please use -V 3 option to enable binary transparency support.", PNG_DEBUG_STR); if (pdf_check_version(1, 4) < 0) dpx_warning("%s: Please use -V 4 option to enable full alpha channel support.", PNG_DEBUG_STR); trans_type = PDF_TRANS_TYPE_NONE; } return trans_type; } /* * sRGB: * * If sRGB chunk is present, cHRM and gAMA chunk must be ignored. * */ static pdf_obj * get_rendering_intent (png_structp png_ptr, png_infop info_ptr) { pdf_obj *intent; int srgb_intent; if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB) && png_get_sRGB (png_ptr, info_ptr, &srgb_intent)) { switch (srgb_intent) { case PNG_sRGB_INTENT_SATURATION: intent = pdf_new_name("Saturation"); break; case PNG_sRGB_INTENT_PERCEPTUAL: intent = pdf_new_name("Perceptual"); break; case PNG_sRGB_INTENT_ABSOLUTE: intent = pdf_new_name("AbsoluteColorimetric"); break; case PNG_sRGB_INTENT_RELATIVE: intent = pdf_new_name("RelativeColorimetric"); break; default: dpx_warning("%s: Invalid value in PNG sRGB chunk: %d", PNG_DEBUG_STR, srgb_intent); intent = NULL; } } else intent = NULL; return intent; } /* Approximated sRGB */ static pdf_obj * create_cspace_sRGB (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorspace; pdf_obj *cal_param; png_byte color_type; color_type = png_get_color_type(png_ptr, info_ptr); /* Parameters taken from PNG spec. section 4.2.2.3. */ cal_param = make_param_Cal(color_type, 2.2, 0.3127, 0.329, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06); if (!cal_param) return NULL; colorspace = pdf_new_array(); switch (color_type) { case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: case PNG_COLOR_TYPE_PALETTE: pdf_add_array(colorspace, pdf_new_name("CalRGB")); break; case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: pdf_add_array(colorspace, pdf_new_name("CalGray")); break; } pdf_add_array(colorspace, cal_param); return colorspace; } static pdf_obj * create_cspace_ICCBased (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorspace; int csp_id, colortype; png_byte color_type; png_charp name; int compression_type; /* Manual page for libpng does not * clarify whether profile data is inflated by libpng. */ #if PNG_LIBPNG_VER_MINOR < 5 png_charp profile; #else png_bytep profile; #endif png_uint_32 proflen; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP) || !png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, &proflen)) return NULL; color_type = png_get_color_type(png_ptr, info_ptr); if (color_type & PNG_COLOR_MASK_COLOR) { colortype = PDF_COLORSPACE_TYPE_RGB; } else { colortype = PDF_COLORSPACE_TYPE_GRAY; } if (iccp_check_colorspace(colortype, profile, proflen) < 0) colorspace = NULL; else { csp_id = iccp_load_profile(name, profile, proflen); if (csp_id < 0) { colorspace = NULL; } else { colorspace = pdf_get_colorspace_reference(csp_id); } } /* Rendering intent ... */ return colorspace; } /* * gAMA, cHRM: * * If cHRM is present, we use CIE-Based color space. gAMA is also used here * if available. */ #define INVALID_CHRM_VALUE(xw,yw,xr,yr,xg,yg,xb,yb) ( \ (xw) <= 0.0 || (yw) < 1.0e-10 || \ (xr) < 0.0 || (yr) < 0.0 || (xg) < 0.0 || (yg) < 0.0 || \ (xb) < 0.0 || (yb) < 0.0) static pdf_obj * create_cspace_CalRGB (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorspace; pdf_obj *cal_param; double xw, yw, xr, yr, xg, yg, xb, yb; double G; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM) || !png_get_cHRM(png_ptr, info_ptr, &xw, &yw, &xr, &yr, &xg, &yg, &xb, &yb)) return NULL; if (xw <= 0.0 || yw < 1.0e-10 || xr < 0.0 || yr < 0.0 || xg < 0.0 || yg < 0.0 || xb < 0.0 || yb < 0.0) { dpx_warning("%s: Invalid cHRM chunk parameters found.", PNG_DEBUG_STR); return NULL; } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && png_get_gAMA (png_ptr, info_ptr, &G)) { if (G < 1.0e-2) { dpx_warning("%s: Unusual Gamma value: 1.0 / %g", PNG_DEBUG_STR, G); return NULL; } G = 1.0 / G; /* Gamma is inverted. */ } else { G = DPX_PNG_DEFAULT_GAMMA; } cal_param = make_param_Cal(PNG_COLOR_TYPE_RGB, G, xw, yw, xr, yr, xg, yg, xb, yb); if (!cal_param) return NULL; colorspace = pdf_new_array(); pdf_add_array(colorspace, pdf_new_name("CalRGB")); pdf_add_array(colorspace, cal_param); return colorspace; } static pdf_obj * create_cspace_CalGray (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorspace; pdf_obj *cal_param; double xw, yw, xr, yr, xg, yg, xb, yb; double G; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM) || !png_get_cHRM(png_ptr, info_ptr, &xw, &yw, &xr, &yr, &xg, &yg, &xb, &yb)) return NULL; if (xw <= 0.0 || yw < 1.0e-10 || xr < 0.0 || yr < 0.0 || xg < 0.0 || yg < 0.0 || xb < 0.0 || yb < 0.0) { dpx_warning("%s: Invalid cHRM chunk parameters found.", PNG_DEBUG_STR); return NULL; } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && png_get_gAMA (png_ptr, info_ptr, &G)) { if (G < 1.0e-2) { dpx_warning("%s: Unusual Gamma value: 1.0 / %g", PNG_DEBUG_STR, G); return NULL; } G = 1.0 / G; /* Gamma is inverted. */ } else { G = DPX_PNG_DEFAULT_GAMMA; } cal_param = make_param_Cal(PNG_COLOR_TYPE_GRAY, G, xw, yw, xr, yr, xg, yg, xb, yb); if (!cal_param) return NULL; colorspace = pdf_new_array(); pdf_add_array(colorspace, pdf_new_name("CalGray")); pdf_add_array(colorspace, cal_param); return colorspace; } static pdf_obj * make_param_Cal (png_byte color_type, double G, /* Gamma */ double xw, double yw, double xr, double yr, double xg, double yg, double xb, double yb) { pdf_obj *cal_param; pdf_obj *white_point, *matrix, *dev_gamma; double Xw, Yw, Zw; /* Yw = 1.0 */ double Xr, Xg, Xb, Yr, Yb, Yg, Zr, Zg, Zb; #ifndef ABS #define ABS(x) ((x) < 0 ? -(x) : (x)) #endif /* * TODO: Check validity * * Conversion found in * * com.sixlegs.image.png - Java package to read and display PNG images * Copyright (C) 1998, 1999, 2001 Chris Nokleberg * * http://www.sixlegs.com/software/png/ * */ { double zw, zr, zg, zb; double fr, fg, fb; double det; /* WhitePoint */ zw = 1 - (xw + yw); zr = 1 - (xr + yr); zg = 1 - (xg + yg); zb = 1 - (xb + yb); Xw = xw / yw; Yw = 1.0; Zw = zw / yw; /* Matrix */ det = xr * (yg * zb - zg * yb) - xg * (yr * zb - zr * yb) + xb * (yr * zg - zr * yg); if (ABS(det) < 1.0e-10) { dpx_warning("Non invertible matrix: Maybe invalid value(s) specified in cHRM chunk."); return NULL; } fr = (Xw * (yg * zb - zg * yb) - xg * (zb - Zw * yb) + xb * (zg - Zw * yg)) / det; fg = (xr * (zb - Zw * yb) - Xw * (yr * zb - zr * yb) + xb * (yr * Zw - zr)) / det; fb = (xr * (yg * Zw - zg) - xg * (yr * Zw - zr) + Xw * (yr * zg - zr * yg)) / det; Xr = fr * xr; Yr = fr * yr; Zr = fr * zr; Xg = fg * xg; Yg = fg * yg; Zg = fg * zg; Xb = fb * xb; Yb = fb * yb; Zb = fb * zb; } if (G < 1.0e-2) { dpx_warning("Unusual Gamma specified: 1.0 / %g", G); return NULL; } cal_param = pdf_new_dict(); /* White point is always required. */ white_point = pdf_new_array(); pdf_add_array(white_point, pdf_new_number(ROUND(Xw, 0.00001))); pdf_add_array(white_point, pdf_new_number(ROUND(Yw, 0.00001))); pdf_add_array(white_point, pdf_new_number(ROUND(Zw, 0.00001))); pdf_add_dict(cal_param, pdf_new_name("WhitePoint"), white_point); /* Matrix - default: Identity */ if (color_type & PNG_COLOR_MASK_COLOR) { if (G != 1.0) { dev_gamma = pdf_new_array(); pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001))); pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001))); pdf_add_array(dev_gamma, pdf_new_number(ROUND(G, 0.00001))); pdf_add_dict(cal_param, pdf_new_name("Gamma"), dev_gamma); } matrix = pdf_new_array(); pdf_add_array(matrix, pdf_new_number(ROUND(Xr, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Yr, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Zr, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Xg, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Yg, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Zg, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Xb, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Yb, 0.00001))); pdf_add_array(matrix, pdf_new_number(ROUND(Zb, 0.00001))); pdf_add_dict (cal_param, pdf_new_name("Matrix"), matrix); } else { /* Gray */ if (G != 1.0) pdf_add_dict(cal_param, pdf_new_name("Gamma"), pdf_new_number(ROUND(G, 0.00001))); } return cal_param; } /* * Set up Indexed ColorSpace for color-type PALETTE: * * PNG allows only RGB color for base color space. If gAMA and/or cHRM * chunk is available, we can use CalRGB color space instead of DeviceRGB * for base color space. * */ static pdf_obj * create_cspace_Indexed (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorspace; pdf_obj *base, *lookup; png_byte *data_ptr; png_colorp plte; int num_plte, i; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE) || !png_get_PLTE(png_ptr, info_ptr, &plte, &num_plte)) { dpx_warning("%s: PNG does not have valid PLTE chunk.", PNG_DEBUG_STR); return NULL; } /* Order is important. */ colorspace = pdf_new_array (); pdf_add_array(colorspace, pdf_new_name("Indexed")); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) base = create_cspace_ICCBased(png_ptr, info_ptr); else { if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) base = create_cspace_sRGB(png_ptr, info_ptr); else base = create_cspace_CalRGB(png_ptr, info_ptr); } if (!base) base = pdf_new_name("DeviceRGB"); pdf_add_array(colorspace, base); pdf_add_array(colorspace, pdf_new_number(num_plte-1)); data_ptr = NEW(num_plte*3, png_byte); for (i = 0; i < num_plte; i++) { data_ptr[3*i] = plte[i].red; data_ptr[3*i+1] = plte[i].green; data_ptr[3*i+2] = plte[i].blue; } lookup = pdf_new_string(data_ptr, num_plte*3); free(data_ptr); pdf_add_array(colorspace, lookup); return colorspace; } /* * Colorkey Mask: array * * [component_0_min component_0_max ... component_n_min component_n_max] * */ static pdf_obj * create_ckey_mask (png_structp png_ptr, png_infop info_ptr) { pdf_obj *colorkeys; png_byte color_type; png_bytep trans; int num_trans, i; png_color_16p colors; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) || !png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &colors)) { dpx_warning("%s: PNG does not have valid tRNS chunk!", PNG_DEBUG_STR); return NULL; } colorkeys = pdf_new_array(); color_type = png_get_color_type(png_ptr, info_ptr); switch (color_type) { case PNG_COLOR_TYPE_PALETTE: for (i = 0; i < num_trans; i++) { if (trans[i] == 0x00) { pdf_add_array(colorkeys, pdf_new_number(i)); pdf_add_array(colorkeys, pdf_new_number(i)); } else if (trans[i] != 0xff) { dpx_warning("%s: You found a bug in pngimage.c.", PNG_DEBUG_STR); } } break; case PNG_COLOR_TYPE_RGB: pdf_add_array(colorkeys, pdf_new_number(colors->red)); pdf_add_array(colorkeys, pdf_new_number(colors->red)); pdf_add_array(colorkeys, pdf_new_number(colors->green)); pdf_add_array(colorkeys, pdf_new_number(colors->green)); pdf_add_array(colorkeys, pdf_new_number(colors->blue)); pdf_add_array(colorkeys, pdf_new_number(colors->blue)); break; case PNG_COLOR_TYPE_GRAY: pdf_add_array(colorkeys, pdf_new_number(colors->gray)); pdf_add_array(colorkeys, pdf_new_number(colors->gray)); break; default: dpx_warning("%s: You found a bug in pngimage.c.", PNG_DEBUG_STR); pdf_release_obj(colorkeys); colorkeys = NULL; } return colorkeys; } /* * Soft-Mask: stream * * << * /Type /XObject * /Subtype /Image * /Width -int- * /Height -int- * /BitsPerComponent bpc * >> * stream .... endstream * * ColorSpace, Mask, SMask must be absent. ImageMask must be false or absent. */ static pdf_obj * create_soft_mask (png_structp png_ptr, png_infop info_ptr, png_bytep image_data_ptr, png_uint_32 width, png_uint_32 height) { pdf_obj *smask, *dict; png_bytep smask_data_ptr; png_bytep trans; int num_trans; png_uint_32 i; png_byte bpc, mask, shift; if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) || !png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL)) { dpx_warning("%s: PNG does not have valid tRNS chunk but tRNS is requested.", PNG_DEBUG_STR); return NULL; } bpc = png_get_bit_depth(png_ptr, info_ptr); mask = 0xff >> (8 - bpc); shift = 8 - bpc; smask = pdf_new_stream(STREAM_COMPRESS); dict = pdf_stream_dict(smask); smask_data_ptr = (png_bytep) NEW(width*height, png_byte); pdf_add_dict(dict, pdf_new_name("Type"), pdf_new_name("XObject")); pdf_add_dict(dict, pdf_new_name("Subtype"), pdf_new_name("Image")); pdf_add_dict(dict, pdf_new_name("Width"), pdf_new_number(width)); pdf_add_dict(dict, pdf_new_name("Height"), pdf_new_number(height)); pdf_add_dict(dict, pdf_new_name("ColorSpace"), pdf_new_name("DeviceGray")); pdf_add_dict(dict, pdf_new_name("BitsPerComponent"), pdf_new_number(8)); for (i = 0; i < width*height; i++) { /* data is packed for 1/2/4 bpc formats, msb first */ png_byte idx = (image_data_ptr[bpc * i / 8] >> (shift - bpc * i % 8)) & mask; smask_data_ptr[i] = (idx < num_trans) ? trans[idx] : 0xff; } pdf_add_stream(smask, (char *)smask_data_ptr, width*height); free(smask_data_ptr); return smask; } /* bitdepth is always 8 (16 is not supported) */ static pdf_obj * strip_soft_mask (png_structp png_ptr, png_infop info_ptr, /* next two values will be modified. */ png_bytep image_data_ptr, png_uint_32p rowbytes_ptr, png_uint_32 width, png_uint_32 height) { pdf_obj *smask, *dict; png_byte color_type, bpc; png_bytep smask_data_ptr; png_uint_32 i; color_type = png_get_color_type(png_ptr, info_ptr); bpc = png_get_bit_depth (png_ptr, info_ptr); if (color_type & PNG_COLOR_MASK_COLOR) { int bps = (bpc == 8) ? 4 : 8; if (*rowbytes_ptr != bps*width*sizeof(png_byte)) { /* Something wrong */ dpx_warning("%s: Inconsistent rowbytes value.", PNG_DEBUG_STR); return NULL; } } else { int bps = (bpc == 8) ? 2 : 4; if (*rowbytes_ptr != bps*width*sizeof(png_byte)) { /* Something wrong */ dpx_warning("%s: Inconsistent rowbytes value.", PNG_DEBUG_STR); return NULL; } } smask = pdf_new_stream(STREAM_COMPRESS); dict = pdf_stream_dict(smask); pdf_add_dict(dict, pdf_new_name("Type"), pdf_new_name("XObject")); pdf_add_dict(dict, pdf_new_name("Subtype"), pdf_new_name("Image")); pdf_add_dict(dict, pdf_new_name("Width"), pdf_new_number(width)); pdf_add_dict(dict, pdf_new_name("Height"), pdf_new_number(height)); pdf_add_dict(dict, pdf_new_name("ColorSpace"), pdf_new_name("DeviceGray")); pdf_add_dict(dict, pdf_new_name("BitsPerComponent"), pdf_new_number(bpc)); smask_data_ptr = (png_bytep) NEW((bpc/8)*width*height, png_byte); switch (color_type) { case PNG_COLOR_TYPE_RGB_ALPHA: if (bpc == 8) { for (i = 0; i < width*height; i++) { memmove(image_data_ptr+(3*i), image_data_ptr+(4*i), 3); smask_data_ptr[i] = image_data_ptr[4*i+3]; } *rowbytes_ptr = 3*width*sizeof(png_byte); } else { for (i = 0; i < width*height; i++) { memmove(image_data_ptr+(6*i), image_data_ptr+(8*i), 6); smask_data_ptr[2*i] = image_data_ptr[8*i+6]; smask_data_ptr[2*i+1] = image_data_ptr[8*i+7]; } *rowbytes_ptr = 6*width*sizeof(png_byte); } break; case PNG_COLOR_TYPE_GRAY_ALPHA: if (bpc == 8) { for (i = 0; i < width*height; i++) { image_data_ptr[i] = image_data_ptr[2*i]; smask_data_ptr[i] = image_data_ptr[2*i+1]; } *rowbytes_ptr = width*sizeof(png_byte); } else { for (i = 0; i < width*height; i++) { image_data_ptr[2*i] = image_data_ptr[4*i]; image_data_ptr[2*i+1] = image_data_ptr[4*i+1]; smask_data_ptr[2*i] = image_data_ptr[4*i+2]; smask_data_ptr[2*i+1] = image_data_ptr[4*i+3]; } *rowbytes_ptr = 2*width*sizeof(png_byte); } break; default: dpx_warning("You found a bug in pngimage.c!"); pdf_release_obj(smask); free(smask_data_ptr); return NULL; } pdf_add_stream(smask, smask_data_ptr, (bpc/8)*width*height); free(smask_data_ptr); return smask; } static void read_image_data (png_structp png_ptr, png_bytep dest_ptr, png_uint_32 height, png_uint_32 rowbytes) { png_bytepp rows_p; png_uint_32 i; rows_p = (png_bytepp) NEW (height, png_bytep); for (i=0; i< height; i++) rows_p[i] = dest_ptr + (rowbytes * i); png_read_image(png_ptr, rows_p); free(rows_p); } int png_get_bbox (rust_input_handle_t handle, uint32_t *width, uint32_t *height, double *xdensity, double *ydensity) { png_structp png_ptr; png_infop png_info_ptr; ttstub_input_seek(handle, 0, SEEK_SET); png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, _png_warning_callback); if (png_ptr == NULL || (png_info_ptr = png_create_info_struct (png_ptr)) == NULL) { dpx_warning("%s: Creating Libpng read/info struct failed.", PNG_DEBUG_STR); if (png_ptr) png_destroy_read_struct(&png_ptr, NULL, NULL); return -1; } /* Rust-backed IO */ png_set_read_fn (png_ptr, handle, _png_read); /* NOTE: could use png_set_sig_bytes() to tell libpng if we started at non-zero file offset */ /* Read PNG info-header and get some info. */ png_read_info(png_ptr, png_info_ptr); *width = png_get_image_width (png_ptr, png_info_ptr); *height = png_get_image_height(png_ptr, png_info_ptr); { png_uint_32 xppm = png_get_x_pixels_per_meter(png_ptr, png_info_ptr); png_uint_32 yppm = png_get_y_pixels_per_meter(png_ptr, png_info_ptr); *xdensity = xppm ? 72.0 / 0.0254 / xppm : 1.0; *ydensity = yppm ? 72.0 / 0.0254 / yppm : 1.0; } /* Cleanup */ if (png_info_ptr) png_destroy_info_struct(png_ptr, &png_info_ptr); if (png_ptr) png_destroy_read_struct(&png_ptr, NULL, NULL); return 0; }