///////////////////////////////////////////////////////////////////////////// // Name: src/common/imaggif.cpp // Purpose: wxGIFHandler // Author: Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K // Copyright: (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_IMAGE && wxUSE_GIF #ifndef WX_PRECOMP #include "wx/intl.h" #include "wx/log.h" #include "wx/palette.h" #include "wx/utils.h" #endif #include "wx/imaggif.h" #include "wx/gifdecod.h" #include "wx/stream.h" #include "wx/anidecod.h" // wxImageArray #include "wx/scopedarray.h" #define GIF89_HDR "GIF89a" #define NETSCAPE_LOOP "NETSCAPE2.0" // see members.aol.com/royalef/gifabout.htm // members.aol.com/royalef/gif89a.txt enum { GIF_MARKER_EXT = '!', // 0x21 GIF_MARKER_SEP = ',', // 0x2C GIF_MARKER_ENDOFDATA = ';', // 0x3B GIF_MARKER_EXT_GRAPHICS_CONTROL = 0xF9, GIF_MARKER_EXT_COMMENT = 0xFE, GIF_MARKER_EXT_APP = 0xFF }; #define LZ_MAX_CODE 4095 // Biggest code possible in 12 bits. #define FLUSH_OUTPUT 4096 // Impossible code, to signal flush. #define FIRST_CODE 4097 // Impossible code, to signal first. #define HT_SIZE 8192 // 12bits = 4096 or twice as big! #define HT_KEY_MASK 0x1FFF // 13bits keys #define HT_GET_KEY(l) (l >> 12) #define HT_GET_CODE(l) (l & 0x0FFF) #define HT_PUT_KEY(l) (l << 12) #define HT_PUT_CODE(l) (l & 0x0FFF) struct wxRGB { wxUint8 red; wxUint8 green; wxUint8 blue; }; struct GifHashTableType { wxUint32 HTable[HT_SIZE]; }; wxIMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler); //---------------------------------------------------------------------------- // Forward declarations //---------------------------------------------------------------------------- static int wxGIFHandler_KeyItem(unsigned long item); #if wxUSE_STREAMS static int wxGIFHandler_BitSize(int n); #if wxUSE_PALETTE static bool wxGIFHandler_GetPalette(const wxImage& image, wxRGB *pal, int *palCount, int *mask_index); #endif static int wxGIFHandler_PaletteFind(const wxRGB& clr, const wxRGB *array, int count); static bool wxGIFHandler_Write(wxOutputStream *, const void *buf, size_t len); static bool wxGIFHandler_WriteByte(wxOutputStream *, wxUint8); static bool wxGIFHandler_WriteWord(wxOutputStream *, wxUint16); static bool wxGIFHandler_WriteHeader(wxOutputStream *, int width, int height, bool loop, const wxRGB *pal, int palCount); static bool wxGIFHandler_WriteRect(wxOutputStream *, int width, int height); #if wxUSE_PALETTE static bool wxGIFHandler_WriteTerm(wxOutputStream *); #endif static bool wxGIFHandler_WriteZero(wxOutputStream *); static bool wxGIFHandler_WritePalette(wxOutputStream *, const wxRGB *pal, size_t palCount, int bpp); static bool wxGIFHandler_WriteControl(wxOutputStream *, int maskIndex, int delayMilliSecs); static bool wxGIFHandler_WriteComment(wxOutputStream *, const wxString&); static bool wxGIFHandler_WriteLoop(wxOutputStream *); static bool wxGIFHandler_BufferedOutput(wxOutputStream *, wxUint8 *buf, int c); #endif // wxUSE_STREAMS //----------------------------------------------------------------------------- // wxGIFHandler //----------------------------------------------------------------------------- #if wxUSE_STREAMS bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream, bool verbose, int index) { wxGIFDecoder decod; switch ( decod.LoadGIF(stream) ) { case wxGIF_OK: break; case wxGIF_INVFORMAT: if ( verbose ) wxLogError(_("GIF: error in GIF image format.")); return false; case wxGIF_MEMERR: if ( verbose ) wxLogError(_("GIF: not enough memory.")); return false; case wxGIF_TRUNCATED: if ( verbose ) wxLogError(_("GIF: data stream seems to be truncated.")); // go on; image data is OK break; } return decod.ConvertToImage(index != -1 ? (size_t)index : 0, image); } bool wxGIFHandler::SaveFile(wxImage *image, wxOutputStream& stream, bool verbose) { #if wxUSE_PALETTE wxRGB pal[256]; int palCount; int maskIndex; return wxGIFHandler_GetPalette(*image, pal, &palCount, &maskIndex) && DoSaveFile(*image, &stream, verbose, true /*first?*/, 0, false /*loop?*/, pal, palCount, maskIndex) && wxGIFHandler_WriteTerm(&stream); #else wxUnusedVar(image); wxUnusedVar(stream); wxUnusedVar(verbose); return false; #endif } bool wxGIFHandler::DoCanRead( wxInputStream& stream ) { wxGIFDecoder decod; return decod.CanRead(stream); // it's ok to modify the stream position here } int wxGIFHandler::DoGetImageCount( wxInputStream& stream ) { wxGIFDecoder decod; wxGIFErrorCode error = decod.LoadGIF(stream); if ( (error != wxGIF_OK) && (error != wxGIF_TRUNCATED) ) return -1; // NOTE: this function modifies the current stream position but it's ok // (see wxImageHandler::GetImageCount) return decod.GetFrameCount(); } bool wxGIFHandler::DoSaveFile(const wxImage& image, wxOutputStream *stream, bool WXUNUSED(verbose), bool first, int delayMilliSecs, bool loop, const wxRGB *pal, int palCount, int maskIndex) { const unsigned long colorcount = image.CountColours(256+1); bool ok = colorcount && (colorcount <= 256); if (!ok) { return false; } int width = image.GetWidth(); int height = image.GetHeight(); wxCHECK_MSG( width && height, false, wxS("can't save 0-sized file") ); int width_even = width + ((width % 2) ? 1 : 0); if (first) { ok = wxGIFHandler_WriteHeader(stream, width, height, loop, pal, palCount); } ok = ok && wxGIFHandler_WriteComment(stream, image.GetOption(wxIMAGE_OPTION_GIF_COMMENT)) && wxGIFHandler_WriteControl(stream, maskIndex, delayMilliSecs) && wxGIFHandler_WriteByte(stream, GIF_MARKER_SEP) && wxGIFHandler_WriteRect(stream, width, height); // local palette if (first) { // we already saved the (global) palette ok = ok && wxGIFHandler_WriteZero(stream); } else { const int bpp = wxGIFHandler_BitSize(palCount); wxUint8 b; b = 0x80; b |=(bpp - 1) << 5; b |=(bpp - 1); b &=~0x40; // clear interlaced ok = ok && wxGIFHandler_WriteByte(stream, b) && wxGIFHandler_WritePalette(stream, pal, palCount, bpp); } if (!ok) { return false; } if (!InitHashTable()) { wxLogError(_("Couldn't initialize GIF hash table.")); return false; } const wxUint8 *src = image.GetData(); wxScopedArray eightBitData(width); SetupCompress(stream, 8); m_pixelCount = height * width_even; for (int y = 0; y < height; y++) { m_pixelCount -= width_even; for (int x = 0; x < width; x++) { wxRGB rgb; rgb.red = src[0]; rgb.green = src[1]; rgb.blue = src[2]; int index = wxGIFHandler_PaletteFind(rgb, pal, palCount); wxASSERT(index != wxNOT_FOUND); eightBitData[x] = (wxUint8)index; src+=3; } ok = CompressLine(stream, eightBitData.get(), width); if (!ok) { break; } } wxDELETE(m_hashTable); return ok; } bool wxGIFHandler::SaveAnimation(const wxImageArray& images, wxOutputStream *stream, bool verbose, int delayMilliSecs) { #if wxUSE_PALETTE bool ok = true; size_t i; wxSize size(0,0); for (i = 0; (i < images.GetCount()) && ok; i++) { const wxImage& image = images.Item(i); wxSize temp(image.GetWidth(), image.GetHeight()); ok = ok && image.HasPalette(); if (i) { ok = ok && (size == temp); } else { size = temp; } } for (i = 0; (i < images.GetCount()) && ok; i++) { const wxImage& image = images.Item(i); wxRGB pal[256]; int palCount; int maskIndex; ok = wxGIFHandler_GetPalette(image, pal, &palCount, &maskIndex) && DoSaveFile(image, stream, verbose, i == 0 /*first?*/, delayMilliSecs, true /*loop?*/, pal, palCount, maskIndex); } return ok && wxGIFHandler_WriteTerm(stream); #else wxUnusedVar(images); wxUnusedVar(stream); wxUnusedVar(verbose); wxUnusedVar(delayMilliSecs); return false; #endif } bool wxGIFHandler::CompressOutput(wxOutputStream *stream, int code) { if (code == FLUSH_OUTPUT) { while (m_crntShiftState > 0) { // Get rid of what is left in DWord, and flush it. if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf, m_crntShiftDWord & 0xff)) { return false; } m_crntShiftDWord >>= 8; m_crntShiftState -= 8; } m_crntShiftState = 0; // For next time. if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf, FLUSH_OUTPUT)) { return false; } } else { m_crntShiftDWord |= ((long) code) << m_crntShiftState; m_crntShiftState += m_runningBits; while (m_crntShiftState >= 8) { // Dump out full bytes: if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf, m_crntShiftDWord & 0xff)) { return false; } m_crntShiftDWord >>= 8; m_crntShiftState -= 8; } } // If code can't fit into RunningBits bits, must raise its size. Note // however that codes above LZ_MAX_CODE are used for special signaling. if ( (m_runningCode >= m_maxCode1) && (code <= LZ_MAX_CODE)) { m_maxCode1 = 1 << ++m_runningBits; } return true; } bool wxGIFHandler::SetupCompress(wxOutputStream *stream, int bpp) { m_LZBuf[0] = 0; // Nothing was output yet. m_clearCode = (1 << bpp); m_EOFCode = m_clearCode + 1; m_runningCode = m_EOFCode + 1; m_runningBits = bpp + 1; // Number of bits per code. m_maxCode1 = 1 << m_runningBits; // Max. code + 1. m_crntCode = FIRST_CODE; // Signal that this is first one! m_crntShiftState = 0; // No information in CrntShiftDWord. m_crntShiftDWord = 0; // Clear hash table and send Clear to make sure the decoder does the same. ClearHashTable(); return wxGIFHandler_WriteByte(stream, (wxUint8)bpp) && CompressOutput(stream, m_clearCode); } bool wxGIFHandler::CompressLine(wxOutputStream *stream, const wxUint8 *line, int lineLen) { int i = 0, crntCode; if (m_crntCode == FIRST_CODE) // It's first time! crntCode = line[i++]; else crntCode = m_crntCode; // Get last code in compression. while (i < lineLen) { // Decode lineLen items. wxUint8 pixel; pixel = line[i++]; // Get next pixel from stream. // Form a new unique key to search hash table for the code combines // crntCode as Prefix string with Pixel as postfix char. unsigned long newKey; newKey = (((unsigned long) crntCode) << 8) + pixel; int newCode; if ((newCode = ExistsHashTable(newKey)) >= 0) { // This Key is already there, or the string is old one, so // simply take new code as our crntCode: crntCode = newCode; } else { // Put it in hash table, output the prefix code, and make our // crntCode equal to Pixel. if (!CompressOutput(stream, crntCode)) { return false; } crntCode = pixel; // If however the HashTable is full, we send a clear first and // Clear the hash table. if (m_runningCode >= LZ_MAX_CODE) { // Time to do some clearance: if (!CompressOutput(stream, m_clearCode)) { return false; } m_runningCode = m_EOFCode + 1; m_runningBits = 8 + 1; m_maxCode1 = 1 << m_runningBits; ClearHashTable(); } else { // Put this unique key with its relative Code in hash table: InsertHashTable(newKey, m_runningCode++); } } } // Preserve the current state of the compression algorithm: m_crntCode = crntCode; if (m_pixelCount == 0) { // We are done - output last Code and flush output buffers: if (!CompressOutput(stream, crntCode) || !CompressOutput(stream, m_EOFCode) || !CompressOutput(stream, FLUSH_OUTPUT)) { return false; } } return true; } #endif // wxUSE_STREAMS bool wxGIFHandler::InitHashTable() { if (!m_hashTable) { m_hashTable = new GifHashTableType(); } if (!m_hashTable) { return false; } ClearHashTable(); return true; } void wxGIFHandler::ClearHashTable() { int index = HT_SIZE; wxUint32 *HTable = m_hashTable->HTable; while (--index>=0) { HTable[index] = 0xfffffffful; } } void wxGIFHandler::InsertHashTable(unsigned long key, int code) { int hKey = wxGIFHandler_KeyItem(key); wxUint32 *HTable = m_hashTable->HTable; while (HT_GET_KEY(HTable[hKey]) != 0xFFFFFL) { hKey = (hKey + 1) & HT_KEY_MASK; } HTable[hKey] = HT_PUT_KEY(key) | HT_PUT_CODE(code); } int wxGIFHandler::ExistsHashTable(unsigned long key) { int hKey = wxGIFHandler_KeyItem(key); wxUint32 *HTable = m_hashTable->HTable, HTKey; while ((HTKey = HT_GET_KEY(HTable[hKey])) != 0xFFFFFL) { if (key == HTKey) { return HT_GET_CODE(HTable[hKey]); } hKey = (hKey + 1) & HT_KEY_MASK; } return -1; } // --------------------------------------------------------------------------- // implementation of global private functions // --------------------------------------------------------------------------- int wxGIFHandler_KeyItem(unsigned long item) { return ((item >> 12) ^ item) & HT_KEY_MASK; } #if wxUSE_STREAMS int wxGIFHandler_BitSize(int n) { int i; for (i = 1; i <= 8; i++) { if ((1 << i) >= n) { break; } } return i; } #if wxUSE_PALETTE bool wxGIFHandler_GetPalette(const wxImage& image, wxRGB *pal, int *pPalCount, int *pMaskIndex) { if (!image.HasPalette()) { return false; } const wxPalette& palette = image.GetPalette(); int palCount = palette.GetColoursCount(); for (int i = 0; i < palCount; ++i) { if (!palette.GetRGB(i, &pal[i].red, &pal[i].green, &pal[i].blue)) { break; } } if (image.HasMask()) { wxRGB mask; mask.red = image.GetMaskRed(); mask.green = image.GetMaskGreen(); mask.blue = image.GetMaskBlue(); *pMaskIndex = wxGIFHandler_PaletteFind(mask, pal, palCount); if ( (*pMaskIndex == wxNOT_FOUND) && (palCount < 256)) { *pMaskIndex = palCount; pal[palCount++] = mask; } } else { *pMaskIndex = wxNOT_FOUND; } *pPalCount = palCount; return true; } #endif // wxUSE_PALETTE int wxGIFHandler_PaletteFind(const wxRGB& clr, const wxRGB *array, int count) { for (int i = 0; i < count; i++) { if ( (clr.red == array[i].red) && (clr.green == array[i].green) && (clr.blue == array[i].blue)) { return i; } } return wxNOT_FOUND; } bool wxGIFHandler_Write(wxOutputStream *stream, const void *buf, size_t len) { return (len == stream->Write(buf, len).LastWrite()); } bool wxGIFHandler_WriteByte(wxOutputStream *stream, wxUint8 byte) { return wxGIFHandler_Write(stream, &byte, sizeof(byte)); } bool wxGIFHandler_WriteWord(wxOutputStream *stream, wxUint16 word) { wxUint8 buf[2]; buf[0] = word & 0xff; buf[1] = (word >> 8) & 0xff; return wxGIFHandler_Write(stream, &buf, sizeof(buf)); } bool wxGIFHandler_WriteHeader(wxOutputStream *stream, int width, int height, bool loop, const wxRGB *pal, int palCount) { const int bpp = wxGIFHandler_BitSize(palCount); wxUint8 buf[3]; bool ok = wxGIFHandler_Write(stream, GIF89_HDR, sizeof(GIF89_HDR)-1) && wxGIFHandler_WriteWord(stream, (wxUint16) width) && wxGIFHandler_WriteWord(stream, (wxUint16) height); buf[0] = 0x80; buf[0] |=(bpp - 1) << 5; buf[0] |=(bpp - 1); buf[1] = 0; // background color == entry 0 buf[2] = 0; // aspect ratio 1:1 ok = ok && wxGIFHandler_Write(stream, buf, sizeof(buf)) && wxGIFHandler_WritePalette(stream, pal, palCount, bpp); if (loop) { ok = ok && wxGIFHandler_WriteLoop(stream); } return ok; } bool wxGIFHandler_WriteRect(wxOutputStream *stream, int width, int height) { return wxGIFHandler_WriteWord(stream, 0) // left && wxGIFHandler_WriteWord(stream, 0) // top && wxGIFHandler_WriteWord(stream, (wxUint16) width) && wxGIFHandler_WriteWord(stream, (wxUint16) height); } #if wxUSE_PALETTE bool wxGIFHandler_WriteTerm(wxOutputStream *stream) { return wxGIFHandler_WriteByte(stream, GIF_MARKER_ENDOFDATA); } #endif bool wxGIFHandler_WriteZero(wxOutputStream *stream) { return wxGIFHandler_WriteByte(stream, 0); } bool wxGIFHandler_WritePalette(wxOutputStream *stream, const wxRGB *array, size_t count, int bpp) { wxUint8 buf[3]; for (int i = 0; (i < (1 << bpp)); i++) { if (i < (int)count) { buf[0] = array[i].red; buf[1] = array[i].green; buf[2] = array[i].blue; } else { buf[0] = buf[1] = buf[2] = 0; } if ( !wxGIFHandler_Write(stream, buf, sizeof(buf)) ) { return false; } } return true; } bool wxGIFHandler_WriteControl(wxOutputStream *stream, int maskIndex, int delayMilliSecs) { wxUint8 buf[8]; const wxUint16 delay = delayMilliSecs / 10; buf[0] = GIF_MARKER_EXT; // extension marker buf[1] = GIF_MARKER_EXT_GRAPHICS_CONTROL; buf[2] = 4; // length of block buf[3] = (maskIndex != wxNOT_FOUND) ? 1 : 0; // has transparency buf[4] = delay & 0xff; // delay time buf[5] = (delay >> 8) & 0xff; // delay time second byte buf[6] = (maskIndex != wxNOT_FOUND) ? (wxUint8) maskIndex : 0; buf[7] = 0; return wxGIFHandler_Write(stream, buf, sizeof(buf)); } bool wxGIFHandler_WriteComment(wxOutputStream *stream, const wxString& comment) { if ( comment.empty() ) { return true; } // Write comment header. wxUint8 buf[2]; buf[0] = GIF_MARKER_EXT; buf[1] = GIF_MARKER_EXT_COMMENT; if ( !wxGIFHandler_Write(stream, buf, sizeof(buf)) ) { return false; } /* If comment is longer than 255 bytes write it in blocks of maximum 255 bytes each. */ wxCharBuffer text( comment.mb_str() ); size_t pos = 0, fullLength = text.length(); do { size_t blockLength = wxMin(fullLength - pos, 255); if ( !wxGIFHandler_WriteByte(stream, (wxUint8) blockLength) || !wxGIFHandler_Write(stream, &text.data()[pos], blockLength) ) { return false; } pos += blockLength; }while (pos < fullLength); // Write comment footer. return wxGIFHandler_WriteZero(stream); } bool wxGIFHandler_WriteLoop(wxOutputStream *stream) { wxUint8 buf[4]; const int loopcount = 0; // infinite buf[0] = GIF_MARKER_EXT; buf[1] = GIF_MARKER_EXT_APP; buf[2] = 0x0B; bool ok = wxGIFHandler_Write(stream, buf, 3) && wxGIFHandler_Write(stream, NETSCAPE_LOOP, sizeof(NETSCAPE_LOOP)-1); buf[0] = 3; buf[1] = 1; buf[2] = loopcount & 0xFF; buf[3] = loopcount >> 8; return ok && wxGIFHandler_Write(stream, buf, 4) && wxGIFHandler_WriteZero(stream); } bool wxGIFHandler_BufferedOutput(wxOutputStream *stream, wxUint8 *buf, int c) { bool ok = true; if (c == FLUSH_OUTPUT) { // Flush everything out. if (buf[0]) { ok = wxGIFHandler_Write(stream, buf, buf[0]+1); } // Mark end of compressed data, by an empty block (see GIF doc): wxGIFHandler_WriteZero(stream); } else { if (buf[0] == 255) { // Dump out this buffer - it is full: ok = wxGIFHandler_Write(stream, buf, buf[0] + 1); buf[0] = 0; } buf[++buf[0]] = c; } return ok; } #endif // wxUSE_STREAMS #endif // wxUSE_IMAGE && wxUSE_GIF