int dt_imageio_png_read_profile(const char *filename, uint8_t **out) { dt_imageio_png_t image; png_charp name; int compression_type; png_uint_32 proflen; #if PNG_LIBPNG_VER >= 10500 /* 1.5.0 */ png_bytep profile; #else png_charp profile; #endif if(!(filename && *filename && out)) return 0; if(read_header(filename, &image) != 0) return DT_IMAGEIO_FILE_CORRUPTED; #ifdef PNG_iCCP_SUPPORTED if(png_get_valid(image.png_ptr, image.info_ptr, PNG_INFO_iCCP) != 0 && png_get_iCCP(image.png_ptr, image.info_ptr, &name, &compression_type, &profile, &proflen) != 0) { *out = (uint8_t *)malloc(proflen); memcpy(*out, profile, proflen); } else #endif proflen = 0; png_destroy_read_struct(&image.png_ptr, &image.info_ptr, NULL); fclose(image.f); return proflen; }
static void getColorProfile(png_structp png, png_infop info, ColorProfile& colorProfile, bool& sRGB) { #ifdef PNG_iCCP_SUPPORTED ASSERT(colorProfile.isEmpty()); if (png_get_valid(png, info, PNG_INFO_sRGB)) { sRGB = true; return; } char* profileName; int compressionType; #if (PNG_LIBPNG_VER < 10500) png_charp profile; #else png_bytep profile; #endif png_uint_32 profileLength; if (!png_get_iCCP(png, info, &profileName, &compressionType, &profile, &profileLength)) return; // Only accept RGB color profiles from input class devices. bool ignoreProfile = false; char* profileData = reinterpret_cast<char*>(profile); if (profileLength < ImageDecoder::iccColorProfileHeaderLength) ignoreProfile = true; else if (!ImageDecoder::rgbColorProfile(profileData, profileLength)) ignoreProfile = true; else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileLength)) ignoreProfile = true; if (!ignoreProfile) colorProfile.append(profileData, profileLength); #endif }
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; #if 0 alternate = create_cspace_CalRGB(png_ptr, info_ptr); #endif } else { colortype = PDF_COLORSPACE_TYPE_GRAY; #if 0 alternate = create_cspace_CalGray(png_ptr, info_ptr); #endif } #if 0 if (alternate) pdf_add_dict(dict, pdf_new_name("Alternate"), alternate); #endif 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; }
cmsHPROFILE loadFromPngData(const QByteArray& data) { QBuffer buffer; buffer.setBuffer(const_cast<QByteArray*>(&data)); buffer.open(QIODevice::ReadOnly); // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); GV_RETURN_VALUE_IF_FAIL(png_ptr, 0); png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); kWarning() << "Could not create info_struct"; return 0; } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); kWarning() << "Could not create info_struct2"; return 0; } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); kWarning() << "Error decoding png file"; return 0; } // Initialize the special png_set_read_fn(png_ptr, &buffer, readPngChunk); // read all PNG info up to image data png_read_info(png_ptr, info_ptr); // Get profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; cmsHPROFILE profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { profile = cmsOpenProfileFromMem(profile_data, proflen); } png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return profile; }
static png_bytep extract(FILE *fp, png_uint_32 *proflen) { png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0); png_infop info_ptr = NULL; png_bytep result = NULL; /* Initialize for error or no profile: */ *proflen = 0; if (png_ptr == NULL) { fprintf(stderr, "iccfrompng: version library mismatch?\n"); return 0; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return 0; } png_init_io(png_ptr, fp); info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) png_error(png_ptr, "OOM allocating info structure"); png_read_info(png_ptr, info_ptr); { png_charp name; int compression_type; png_bytep profile; if (png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, proflen) & PNG_INFO_iCCP) { result = malloc(*proflen); if (result != NULL) memcpy(result, profile, *proflen); else png_error(png_ptr, "OOM allocating profile buffer"); } else result = no_profile; } png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return result; }
static ColorProfile readColorProfile(png_structp png, png_infop info) { #ifdef PNG_iCCP_SUPPORTED char* profileName; int compressionType; char* profile; png_uint_32 profileLength; if (png_get_iCCP(png, info, &profileName, &compressionType, &profile, &profileLength)) { ColorProfile colorProfile; colorProfile.append(profile, profileLength); return colorProfile; } #endif return ColorProfile(); }
// Get the chunk information of the PNG file. IDAT is marked as existing, only after decoding or reading the header. // Bits (if set indicates existence of the chunk): // 0 - gAMA // 1 - sBIT // 2 - cHRM // 3 - PLTE // 4 - tRNS // 5 - bKGD // 6 - hIST // 7 - pHYs // 8 - oFFs // 9 - tIME // 10 - pCAL // 11 - sRGB // 12 - iCCP // 13 - sPLT // 14 - sCAL // 15 - IDAT // 16:30 - reserved be_t<u32> pngDecGetChunkInformation(PStream stream, bool IDAT = false) { // The end result of the chunk information (bigger-endian) be_t<u32> chunk_information = 0; // Needed pointers for getting the chunk information f64 gamma; f64 red_x; f64 red_y; f64 green_x; f64 green_y; f64 blue_x; f64 blue_y; f64 white_x; f64 white_y; f64 width; f64 height; s32 intent; s32 num_trans; s32 num_palette; s32 unit_type; s32 type; s32 nparams; s32 compression_type; s32 unit; u16* hist; u32 proflen; png_bytep profile; png_bytep trans_alpha; png_charp units; png_charp name; png_charp purpose; png_charpp params; png_int_32 X0; png_int_32 X1; png_int_32 offset_x; png_int_32 offset_y; png_uint_32 res_x; png_uint_32 res_y; png_colorp palette; png_color_8p sig_bit; png_color_16p background; png_color_16p trans_color; png_sPLT_tp entries; png_timep mod_time; // Get chunk information and set the appropriate bits if (png_get_gAMA(stream->png_ptr, stream->info_ptr, &gamma)) { chunk_information |= 1 << 0; // gAMA } if (png_get_sBIT(stream->png_ptr, stream->info_ptr, &sig_bit)) { chunk_information |= 1 << 1; // sBIT } if (png_get_cHRM(stream->png_ptr, stream->info_ptr, &white_x, &white_y, &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y)) { chunk_information |= 1 << 2; // cHRM } if (png_get_PLTE(stream->png_ptr, stream->info_ptr, &palette, &num_palette)) { chunk_information |= 1 << 3; // PLTE } if (png_get_tRNS(stream->png_ptr, stream->info_ptr, &trans_alpha, &num_trans, &trans_color)) { chunk_information |= 1 << 4; // tRNS } if (png_get_bKGD(stream->png_ptr, stream->info_ptr, &background)) { chunk_information |= 1 << 5; // bKGD } if (png_get_hIST(stream->png_ptr, stream->info_ptr, &hist)) { chunk_information |= 1 << 6; // hIST } if (png_get_pHYs(stream->png_ptr, stream->info_ptr, &res_x, &res_y, &unit_type)) { chunk_information |= 1 << 7; // pHYs } if (png_get_oFFs(stream->png_ptr, stream->info_ptr, &offset_x, &offset_y, &unit_type)) { chunk_information |= 1 << 8; // oFFs } if (png_get_tIME(stream->png_ptr, stream->info_ptr, &mod_time)) { chunk_information |= 1 << 9; // tIME } if (png_get_pCAL(stream->png_ptr, stream->info_ptr, &purpose, &X0, &X1, &type, &nparams, &units, ¶ms)) { chunk_information |= 1 << 10; // pCAL } if (png_get_sRGB(stream->png_ptr, stream->info_ptr, &intent)) { chunk_information |= 1 << 11; // sRGB } if (png_get_iCCP(stream->png_ptr, stream->info_ptr, &name, &compression_type, &profile, &proflen)) { chunk_information |= 1 << 12; // iCCP } if (png_get_sPLT(stream->png_ptr, stream->info_ptr, &entries)) { chunk_information |= 1 << 13; // sPLT } if (png_get_sCAL(stream->png_ptr, stream->info_ptr, &unit, &width, &height)) { chunk_information |= 1 << 14; // sCAL } if (IDAT) { chunk_information |= 1 << 15; // IDAT } return chunk_information; }
static void _png_load_bmp_attribute(png_structp png_ptr, png_infop info_ptr, CFX_DIBAttribute* pAttribute) { if (pAttribute) { #if defined(PNG_pHYs_SUPPORTED) pAttribute->m_nXDPI = png_get_x_pixels_per_meter(png_ptr, info_ptr); pAttribute->m_nYDPI = png_get_y_pixels_per_meter(png_ptr, info_ptr); png_uint_32 res_x, res_y; int unit_type; png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type); switch (unit_type) { case PNG_RESOLUTION_METER: pAttribute->m_wDPIUnit = FXCODEC_RESUNIT_METER; break; default: pAttribute->m_wDPIUnit = FXCODEC_RESUNIT_NONE; } #endif #if defined(PNG_iCCP_SUPPORTED) png_charp icc_name; png_bytep icc_profile; png_uint_32 icc_proflen; int compress_type; png_get_iCCP(png_ptr, info_ptr, &icc_name, &compress_type, &icc_profile, &icc_proflen); #endif int bTime = 0; #if defined(PNG_tIME_SUPPORTED) png_timep t = nullptr; png_get_tIME(png_ptr, info_ptr, &t); if (t) { FXSYS_memset(pAttribute->m_strTime, 0, sizeof(pAttribute->m_strTime)); FXSYS_snprintf((FX_CHAR*)pAttribute->m_strTime, sizeof(pAttribute->m_strTime), "%4u:%2u:%2u %2u:%2u:%2u", t->year, t->month, t->day, t->hour, t->minute, t->second); pAttribute->m_strTime[sizeof(pAttribute->m_strTime) - 1] = 0; bTime = 1; } #endif #if defined(PNG_TEXT_SUPPORTED) int i; FX_STRSIZE len; const FX_CHAR* buf; int num_text; png_textp text = nullptr; png_get_text(png_ptr, info_ptr, &text, &num_text); for (i = 0; i < num_text; i++) { len = FXSYS_strlen(text[i].key); buf = "Time"; if (!FXSYS_memcmp(buf, text[i].key, std::min(len, FXSYS_strlen(buf)))) { if (!bTime) { FXSYS_memset(pAttribute->m_strTime, 0, sizeof(pAttribute->m_strTime)); FXSYS_memcpy( pAttribute->m_strTime, text[i].text, std::min(sizeof(pAttribute->m_strTime) - 1, text[i].text_length)); } } else { buf = "Author"; if (!FXSYS_memcmp(buf, text[i].key, std::min(len, FXSYS_strlen(buf)))) { pAttribute->m_strAuthor = CFX_ByteString(reinterpret_cast<uint8_t*>(text[i].text), static_cast<FX_STRSIZE>(text[i].text_length)); } } } #endif } }
static GpStatus gdip_load_png_properties (png_structp png_ptr, png_infop info_ptr, png_infop end_ptr, BitmapData *bitmap_data) { #if defined(PNG_INCH_CONVERSIONS) && defined(PNG_FLOATING_POINT_SUPPORTED) bitmap_data->image_flags |= ImageFlagsHasRealDPI; bitmap_data->dpi_horz = png_get_x_pixels_per_inch(png_ptr, info_ptr); bitmap_data->dpi_vert = png_get_y_pixels_per_inch(png_ptr, info_ptr); #elif defined(PNG_pHYs_SUPPORTED) if ((info_ptr->valid & PNG_INFO_pHYs) && (info_ptr->phys_unit_type == PNG_RESOLUTION_METER)) { bitmap_data->image_flags |= ImageFlagsHasRealDPI; bitmap_data->dpi_horz = info_ptr->x_pixels_per_unit * 0.0254; bitmap_data->dpi_vert = info_ptr->y_pixels_per_unit * 0.0254; } #endif /* default to screen resolution (if nothing was provided or available) */ if (bitmap_data->dpi_horz == 0 || bitmap_data->dpi_vert == 0) { bitmap_data->dpi_horz = bitmap_data->dpi_vert = gdip_get_display_dpi (); } #if defined(PNG_iCCP_SUPPORTED) { png_charp name; png_charp profile; png_uint_32 proflen; int compression_type; if (png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, &proflen)) { gdip_bitmapdata_property_add_ASCII(bitmap_data, PropertyTagICCProfileDescriptor, (BYTE*)name); gdip_bitmapdata_property_add_byte(bitmap_data, PropertyTagICCProfile, (BYTE)compression_type); } } #endif #if defined(PNG_gAMA_SUPPORTED) { double gamma; if (png_get_gAMA(png_ptr, info_ptr, &gamma)) { gdip_bitmapdata_property_add_rational(bitmap_data, PropertyTagGamma, 100000, gamma * 100000); } } #endif #if defined(PNG_cHRM_SUPPORTED) { double white_x; double white_y; double red_x; double red_y; double green_x; double green_y; double blue_x; double blue_y; if (png_get_cHRM(png_ptr, info_ptr, &white_x, &white_y, &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y)) { BYTE *buffer; guint32 *ptr; buffer = GdipAlloc(6 * (sizeof(png_uint_32) + sizeof(png_uint_32))); if (buffer != NULL) { ptr = (guint32 *)buffer; ptr[0] = (guint32)(red_x * 100000); ptr[1] = 1000000; ptr[2] = (guint32)(red_y * 100000); ptr[3] = 100000; ptr[4] = (guint32)(green_x * 100000); ptr[5] = 1000000; ptr[6] = (guint32)(green_y * 100000); ptr[7] = 100000; ptr[8] = (guint32)(blue_x * 100000); ptr[9] = 100000; ptr[10] = (guint32)(blue_y * 100000); ptr[11] = 100000; gdip_bitmapdata_property_add (bitmap_data, PropertyTagPrimaryChromaticities, 6 * (sizeof(guint32) + sizeof(guint32)), PropertyTagTypeRational, buffer); ptr[0] = (guint32)(white_x * 100000); ptr[1] = 1000000; ptr[2] = (guint32)(white_y * 100000); ptr[3] = 100000; gdip_bitmapdata_property_add (bitmap_data, PropertyTagWhitePoint, 2 * (sizeof(guint32) + sizeof(guint32)), PropertyTagTypeRational, buffer); GdipFree(buffer); } } } #endif #if defined(PNG_pHYs_SUPPORTED) { int unit_type; png_uint_32 res_x; png_uint_32 res_y; if (png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type)) { gdip_bitmapdata_property_add_byte(bitmap_data, PropertyTagPixelUnit, (BYTE)unit_type); gdip_bitmapdata_property_add_long(bitmap_data, PropertyTagPixelPerUnitX, res_x); gdip_bitmapdata_property_add_long(bitmap_data, PropertyTagPixelPerUnitY, res_y); } } #endif #if defined(PNG_TEXT_SUPPORTED) { int num_text; png_textp text_ptr; if (png_get_text(png_ptr, info_ptr, &text_ptr, &num_text)) { if (num_text > 0) { gdip_bitmapdata_property_add_ASCII(bitmap_data, PropertyTagExifUserComment, (BYTE*)text_ptr[0].text); } } } #endif return Ok; }
// Looks for metadata at both the beginning and end of the PNG file, giving // preference to the head. // Returns true on success. The caller must use MetadataFree() on 'metadata' in // all cases. static int ExtractMetadataFromPNG(png_structp png, png_infop const head_info, png_infop const end_info, Metadata* const metadata) { int p; for (p = 0; p < 2; ++p) { png_infop const info = (p == 0) ? head_info : end_info; png_textp text = NULL; const png_uint_32 num = png_get_text(png, info, &text, NULL); png_uint_32 i; // Look for EXIF / XMP metadata. for (i = 0; i < num; ++i, ++text) { int j; for (j = 0; kPNGMetadataMap[j].name != NULL; ++j) { if (!strcmp(text->key, kPNGMetadataMap[j].name)) { MetadataPayload* const payload = (MetadataPayload*)((uint8_t*)metadata + kPNGMetadataMap[j].storage_offset); png_size_t text_length; switch (text->compression) { #ifdef PNG_iTXt_SUPPORTED case PNG_ITXT_COMPRESSION_NONE: case PNG_ITXT_COMPRESSION_zTXt: text_length = text->itxt_length; break; #endif case PNG_TEXT_COMPRESSION_NONE: case PNG_TEXT_COMPRESSION_zTXt: default: text_length = text->text_length; break; } if (payload->bytes != NULL) { fprintf(stderr, "Ignoring additional '%s'\n", text->key); } else if (!kPNGMetadataMap[j].process(text->text, text_length, payload)) { fprintf(stderr, "Failed to process: '%s'\n", text->key); return 0; } break; } } } // Look for an ICC profile. { png_charp name; int comp_type; #if LOCAL_PNG_PREREQ(1,5) png_bytep profile; #else png_charp profile; #endif png_uint_32 len; if (png_get_iCCP(png, info, &name, &comp_type, &profile, &len) == PNG_INFO_iCCP) { if (!MetadataCopy((const char*)profile, len, &metadata->iccp)) return 0; } } } return 1; }
PyObject * load_png_fast_progressive (char *filename, PyObject *get_buffer_callback) { // Note: we are not using the method that libpng calls "Reading PNG // files progressively". That method would involve feeding the data // into libpng piece by piece, which is not necessary if we can give // libpng a simple FILE pointer. png_structp png_ptr = NULL; png_infop info_ptr = NULL; PyObject * result = NULL; FILE *fp = NULL; uint32_t width, height; uint32_t rows_left; png_byte color_type; png_byte bit_depth; bool have_alpha; char *cm_processing = NULL; // ICC profile-based colour conversion data. png_charp icc_profile_name = NULL; int icc_compression_type = 0; #if PNG_LIBPNG_VER < 10500 // 1.5.0beta36, according to libpng CHANGES png_charp icc_profile = NULL; #else png_bytep icc_profile = NULL; #endif png_uint_32 icc_proflen = 0; // The sRGB flag has an intent field, which we ignore - // the target gamut is sRGB already. int srgb_intent = 0; // Generic RGB space conversion params. // The assumptions we're making are those of sRGB, // but they'll be overridden by gammas or primaries in the file if used. bool generic_rgb_have_gAMA = false; bool generic_rgb_have_cHRM = false; double generic_rgb_file_gamma = 45455 / PNG_gAMA_scale; double generic_rgb_white_x = 31270 / PNG_cHRM_scale; double generic_rgb_white_y = 32900 / PNG_cHRM_scale; double generic_rgb_red_x = 64000 / PNG_cHRM_scale; double generic_rgb_red_y = 33000 / PNG_cHRM_scale; double generic_rgb_green_x = 30000 / PNG_cHRM_scale; double generic_rgb_green_y = 60000 / PNG_cHRM_scale; double generic_rgb_blue_x = 15000 / PNG_cHRM_scale; double generic_rgb_blue_y = 6000 / PNG_cHRM_scale; // Indicates the case where no CM information was present in the file and we // treated it as sRGB. bool possible_legacy_png = false; // LCMS stuff cmsHPROFILE input_buffer_profile = NULL; cmsHPROFILE nparray_data_profile = cmsCreate_sRGBProfile(); cmsHTRANSFORM input_buffer_to_nparray = NULL; cmsToneCurve *gamma_transfer_func = NULL; cmsUInt32Number input_buffer_format = 0; cmsSetLogErrorHandler(log_lcms2_error); fp = fopen(filename, "rb"); if (!fp) { PyErr_SetFromErrno(PyExc_IOError); //PyErr_Format(PyExc_IOError, "Could not open PNG file for writing: %s", // filename); goto cleanup; } png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, (png_voidp)NULL, png_read_error_callback, NULL); if (!png_ptr) { PyErr_SetString(PyExc_MemoryError, "png_create_write_struct() failed"); goto cleanup; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { PyErr_SetString(PyExc_MemoryError, "png_create_info_struct() failed"); goto cleanup; } if (setjmp(png_jmpbuf(png_ptr))) { goto cleanup; } png_init_io(png_ptr, fp); png_read_info(png_ptr, info_ptr); // If there's an embedded ICC profile, use it in preference to any other // colour management information present. if (png_get_iCCP (png_ptr, info_ptr, &icc_profile_name, &icc_compression_type, &icc_profile, &icc_proflen)) { input_buffer_profile = cmsOpenProfileFromMem(icc_profile, icc_proflen); if (! input_buffer_profile) { PyErr_SetString(PyExc_MemoryError, "cmsOpenProfileFromMem() failed"); goto cleanup; } cm_processing = "iCCP (use embedded colour profile)"; } // Shorthand for sRGB. else if (png_get_sRGB (png_ptr, info_ptr, &srgb_intent)) { input_buffer_profile = cmsCreate_sRGBProfile(); cm_processing = "sRGB (explicit sRGB chunk)"; } else { // We might have generic RGB transformation information in the form of // the chromaticities for R, G and B and a generic gamma curve. if (png_get_cHRM (png_ptr, info_ptr, &generic_rgb_white_x, &generic_rgb_white_y, &generic_rgb_red_x, &generic_rgb_red_y, &generic_rgb_green_x, &generic_rgb_green_y, &generic_rgb_blue_x, &generic_rgb_blue_y)) { generic_rgb_have_cHRM = true; } if (png_get_gAMA(png_ptr, info_ptr, &generic_rgb_file_gamma)) { generic_rgb_have_gAMA = true; } if (generic_rgb_have_gAMA || generic_rgb_have_cHRM) { cmsCIExyYTRIPLE primaries = {{generic_rgb_red_x, generic_rgb_red_y}, {generic_rgb_green_x, generic_rgb_green_y}, {generic_rgb_blue_x, generic_rgb_blue_y}}; cmsCIExyY white_point = {generic_rgb_white_x, generic_rgb_white_y}; gamma_transfer_func = cmsBuildGamma(NULL, 1.0/generic_rgb_file_gamma); cmsToneCurve *transfer_funcs[3] = {gamma_transfer_func, gamma_transfer_func, gamma_transfer_func }; input_buffer_profile = cmsCreateRGBProfile(&white_point, &primaries, transfer_funcs); cm_processing = "cHRM and/or gAMA (generic RGB space)"; } // Possible legacy PNG, or rather one which might have been written with an // old version of MyPaint. Treat as sRGB, but flag the strangeness because // it might be important for PNGs in old OpenRaster files. else { possible_legacy_png = true; input_buffer_profile = cmsCreate_sRGBProfile(); cm_processing = "sRGB (no CM chunks present)"; } } if (png_get_interlace_type (png_ptr, info_ptr) != PNG_INTERLACE_NONE) { PyErr_SetString(PyExc_RuntimeError, "Interlaced PNG files are not supported!"); goto cleanup; } color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); have_alpha = color_type & PNG_COLOR_MASK_ALPHA; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { png_set_expand_gray_1_2_4_to_8(png_ptr); } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); have_alpha = true; } if (bit_depth < 8) { png_set_packing(png_ptr); } if (!have_alpha) { png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Verify what we have done bit_depth = png_get_bit_depth(png_ptr, info_ptr); if (! (bit_depth == 8 || bit_depth == 16)) { PyErr_SetString(PyExc_RuntimeError, "Failed to convince libpng to convert " "to 8 or 16 bits per channel"); goto cleanup; } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA) { PyErr_SetString(PyExc_RuntimeError, "Failed to convince libpng to convert " "to RGBA (wrong color_type)"); goto cleanup; } if (png_get_channels(png_ptr, info_ptr) != 4) { PyErr_SetString(PyExc_RuntimeError, "Failed to convince libpng to convert " "to RGBA (wrong number of channels)"); goto cleanup; } // PNGs use network byte order, i.e. big-endian in descending order // of bit significance. LittleCMS uses whatever's detected for the compiler. // ref: http://www.w3.org/TR/2003/REC-PNG-20031110/#7Integers-and-byte-order if (bit_depth == 16) { #ifdef CMS_USE_BIG_ENDIAN input_buffer_format = TYPE_RGBA_16; #else input_buffer_format = TYPE_RGBA_16_SE; #endif } else { input_buffer_format = TYPE_RGBA_8; } input_buffer_to_nparray = cmsCreateTransform (input_buffer_profile, input_buffer_format, nparray_data_profile, TYPE_RGBA_8, INTENT_PERCEPTUAL, 0); width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); rows_left = height; while (rows_left) { PyObject *pyarr = NULL; uint32_t rows = 0; uint32_t row = 0; const uint8_t input_buf_bytes_per_pixel = (bit_depth==8) ? 4 : 8; const uint32_t input_buf_row_stride = sizeof(png_byte) * width * input_buf_bytes_per_pixel; png_byte *input_buffer = NULL; png_bytep *input_buf_row_pointers = NULL; pyarr = PyObject_CallFunction(get_buffer_callback, "ii", width, height); if (! pyarr) { PyErr_Format(PyExc_RuntimeError, "Get-buffer callback failed"); goto cleanup; } #ifdef HEAVY_DEBUG //assert(PyArray_ISCARRAY(arr)); assert(PyArray_NDIM(pyarr) == 3); assert(PyArray_DIM(pyarr, 1) == width); assert(PyArray_DIM(pyarr, 2) == 4); assert(PyArray_TYPE(pyarr) == NPY_UINT8); assert(PyArray_ISBEHAVED(pyarr)); assert(PyArray_STRIDE(pyarr, 1) == 4*sizeof(uint8_t)); assert(PyArray_STRIDE(pyarr, 2) == sizeof(uint8_t)); #endif rows = PyArray_DIM(pyarr, 0); if (rows > rows_left) { PyErr_Format(PyExc_RuntimeError, "Attempt to read %d rows from the PNG, " "but only %d are left", rows, rows_left); goto cleanup; } input_buffer = (png_byte *) malloc(rows * input_buf_row_stride); input_buf_row_pointers = (png_bytep *)malloc(rows * sizeof(png_bytep)); for (row=0; row<rows; row++) { input_buf_row_pointers[row] = input_buffer + (row * input_buf_row_stride); } png_read_rows(png_ptr, input_buf_row_pointers, NULL, rows); rows_left -= rows; for (row=0; row<rows; row++) { uint8_t *pyarr_row = (uint8_t *)PyArray_DATA(pyarr) + row*PyArray_STRIDE(pyarr, 0); uint8_t *input_row = input_buf_row_pointers[row]; // Really minimal fake colour management. Just remaps to sRGB. cmsDoTransform(input_buffer_to_nparray, input_row, pyarr_row, width); // lcms2 ignores alpha, so copy that verbatim // If it's 8bpc RGBA, use A. // If it's 16bpc RrGgBbAa, use A. for (uint32_t i=0; i<width; ++i) { const uint32_t pyarr_alpha_byte = (i*4) + 3; const uint32_t buf_alpha_byte = (i*input_buf_bytes_per_pixel) + ((bit_depth==8) ? 3 : 6); pyarr_row[pyarr_alpha_byte] = input_row[buf_alpha_byte]; } } free(input_buf_row_pointers); free(input_buffer); Py_DECREF(pyarr); } png_read_end(png_ptr, NULL); result = Py_BuildValue("{s:b,s:i,s:i,s:s}", "possible_legacy_png", possible_legacy_png, "width", width, "height", height, "cm_conversions_applied", cm_processing); cleanup: if (info_ptr) png_destroy_read_struct (&png_ptr, &info_ptr, NULL); // libpng's style is to free internally allocated stuff like the icc // tables in png_destroy_*(). I think. if (fp) fclose(fp); if (input_buffer_profile) cmsCloseProfile(input_buffer_profile); if (nparray_data_profile) cmsCloseProfile(nparray_data_profile); if (input_buffer_to_nparray) cmsDeleteTransform(input_buffer_to_nparray); if (gamma_transfer_func) cmsFreeToneCurve(gamma_transfer_func); return result; }
/* Shared library entry point */ static GdkPixbuf * gdk_pixbuf__png_image_load (FILE *f, GError **error) { GdkPixbuf * volatile pixbuf = NULL; png_structp png_ptr; png_infop info_ptr; png_textp text_ptr; gint i, ctype; png_uint_32 w, h; png_bytepp volatile rows = NULL; gint num_texts; gchar *key; gchar *value; gchar *icc_profile_base64; const gchar *icc_profile_title; const gchar *icc_profile; png_uint_32 icc_profile_size; guint32 retval; gint compression_type; #ifdef PNG_USER_MEM_SUPPORTED png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING, error, png_simple_error_callback, png_simple_warning_callback, NULL, png_malloc_callback, png_free_callback); #else png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, error, png_simple_error_callback, png_simple_warning_callback); #endif if (!png_ptr) return NULL; info_ptr = png_create_info_struct (png_ptr); if (!info_ptr) { png_destroy_read_struct (&png_ptr, NULL, NULL); return NULL; } if (setjmp (png_jmpbuf(png_ptr))) { g_free (rows); if (pixbuf) g_object_unref (pixbuf); png_destroy_read_struct (&png_ptr, &info_ptr, NULL); return NULL; } png_init_io (png_ptr, f); png_read_info (png_ptr, info_ptr); if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) { png_destroy_read_struct (&png_ptr, &info_ptr, NULL); return NULL; } pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ctype & PNG_COLOR_MASK_ALPHA, 8, w, h); if (!pixbuf) { if (error && *error == NULL) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to load PNG file")); } png_destroy_read_struct (&png_ptr, &info_ptr, NULL); return NULL; } rows = g_new (png_bytep, h); for (i = 0; i < h; i++) rows[i] = pixbuf->pixels + i * pixbuf->rowstride; png_read_image (png_ptr, rows); png_read_end (png_ptr, info_ptr); if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) { for (i = 0; i < num_texts; i++) { png_text_to_pixbuf_option (text_ptr[i], &key, &value); gdk_pixbuf_set_option (pixbuf, key, value); g_free (key); g_free (value); } } #if defined(PNG_cHRM_SUPPORTED) /* Extract embedded ICC profile */ retval = png_get_iCCP (png_ptr, info_ptr, (png_charpp) &icc_profile_title, &compression_type, (png_bytepp) &icc_profile, (png_uint_32*) &icc_profile_size); if (retval != 0) { icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size); gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64); g_free (icc_profile_base64); } #endif g_free (rows); png_destroy_read_struct (&png_ptr, &info_ptr, NULL); return pixbuf; }
/* Called at the start of the progressive load, once we have image info */ static void png_info_callback (png_structp png_read_ptr, png_infop png_info_ptr) { LoadContext* lc; png_uint_32 width, height; png_textp png_text_ptr; int i, num_texts; int color_type; gboolean have_alpha = FALSE; gchar *icc_profile_base64; const gchar *icc_profile_title; const gchar *icc_profile; png_uint_32 icc_profile_size; guint32 retval; gint compression_type; lc = png_get_progressive_ptr(png_read_ptr); if (lc->fatal_error_occurred) return; if (!setup_png_transformations(lc->png_read_ptr, lc->png_info_ptr, lc->error, &width, &height, &color_type)) { lc->fatal_error_occurred = TRUE; return; } /* If we have alpha, set a flag */ if (color_type & PNG_COLOR_MASK_ALPHA) have_alpha = TRUE; if (lc->size_func) { gint w = width; gint h = height; (* lc->size_func) (&w, &h, lc->notify_user_data); if (w == 0 || h == 0) { lc->fatal_error_occurred = TRUE; if (lc->error && *lc->error == NULL) { g_set_error_literal (lc->error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Transformed PNG has zero width or height.")); } return; } } lc->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, have_alpha, 8, width, height); if (lc->pixbuf == NULL) { /* Failed to allocate memory */ lc->fatal_error_occurred = TRUE; if (lc->error && *lc->error == NULL) { g_set_error (lc->error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to store a %lu by %lu image; try exiting some applications to reduce memory usage"), (gulong) width, (gulong) height); } return; } /* Extract text chunks and attach them as pixbuf options */ if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) { for (i = 0; i < num_texts; i++) { gchar *key, *value; if (png_text_to_pixbuf_option (png_text_ptr[i], &key, &value)) { gdk_pixbuf_set_option (lc->pixbuf, key, value); g_free (key); g_free (value); } } } #if defined(PNG_cHRM_SUPPORTED) /* Extract embedded ICC profile */ retval = png_get_iCCP (png_read_ptr, png_info_ptr, (png_charpp) &icc_profile_title, &compression_type, (png_bytepp) &icc_profile, &icc_profile_size); if (retval != 0) { icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size); gdk_pixbuf_set_option (lc->pixbuf, "icc-profile", icc_profile_base64); g_free (icc_profile_base64); } #endif /* Notify the client that we are ready to go */ if (lc->prepare_func) (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data); return; }
/* Read a png header. */ static int png2vips_header( Read *read, VipsImage *out ) { png_uint_32 width, height; int bit_depth, color_type; int interlace_type; png_uint_32 res_x, res_y; int unit_type; png_charp name; int compression_type; /* Well thank you, libpng. */ #if PNG_LIBPNG_VER < 10400 png_charp profile; #else png_bytep profile; #endif png_uint_32 proflen; int bands; VipsInterpretation interpretation; double Xres, Yres; if( setjmp( png_jmpbuf( read->pPng ) ) ) return( -1 ); png_get_IHDR( read->pPng, read->pInfo, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL ); /* png_get_channels() gives us 1 band for palette images ... so look * at colour_type for output bands. * * Ignore alpha, we detect that separately below. */ switch( color_type ) { case PNG_COLOR_TYPE_PALETTE: bands = 3; break; case PNG_COLOR_TYPE_GRAY_ALPHA: case PNG_COLOR_TYPE_GRAY: bands = 1; break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: bands = 3; break; default: vips_error( "png2vips", "%s", _( "unsupported color type" ) ); return( -1 ); } if( bit_depth > 8 ) { if( bands < 3 ) interpretation = VIPS_INTERPRETATION_GREY16; else interpretation = VIPS_INTERPRETATION_RGB16; } else { if( bands < 3 ) interpretation = VIPS_INTERPRETATION_B_W; else interpretation = VIPS_INTERPRETATION_sRGB; } /* Expand palette images. */ if( color_type == PNG_COLOR_TYPE_PALETTE ) png_set_palette_to_rgb( read->pPng ); /* Expand transparency. */ if( png_get_valid( read->pPng, read->pInfo, PNG_INFO_tRNS ) ) { png_set_tRNS_to_alpha( read->pPng ); bands += 1; } else if( color_type == PNG_COLOR_TYPE_GRAY_ALPHA || color_type == PNG_COLOR_TYPE_RGB_ALPHA ) { /* Some images have no transparency chunk, but still set * color_type to alpha. */ bands += 1; } /* Expand <8 bit images to full bytes. */ if( color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8 ) png_set_expand_gray_1_2_4_to_8( read->pPng ); /* If we're an INTEL byte order machine and this is 16bits, we need * to swap bytes. */ if( bit_depth > 8 && !vips_amiMSBfirst() ) png_set_swap( read->pPng ); /* Get resolution. Default to 72 pixels per inch, the usual png value. */ unit_type = PNG_RESOLUTION_METER; res_x = (72 / 2.54 * 100); res_y = (72 / 2.54 * 100); png_get_pHYs( read->pPng, read->pInfo, &res_x, &res_y, &unit_type ); switch( unit_type ) { case PNG_RESOLUTION_METER: Xres = res_x / 1000.0; Yres = res_y / 1000.0; break; default: Xres = res_x; Yres = res_y; break; } /* Set VIPS header. */ vips_image_init_fields( out, width, height, bands, bit_depth > 8 ? VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, interpretation, Xres, Yres ); /* Sequential mode needs thinstrip to work with things like * vips_shrink(). */ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); /* Fetch the ICC profile. @name is useless, something like "icc" or * "ICC Profile" etc. Ignore it. * * @profile was png_charpp in libpngs < 1.5, png_bytepp is the * modern one. Ignore the warning, if any. */ if( png_get_iCCP( read->pPng, read->pInfo, &name, &compression_type, &profile, &proflen ) ) { void *profile_copy; #ifdef DEBUG printf( "png2vips_header: attaching %zd bytes of ICC profile\n", proflen ); printf( "png2vips_header: name = \"%s\"\n", name ); #endif /*DEBUG*/ if( !(profile_copy = vips_malloc( NULL, proflen )) ) return( -1 ); memcpy( profile_copy, profile, proflen ); vips_image_set_blob( out, VIPS_META_ICC_NAME, (VipsCallbackFn) vips_free, profile_copy, proflen ); } /* Sanity-check line size. */ png_read_update_info( read->pPng, read->pInfo ); if( png_get_rowbytes( read->pPng, read->pInfo ) != VIPS_IMAGE_SIZEOF_LINE( out ) ) { vips_error( "vipspng", "%s", _( "unable to read PNG header" ) ); return( -1 ); } return( 0 ); }
// Adapted from http://www.littlecms.com/pngchrm.c example code static qcms_profile* PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, int color_type, qcms_data_type* inType, uint32_t* intent) { qcms_profile* profile = nullptr; *intent = QCMS_INTENT_PERCEPTUAL; // Our default // First try to see if iCCP chunk is present if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { png_uint_32 profileLen; png_bytep profileData; png_charp profileName; int compression; png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, &profileData, &profileLen); profile = qcms_profile_from_memory((char*)profileData, profileLen); if (profile) { uint32_t profileSpace = qcms_profile_get_color_space(profile); bool mismatch = false; if (color_type & PNG_COLOR_MASK_COLOR) { if (profileSpace != icSigRgbData) { mismatch = true; } } else { if (profileSpace == icSigRgbData) { png_set_gray_to_rgb(png_ptr); } else if (profileSpace != icSigGrayData) { mismatch = true; } } if (mismatch) { qcms_profile_release(profile); profile = nullptr; } else { *intent = qcms_profile_get_rendering_intent(profile); } } } // Check sRGB chunk if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { profile = qcms_profile_sRGB(); if (profile) { int fileIntent; png_set_gray_to_rgb(png_ptr); png_get_sRGB(png_ptr, info_ptr, &fileIntent); uint32_t map[] = { QCMS_INTENT_PERCEPTUAL, QCMS_INTENT_RELATIVE_COLORIMETRIC, QCMS_INTENT_SATURATION, QCMS_INTENT_ABSOLUTE_COLORIMETRIC }; *intent = map[fileIntent]; } } // Check gAMA/cHRM chunks if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { qcms_CIE_xyYTRIPLE primaries; qcms_CIE_xyY whitePoint; png_get_cHRM(png_ptr, info_ptr, &whitePoint.x, &whitePoint.y, &primaries.red.x, &primaries.red.y, &primaries.green.x, &primaries.green.y, &primaries.blue.x, &primaries.blue.y); whitePoint.Y = primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0; double gammaOfFile; png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, 1.0/gammaOfFile); if (profile) { png_set_gray_to_rgb(png_ptr); } } if (profile) { uint32_t profileSpace = qcms_profile_get_color_space(profile); if (profileSpace == icSigGrayData) { if (color_type & PNG_COLOR_MASK_ALPHA) { *inType = QCMS_DATA_GRAYA_8; } else { *inType = QCMS_DATA_GRAY_8; } } else { if (color_type & PNG_COLOR_MASK_ALPHA || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { *inType = QCMS_DATA_RGBA_8; } else { *inType = QCMS_DATA_RGB_8; } } } return profile; }
// Returns a colorSpace object that represents any color space information in // the encoded data. If the encoded data contains no color space, this will // return NULL. sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) // First check for an ICC profile png_bytep profile; png_uint_32 length; // The below variables are unused, however, we need to pass them in anyway or // png_get_iCCP() will return nothing. // Could knowing the |name| of the profile ever be interesting? Maybe for debugging? png_charp name; // The |compression| is uninteresting since: // (1) libpng has already decompressed the profile for us. // (2) "deflate" is the only mode of decompression that libpng supports. int compression; if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile, &length)) { return SkColorSpace::NewICC(profile, length); } // Second, check for sRGB. if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { // sRGB chunks also store a rendering intent: Absolute, Relative, // Perceptual, and Saturation. // FIXME (msarett): Extract this information from the sRGB chunk once // we are able to handle this information in // SkColorSpace. return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); } // Next, check for chromaticities. png_fixed_point XYZ[9]; float toXYZD50[9]; png_fixed_point gamma; float gammas[3]; if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &XYZ[0], &XYZ[1], &XYZ[2], &XYZ[3], &XYZ[4], &XYZ[5], &XYZ[6], &XYZ[7], &XYZ[8])) { // FIXME (msarett): Here we are treating XYZ values as D50 even though the color // temperature is unspecified. I suspect that this assumption // is most often ok, but we could also calculate the color // temperature (D value) and then convert the XYZ to D50. Maybe // we should add a new constructor to SkColorSpace that accepts // XYZ with D-Unkown? for (int i = 0; i < 9; i++) { toXYZD50[i] = png_fixed_point_to_float(XYZ[i]); } if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { float value = png_inverted_fixed_point_to_float(gamma); gammas[0] = value; gammas[1] = value; gammas[2] = value; } else { // Default to sRGB (gamma = 2.2f) if the image has color space information, // but does not specify gamma. gammas[0] = 2.2f; gammas[1] = 2.2f; gammas[2] = 2.2f; } SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); mat.set3x3ColMajorf(toXYZD50); return SkColorSpace::NewRGB(gammas, mat); } // Last, check for gamma. if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { // Guess a default value for cHRM? Or should we just give up? // Here we use the identity matrix as a default. // Set the gammas. float value = png_inverted_fixed_point_to_float(gamma); gammas[0] = value; gammas[1] = value; gammas[2] = value; return SkColorSpace::NewRGB(gammas, SkMatrix44::I()); } #endif // LIBPNG >= 1.6 // Finally, what should we do if there is no color space information in the PNG? // The specification says that this indicates "gamma is unknown" and that the // "color is device dependent". I'm assuming we can represent this with NULL. // But should we guess sRGB? Most images are sRGB, even if they don't specify. return nullptr; }
static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { png_structp png_ptr = NULL; png_infop info_ptr; png_uint_32 width, height; png_colorp png_palette = NULL; int color_type, palette_entries = 0; int bit_depth, pixel_depth; // pixel_depth = bit_depth * channels FIBITMAP *dib = NULL; RGBQUAD *palette = NULL; // pointer to dib palette png_bytepp row_pointers = NULL; int i; fi_ioStructure fio; fio.s_handle = handle; fio.s_io = io; if (handle) { BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; try { // check to see if the file is in fact a PNG file BYTE png_check[PNG_BYTES_TO_CHECK]; io->read_proc(png_check, PNG_BYTES_TO_CHECK, 1, handle); if (png_sig_cmp(png_check, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0) { return NULL; // Bad signature } // create the chunk manage structure png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler); if (!png_ptr) { return NULL; } // create the info structure info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return NULL; } // init the IO png_set_read_fn(png_ptr, &fio, _ReadProc); if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return NULL; } // because we have already read the signature... png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK); // read the IHDR chunk png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); pixel_depth = png_get_bit_depth(png_ptr, info_ptr) * png_get_channels(png_ptr, info_ptr); // get image data type (assume standard image type) FREE_IMAGE_TYPE image_type = FIT_BITMAP; if (bit_depth == 16) { if ((pixel_depth == 16) && (color_type == PNG_COLOR_TYPE_GRAY)) { image_type = FIT_UINT16; } else if ((pixel_depth == 48) && (color_type == PNG_COLOR_TYPE_RGB)) { image_type = FIT_RGB16; } else if ((pixel_depth == 64) && (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { image_type = FIT_RGBA16; } else { // tell libpng to strip 16 bit/color files down to 8 bits/color png_set_strip_16(png_ptr); bit_depth = 8; } } #ifndef FREEIMAGE_BIGENDIAN if((image_type == FIT_UINT16) || (image_type == FIT_RGB16) || (image_type == FIT_RGBA16)) { // turn on 16 bit byte swapping png_set_swap(png_ptr); } #endif // set some additional flags switch(color_type) { case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR // flip the RGB pixels to BGR (or RGBA to BGRA) if(image_type == FIT_BITMAP) { png_set_bgr(png_ptr); } #endif break; case PNG_COLOR_TYPE_PALETTE: // expand palette images to the full 8 bits from 2 bits/pixel if (pixel_depth == 2) { png_set_packing(png_ptr); pixel_depth = 8; } break; case PNG_COLOR_TYPE_GRAY: // expand grayscale images to the full 8 bits from 2 bits/pixel // but *do not* expand fully transparent palette entries to a full alpha channel if (pixel_depth == 2) { png_set_expand_gray_1_2_4_to_8(png_ptr); pixel_depth = 8; } break; case PNG_COLOR_TYPE_GRAY_ALPHA: // expand 8-bit greyscale + 8-bit alpha to 32-bit png_set_gray_to_rgb(png_ptr); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR // flip the RGBA pixels to BGRA png_set_bgr(png_ptr); #endif pixel_depth = 32; break; default: throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } // unlike the example in the libpng documentation, we have *no* idea where // this file may have come from--so if it doesn't have a file gamma, don't // do any correction ("do no harm") if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) { double gamma = 0; double screen_gamma = 2.2; if (png_get_gAMA(png_ptr, info_ptr, &gamma) && ( flags & PNG_IGNOREGAMMA ) != PNG_IGNOREGAMMA) { png_set_gamma(png_ptr, screen_gamma, gamma); } } // all transformations have been registered; now update info_ptr data png_read_update_info(png_ptr, info_ptr); // color type may have changed, due to our transformations color_type = png_get_color_type(png_ptr,info_ptr); // create a DIB and write the bitmap header // set up the DIB palette, if needed switch (color_type) { case PNG_COLOR_TYPE_RGB: png_set_invert_alpha(png_ptr); if(image_type == FIT_BITMAP) { dib = FreeImage_AllocateHeader(header_only, width, height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); } else { dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth); } break; case PNG_COLOR_TYPE_RGB_ALPHA: if(image_type == FIT_BITMAP) { dib = FreeImage_AllocateHeader(header_only, width, height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); } else { dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth); } break; case PNG_COLOR_TYPE_PALETTE: dib = FreeImage_AllocateHeader(header_only, width, height, pixel_depth); png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries); palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib)); palette = FreeImage_GetPalette(dib); // store the palette for (i = 0; i < palette_entries; i++) { palette[i].rgbRed = png_palette[i].red; palette[i].rgbGreen = png_palette[i].green; palette[i].rgbBlue = png_palette[i].blue; } break; case PNG_COLOR_TYPE_GRAY: dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth); if(pixel_depth <= 8) { palette = FreeImage_GetPalette(dib); palette_entries = 1 << pixel_depth; for (i = 0; i < palette_entries; i++) { palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)((i * 255) / (palette_entries - 1)); } } break; default: throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } // store the transparency table if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { // array of alpha (transparency) entries for palette png_bytep trans_alpha = NULL; // number of transparent entries int num_trans = 0; // graylevel or color sample values of the single transparent color for non-paletted images png_color_16p trans_color = NULL; png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color); if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) { // single transparent color if (trans_color->gray < palette_entries) { BYTE table[256]; memset(table, 0xFF, palette_entries); table[trans_color->gray] = 0; FreeImage_SetTransparencyTable(dib, table, palette_entries); } } else if((color_type == PNG_COLOR_TYPE_PALETTE) && trans_alpha) { // transparency table FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans); } } // store the background color if (png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { // Get the background color to draw transparent and alpha images over. // Note that even if the PNG file supplies a background, you are not required to // use it - you should use the (solid) application background if it has one. png_color_16p image_background = NULL; RGBQUAD rgbBkColor; if (png_get_bKGD(png_ptr, info_ptr, &image_background)) { rgbBkColor.rgbRed = (BYTE)image_background->red; rgbBkColor.rgbGreen = (BYTE)image_background->green; rgbBkColor.rgbBlue = (BYTE)image_background->blue; rgbBkColor.rgbReserved = 0; FreeImage_SetBackgroundColor(dib, &rgbBkColor); } } // get physical resolution if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) { png_uint_32 res_x, res_y; // we'll overload this var and use 0 to mean no phys data, // since if it's not in meters we can't use it anyway int res_unit_type = PNG_RESOLUTION_UNKNOWN; png_get_pHYs(png_ptr,info_ptr, &res_x, &res_y, &res_unit_type); if (res_unit_type == PNG_RESOLUTION_METER) { FreeImage_SetDotsPerMeterX(dib, res_x); FreeImage_SetDotsPerMeterY(dib, res_y); } } // get possible ICC profile if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { png_charp profile_name = NULL; png_bytep profile_data = NULL; png_uint_32 profile_length = 0; int compression_type; png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_length); // copy ICC profile data (must be done after FreeImage_AllocateHeader) FreeImage_CreateICCProfile(dib, profile_data, profile_length); } // --- header only mode => clean-up and return if (header_only) { // get possible metadata (it can be located both before and after the image data) ReadMetadata(png_ptr, info_ptr, dib); if (png_ptr) { // clean up after the read, and free any memory allocated - REQUIRED png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } return dib; } // set the individual row_pointers to point at the correct offsets row_pointers = (png_bytepp)malloc(height * sizeof(png_bytep)); if (!row_pointers) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); FreeImage_Unload(dib); return NULL; } // read in the bitmap bits via the pointer table // allow loading of PNG with minor errors (such as images with several IDAT chunks) for (png_uint_32 k = 0; k < height; k++) { row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k); } png_set_benign_errors(png_ptr, 1); png_read_image(png_ptr, row_pointers); // check if the bitmap contains transparency, if so enable it in the header if (FreeImage_GetBPP(dib) == 32) { if (FreeImage_GetColorType(dib) == FIC_RGBALPHA) { FreeImage_SetTransparent(dib, TRUE); } else { FreeImage_SetTransparent(dib, FALSE); } } // cleanup if (row_pointers) { free(row_pointers); row_pointers = NULL; } // read the rest of the file, getting any additional chunks in info_ptr png_read_end(png_ptr, info_ptr); // get possible metadata (it can be located both before and after the image data) ReadMetadata(png_ptr, info_ptr, dib); if (png_ptr) { // clean up after the read, and free any memory allocated - REQUIRED png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } return dib; } catch (const char *text) { if (png_ptr) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } if (row_pointers) { free(row_pointers); } if (dib) { FreeImage_Unload(dib); } FreeImage_OutputMessageProc(s_format_id, text); return NULL; } } return NULL; }
static GstFlowReturn gst_pngdec_caps_create_and_set (GstPngDec * pngdec) { GstFlowReturn ret = GST_FLOW_OK; gint bpc = 0, color_type; png_uint_32 width, height; GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; g_return_val_if_fail (GST_IS_PNGDEC (pngdec), GST_FLOW_ERROR); /* Get bits per channel */ bpc = png_get_bit_depth (pngdec->png, pngdec->info); /* Get Color type */ color_type = png_get_color_type (pngdec->png, pngdec->info); /* Add alpha channel if 16-bit depth, but not for GRAY images */ if ((bpc > 8) && (color_type != PNG_COLOR_TYPE_GRAY)) { png_set_add_alpha (pngdec->png, 0xffff, PNG_FILLER_BEFORE); png_set_swap (pngdec->png); } #if 0 /* We used to have this HACK to reverse the outgoing bytes, but the problem * that originally required the hack seems to have been in videoconvert's * RGBA descriptions. It doesn't seem needed now that's fixed, but might * still be needed on big-endian systems, I'm not sure. J.S. 6/7/2007 */ if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) png_set_bgr (pngdec->png); #endif /* Gray scale with alpha channel converted to RGB */ if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { GST_LOG_OBJECT (pngdec, "converting grayscale png with alpha channel to RGB"); png_set_gray_to_rgb (pngdec->png); } /* Gray scale converted to upscaled to 8 bits */ if ((color_type == PNG_COLOR_TYPE_GRAY_ALPHA) || (color_type == PNG_COLOR_TYPE_GRAY)) { if (bpc < 8) { /* Convert to 8 bits */ GST_LOG_OBJECT (pngdec, "converting grayscale image to 8 bits"); #if PNG_LIBPNG_VER < 10400 png_set_gray_1_2_4_to_8 (pngdec->png); #else png_set_expand_gray_1_2_4_to_8 (pngdec->png); #endif } } /* Palette converted to RGB */ if (color_type == PNG_COLOR_TYPE_PALETTE) { GST_LOG_OBJECT (pngdec, "converting palette png to RGB"); png_set_palette_to_rgb (pngdec->png); } png_set_interlace_handling (pngdec->png); /* Update the info structure */ png_read_update_info (pngdec->png, pngdec->info); /* Get IHDR header again after transformation settings */ png_get_IHDR (pngdec->png, pngdec->info, &width, &height, &bpc, &pngdec->color_type, NULL, NULL, NULL); GST_LOG_OBJECT (pngdec, "this is a %dx%d PNG image", (gint) width, (gint) height); switch (pngdec->color_type) { case PNG_COLOR_TYPE_RGB: GST_LOG_OBJECT (pngdec, "we have no alpha channel, depth is 24 bits"); if (bpc == 8) format = GST_VIDEO_FORMAT_RGB; break; case PNG_COLOR_TYPE_RGB_ALPHA: GST_LOG_OBJECT (pngdec, "we have an alpha channel, depth is 32 or 64 bits"); if (bpc == 8) format = GST_VIDEO_FORMAT_RGBA; else if (bpc == 16) format = GST_VIDEO_FORMAT_ARGB64; break; case PNG_COLOR_TYPE_GRAY: GST_LOG_OBJECT (pngdec, "We have an gray image, depth is 8 or 16 (be) bits"); if (bpc == 8) format = GST_VIDEO_FORMAT_GRAY8; else if (bpc == 16) format = GST_VIDEO_FORMAT_GRAY16_BE; break; default: break; } if (format == GST_VIDEO_FORMAT_UNKNOWN) { GST_ELEMENT_ERROR (pngdec, STREAM, NOT_IMPLEMENTED, (NULL), ("pngdec does not support this color type")); ret = GST_FLOW_NOT_SUPPORTED; goto beach; } /* Check if output state changed */ if (pngdec->output_state) { GstVideoInfo *info = &pngdec->output_state->info; if (width == GST_VIDEO_INFO_WIDTH (info) && height == GST_VIDEO_INFO_HEIGHT (info) && GST_VIDEO_INFO_FORMAT (info) == format) { goto beach; } gst_video_codec_state_unref (pngdec->output_state); } #ifdef HAVE_LIBPNG_1_5 if ((pngdec->color_type & PNG_COLOR_MASK_COLOR) && !(pngdec->color_type & PNG_COLOR_MASK_PALETTE) && png_get_valid (pngdec->png, pngdec->info, PNG_INFO_iCCP)) { png_charp icc_name; png_bytep icc_profile; int icc_compression_type; png_uint_32 icc_proflen = 0; png_uint_32 ret = png_get_iCCP (pngdec->png, pngdec->info, &icc_name, &icc_compression_type, &icc_profile, &icc_proflen); if ((ret & PNG_INFO_iCCP)) { gpointer gst_icc_prof = g_memdup (icc_profile, icc_proflen); GstBuffer *tagbuffer = NULL; GstSample *tagsample = NULL; GstTagList *taglist = NULL; GstStructure *info = NULL; GstCaps *caps; GST_DEBUG_OBJECT (pngdec, "extracted ICC profile '%s' length=%i", icc_name, (guint32) icc_proflen); tagbuffer = gst_buffer_new_wrapped (gst_icc_prof, icc_proflen); caps = gst_caps_new_empty_simple ("application/vnd.iccprofile"); info = gst_structure_new_empty ("application/vnd.iccprofile"); if (icc_name) gst_structure_set (info, "icc-name", G_TYPE_STRING, icc_name, NULL); tagsample = gst_sample_new (tagbuffer, caps, NULL, info); gst_buffer_unref (tagbuffer); gst_caps_unref (caps); taglist = gst_tag_list_new_empty (); gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_ATTACHMENT, tagsample, NULL); gst_sample_unref (tagsample); gst_video_decoder_merge_tags (GST_VIDEO_DECODER (pngdec), taglist, GST_TAG_MERGE_APPEND); gst_tag_list_unref (taglist); } } #endif pngdec->output_state = gst_video_decoder_set_output_state (GST_VIDEO_DECODER (pngdec), format, width, height, pngdec->input_state); gst_video_decoder_negotiate (GST_VIDEO_DECODER (pngdec)); GST_DEBUG ("Final %d %d", GST_VIDEO_INFO_WIDTH (&pngdec->output_state->info), GST_VIDEO_INFO_HEIGHT (&pngdec->output_state->info)); beach: return ret; }
bool MCImageDecodePNG(IO_handle p_stream, MCImageBitmap *&r_bitmap) { bool t_success = true; MCImageBitmap *t_bitmap = nil; png_structp t_png = nil; png_infop t_info = nil; png_infop t_end_info = nil; t_success = nil != (t_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)); if (t_success) t_success = nil != (t_info = png_create_info_struct(t_png)); if (t_success) t_success = nil != (t_end_info = png_create_info_struct(t_png)); if (setjmp(png_jmpbuf(t_png))) { t_success = false; } if (t_success) { png_set_read_fn(t_png, p_stream, stream_read); png_read_info(t_png, t_info); } png_uint_32 t_width, t_height; int t_bit_depth, t_color_type; int t_interlace_method, t_compression_method, t_filter_method; int t_interlace_passes; if (t_success) { png_get_IHDR(t_png, t_info, &t_width, &t_height, &t_bit_depth, &t_color_type, &t_interlace_method, &t_compression_method, &t_filter_method); } if (t_success) t_success = MCImageBitmapCreate(t_width, t_height, t_bitmap); if (t_success) { bool t_need_alpha = false; t_interlace_passes = png_set_interlace_handling(t_png); if (t_color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(t_png); if (t_color_type == PNG_COLOR_TYPE_GRAY || t_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(t_png); if (png_get_valid(t_png, t_info, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(t_png); t_need_alpha = true; /* OVERHAUL - REVISIT - assume image has transparent pixels if tRNS is present */ t_bitmap->has_transparency = true; } if (t_color_type & PNG_COLOR_MASK_ALPHA) { t_need_alpha = true; /* OVERHAUL - REVISIT - assume image has alpha if color type allows it */ t_bitmap->has_alpha = t_bitmap->has_transparency = true; } else if (!t_need_alpha) png_set_add_alpha(t_png, 0xFF, MCswapbytes ? PNG_FILLER_AFTER : PNG_FILLER_BEFORE); if (t_bit_depth == 16) png_set_strip_16(t_png); if (MCswapbytes) png_set_bgr(t_png); else png_set_swap_alpha(t_png); } // MW-2009-12-10: Support for color profiles MCColorTransformRef t_color_xform; t_color_xform = nil; // Try to get an embedded ICC profile... if (t_success && t_color_xform == nil && png_get_valid(t_png, t_info, PNG_INFO_iCCP)) { png_charp t_ccp_name; png_bytep t_ccp_profile; int t_ccp_compression_type; png_uint_32 t_ccp_profile_length; png_get_iCCP(t_png, t_info, &t_ccp_name, &t_ccp_compression_type, &t_ccp_profile, &t_ccp_profile_length); MCColorSpaceInfo t_csinfo; t_csinfo . type = kMCColorSpaceEmbedded; t_csinfo . embedded . data = t_ccp_profile; t_csinfo . embedded . data_size = t_ccp_profile_length; t_color_xform = MCscreen -> createcolortransform(t_csinfo); } // Next try an sRGB style profile... if (t_success && t_color_xform == nil && png_get_valid(t_png, t_info, PNG_INFO_sRGB)) { int t_intent; png_get_sRGB(t_png, t_info, &t_intent); MCColorSpaceInfo t_csinfo; t_csinfo . type = kMCColorSpaceStandardRGB; t_csinfo . standard . intent = (MCColorSpaceIntent)t_intent; t_color_xform = MCscreen -> createcolortransform(t_csinfo); } // Finally try for cHRM + gAMA... if (t_success && t_color_xform == nil && png_get_valid(t_png, t_info, PNG_INFO_cHRM) && png_get_valid(t_png, t_info, PNG_INFO_gAMA)) { MCColorSpaceInfo t_csinfo; t_csinfo . type = kMCColorSpaceCalibratedRGB; png_get_cHRM(t_png, t_info, &t_csinfo . calibrated . white_x, &t_csinfo . calibrated . white_y, &t_csinfo . calibrated . red_x, &t_csinfo . calibrated . red_y, &t_csinfo . calibrated . green_x, &t_csinfo . calibrated . green_y, &t_csinfo . calibrated . blue_x, &t_csinfo . calibrated . blue_y); png_get_gAMA(t_png, t_info, &t_csinfo . calibrated . gamma); t_color_xform = MCscreen -> createcolortransform(t_csinfo); } // Could not create any kind, so fallback to gamma transform. if (t_success && t_color_xform == nil) { double image_gamma; if (png_get_gAMA(t_png, t_info, &image_gamma)) png_set_gamma(t_png, MCgamma, image_gamma); else png_set_gamma(t_png, MCgamma, 0.45); } if (t_success) { for (uindex_t t_pass = 0; t_pass < t_interlace_passes; t_pass++) { png_bytep t_data_ptr = (png_bytep)t_bitmap->data; for (uindex_t i = 0; i < t_height; i++) { png_read_row(t_png, t_data_ptr, nil); t_data_ptr += t_bitmap->stride; } } } if (t_success) png_read_end(t_png, t_end_info); if (t_png != nil) png_destroy_read_struct(&t_png, &t_info, &t_end_info); // transform colours using extracted colour profile if (t_success && t_color_xform != nil) MCImageBitmapApplyColorTransform(t_bitmap, t_color_xform); if (t_color_xform != nil) MCscreen -> destroycolortransform(t_color_xform); if (t_success) r_bitmap = t_bitmap; else { if (t_bitmap != nil) MCImageFreeBitmap(t_bitmap); } return t_success; }
// Returns a colorSpace object that represents any color space information in // the encoded data. If the encoded data contains no color space, this will // return NULL. sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) // First check for an ICC profile png_bytep profile; png_uint_32 length; // The below variables are unused, however, we need to pass them in anyway or // png_get_iCCP() will return nothing. // Could knowing the |name| of the profile ever be interesting? Maybe for debugging? png_charp name; // The |compression| is uninteresting since: // (1) libpng has already decompressed the profile for us. // (2) "deflate" is the only mode of decompression that libpng supports. int compression; if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile, &length)) { return SkColorSpace::NewICC(profile, length); } // Second, check for sRGB. if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { // sRGB chunks also store a rendering intent: Absolute, Relative, // Perceptual, and Saturation. // FIXME (msarett): Extract this information from the sRGB chunk once // we are able to handle this information in // SkColorSpace. return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); } // Next, check for chromaticities. png_fixed_point toXYZFixed[9]; float toXYZ[9]; png_fixed_point whitePointFixed[2]; float whitePoint[2]; png_fixed_point gamma; float gammas[3]; if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &toXYZFixed[0], &toXYZFixed[1], &toXYZFixed[2], &toXYZFixed[3], &toXYZFixed[4], &toXYZFixed[5], &toXYZFixed[6], &toXYZFixed[7], &toXYZFixed[8]) && png_get_cHRM_fixed(png_ptr, info_ptr, &whitePointFixed[0], &whitePointFixed[1], nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { for (int i = 0; i < 9; i++) { toXYZ[i] = png_fixed_point_to_float(toXYZFixed[i]); } whitePoint[0] = png_fixed_point_to_float(whitePointFixed[0]); whitePoint[1] = png_fixed_point_to_float(whitePointFixed[1]); SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); if (!convert_to_D50(&toXYZD50, toXYZ, whitePoint)) { toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50); } if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { float value = png_inverted_fixed_point_to_float(gamma); gammas[0] = value; gammas[1] = value; gammas[2] = value; return SkColorSpace_Base::NewRGB(gammas, toXYZD50); } // Default to sRGB gamma if the image has color space information, // but does not specify gamma. return SkColorSpace::NewRGB(SkColorSpace::kSRGB_GammaNamed, toXYZD50); } // Last, check for gamma. if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { // Set the gammas. float value = png_inverted_fixed_point_to_float(gamma); gammas[0] = value; gammas[1] = value; gammas[2] = value; // Since there is no cHRM, we will guess sRGB gamut. SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50); return SkColorSpace_Base::NewRGB(gammas, toXYZD50); } #endif // LIBPNG >= 1.6 // Report that there is no color space information in the PNG. SkPngCodec is currently // implemented to guess sRGB in this case. return nullptr; }
static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; int color_type; int bit_depth; int pixel_depth = 0; // pixel_depth = bit_depth * channels FIBITMAP *dib = NULL; png_bytepp row_pointers = NULL; fi_ioStructure fio; fio.s_handle = handle; fio.s_io = io; if (handle) { BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; try { // check to see if the file is in fact a PNG file BYTE png_check[PNG_BYTES_TO_CHECK]; io->read_proc(png_check, PNG_BYTES_TO_CHECK, 1, handle); if (png_sig_cmp(png_check, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0) { return NULL; // Bad signature } // create the chunk manage structure png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler); if (!png_ptr) { return NULL; } // create the info structure info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return NULL; } // init the IO png_set_read_fn(png_ptr, &fio, _ReadProc); if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return NULL; } // because we have already read the signature... png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK); // read the IHDR chunk png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); // configure the decoder FREE_IMAGE_TYPE image_type = FIT_BITMAP; if(!ConfigureDecoder(png_ptr, info_ptr, flags, &image_type)) { throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } // update image info color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); pixel_depth = bit_depth * png_get_channels(png_ptr, info_ptr); // create a dib and write the bitmap header // set up the dib palette, if needed switch (color_type) { case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); break; case PNG_COLOR_TYPE_PALETTE: dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if(dib) { png_colorp png_palette = NULL; int palette_entries = 0; png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries); palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib)); // store the palette RGBQUAD *palette = FreeImage_GetPalette(dib); for(int i = 0; i < palette_entries; i++) { palette[i].rgbRed = png_palette[i].red; palette[i].rgbGreen = png_palette[i].green; palette[i].rgbBlue = png_palette[i].blue; } } break; case PNG_COLOR_TYPE_GRAY: dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if(dib && (pixel_depth <= 8)) { RGBQUAD *palette = FreeImage_GetPalette(dib); const int palette_entries = 1 << pixel_depth; for(int i = 0; i < palette_entries; i++) { palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)((i * 255) / (palette_entries - 1)); } } break; default: throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } if(!dib) { throw FI_MSG_ERROR_DIB_MEMORY; } // store the transparency table if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { // array of alpha (transparency) entries for palette png_bytep trans_alpha = NULL; // number of transparent entries int num_trans = 0; // graylevel or color sample values of the single transparent color for non-paletted images png_color_16p trans_color = NULL; png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color); if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) { // single transparent color if (trans_color->gray < 256) { BYTE table[256]; memset(table, 0xFF, 256); table[trans_color->gray] = 0; FreeImage_SetTransparencyTable(dib, table, 256); } // check for a full transparency table, too else if ((trans_alpha) && (pixel_depth <= 8)) { FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans); } } else if((color_type == PNG_COLOR_TYPE_PALETTE) && trans_alpha) { // transparency table FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans); } } // store the background color (only supported for FIT_BITMAP types) if ((image_type == FIT_BITMAP) && png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { // Get the background color to draw transparent and alpha images over. // Note that even if the PNG file supplies a background, you are not required to // use it - you should use the (solid) application background if it has one. png_color_16p image_background = NULL; RGBQUAD rgbBkColor; if (png_get_bKGD(png_ptr, info_ptr, &image_background)) { rgbBkColor.rgbRed = (BYTE)image_background->red; rgbBkColor.rgbGreen = (BYTE)image_background->green; rgbBkColor.rgbBlue = (BYTE)image_background->blue; rgbBkColor.rgbReserved = 0; FreeImage_SetBackgroundColor(dib, &rgbBkColor); } } // get physical resolution if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) { png_uint_32 res_x, res_y; // we'll overload this var and use 0 to mean no phys data, // since if it's not in meters we can't use it anyway int res_unit_type = PNG_RESOLUTION_UNKNOWN; png_get_pHYs(png_ptr,info_ptr, &res_x, &res_y, &res_unit_type); if (res_unit_type == PNG_RESOLUTION_METER) { FreeImage_SetDotsPerMeterX(dib, res_x); FreeImage_SetDotsPerMeterY(dib, res_y); } } // get possible ICC profile if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { png_charp profile_name = NULL; png_bytep profile_data = NULL; png_uint_32 profile_length = 0; int compression_type; png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_length); // copy ICC profile data (must be done after FreeImage_AllocateHeader) FreeImage_CreateICCProfile(dib, profile_data, profile_length); } // --- header only mode => clean-up and return if (header_only) { // get possible metadata (it can be located both before and after the image data) ReadMetadata(png_ptr, info_ptr, dib); if (png_ptr) { // clean up after the read, and free any memory allocated - REQUIRED png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } return dib; } // set the individual row_pointers to point at the correct offsets row_pointers = (png_bytepp)malloc(height * sizeof(png_bytep)); if (!row_pointers) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); FreeImage_Unload(dib); return NULL; } // read in the bitmap bits via the pointer table // allow loading of PNG with minor errors (such as images with several IDAT chunks) for (png_uint_32 k = 0; k < height; k++) { row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k); } png_set_benign_errors(png_ptr, 1); png_read_image(png_ptr, row_pointers); // check if the bitmap contains transparency, if so enable it in the header if (FreeImage_GetBPP(dib) == 32) { if (FreeImage_GetColorType(dib) == FIC_RGBALPHA) { FreeImage_SetTransparent(dib, TRUE); } else { FreeImage_SetTransparent(dib, FALSE); } } // cleanup if (row_pointers) { free(row_pointers); row_pointers = NULL; } // read the rest of the file, getting any additional chunks in info_ptr png_read_end(png_ptr, info_ptr); // get possible metadata (it can be located both before and after the image data) ReadMetadata(png_ptr, info_ptr, dib); if (png_ptr) { // clean up after the read, and free any memory allocated - REQUIRED png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } return dib; } catch (const char *text) { if (png_ptr) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); } if (row_pointers) { free(row_pointers); } if (dib) { FreeImage_Unload(dib); } FreeImage_OutputMessageProc(s_format_id, text); return NULL; } } return NULL; }
pngquant_error rwpng_read_image24_libpng(FILE *infile, png24_image *mainprog_ptr) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_size_t rowbytes; int color_type, bit_depth; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, mainprog_ptr, rwpng_error_handler, NULL); if (!png_ptr) { return PNG_OUT_OF_MEMORY_ERROR; /* out of memory */ } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); return PNG_OUT_OF_MEMORY_ERROR; /* out of memory */ } /* setjmp() must be called in every function that calls a non-trivial * libpng function */ if (setjmp(mainprog_ptr->jmpbuf)) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return LIBPNG_FATAL_ERROR; /* fatal libpng error (via longjmp()) */ } struct rwpng_read_data read_data = {infile, 0}; png_set_read_fn(png_ptr, &read_data, user_read_data); png_read_info(png_ptr, info_ptr); /* read all PNG info up to image data */ /* alternatively, could make separate calls to png_get_image_width(), * etc., but want bit_depth and color_type for later [don't care about * compression_type and filter_type => NULLs] */ png_get_IHDR(png_ptr, info_ptr, &mainprog_ptr->width, &mainprog_ptr->height, &bit_depth, &color_type, NULL, NULL, NULL); /* expand palette images to RGB, low-bit-depth grayscale images to 8 bits, * transparency chunks to full alpha channel; strip 16-bit-per-sample * images to 8 bits per sample; and convert grayscale to RGB[A] */ /* GRR TO DO: preserve all safe-to-copy ancillary PNG chunks */ if (!(color_type & PNG_COLOR_MASK_ALPHA)) { #ifdef PNG_READ_FILLER_SUPPORTED png_set_expand(png_ptr); png_set_filler(png_ptr, 65535L, PNG_FILLER_AFTER); #else fprintf(stderr, "pngquant readpng: image is neither RGBA nor GA\n"); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); mainprog_ptr->retval = 26; return mainprog_ptr->retval; #endif } /* 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); */ if (bit_depth == 16) png_set_strip_16(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); /* get and save the gamma info (if any) for writing */ double gamma; mainprog_ptr->gamma = png_get_gAMA(png_ptr, info_ptr, &gamma) ? gamma : 0.45455; png_set_interlace_handling(png_ptr); /* all transformations have been registered; now update info_ptr data, * get rowbytes and channels, and allocate image memory */ png_read_update_info(png_ptr, info_ptr); rowbytes = png_get_rowbytes(png_ptr, info_ptr); if ((mainprog_ptr->rgba_data = malloc(rowbytes*mainprog_ptr->height)) == NULL) { fprintf(stderr, "pngquant readpng: unable to allocate image data\n"); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return PNG_OUT_OF_MEMORY_ERROR; } png_bytepp row_pointers = rwpng_create_row_pointers(info_ptr, png_ptr, mainprog_ptr->rgba_data, mainprog_ptr->height, 0); /* now we can go ahead and just read the whole image */ png_read_image(png_ptr, row_pointers); /* and we're done! (png_read_end() can be omitted if no processing of * post-IDAT text/time/etc. is desired) */ png_read_end(png_ptr, NULL); #if USE_LCMS if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { png_uint_32 ProfileLen; png_bytep ProfileData; int Compression; png_charp ProfileName; png_get_iCCP(png_ptr, info_ptr, &ProfileName, &Compression, &ProfileData, &ProfileLen); cmsHPROFILE hInProfile = cmsOpenProfileFromMem(ProfileData, ProfileLen); cmsHPROFILE hOutProfile = cmsCreate_sRGBProfile(); cmsHTRANSFORM hTransform = cmsCreateTransform(hInProfile, TYPE_RGBA_8, hOutProfile, TYPE_RGBA_8, INTENT_PERCEPTUAL, 0); // suprisingly, using the same input and output works cmsDoTransform(hTransform, mainprog_ptr->rgba_data, mainprog_ptr->rgba_data, mainprog_ptr->height * mainprog_ptr->width); cmsDeleteTransform(hTransform); cmsCloseProfile(hOutProfile); cmsCloseProfile(hInProfile); } #endif png_destroy_read_struct(&png_ptr, &info_ptr, NULL); mainprog_ptr->file_size = read_data.bytes_read; mainprog_ptr->row_pointers = (unsigned char **)row_pointers; return SUCCESS; }