diff options
Diffstat (limited to 'plugins/AdvaImg/src/FreeImage/PluginPNG.cpp')
-rw-r--r-- | plugins/AdvaImg/src/FreeImage/PluginPNG.cpp | 442 |
1 files changed, 296 insertions, 146 deletions
diff --git a/plugins/AdvaImg/src/FreeImage/PluginPNG.cpp b/plugins/AdvaImg/src/FreeImage/PluginPNG.cpp index f6b59e299b..fe80a2b533 100644 --- a/plugins/AdvaImg/src/FreeImage/PluginPNG.cpp +++ b/plugins/AdvaImg/src/FreeImage/PluginPNG.cpp @@ -6,6 +6,7 @@ // - Herve Drolon (drolon@infonie.fr) // - Detlev Vendt (detlev.vendt@brillit.de) // - Aaron Shumate (trek@startreker.com) +// - Tanner Helland (tannerhelland@users.sf.net) // // This file is part of FreeImage 3 // @@ -49,9 +50,9 @@ typedef struct { fi_handle s_handle; } fi_ioStructure, *pfi_ioStructure; -///////////////////////////////////////////////////////////////////////////// -// libpng interface -// +// ========================================================== +// libpng interface +// ========================================================== static void _ReadProc(png_structp png_ptr, unsigned char *data, png_size_t size) { @@ -99,6 +100,7 @@ ReadMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { FITAG *tag = NULL; png_textp text_ptr = NULL; + png_timep mod_time = NULL; int num_text = 0; // iTXt/tEXt/zTXt chuncks @@ -130,6 +132,31 @@ ReadMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { } } + // timestamp chunk + if(png_get_tIME(png_ptr, info_ptr, &mod_time)) { + char timestamp[32]; + // create a tag + tag = FreeImage_CreateTag(); + if(!tag) return FALSE; + + // convert as 'yyyy:MM:dd hh:mm:ss' + sprintf(timestamp, "%4d:%02d:%02d %2d:%02d:%02d", mod_time->year, mod_time->month, mod_time->day, mod_time->hour, mod_time->minute, mod_time->second); + + DWORD tag_length = (DWORD)strlen(timestamp) + 1; + FreeImage_SetTagLength(tag, tag_length); + FreeImage_SetTagCount(tag, tag_length); + FreeImage_SetTagType(tag, FIDT_ASCII); + FreeImage_SetTagID(tag, TAG_DATETIME); + FreeImage_SetTagValue(tag, timestamp); + + // store the tag as Exif-TIFF + FreeImage_SetTagKey(tag, "DateTime"); + FreeImage_SetMetadata(FIMD_EXIF_MAIN, dib, FreeImage_GetTagKey(tag), tag); + + // destroy the tag + FreeImage_DeleteTag(tag); + } + return TRUE; } @@ -143,6 +170,7 @@ WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { BOOL bResult = TRUE; png_text text_metadata; + png_time mod_time; // set the 'Comments' metadata as iTXt chuncks @@ -174,7 +202,7 @@ WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { if(tag && FreeImage_GetTagLength(tag)) { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none - text_metadata.key = (char*)g_png_xmp_keyword; // keyword, 1-79 character description of "text" + text_metadata.key = (char*)g_png_xmp_keyword; // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string @@ -186,6 +214,23 @@ WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { bResult &= TRUE; } + // set the Exif-TIFF 'DateTime' metadata as a tIME chunk + tag = NULL; + FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "DateTime", &tag); + if(tag && FreeImage_GetTagLength(tag)) { + int year, month, day, hour, minute, second; + const char *value = (char*)FreeImage_GetTagValue(tag); + if(sscanf(value, "%4d:%02d:%02d %2d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) == 6) { + mod_time.year = (png_uint_16)year; + mod_time.month = (png_byte)month; + mod_time.day = (png_byte)day; + mod_time.hour = (png_byte)hour; + mod_time.minute = (png_byte)minute; + mod_time.second = (png_byte)second; + png_set_tIME (png_ptr, info_ptr, &mod_time); + } + } + return bResult; } @@ -265,21 +310,207 @@ SupportsNoPixels() { return TRUE; } -// ---------------------------------------------------------- +// -------------------------------------------------------------------------- + +/** +Configure the decoder so that decoded pixels are compatible with a FREE_IMAGE_TYPE format. +Set conversion instructions as needed. +@param png_ptr PNG handle +@param info_ptr PNG info handle +@param flags Decoder flags +@param output_image_type Returned FreeImage converted image type +@return Returns TRUE if successful, returns FALSE otherwise +@see png_read_update_info +*/ +static BOOL +ConfigureDecoder(png_structp png_ptr, png_infop info_ptr, int flags, FREE_IMAGE_TYPE *output_image_type) { + // get original image info + const int color_type = png_get_color_type(png_ptr, info_ptr); + const int bit_depth = png_get_bit_depth(png_ptr, info_ptr); + const int pixel_depth = bit_depth * png_get_channels(png_ptr, info_ptr); + + FREE_IMAGE_TYPE image_type = FIT_BITMAP; // assume standard image type + + // check for transparency table or single transparent color + BOOL bIsTransparent = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) == PNG_INFO_tRNS ? TRUE : FALSE; + + // check allowed combinations of colour type and bit depth + // then get converted FreeImage type + + switch(color_type) { + case PNG_COLOR_TYPE_GRAY: // color type '0', bitdepth = 1, 2, 4, 8, 16 + switch(bit_depth) { + case 1: + case 2: + case 4: + case 8: + // expand grayscale images to the full 8-bit from 2-bit/pixel + if (pixel_depth == 2) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + + // if a tRNS chunk is provided, we must also expand the grayscale data to 8-bits, + // this allows us to make use of the transparency table with existing FreeImage methods + if (bIsTransparent && (pixel_depth < 8)) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + break; + + case 16: + image_type = (pixel_depth == 16) ? FIT_UINT16 : FIT_UNKNOWN; + + // 16-bit grayscale images can contain a transparent value (shade) + // if found, expand the transparent value to a full alpha channel + if (bIsTransparent && (image_type != FIT_UNKNOWN)) { + // expand tRNS to a full alpha channel + png_set_tRNS_to_alpha(png_ptr); + + // expand new 16-bit gray + 16-bit alpha to full 64-bit RGBA + png_set_gray_to_rgb(png_ptr); + + image_type = FIT_RGBA16; + } + break; + + default: + image_type = FIT_UNKNOWN; + break; + } + break; + + case PNG_COLOR_TYPE_RGB: // color type '2', bitdepth = 8, 16 + switch(bit_depth) { + case 8: + image_type = (pixel_depth == 24) ? FIT_BITMAP : FIT_UNKNOWN; + break; + case 16: + image_type = (pixel_depth == 48) ? FIT_RGB16 : FIT_UNKNOWN; + break; + default: + image_type = FIT_UNKNOWN; + break; + } + // sometimes, 24- or 48-bit images may contain transparency information + // check for this use case and convert to an alpha-compatible format + if (bIsTransparent && (image_type != FIT_UNKNOWN)) { + // if the image is 24-bit RGB, mark it as 32-bit; if it is 48-bit, mark it as 64-bit + image_type = (pixel_depth == 24) ? FIT_BITMAP : (pixel_depth == 48) ? FIT_RGBA16 : FIT_UNKNOWN; + // expand tRNS chunk to alpha channel + png_set_tRNS_to_alpha(png_ptr); + } + break; + + case PNG_COLOR_TYPE_PALETTE: // color type '3', bitdepth = 1, 2, 4, 8 + switch(bit_depth) { + case 1: + case 2: + case 4: + case 8: + // expand palette images to the full 8 bits from 2 bits/pixel + if (pixel_depth == 2) { + png_set_packing(png_ptr); + } + + // if a tRNS chunk is provided, we must also expand the palletized data to 8-bits, + // this allows us to make use of the transparency table with existing FreeImage methods + if (bIsTransparent && (pixel_depth < 8)) { + png_set_packing(png_ptr); + } + break; + + default: + image_type = FIT_UNKNOWN; + break; + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: // color type '4', bitdepth = 8, 16 + switch(bit_depth) { + case 8: + // 8-bit grayscale + 8-bit alpha => convert to 32-bit RGBA + image_type = (pixel_depth == 16) ? FIT_BITMAP : FIT_UNKNOWN; + break; + case 16: + // 16-bit grayscale + 16-bit alpha => convert to 64-bit RGBA + image_type = (pixel_depth == 32) ? FIT_RGBA16 : FIT_UNKNOWN; + break; + default: + image_type = FIT_UNKNOWN; + break; + } + // expand 8-bit greyscale + 8-bit alpha to 32-bit + // expand 16-bit greyscale + 16-bit alpha to 64-bit + png_set_gray_to_rgb(png_ptr); + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: // color type '6', bitdepth = 8, 16 + switch(bit_depth) { + case 8: + break; + case 16: + image_type = (pixel_depth == 64) ? FIT_RGBA16 : FIT_UNKNOWN; + break; + default: + image_type = FIT_UNKNOWN; + break; + } + break; + } + + // check for unknown or invalid formats + if(image_type == FIT_UNKNOWN) { + *output_image_type = image_type; + return FALSE; + } + +#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 + +#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR + if((image_type == FIT_BITMAP) && ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA))) { + // flip the RGB pixels to BGR (or RGBA to BGRA) + png_set_bgr(png_ptr); + } +#endif + + // gamma correction + // 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); + + // return the output image type + *output_image_type = image_type; + + return TRUE; +} 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_infop info_ptr = NULL; 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 + int color_type; + int bit_depth; + int pixel_depth = 0; // pixel_depth = bit_depth * channels FIBITMAP *dib = NULL; - RGBQUAD *palette = NULL; // pointer to dib palette - png_bytepp row_pointers = NULL; - int i; + png_bytepp row_pointers = NULL; fi_ioStructure fio; fio.s_handle = handle; @@ -334,154 +565,58 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { 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) + // configure the decoder 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; + if(!ConfigureDecoder(png_ptr, info_ptr, flags, &image_type)) { + 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); + // update image info - // color type may have changed, due to our transformations + 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); - 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 + // 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); - } + 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_AllocateHeader(header_only, width, height, pixel_depth); + 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); + png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries); - palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib)); - palette = FreeImage_GetPalette(dib); + palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib)); - // store the palette + // 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; + 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); + dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); - if(pixel_depth <= 8) { - palette = FreeImage_GetPalette(dib); - palette_entries = 1 << pixel_depth; + if(dib && (pixel_depth <= 8)) { + RGBQUAD *palette = FreeImage_GetPalette(dib); + const int palette_entries = 1 << pixel_depth; - for (i = 0; i < palette_entries; i++) { + for(int i = 0; i < palette_entries; i++) { palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)((i * 255) / (palette_entries - 1)); @@ -493,6 +628,10 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { 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)) { @@ -507,21 +646,26 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) { // single transparent color - if (trans_color->gray < palette_entries) { + if (trans_color->gray < 256) { BYTE table[256]; - memset(table, 0xFF, palette_entries); + memset(table, 0xFF, 256); table[trans_color->gray] = 0; - FreeImage_SetTransparencyTable(dib, table, palette_entries); + 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 + // store the background color (only supported for FIT_BITMAP types) - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { + 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. @@ -598,7 +742,7 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { // 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); + row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k); } png_set_benign_errors(png_ptr, 1); @@ -644,7 +788,7 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { free(row_pointers); } if (dib) { - FreeImage_Unload(dib); + FreeImage_Unload(dib); } FreeImage_OutputMessageProc(s_format_id, text); @@ -655,6 +799,8 @@ Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { return NULL; } +// -------------------------------------------------------------------------- + static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { png_structp png_ptr; @@ -903,7 +1049,7 @@ Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void // the number of passes is either 1 for non-interlaced images, or 7 for interlaced images for (int pass = 0; pass < number_passes; pass++) { for (png_uint_32 k = 0; k < height; k++) { - FreeImage_ConvertLine32To24(buffer, FreeImage_GetScanLine(dib, height - k - 1), width); + FreeImage_ConvertLine32To24(buffer, FreeImage_GetScanLine(dib, height - k - 1), width); png_write_row(png_ptr, buffer); } } @@ -911,8 +1057,8 @@ Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void } else { // the number of passes is either 1 for non-interlaced images, or 7 for interlaced images for (int pass = 0; pass < number_passes; pass++) { - for (png_uint_32 k = 0; k < height; k++) { - png_write_row(png_ptr, FreeImage_GetScanLine(dib, height - k - 1)); + for (png_uint_32 k = 0; k < height; k++) { + png_write_row(png_ptr, FreeImage_GetScanLine(dib, height - k - 1)); } } } @@ -930,7 +1076,11 @@ Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void png_destroy_write_struct(&png_ptr, &info_ptr); return TRUE; + } catch (const char *text) { + if(png_ptr) { + png_destroy_write_struct(&png_ptr, &info_ptr); + } FreeImage_OutputMessageProc(s_format_id, text); } } |