| // ========================================================== |
| // GIF Loader and Writer |
| // |
| // Design and implementation by |
| // - Ryan Rubley <ryan@lostreality.org> |
| // - Raphaƫl Gaquer <raphael.gaquer@alcer.com> |
| // - Aaron Shumate <aaron@shumate.us> |
| // |
| // This file is part of FreeImage 3 |
| // |
| // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY |
| // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES |
| // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE |
| // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED |
| // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT |
| // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY |
| // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL |
| // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER |
| // THIS DISCLAIMER. |
| // |
| // Use at your own risk! |
| // ========================================================== |
| |
| #ifdef _MSC_VER |
| #pragma warning (disable : 4786) // identifier was truncated to 'number' characters |
| #endif |
| |
| #include "FreeImage.h" |
| #include "Utilities.h" |
| #include "../Metadata/FreeImageTag.h" |
| |
| // ========================================================== |
| // Metadata declarations |
| // ========================================================== |
| |
| #define GIF_DISPOSAL_UNSPECIFIED 0 |
| #define GIF_DISPOSAL_LEAVE 1 |
| #define GIF_DISPOSAL_BACKGROUND 2 |
| #define GIF_DISPOSAL_PREVIOUS 3 |
| |
| // ========================================================== |
| // Constant/Typedef declarations |
| // ========================================================== |
| |
| |
| struct GIFinfo { |
| BOOL read; |
| //only really used when reading |
| size_t global_color_table_offset; |
| int global_color_table_size; |
| BYTE background_color; |
| std::vector<size_t> application_extension_offsets; |
| std::vector<size_t> comment_extension_offsets; |
| std::vector<size_t> graphic_control_extension_offsets; |
| std::vector<size_t> image_descriptor_offsets; |
| |
| GIFinfo() : read(0), global_color_table_offset(0), global_color_table_size(0), background_color(0) |
| { |
| } |
| }; |
| |
| struct PageInfo { |
| PageInfo(int d, int l, int t, int w, int h) { |
| disposal_method = d; left = (WORD)l; top = (WORD)t; width = (WORD)w; height = (WORD)h; |
| } |
| int disposal_method; |
| WORD left, top, width, height; |
| }; |
| |
| //GIF defines a max of 12 bits per code |
| #define MAX_LZW_CODE 4096 |
| |
| // This change is for building for SketchUp on Linux. We don't want to use |
| // StringTable because it conflicts with a StringTable elsewhere, so I typedef |
| // To FreeImage_StringTable. |
| #ifdef USE_THIRD_PARTY_TIFF |
| #define StringTable FreeImage_StringTable |
| #endif |
| |
| class StringTable |
| { |
| public: |
| StringTable(); |
| ~StringTable(); |
| void Initialize(int minCodeSize); |
| BYTE *FillInputBuffer(int len); |
| void CompressStart(int bpp, int width); |
| int CompressEnd(BYTE *buf); //0-4 bytes |
| bool Compress(BYTE *buf, int *len); |
| bool Decompress(BYTE *buf, int *len); |
| void Done(void); |
| |
| protected: |
| bool m_done; |
| |
| int m_minCodeSize, m_clearCode, m_endCode, m_nextCode; |
| |
| int m_bpp, m_slack; //Compressor information |
| |
| int m_prefix; //Compressor state variable |
| int m_codeSize, m_codeMask; //Compressor/Decompressor state variables |
| int m_oldCode; //Decompressor state variable |
| int m_partial, m_partialSize; //Compressor/Decompressor bit buffer |
| |
| int firstPixelPassed; // A specific flag that indicates if the first pixel |
| // of the whole image had already been read |
| |
| std::string m_strings[MAX_LZW_CODE]; //This is what is really the "string table" data for the Decompressor |
| int* m_strmap; |
| |
| //input buffer |
| BYTE *m_buffer; |
| int m_bufferSize, m_bufferRealSize, m_bufferPos, m_bufferShift; |
| |
| void ClearCompressorTable(void); |
| void ClearDecompressorTable(void); |
| }; |
| |
| #define GIF_PACKED_LSD_HAVEGCT 0x80 |
| #define GIF_PACKED_LSD_COLORRES 0x70 |
| #define GIF_PACKED_LSD_GCTSORTED 0x08 |
| #define GIF_PACKED_LSD_GCTSIZE 0x07 |
| #define GIF_PACKED_ID_HAVELCT 0x80 |
| #define GIF_PACKED_ID_INTERLACED 0x40 |
| #define GIF_PACKED_ID_LCTSORTED 0x20 |
| #define GIF_PACKED_ID_RESERVED 0x18 |
| #define GIF_PACKED_ID_LCTSIZE 0x07 |
| #define GIF_PACKED_GCE_RESERVED 0xE0 |
| #define GIF_PACKED_GCE_DISPOSAL 0x1C |
| #define GIF_PACKED_GCE_WAITINPUT 0x02 |
| #define GIF_PACKED_GCE_HAVETRANS 0x01 |
| |
| #define GIF_BLOCK_IMAGE_DESCRIPTOR 0x2C |
| #define GIF_BLOCK_EXTENSION 0x21 |
| #define GIF_BLOCK_TRAILER 0x3B |
| |
| #define GIF_EXT_PLAINTEXT 0x01 |
| #define GIF_EXT_GRAPHIC_CONTROL 0xF9 |
| #define GIF_EXT_COMMENT 0xFE |
| #define GIF_EXT_APPLICATION 0xFF |
| |
| #define GIF_INTERLACE_PASSES 4 |
| static int g_GifInterlaceOffset[GIF_INTERLACE_PASSES] = {0, 4, 2, 1}; |
| static int g_GifInterlaceIncrement[GIF_INTERLACE_PASSES] = {8, 8, 4, 2}; |
| |
| // ========================================================== |
| // Helpers Functions |
| // ========================================================== |
| |
| static BOOL |
| FreeImage_SetMetadataEx(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, WORD id, FREE_IMAGE_MDTYPE type, DWORD count, DWORD length, const void *value) |
| { |
| BOOL bResult = FALSE; |
| FITAG *tag = FreeImage_CreateTag(); |
| if(tag) { |
| FreeImage_SetTagKey(tag, key); |
| FreeImage_SetTagID(tag, id); |
| FreeImage_SetTagType(tag, type); |
| FreeImage_SetTagCount(tag, count); |
| FreeImage_SetTagLength(tag, length); |
| FreeImage_SetTagValue(tag, value); |
| if(model == FIMD_ANIMATION) { |
| TagLib& s = TagLib::instance(); |
| // get the tag description |
| const char *description = s.getTagDescription(TagLib::ANIMATION, id); |
| FreeImage_SetTagDescription(tag, description); |
| } |
| // store the tag |
| bResult = FreeImage_SetMetadata(model, dib, key, tag); |
| FreeImage_DeleteTag(tag); |
| } |
| return bResult; |
| } |
| |
| static BOOL |
| FreeImage_GetMetadataEx(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, FREE_IMAGE_MDTYPE type, FITAG **tag) |
| { |
| if( FreeImage_GetMetadata(model, dib, key, tag) ) { |
| if( FreeImage_GetTagType(*tag) == type ) { |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| StringTable::StringTable() |
| { |
| m_buffer = NULL; |
| firstPixelPassed = 0; // Still no pixel read |
| // Maximum number of entries in the map is MAX_LZW_CODE * 256 |
| // (aka 2**12 * 2**8 => a 20 bits key) |
| // This Map could be optmized to only handle MAX_LZW_CODE * 2**(m_bpp) |
| m_strmap = new(std::nothrow) int[1<<20]; |
| } |
| |
| StringTable::~StringTable() |
| { |
| if( m_buffer != NULL ) { |
| delete [] m_buffer; |
| } |
| if( m_strmap != NULL ) { |
| delete [] m_strmap; |
| m_strmap = NULL; |
| } |
| } |
| |
| void StringTable::Initialize(int minCodeSize) |
| { |
| m_done = false; |
| |
| m_bpp = 8; |
| m_minCodeSize = minCodeSize; |
| m_clearCode = 1 << m_minCodeSize; |
| if(m_clearCode > MAX_LZW_CODE) { |
| m_clearCode = MAX_LZW_CODE; |
| } |
| m_endCode = m_clearCode + 1; |
| |
| m_partial = 0; |
| m_partialSize = 0; |
| |
| m_bufferSize = 0; |
| ClearCompressorTable(); |
| ClearDecompressorTable(); |
| } |
| |
| BYTE *StringTable::FillInputBuffer(int len) |
| { |
| if( m_buffer == NULL ) { |
| m_buffer = new(std::nothrow) BYTE[len]; |
| m_bufferRealSize = len; |
| } else if( len > m_bufferRealSize ) { |
| delete [] m_buffer; |
| m_buffer = new(std::nothrow) BYTE[len]; |
| m_bufferRealSize = len; |
| } |
| m_bufferSize = len; |
| m_bufferPos = 0; |
| m_bufferShift = 8 - m_bpp; |
| return m_buffer; |
| } |
| |
| void StringTable::CompressStart(int bpp, int width) |
| { |
| m_bpp = bpp; |
| m_slack = (8 - ((width * bpp) % 8)) % 8; |
| |
| m_partial |= m_clearCode << m_partialSize; |
| m_partialSize += m_codeSize; |
| ClearCompressorTable(); |
| } |
| |
| int StringTable::CompressEnd(BYTE *buf) |
| { |
| int len = 0; |
| |
| //output code for remaining prefix |
| m_partial |= m_prefix << m_partialSize; |
| m_partialSize += m_codeSize; |
| while( m_partialSize >= 8 ) { |
| *buf++ = (BYTE)m_partial; |
| m_partial >>= 8; |
| m_partialSize -= 8; |
| len++; |
| } |
| |
| //add the end of information code and flush the entire buffer out |
| m_partial |= m_endCode << m_partialSize; |
| m_partialSize += m_codeSize; |
| while( m_partialSize > 0 ) { |
| *buf++ = (BYTE)m_partial; |
| m_partial >>= 8; |
| m_partialSize -= 8; |
| len++; |
| } |
| |
| //most this can be is 4 bytes. 7 bits in m_partial to start + 12 for the |
| //last code + 12 for the end code = 31 bits total. |
| return len; |
| } |
| |
| bool StringTable::Compress(BYTE *buf, int *len) |
| { |
| if( m_bufferSize == 0 || m_done ) { |
| return false; |
| } |
| |
| int mask = (1 << m_bpp) - 1; |
| BYTE *bufpos = buf; |
| while( m_bufferPos < m_bufferSize ) { |
| //get the current pixel value |
| char ch = (char)((m_buffer[m_bufferPos] >> m_bufferShift) & mask); |
| |
| // The next prefix is : |
| // <the previous LZW code (on 12 bits << 8)> | <the code of the current pixel (on 8 bits)> |
| int nextprefix = (((m_prefix)<<8)&0xFFF00) + (ch & 0x000FF); |
| if(firstPixelPassed) { |
| |
| if( m_strmap[nextprefix] > 0) { |
| m_prefix = m_strmap[nextprefix]; |
| } else { |
| m_partial |= m_prefix << m_partialSize; |
| m_partialSize += m_codeSize; |
| //grab full bytes for the output buffer |
| while( m_partialSize >= 8 && bufpos - buf < *len ) { |
| *bufpos++ = (BYTE)m_partial; |
| m_partial >>= 8; |
| m_partialSize -= 8; |
| } |
| |
| //add the code to the "table map" |
| m_strmap[nextprefix] = m_nextCode; |
| |
| //increment the next highest valid code, increase the code size |
| if( m_nextCode == (1 << m_codeSize) ) { |
| m_codeSize++; |
| } |
| m_nextCode++; |
| |
| //if we're out of codes, restart the string table |
| if( m_nextCode == MAX_LZW_CODE ) { |
| m_partial |= m_clearCode << m_partialSize; |
| m_partialSize += m_codeSize; |
| ClearCompressorTable(); |
| } |
| |
| // Only keep the 8 lowest bits (prevent problems with "negative chars") |
| m_prefix = ch & 0x000FF; |
| } |
| |
| //increment to the next pixel |
| if( m_bufferShift > 0 && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack) ) { |
| m_bufferShift -= m_bpp; |
| } else { |
| m_bufferPos++; |
| m_bufferShift = 8 - m_bpp; |
| } |
| |
| //jump out here if the output buffer is full |
| if( bufpos - buf == *len ) { |
| return true; |
| } |
| |
| } else { |
| // Specific behavior for the first pixel of the whole image |
| |
| firstPixelPassed=1; |
| // Only keep the 8 lowest bits (prevent problems with "negative chars") |
| m_prefix = ch & 0x000FF; |
| |
| //increment to the next pixel |
| if( m_bufferShift > 0 && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack) ) { |
| m_bufferShift -= m_bpp; |
| } else { |
| m_bufferPos++; |
| m_bufferShift = 8 - m_bpp; |
| } |
| |
| //jump out here if the output buffer is full |
| if( bufpos - buf == *len ) { |
| return true; |
| } |
| } |
| } |
| |
| m_bufferSize = 0; |
| *len = (int)(bufpos - buf); |
| |
| return true; |
| } |
| |
| bool StringTable::Decompress(BYTE *buf, int *len) |
| { |
| if( m_bufferSize == 0 || m_done ) { |
| return false; |
| } |
| |
| BYTE *bufpos = buf; |
| for( ; m_bufferPos < m_bufferSize; m_bufferPos++ ) { |
| m_partial |= (int)m_buffer[m_bufferPos] << m_partialSize; |
| m_partialSize += 8; |
| while( m_partialSize >= m_codeSize ) { |
| int code = m_partial & m_codeMask; |
| m_partial >>= m_codeSize; |
| m_partialSize -= m_codeSize; |
| |
| if( code > m_nextCode || /*(m_nextCode == MAX_LZW_CODE && code != m_clearCode) || */code == m_endCode ) { |
| m_done = true; |
| *len = (int)(bufpos - buf); |
| return true; |
| } |
| if( code == m_clearCode ) { |
| ClearDecompressorTable(); |
| continue; |
| } |
| |
| //add new string to string table, if not the first pass since a clear code |
| if( m_oldCode != MAX_LZW_CODE && m_nextCode < MAX_LZW_CODE) { |
| m_strings[m_nextCode] = m_strings[m_oldCode] + m_strings[code == m_nextCode ? m_oldCode : code][0]; |
| } |
| |
| if( (int)m_strings[code].size() > *len - (bufpos - buf) ) { |
| //out of space, stuff the code back in for next time |
| m_partial <<= m_codeSize; |
| m_partialSize += m_codeSize; |
| m_partial |= code; |
| m_bufferPos++; |
| *len = (int)(bufpos - buf); |
| return true; |
| } |
| |
| //output the string into the buffer |
| memcpy(bufpos, m_strings[code].data(), m_strings[code].size()); |
| bufpos += m_strings[code].size(); |
| |
| //increment the next highest valid code, add a bit to the mask if we need to increase the code size |
| if( m_oldCode != MAX_LZW_CODE && m_nextCode < MAX_LZW_CODE ) { |
| if( ++m_nextCode < MAX_LZW_CODE ) { |
| if( (m_nextCode & m_codeMask) == 0 ) { |
| m_codeSize++; |
| m_codeMask |= m_nextCode; |
| } |
| } |
| } |
| |
| m_oldCode = code; |
| } |
| } |
| |
| m_bufferSize = 0; |
| *len = (int)(bufpos - buf); |
| |
| return true; |
| } |
| |
| void StringTable::Done(void) |
| { |
| m_done = true; |
| } |
| |
| void StringTable::ClearCompressorTable(void) |
| { |
| if(m_strmap) { |
| memset(m_strmap, 0xFF, sizeof(unsigned int)*(1<<20)); |
| } |
| m_nextCode = m_endCode + 1; |
| |
| m_prefix = 0; |
| m_codeSize = m_minCodeSize + 1; |
| } |
| |
| void StringTable::ClearDecompressorTable(void) |
| { |
| for( int i = 0; i < m_clearCode; i++ ) { |
| m_strings[i].resize(1); |
| m_strings[i][0] = (char)i; |
| } |
| m_nextCode = m_endCode + 1; |
| |
| m_codeSize = m_minCodeSize + 1; |
| m_codeMask = (1 << m_codeSize) - 1; |
| m_oldCode = MAX_LZW_CODE; |
| } |
| |
| // ========================================================== |
| // Plugin Interface |
| // ========================================================== |
| |
| static int s_format_id; |
| |
| // ========================================================== |
| // Plugin Implementation |
| // ========================================================== |
| |
| static const char * DLL_CALLCONV |
| Format() { |
| return "GIF"; |
| } |
| |
| static const char * DLL_CALLCONV |
| Description() { |
| return "Graphics Interchange Format"; |
| } |
| |
| static const char * DLL_CALLCONV |
| Extension() { |
| return "gif"; |
| } |
| |
| static const char * DLL_CALLCONV |
| RegExpr() { |
| return "^GIF"; |
| } |
| |
| static const char * DLL_CALLCONV |
| MimeType() { |
| return "image/gif"; |
| } |
| |
| static BOOL DLL_CALLCONV |
| Validate(FreeImageIO *io, fi_handle handle) { |
| char buf[6]; |
| if( io->read_proc(buf, 6, 1, handle) < 1 ) { |
| return FALSE; |
| } |
| |
| BOOL bResult = FALSE; |
| if( !strncmp(buf, "GIF", 3) ) { |
| if( buf[3] >= '0' && buf[3] <= '9' && buf[4] >= '0' && buf[4] <= '9' && buf[5] >= 'a' && buf[5] <= 'z' ) { |
| bResult = TRUE; |
| } |
| } |
| |
| io->seek_proc(handle, -6, SEEK_CUR); |
| |
| return bResult; |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsExportDepth(int depth) { |
| return (depth == 1) || |
| (depth == 4) || |
| (depth == 8); |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsExportType(FREE_IMAGE_TYPE type) { |
| return (type == FIT_BITMAP) ? TRUE : FALSE; |
| } |
| |
| // ---------------------------------------------------------- |
| |
| static void *DLL_CALLCONV |
| Open(FreeImageIO *io, fi_handle handle, BOOL read) { |
| GIFinfo *info = new(std::nothrow) GIFinfo; |
| if( info == NULL ) { |
| return NULL; |
| } |
| |
| // 25/02/2008 MDA: Not safe to memset GIFinfo structure with VS 2008 (safe iterators), |
| // perform initialization in constructor instead. |
| // memset(info, 0, sizeof(GIFinfo)); |
| |
| info->read = read; |
| if( read ) { |
| try { |
| //Header |
| if( !Validate(io, handle) ) { |
| throw FI_MSG_ERROR_MAGIC_NUMBER; |
| } |
| io->seek_proc(handle, 6, SEEK_CUR); |
| |
| //Logical Screen Descriptor |
| io->seek_proc(handle, 4, SEEK_CUR); |
| BYTE packed; |
| if( io->read_proc(&packed, 1, 1, handle) < 1 ) { |
| throw "EOF reading Logical Screen Descriptor"; |
| } |
| if( io->read_proc(&info->background_color, 1, 1, handle) < 1 ) { |
| throw "EOF reading Logical Screen Descriptor"; |
| } |
| io->seek_proc(handle, 1, SEEK_CUR); |
| |
| //Global Color Table |
| if( packed & GIF_PACKED_LSD_HAVEGCT ) { |
| info->global_color_table_offset = io->tell_proc(handle); |
| info->global_color_table_size = 2 << (packed & GIF_PACKED_LSD_GCTSIZE); |
| io->seek_proc(handle, 3 * info->global_color_table_size, SEEK_CUR); |
| } |
| |
| //Scan through all the rest of the blocks, saving offsets |
| size_t gce_offset = 0; |
| BYTE block = 0; |
| while( block != GIF_BLOCK_TRAILER ) { |
| if( io->read_proc(&block, 1, 1, handle) < 1 ) { |
| throw "EOF reading blocks"; |
| } |
| if( block == GIF_BLOCK_IMAGE_DESCRIPTOR ) { |
| info->image_descriptor_offsets.push_back(io->tell_proc(handle)); |
| //GCE may be 0, meaning no GCE preceded this ID |
| info->graphic_control_extension_offsets.push_back(gce_offset); |
| gce_offset = 0; |
| |
| io->seek_proc(handle, 8, SEEK_CUR); |
| if( io->read_proc(&packed, 1, 1, handle) < 1 ) { |
| throw "EOF reading Image Descriptor"; |
| } |
| |
| //Local Color Table |
| if( packed & GIF_PACKED_ID_HAVELCT ) { |
| io->seek_proc(handle, 3 * (2 << (packed & GIF_PACKED_ID_LCTSIZE)), SEEK_CUR); |
| } |
| |
| //LZW Minimum Code Size |
| io->seek_proc(handle, 1, SEEK_CUR); |
| } else if( block == GIF_BLOCK_EXTENSION ) { |
| BYTE ext; |
| if( io->read_proc(&ext, 1, 1, handle) < 1 ) { |
| throw "EOF reading extension"; |
| } |
| |
| if( ext == GIF_EXT_GRAPHIC_CONTROL ) { |
| //overwrite previous offset if more than one GCE found before an ID |
| gce_offset = io->tell_proc(handle); |
| } else if( ext == GIF_EXT_COMMENT ) { |
| info->comment_extension_offsets.push_back(io->tell_proc(handle)); |
| } else if( ext == GIF_EXT_APPLICATION ) { |
| info->application_extension_offsets.push_back(io->tell_proc(handle)); |
| } |
| } else if( block == GIF_BLOCK_TRAILER ) { |
| continue; |
| } else { |
| throw "Invalid GIF block found"; |
| } |
| |
| //Data Sub-blocks |
| BYTE len; |
| if( io->read_proc(&len, 1, 1, handle) < 1 ) { |
| throw "EOF reading sub-block"; |
| } |
| while( len != 0 ) { |
| io->seek_proc(handle, len, SEEK_CUR); |
| if( io->read_proc(&len, 1, 1, handle) < 1 ) { |
| throw "EOF reading sub-block"; |
| } |
| } |
| } |
| } catch (const char *msg) { |
| FreeImage_OutputMessageProc(s_format_id, msg); |
| delete info; |
| return NULL; |
| } |
| } else { |
| //Header |
| io->write_proc((void *)"GIF89a", 6, 1, handle); |
| } |
| |
| return info; |
| } |
| |
| static void DLL_CALLCONV |
| Close(FreeImageIO *io, fi_handle handle, void *data) { |
| if( data == NULL ) { |
| return; |
| } |
| GIFinfo *info = (GIFinfo *)data; |
| |
| if( !info->read ) { |
| //Trailer |
| BYTE b = GIF_BLOCK_TRAILER; |
| io->write_proc(&b, 1, 1, handle); |
| } |
| |
| delete info; |
| } |
| |
| static int DLL_CALLCONV |
| PageCount(FreeImageIO *io, fi_handle handle, void *data) { |
| if( data == NULL ) { |
| return 0; |
| } |
| GIFinfo *info = (GIFinfo *)data; |
| |
| return (int) info->image_descriptor_offsets.size(); |
| } |
| |
| 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, (long)(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 FI_MSG_ERROR_DIB_MEMORY; |
| } |
| |
| //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, (long)(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, (long)(info->image_descriptor_offsets[start]), 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 |
| delay_time = 0; |
| 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++ ) { |
| const int scanidx = logicalheight - (y + info.top) - 1; |
| if ( scanidx < 0 ) { |
| break; // If data is corrupt, don't calculate in invalid scanline |
| } |
| scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, scanidx) + info.left; |
| for( x = 0; x < info.width; x++ ) { |
| *scanline++ = background; |
| } |
| } |
| continue; |
| } |
| } |
| |
| //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++ ) { |
| const int scanidx = logicalheight - (y + info.top) - 1; |
| if ( scanidx < 0 ) { |
| break; // If data is corrupt, don't calculate in invalid scanline |
| } |
| scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, scanidx) + 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++; |
| } |
| } |
| //copy frame time |
| if( page == end ) { |
| FITAG *tag; |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, pagedib, "FrameTime", FIDT_LONG, &tag) ) { |
| delay_time = *(LONG *)FreeImage_GetTagValue(tag); |
| } |
| } |
| FreeImage_Unload(pagedib); |
| } |
| } |
| |
| //setup frame time |
| FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", ANIMTAG_FRAMETIME, FIDT_LONG, 1, 4, &delay_time); |
| return dib; |
| } |
| |
| //get the actual frame image data for a single frame |
| |
| //Image Descriptor |
| io->seek_proc(handle, (long)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 FI_MSG_ERROR_DIB_MEMORY; |
| } |
| |
| 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, (long)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 = (BYTE)i; |
| pal[i].rgbGreen = (BYTE)i; |
| pal[i].rgbBlue = (BYTE)i; |
| } |
| } |
| |
| //LZW Minimum Code Size |
| io->read_proc(&b, 1, 1, handle); |
| StringTable *stringtable = new(std::nothrow) 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[4096]; |
| 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, (long)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, (long)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, (long)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); |
| DWORD comment_size = (DWORD)comment.size(); |
| 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, (long)(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); |
| } |
| } |
| } |
| FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", ANIMTAG_FRAMETIME, FIDT_LONG, 1, 4, &delay_time); |
| b = (BYTE)disposal_method; |
| FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "DisposalMethod", ANIMTAG_DISPOSALMETHOD, FIDT_BYTE, 1, 1, &b); |
| |
| delete stringtable; |
| |
| } catch (const char *msg) { |
| if( dib != NULL ) { |
| FreeImage_Unload(dib); |
| } |
| FreeImage_OutputMessageProc(s_format_id, msg); |
| return NULL; |
| } |
| |
| return dib; |
| } |
| |
| static BOOL DLL_CALLCONV |
| Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { |
| if( data == NULL ) { |
| return FALSE; |
| } |
| //GIFinfo *info = (GIFinfo *)data; |
| |
| if( page == -1 ) { |
| page = 0; |
| } |
| |
| try { |
| BYTE packed, b; |
| WORD w; |
| FITAG *tag; |
| |
| int bpp = FreeImage_GetBPP(dib); |
| if( bpp != 1 && bpp != 4 && bpp != 8 ) { |
| throw "Only 1, 4, or 8 bpp images supported"; |
| } |
| |
| bool have_transparent = false, no_local_palette = false, interlaced = false; |
| int disposal_method = GIF_DISPOSAL_BACKGROUND, delay_time = 100, transparent_color = 0; |
| WORD left = 0, top = 0, width = (WORD)FreeImage_GetWidth(dib), height = (WORD)FreeImage_GetHeight(dib); |
| WORD output_height = height; |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameLeft", FIDT_SHORT, &tag) ) { |
| left = *(WORD *)FreeImage_GetTagValue(tag); |
| } |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameTop", FIDT_SHORT, &tag) ) { |
| top = *(WORD *)FreeImage_GetTagValue(tag); |
| } |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "NoLocalPalette", FIDT_BYTE, &tag) ) { |
| no_local_palette = *(BYTE *)FreeImage_GetTagValue(tag) ? true : false; |
| } |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "Interlaced", FIDT_BYTE, &tag) ) { |
| interlaced = *(BYTE *)FreeImage_GetTagValue(tag) ? true : false; |
| } |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", FIDT_LONG, &tag) ) { |
| delay_time = *(LONG *)FreeImage_GetTagValue(tag); |
| } |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "DisposalMethod", FIDT_BYTE, &tag) ) { |
| disposal_method = *(BYTE *)FreeImage_GetTagValue(tag); |
| } |
| |
| RGBQUAD *pal = FreeImage_GetPalette(dib); |
| #ifdef FREEIMAGE_BIGENDIAN |
| SwapShort(&left); |
| SwapShort(&top); |
| SwapShort(&width); |
| SwapShort(&height); |
| #endif |
| |
| if( page == 0 ) { |
| //gather some info |
| WORD logicalwidth = width; // width has already been swapped... |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "LogicalWidth", FIDT_SHORT, &tag) ) { |
| logicalwidth = *(WORD *)FreeImage_GetTagValue(tag); |
| #ifdef FREEIMAGE_BIGENDIAN |
| SwapShort(&logicalwidth); |
| #endif |
| } |
| WORD logicalheight = height; // height has already been swapped... |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "LogicalHeight", FIDT_SHORT, &tag) ) { |
| logicalheight = *(WORD *)FreeImage_GetTagValue(tag); |
| #ifdef FREEIMAGE_BIGENDIAN |
| SwapShort(&logicalheight); |
| #endif |
| } |
| RGBQUAD *globalpalette = NULL; |
| int globalpalette_size = 0; |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "GlobalPalette", FIDT_PALETTE, &tag) ) { |
| globalpalette_size = FreeImage_GetTagCount(tag); |
| if( globalpalette_size >= 2 ) { |
| globalpalette = (RGBQUAD *)FreeImage_GetTagValue(tag); |
| } |
| } |
| |
| //Logical Screen Descriptor |
| io->write_proc(&logicalwidth, 2, 1, handle); |
| io->write_proc(&logicalheight, 2, 1, handle); |
| packed = GIF_PACKED_LSD_COLORRES; |
| b = 0; |
| RGBQUAD background_color; |
| if( globalpalette != NULL ) { |
| packed |= GIF_PACKED_LSD_HAVEGCT; |
| if( globalpalette_size < 4 ) { |
| globalpalette_size = 2; |
| packed |= 0 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 8 ) { |
| globalpalette_size = 4; |
| packed |= 1 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 16 ) { |
| globalpalette_size = 8; |
| packed |= 2 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 32 ) { |
| globalpalette_size = 16; |
| packed |= 3 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 64 ) { |
| globalpalette_size = 32; |
| packed |= 4 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 128 ) { |
| globalpalette_size = 64; |
| packed |= 5 & GIF_PACKED_LSD_GCTSIZE; |
| } else if( globalpalette_size < 256 ) { |
| globalpalette_size = 128; |
| packed |= 6 & GIF_PACKED_LSD_GCTSIZE; |
| } else { |
| globalpalette_size = 256; |
| packed |= 7 & GIF_PACKED_LSD_GCTSIZE; |
| } |
| if( FreeImage_GetBackgroundColor(dib, &background_color) ) { |
| for( int i = 0; i < globalpalette_size; i++ ) { |
| if( background_color.rgbRed == globalpalette[i].rgbRed && |
| background_color.rgbGreen == globalpalette[i].rgbGreen && |
| background_color.rgbBlue == globalpalette[i].rgbBlue ) { |
| |
| b = (BYTE)i; |
| break; |
| } |
| } |
| } |
| } else { |
| packed |= (bpp - 1) & GIF_PACKED_LSD_GCTSIZE; |
| } |
| io->write_proc(&packed, 1, 1, handle); |
| io->write_proc(&b, 1, 1, handle); |
| b = 0; |
| io->write_proc(&b, 1, 1, handle); |
| |
| //Global Color Table |
| if( globalpalette != NULL ) { |
| int i = 0; |
| while( i < globalpalette_size ) { |
| io->write_proc(&globalpalette[i].rgbRed, 1, 1, handle); |
| io->write_proc(&globalpalette[i].rgbGreen, 1, 1, handle); |
| io->write_proc(&globalpalette[i].rgbBlue, 1, 1, handle); |
| i++; |
| } |
| } |
| |
| //Application Extension |
| LONG loop = 0; |
| if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "Loop", FIDT_LONG, &tag) ) { |
| loop = *(LONG *)FreeImage_GetTagValue(tag); |
| } |
| if( loop != 1 ) { |
| //the Netscape extension is really "repeats" not "loops" |
| if( loop > 1 ) loop--; |
| if( loop > 0xFFFF ) loop = 0xFFFF; |
| w = (WORD)loop; |
| #ifdef FREEIMAGE_BIGENDIAN |
| SwapShort(&w); |
| #endif |
| io->write_proc((void *)"\x21\xFF\x0BNETSCAPE2.0\x03\x01", 16, 1, handle); |
| io->write_proc(&w, 2, 1, handle); |
| b = 0; |
| io->write_proc(&b, 1, 1, handle); |
| } |
| |
| //Comment Extension |
| FIMETADATA *mdhandle = NULL; |
| FITAG *tag = NULL; |
| mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag); |
| if( mdhandle ) { |
| do { |
| if( FreeImage_GetTagType(tag) == FIDT_ASCII ) { |
| int length = FreeImage_GetTagLength(tag) - 1; |
| char *value = (char *)FreeImage_GetTagValue(tag); |
| io->write_proc((void *)"\x21\xFE", 2, 1, handle); |
| while( length > 0 ) { |
| b = (BYTE)(length >= 255 ? 255 : length); |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(value, b, 1, handle); |
| value += b; |
| length -= b; |
| } |
| b = 0; |
| io->write_proc(&b, 1, 1, handle); |
| } |
| } while(FreeImage_FindNextMetadata(mdhandle, &tag)); |
| |
| FreeImage_FindCloseMetadata(mdhandle); |
| } |
| } |
| |
| //Graphic Control Extension |
| if( FreeImage_IsTransparent(dib) ) { |
| int count = FreeImage_GetTransparencyCount(dib); |
| BYTE *table = FreeImage_GetTransparencyTable(dib); |
| for( int i = 0; i < count; i++ ) { |
| if( table[i] == 0 ) { |
| have_transparent = true; |
| transparent_color = i; |
| break; |
| } |
| } |
| } |
| io->write_proc((void *)"\x21\xF9\x04", 3, 1, handle); |
| b = (BYTE)((disposal_method << 2) & GIF_PACKED_GCE_DISPOSAL); |
| if( have_transparent ) b |= GIF_PACKED_GCE_HAVETRANS; |
| io->write_proc(&b, 1, 1, handle); |
| //Notes about delay time for GIFs: |
| //IE5/IE6 have a minimum and default of 100ms |
| //Mozilla/Firefox/Netscape 6+/Opera have a minimum of 20ms and a default of 100ms if <20ms is specified or the GCE is absent |
| //Netscape 4 has a minimum of 10ms if 0ms is specified, but will use 0ms if the GCE is absent |
| w = (WORD)(delay_time / 10); //convert ms to cs |
| #ifdef FREEIMAGE_BIGENDIAN |
| SwapShort(&w); |
| #endif |
| io->write_proc(&w, 2, 1, handle); |
| b = (BYTE)transparent_color; |
| io->write_proc(&b, 1, 1, handle); |
| b = 0; |
| io->write_proc(&b, 1, 1, handle); |
| |
| //Image Descriptor |
| b = GIF_BLOCK_IMAGE_DESCRIPTOR; |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(&left, 2, 1, handle); |
| io->write_proc(&top, 2, 1, handle); |
| io->write_proc(&width, 2, 1, handle); |
| io->write_proc(&height, 2, 1, handle); |
| packed = 0; |
| if( !no_local_palette ) packed |= GIF_PACKED_ID_HAVELCT | ((bpp - 1) & GIF_PACKED_ID_LCTSIZE); |
| if( interlaced ) packed |= GIF_PACKED_ID_INTERLACED; |
| io->write_proc(&packed, 1, 1, handle); |
| |
| //Local Color Table |
| if( !no_local_palette ) { |
| int palsize = 1 << bpp; |
| for( int i = 0; i < palsize; i++ ) { |
| io->write_proc(&pal[i].rgbRed, 1, 1, handle); |
| io->write_proc(&pal[i].rgbGreen, 1, 1, handle); |
| io->write_proc(&pal[i].rgbBlue, 1, 1, handle); |
| } |
| } |
| |
| |
| //LZW Minimum Code Size |
| b = (BYTE)(bpp == 1 ? 2 : bpp); |
| io->write_proc(&b, 1, 1, handle); |
| StringTable *stringtable = new(std::nothrow) StringTable; |
| stringtable->Initialize(b); |
| stringtable->CompressStart(bpp, width); |
| |
| //Image Data Sub-blocks |
| int y = 0, interlacepass = 0, line = FreeImage_GetLine(dib); |
| BYTE buf[255], *bufptr = buf; //255 is the max sub-block length |
| int size = sizeof(buf); |
| b = sizeof(buf); |
| while( y < output_height ) { |
| memcpy(stringtable->FillInputBuffer(line), FreeImage_GetScanLine(dib, output_height - y - 1), line); |
| while( stringtable->Compress(bufptr, &size) ) { |
| bufptr += size; |
| if( bufptr - buf == sizeof(buf) ) { |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(buf, sizeof(buf), 1, handle); |
| size = sizeof(buf); |
| bufptr = buf; |
| } else { |
| size = (int)(sizeof(buf) - (bufptr - buf)); |
| } |
| } |
| if( interlaced ) { |
| y += g_GifInterlaceIncrement[interlacepass]; |
| if( y >= output_height && ++interlacepass < GIF_INTERLACE_PASSES ) { |
| y = g_GifInterlaceOffset[interlacepass]; |
| } |
| } else { |
| y++; |
| } |
| } |
| size = (int)(bufptr - buf); |
| BYTE last[4]; |
| w = (WORD)stringtable->CompressEnd(last); |
| if( size + w >= sizeof(buf) ) { |
| //one last full size sub-block |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(buf, size, 1, handle); |
| io->write_proc(last, sizeof(buf) - size, 1, handle); |
| //and possibly a tiny additional sub-block |
| b = (BYTE)(w - (sizeof(buf) - size)); |
| if( b > 0 ) { |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(last + w - b, b, 1, handle); |
| } |
| } else { |
| //last sub-block less than full size |
| b = (BYTE)(size + w); |
| io->write_proc(&b, 1, 1, handle); |
| io->write_proc(buf, size, 1, handle); |
| io->write_proc(last, w, 1, handle); |
| } |
| |
| //Block Terminator |
| b = 0; |
| io->write_proc(&b, 1, 1, handle); |
| |
| delete stringtable; |
| |
| } catch (const char *msg) { |
| FreeImage_OutputMessageProc(s_format_id, msg); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| // ========================================================== |
| // Init |
| // ========================================================== |
| |
| void DLL_CALLCONV |
| InitGIF(Plugin *plugin, int format_id) { |
| s_format_id = format_id; |
| |
| plugin->format_proc = Format; |
| plugin->description_proc = Description; |
| plugin->extension_proc = Extension; |
| plugin->regexpr_proc = RegExpr; |
| plugin->open_proc = Open; |
| plugin->close_proc = Close; |
| plugin->pagecount_proc = PageCount; |
| plugin->pagecapability_proc = NULL; |
| plugin->load_proc = Load; |
| plugin->save_proc = Save; |
| plugin->validate_proc = Validate; |
| plugin->mime_proc = MimeType; |
| plugin->supports_export_bpp_proc = SupportsExportDepth; |
| plugin->supports_export_type_proc = SupportsExportType; |
| plugin->supports_icc_profiles_proc = NULL; |
| } |