/** @brief Returns the palette entry used as transparent color for the image specified. Works for palletised images only and returns -1 for high color images or if the image has no color set to be transparent. Although it is possible for palletised images to have more than one transparent color, this function always returns the index of the first palette entry, set to be transparent. @param dib Input image, whose transparent color is to be returned. @return Returns the index of the palette entry used as transparent color for the image specified or -1 if there is no transparent color found (e.g. the image is a high color image). */ int DLL_CALLCONV FreeImage_GetTransparentIndex(FIBITMAP *dib) { int count = FreeImage_GetTransparencyCount(dib); BYTE *tt = FreeImage_GetTransparencyTable(dib); for (int i = 0; i < count; i++) { if (tt[i] == 0) { return i; } } return -1; }
static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { png_structp png_ptr; png_infop info_ptr; png_colorp palette = NULL; png_uint_32 width, height; BOOL has_alpha_channel = FALSE; RGBQUAD *pal; // pointer to dib palette int bit_depth, pixel_depth; // pixel_depth = bit_depth * channels int palette_entries; int interlace_type; fi_ioStructure fio; fio.s_handle = handle; fio.s_io = io; if ((dib) && (handle)) { try { // create the chunk manage structure png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler); if (!png_ptr) { return FALSE; } // allocate/initialize the image information data. info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return FALSE; } // Set error handling. REQUIRED if you aren't supplying your own // error handling functions in the png_create_write_struct() call. if (setjmp(png_jmpbuf(png_ptr))) { // if we get here, we had a problem reading the file png_destroy_write_struct(&png_ptr, &info_ptr); return FALSE; } // init the IO png_set_write_fn(png_ptr, &fio, _WriteProc, _FlushProc); // set physical resolution png_uint_32 res_x = (png_uint_32)FreeImage_GetDotsPerMeterX(dib); png_uint_32 res_y = (png_uint_32)FreeImage_GetDotsPerMeterY(dib); if ((res_x > 0) && (res_y > 0)) { png_set_pHYs(png_ptr, info_ptr, res_x, res_y, PNG_RESOLUTION_METER); } // Set the image information here. Width and height are up to 2^31, // bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on // the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, // PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, // or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or // PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST // currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED width = FreeImage_GetWidth(dib); height = FreeImage_GetHeight(dib); pixel_depth = FreeImage_GetBPP(dib); BOOL bInterlaced = FALSE; if( (flags & PNG_INTERLACED) == PNG_INTERLACED) { interlace_type = PNG_INTERLACE_ADAM7; bInterlaced = TRUE; } else { interlace_type = PNG_INTERLACE_NONE; } // set the ZLIB compression level or default to PNG default compression level (ZLIB level = 6) int zlib_level = flags & 0x0F; if((zlib_level >= 1) && (zlib_level <= 9)) { png_set_compression_level(png_ptr, zlib_level); } else if((flags & PNG_Z_NO_COMPRESSION) == PNG_Z_NO_COMPRESSION) { png_set_compression_level(png_ptr, Z_NO_COMPRESSION); } // filtered strategy works better for high color images if(pixel_depth >= 16){ png_set_compression_strategy(png_ptr, Z_FILTERED); png_set_filter(png_ptr, 0, PNG_FILTER_NONE|PNG_FILTER_SUB|PNG_FILTER_PAETH); } else { png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); } FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); if(image_type == FIT_BITMAP) { // standard image type bit_depth = (pixel_depth > 8) ? 8 : pixel_depth; } else { // 16-bit greyscale or 16-bit RGB(A) bit_depth = 16; } switch (FreeImage_GetColorType(dib)) { case FIC_MINISWHITE: // Invert monochrome files to have 0 as black and 1 as white (no break here) png_set_invert_mono(png_ptr); case FIC_MINISBLACK: png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_GRAY, interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); break; case FIC_PALETTE: { png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_PALETTE, interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); // set the palette palette_entries = 1 << bit_depth; palette = (png_colorp)png_malloc(png_ptr, palette_entries * sizeof (png_color)); pal = FreeImage_GetPalette(dib); for (int i = 0; i < palette_entries; i++) { palette[i].red = pal[i].rgbRed; palette[i].green = pal[i].rgbGreen; palette[i].blue = pal[i].rgbBlue; } png_set_PLTE(png_ptr, info_ptr, palette, palette_entries); // You must not free palette here, because png_set_PLTE only makes a link to // the palette that you malloced. Wait until you are about to destroy // the png structure. break; } case FIC_RGBALPHA : has_alpha_channel = TRUE; png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_RGBA, interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR // flip BGR pixels to RGB if(image_type == FIT_BITMAP) { png_set_bgr(png_ptr); } #endif break; case FIC_RGB: png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_RGB, interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR // flip BGR pixels to RGB if(image_type == FIT_BITMAP) { png_set_bgr(png_ptr); } #endif break; case FIC_CMYK: break; } // write possible ICC profile FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); if (iccProfile->size && iccProfile->data) { png_set_iCCP(png_ptr, info_ptr, "Embedded Profile", 0, (png_const_bytep)iccProfile->data, iccProfile->size); } // write metadata WriteMetadata(png_ptr, info_ptr, dib); // Optional gamma chunk is strongly suggested if you have any guess // as to the correct gamma of the image. // png_set_gAMA(png_ptr, info_ptr, gamma); // set the transparency table if (FreeImage_IsTransparent(dib) && (FreeImage_GetTransparencyCount(dib) > 0)) { png_set_tRNS(png_ptr, info_ptr, FreeImage_GetTransparencyTable(dib), FreeImage_GetTransparencyCount(dib), NULL); } // set the background color if(FreeImage_HasBackgroundColor(dib)) { png_color_16 image_background; RGBQUAD rgbBkColor; FreeImage_GetBackgroundColor(dib, &rgbBkColor); memset(&image_background, 0, sizeof(png_color_16)); image_background.blue = rgbBkColor.rgbBlue; image_background.green = rgbBkColor.rgbGreen; image_background.red = rgbBkColor.rgbRed; image_background.index = rgbBkColor.rgbReserved; png_set_bKGD(png_ptr, info_ptr, &image_background); } // Write the file header information. png_write_info(png_ptr, info_ptr); // write out the image data #ifndef FREEIMAGE_BIGENDIAN if (bit_depth == 16) { // turn on 16 bit byte swapping png_set_swap(png_ptr); } #endif int number_passes = 1; if (bInterlaced) { number_passes = png_set_interlace_handling(png_ptr); } if ((pixel_depth == 32) && (!has_alpha_channel)) { BYTE *buffer = (BYTE *)malloc(width * 3); // transparent conversion to 24-bit // 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); png_write_row(png_ptr, buffer); } } free(buffer); } 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)); } } } // It is REQUIRED to call this to finish writing the rest of the file // Bug with png_flush png_write_end(png_ptr, info_ptr); // clean up after the write, and free any memory allocated if (palette) { png_free(png_ptr, palette); } png_destroy_write_struct(&png_ptr, &info_ptr); return TRUE; } catch (const char *text) { FreeImage_OutputMessageProc(s_format_id, text); } } return FALSE; }
unsigned fipImage::getTransparencyCount() const { return FreeImage_GetTransparencyCount(_dib); }
static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { if( data == NULL ) { return NULL; } GIFinfo *info = (GIFinfo *)data; if( page == -1 ) { page = 0; } if( page < 0 || page >= (int)info->image_descriptor_offsets.size() ) { return NULL; } FIBITMAP *dib = NULL; try { bool have_transparent = false, no_local_palette = false, interlaced = false; int disposal_method = GIF_DISPOSAL_LEAVE, delay_time = 0, transparent_color = 0; WORD left, top, width, height; BYTE packed, b; WORD w; //playback pages to generate what the user would see for this frame if( (flags & GIF_PLAYBACK) == GIF_PLAYBACK ) { //Logical Screen Descriptor io->seek_proc(handle, 6, SEEK_SET); WORD logicalwidth, logicalheight; io->read_proc(&logicalwidth, 2, 1, handle); io->read_proc(&logicalheight, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&logicalwidth); SwapShort(&logicalheight); #endif //set the background color with 0 alpha RGBQUAD background; if( info->global_color_table_offset != 0 && info->background_color < info->global_color_table_size ) { io->seek_proc(handle, info->global_color_table_offset + (info->background_color * 3), SEEK_SET); io->read_proc(&background.rgbRed, 1, 1, handle); io->read_proc(&background.rgbGreen, 1, 1, handle); io->read_proc(&background.rgbBlue, 1, 1, handle); } else { background.rgbRed = 0; background.rgbGreen = 0; background.rgbBlue = 0; } background.rgbReserved = 0; //allocate entire logical area dib = FreeImage_Allocate(logicalwidth, logicalheight, 32); if( dib == NULL ) { throw "DIB allocated failed"; } //fill with background color to start int x, y; RGBQUAD *scanline; for( y = 0; y < logicalheight; y++ ) { scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, y); for( x = 0; x < logicalwidth; x++ ) { *scanline++ = background; } } //cache some info about each of the pages so we can avoid decoding as many of them as possible std::vector<PageInfo> pageinfo; int start = page, end = page; while( start >= 0 ) { //Graphic Control Extension io->seek_proc(handle, info->graphic_control_extension_offsets[start] + 1, SEEK_SET); io->read_proc(&packed, 1, 1, handle); have_transparent = (packed & GIF_PACKED_GCE_HAVETRANS) ? true : false; disposal_method = (packed & GIF_PACKED_GCE_DISPOSAL) >> 2; //Image Descriptor io->seek_proc(handle, info->image_descriptor_offsets[page], SEEK_SET); io->read_proc(&left, 2, 1, handle); io->read_proc(&top, 2, 1, handle); io->read_proc(&width, 2, 1, handle); io->read_proc(&height, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&left); SwapShort(&top); SwapShort(&width); SwapShort(&height); #endif pageinfo.push_back(PageInfo(disposal_method, left, top, width, height)); if( start != end ) { if( left == 0 && top == 0 && width == logicalwidth && height == logicalheight ) { if( disposal_method == GIF_DISPOSAL_BACKGROUND ) { pageinfo.pop_back(); start++; break; } else if( disposal_method != GIF_DISPOSAL_PREVIOUS ) { if( !have_transparent ) { break; } } } } start--; } if( start < 0 ) { start = 0; } //draw each page into the logical area for( page = start; page <= end; page++ ) { PageInfo &info = pageinfo[end - page]; //things we can skip having to decode if( page != end ) { if( info.disposal_method == GIF_DISPOSAL_PREVIOUS ) { continue; } if( info.disposal_method == GIF_DISPOSAL_BACKGROUND ) { for( y = 0; y < info.height; y++ ) { scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, logicalheight - (y + info.top) - 1) + info.left; for( x = 0; x < info.width; x++ ) { *scanline++ = background; } } } } //decode page FIBITMAP *pagedib = Load(io, handle, page, GIF_LOAD256, data); if( pagedib != NULL ) { RGBQUAD *pal = FreeImage_GetPalette(pagedib); have_transparent = false; if( FreeImage_IsTransparent(pagedib) ) { int count = FreeImage_GetTransparencyCount(pagedib); BYTE *table = FreeImage_GetTransparencyTable(pagedib); for( int i = 0; i < count; i++ ) { if( table[i] == 0 ) { have_transparent = true; transparent_color = i; break; } } } //copy page data into logical buffer, with full alpha opaqueness for( y = 0; y < info.height; y++ ) { scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, logicalheight - (y + info.top) - 1) + info.left; BYTE *pageline = FreeImage_GetScanLine(pagedib, info.height - y - 1); for( x = 0; x < info.width; x++ ) { if( !have_transparent || *pageline != transparent_color ) { *scanline = pal[*pageline]; scanline->rgbReserved = 255; } scanline++; pageline++; } } FreeImage_Unload(pagedib); } } return dib; } //get the actual frame image data for a single frame //Image Descriptor io->seek_proc(handle, info->image_descriptor_offsets[page], SEEK_SET); io->read_proc(&left, 2, 1, handle); io->read_proc(&top, 2, 1, handle); io->read_proc(&width, 2, 1, handle); io->read_proc(&height, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&left); SwapShort(&top); SwapShort(&width); SwapShort(&height); #endif io->read_proc(&packed, 1, 1, handle); interlaced = (packed & GIF_PACKED_ID_INTERLACED) ? true : false; no_local_palette = (packed & GIF_PACKED_ID_HAVELCT) ? false : true; int bpp = 8; if( (flags & GIF_LOAD256) == 0 ) { if( !no_local_palette ) { int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE); if( size <= 2 ) bpp = 1; else if( size <= 16 ) bpp = 4; } else if( info->global_color_table_offset != 0 ) { if( info->global_color_table_size <= 2 ) bpp = 1; else if( info->global_color_table_size <= 16 ) bpp = 4; } } dib = FreeImage_Allocate(width, height, bpp); if( dib == NULL ) { throw "DIB allocated failed"; } FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameLeft", ANIMTAG_FRAMELEFT, FIDT_SHORT, 1, 2, &left); FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTop", ANIMTAG_FRAMETOP, FIDT_SHORT, 1, 2, &top); b = no_local_palette ? 1 : 0; FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "NoLocalPalette", ANIMTAG_NOLOCALPALETTE, FIDT_BYTE, 1, 1, &b); b = interlaced ? 1 : 0; FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "Interlaced", ANIMTAG_INTERLACED, FIDT_BYTE, 1, 1, &b); //Palette RGBQUAD *pal = FreeImage_GetPalette(dib); if( !no_local_palette ) { int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE); int i = 0; while( i < size ) { io->read_proc(&pal[i].rgbRed, 1, 1, handle); io->read_proc(&pal[i].rgbGreen, 1, 1, handle); io->read_proc(&pal[i].rgbBlue, 1, 1, handle); i++; } } else if( info->global_color_table_offset != 0 ) { long pos = io->tell_proc(handle); io->seek_proc(handle, info->global_color_table_offset, SEEK_SET); int i = 0; while( i < info->global_color_table_size ) { io->read_proc(&pal[i].rgbRed, 1, 1, handle); io->read_proc(&pal[i].rgbGreen, 1, 1, handle); io->read_proc(&pal[i].rgbBlue, 1, 1, handle); i++; } io->seek_proc(handle, pos, SEEK_SET); } else { //its legal to have no palette, but we're going to generate *something* for( int i = 0; i < 256; i++ ) { pal[i].rgbRed = i; pal[i].rgbGreen = i; pal[i].rgbBlue = i; } } //LZW Minimum Code Size io->read_proc(&b, 1, 1, handle); StringTable stringtable; stringtable.Initialize(b); //Image Data Sub-blocks int x = 0, xpos = 0, y = 0, shift = 8 - bpp, mask = (1 << bpp) - 1, interlacepass = 0; BYTE *scanline = FreeImage_GetScanLine(dib, height - 1); BYTE buf[1024]; io->read_proc(&b, 1, 1, handle); while( b ) { io->read_proc(stringtable.FillInputBuffer(b), b, 1, handle); int size = sizeof(buf); while( stringtable.Decompress(buf, &size) ) { for( int i = 0; i < size; i++ ) { scanline[xpos] |= (buf[i] & mask) << shift; if( shift > 0 ) { shift -= bpp; } else { xpos++; shift = 8 - bpp; } if( ++x >= width ) { if( interlaced ) { y += g_GifInterlaceIncrement[interlacepass]; if( y >= height && interlacepass < GIF_INTERLACE_PASSES ) { y = g_GifInterlaceOffset[++interlacepass]; } } else { y++; } if( y >= height ) { stringtable.Done(); break; } x = xpos = 0; shift = 8 - bpp; scanline = FreeImage_GetScanLine(dib, height - y - 1); } } size = sizeof(buf); } io->read_proc(&b, 1, 1, handle); } if( page == 0 ) { size_t idx; //Logical Screen Descriptor io->seek_proc(handle, 6, SEEK_SET); WORD logicalwidth, logicalheight; io->read_proc(&logicalwidth, 2, 1, handle); io->read_proc(&logicalheight, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&logicalwidth); SwapShort(&logicalheight); #endif FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "LogicalWidth", ANIMTAG_LOGICALWIDTH, FIDT_SHORT, 1, 2, &logicalwidth); FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "LogicalHeight", ANIMTAG_LOGICALHEIGHT, FIDT_SHORT, 1, 2, &logicalheight); //Global Color Table if( info->global_color_table_offset != 0 ) { RGBQUAD globalpalette[256]; io->seek_proc(handle, info->global_color_table_offset, SEEK_SET); int i = 0; while( i < info->global_color_table_size ) { io->read_proc(&globalpalette[i].rgbRed, 1, 1, handle); io->read_proc(&globalpalette[i].rgbGreen, 1, 1, handle); io->read_proc(&globalpalette[i].rgbBlue, 1, 1, handle); globalpalette[i].rgbReserved = 0; i++; } FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "GlobalPalette", ANIMTAG_GLOBALPALETTE, FIDT_PALETTE, info->global_color_table_size, info->global_color_table_size * 4, globalpalette); //background color if( info->background_color < info->global_color_table_size ) { FreeImage_SetBackgroundColor(dib, &globalpalette[info->background_color]); } } //Application Extension LONG loop = 1; //If no AE with a loop count is found, the default must be 1 for( idx = 0; idx < info->application_extension_offsets.size(); idx++ ) { io->seek_proc(handle, info->application_extension_offsets[idx], SEEK_SET); io->read_proc(&b, 1, 1, handle); if( b == 11 ) { //All AEs start with an 11 byte sub-block to determine what type of AE it is char buf[11]; io->read_proc(buf, 11, 1, handle); if( !memcmp(buf, "NETSCAPE2.0", 11) || !memcmp(buf, "ANIMEXTS1.0", 11) ) { //Not everybody recognizes ANIMEXTS1.0 but it is valid io->read_proc(&b, 1, 1, handle); if( b == 3 ) { //we're supposed to have a 3 byte sub-block now io->read_proc(&b, 1, 1, handle); //this should be 0x01 but isn't really important io->read_proc(&w, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&w); #endif loop = w; if( loop > 0 ) loop++; break; } } } } FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "Loop", ANIMTAG_LOOP, FIDT_LONG, 1, 4, &loop); //Comment Extension for( idx = 0; idx < info->comment_extension_offsets.size(); idx++ ) { io->seek_proc(handle, info->comment_extension_offsets[idx], SEEK_SET); std::string comment; char buf[255]; io->read_proc(&b, 1, 1, handle); while( b ) { io->read_proc(buf, b, 1, handle); comment.append(buf, b); io->read_proc(&b, 1, 1, handle); } comment.append(1, '\0'); sprintf(buf, "Comment%d", idx); FreeImage_SetMetadataEx(FIMD_COMMENTS, dib, buf, 1, FIDT_ASCII, comment.size(), comment.size(), comment.c_str()); } } //Graphic Control Extension if( info->graphic_control_extension_offsets[page] != 0 ) { io->seek_proc(handle, info->graphic_control_extension_offsets[page] + 1, SEEK_SET); io->read_proc(&packed, 1, 1, handle); io->read_proc(&w, 2, 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapShort(&w); #endif io->read_proc(&b, 1, 1, handle); have_transparent = (packed & GIF_PACKED_GCE_HAVETRANS) ? true : false; disposal_method = (packed & GIF_PACKED_GCE_DISPOSAL) >> 2; delay_time = w * 10; //convert cs to ms transparent_color = b; if( have_transparent ) { int size = 1 << bpp; if( transparent_color <= size ) { BYTE table[256]; memset(table, 0xFF, size); table[transparent_color] = 0; FreeImage_SetTransparencyTable(dib, table, size); } } }