diff options
Diffstat (limited to 'plugins/AdvaImg/src/FreeImage/PluginGIF.cpp')
-rw-r--r-- | plugins/AdvaImg/src/FreeImage/PluginGIF.cpp | 1398 |
1 files changed, 1398 insertions, 0 deletions
diff --git a/plugins/AdvaImg/src/FreeImage/PluginGIF.cpp b/plugins/AdvaImg/src/FreeImage/PluginGIF.cpp new file mode 100644 index 0000000000..7af1d821d8 --- /dev/null +++ b/plugins/AdvaImg/src/FreeImage/PluginGIF.cpp @@ -0,0 +1,1398 @@ +// ========================================================== +// GIF Loader and Writer +// +// Design and implementation by +// - Ryan Rubley <ryan@lostreality.org> +// - Raphaël Gaquer <raphael.gaquer@alcer.com> +// +// 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 + +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_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++ ) { + scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, logicalheight - (y + info.top) - 1) + 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++ ) { + 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++; + } + } + //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; +} |