]> git.saurik.com Git - wxWidgets.git/commitdiff
Added GIF and animated GIF saving support.
authorDimitri Schoolwerth <dimitri.schoolwerth@gmail.com>
Wed, 19 Jan 2011 12:28:31 +0000 (12:28 +0000)
committerDimitri Schoolwerth <dimitri.schoolwerth@gmail.com>
Wed, 19 Jan 2011 12:28:31 +0000 (12:28 +0000)
Applied (modified) patch by troelsk. Also added a basic unit test for checking the frames of a saved animated GIF (a previous unit test already handles content of a GIF with a single frame).

Closes #8583.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66716 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/changes.txt
include/wx/anidecod.h
include/wx/imaggif.h
interface/wx/image.h
src/common/imaggif.cpp
tests/image/image.cpp

index 0e564f755a12fa9d614857f31f83f2076993e408..31750ae2f1d85d35b9cf1fbb74e8470001441213 100644 (file)
@@ -457,6 +457,7 @@ All (GUI):
 - Added wxArtProvider returning higher quality icons from Tango project.
 - wxPropertyGrid: Added "HasAlpha" attribute for wxColourProperty.
 - Added support for saving PNG files with palette (troelsk).
 - Added wxArtProvider returning higher quality icons from Tango project.
 - wxPropertyGrid: Added "HasAlpha" attribute for wxColourProperty.
 - Added support for saving PNG files with palette (troelsk).
+- Added support for saving as GIF and animated GIF (troelsk).
 
 GTK:
 
 
 GTK:
 
index 86970ae771894c72618d7e92bcda732eb17065bb..15b3adfe11fe535c25f85e6a2955e30ee50f7a2a 100644 (file)
@@ -12,7 +12,7 @@
 
 #include "wx/defs.h"
 
 
 #include "wx/defs.h"
 
-#if wxUSE_STREAMS && wxUSE_ICO_CUR
+#if wxUSE_STREAMS && (wxUSE_ICO_CUR || wxUSE_GIF)
 
 #include "wx/stream.h"
 #include "wx/image.h"
 
 #include "wx/stream.h"
 #include "wx/image.h"
@@ -76,6 +76,6 @@ private:
 };
 
 
 };
 
 
-#endif  // wxUSE_STREAMS && wxUSE_ICO_CUR
+#endif  // wxUSE_STREAMS && (wxUSE_ICO_CUR || wxUSE_GIF)
 
 #endif  // _WX_ANIDECOD_H
 
 #endif  // _WX_ANIDECOD_H
index 602c2e3233c37bb0e9c1473ac99c77f4acad7aa7..103e934fb6043ee0e5b1956e140d7b34c7c0fe68 100644 (file)
@@ -1,9 +1,9 @@
 /////////////////////////////////////////////////////////////////////////////
 // Name:        wx/imaggif.h
 // Purpose:     wxImage GIF handler
 /////////////////////////////////////////////////////////////////////////////
 // Name:        wx/imaggif.h
 // Purpose:     wxImage GIF handler
-// Author:      Vaclav Slavik & Guillermo Rodriguez Garcia
+// Author:      Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // RCS-ID:      $Id$
 // RCS-ID:      $Id$
-// Copyright:   (c) Guillermo Rodriguez Garcia
+// Copyright:   (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 
 #if wxUSE_GIF
 
 
 #if wxUSE_GIF
 
+struct wxRGB;
+struct GifHashTableType;
+class WXDLLIMPEXP_FWD_CORE wxImageArray; // anidecod.h
+
 class WXDLLIMPEXP_CORE wxGIFHandler : public wxImageHandler
 {
 public:
 class WXDLLIMPEXP_CORE wxGIFHandler : public wxImageHandler
 {
 public:
@@ -28,6 +32,7 @@ public:
         m_extension = wxT("gif");
         m_type = wxBITMAP_TYPE_GIF;
         m_mime = wxT("image/gif");
         m_extension = wxT("gif");
         m_type = wxBITMAP_TYPE_GIF;
         m_mime = wxT("image/gif");
+        m_hashTable = NULL;
     }
 
 #if wxUSE_STREAMS
     }
 
 #if wxUSE_STREAMS
@@ -36,11 +41,49 @@ public:
     virtual bool SaveFile(wxImage *image, wxOutputStream& stream,
                           bool verbose=true);
 
     virtual bool SaveFile(wxImage *image, wxOutputStream& stream,
                           bool verbose=true);
 
+    // Save animated gif
+    bool SaveAnimation(const wxImageArray& images, wxOutputStream *stream,
+        bool verbose = true, int delayMilliSecs = 1000,
+        const wxString& comment = wxEmptyString);
+
 protected:
     virtual int DoGetImageCount(wxInputStream& stream);
     virtual bool DoCanRead(wxInputStream& stream);
 protected:
     virtual int DoGetImageCount(wxInputStream& stream);
     virtual bool DoCanRead(wxInputStream& stream);
+
+    bool DoSaveFile(const wxImage&, wxOutputStream *, bool verbose,
+        bool first, int delayMilliSecs, bool loop,
+        const wxRGB *pal, int palCount,
+        int mask_index, const wxString& comment = wxEmptyString);
 #endif // wxUSE_STREAMS
 #endif // wxUSE_STREAMS
+protected:
+
+    // Declarations for saving
 
 
+    unsigned long m_crntShiftDWord;   /* For bytes decomposition into codes. */
+    int m_pixelCount;
+    struct GifHashTableType *m_hashTable;
+    wxInt16
+      m_EOFCode,     /* The EOF LZ code. */
+      m_clearCode,   /* The CLEAR LZ code. */
+      m_runningCode, /* The next code algorithm can generate. */
+      m_runningBits, /* The number of bits required to represent RunningCode. */
+      m_maxCode1,    /* 1 bigger than max. possible code, in RunningBits bits. */
+      m_crntCode,    /* Current algorithm code. */
+      m_crntShiftState;    /* Number of bits in CrntShiftDWord. */
+    wxUint8 m_LZBuf[256];   /* Compressed input is buffered here. */
+
+    bool InitHashTable();
+    void ClearHashTable();
+    void InsertHashTable(unsigned long key, int code);
+    int  ExistsHashTable(unsigned long key);
+
+#if wxUSE_STREAMS
+    bool CompressOutput(wxOutputStream *, int code);
+    bool SetupCompress(wxOutputStream *, int bpp);
+    bool CompressLine(wxOutputStream *, const wxUint8 *line, int lineLen);
+#endif
+public:
+    static wxString ms_comment;
 private:
     DECLARE_DYNAMIC_CLASS(wxGIFHandler)
 };
 private:
     DECLARE_DYNAMIC_CLASS(wxGIFHandler)
 };
index 5ac8bca17347bc17ff7c3edcc09940b778e4bcc5..e106e4306104c5e12fa73a9cc623ccd972524e52 100644 (file)
@@ -334,7 +334,7 @@ const unsigned char wxIMAGE_ALPHA_OPAQUE = 0xff;
     - wxBMPHandler: For loading (including alpha support) and saving, always installed.
     - wxPNGHandler: For loading and saving. Includes alpha support.
     - wxJPEGHandler: For loading and saving.
     - wxBMPHandler: For loading (including alpha support) and saving, always installed.
     - wxPNGHandler: For loading and saving. Includes alpha support.
     - wxJPEGHandler: For loading and saving.
-    - wxGIFHandler: Only for loading, due to legal issues.
+    - wxGIFHandler: For loading and saving (see below).
     - wxPCXHandler: For loading and saving (see below).
     - wxPNMHandler: For loading and saving (see below).
     - wxTIFFHandler: For loading (including alpha support) and saving.
     - wxPCXHandler: For loading and saving (see below).
     - wxPNMHandler: For loading and saving (see below).
     - wxTIFFHandler: For loading (including alpha support) and saving.
@@ -352,6 +352,8 @@ const unsigned char wxIMAGE_ALPHA_OPAQUE = 0xff;
     Loading PNMs only works for ASCII or raw RGB images.
     When saving in PNM format, wxPNMHandler will always save as raw RGB.
 
     Loading PNMs only works for ASCII or raw RGB images.
     When saving in PNM format, wxPNMHandler will always save as raw RGB.
 
+    Saving GIFs requires images of maximum 8 bpp (see wxQuantize), and the alpha channel converted to a mask (see wxImage::ConvertAlphaToMask).
+    Saving an animated GIF requires images of the same size (see wxGIFHandler::SaveAnimation)
 
     @library{wxcore}
     @category{gdi}
 
     @library{wxcore}
     @category{gdi}
index fdb9a4971d1af78c4521d8d47c9fe2bb64c1d961..b0c4399d9c803aab81b9aaa04c3402ce882a10c4 100644 (file)
@@ -1,9 +1,9 @@
 /////////////////////////////////////////////////////////////////////////////
 // Name:        src/common/imaggif.cpp
 // Purpose:     wxGIFHandler
 /////////////////////////////////////////////////////////////////////////////
 // Name:        src/common/imaggif.cpp
 // Purpose:     wxGIFHandler
-// Author:      Vaclav Slavik & Guillermo Rodriguez Garcia
+// Author:      Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // RCS-ID:      $Id$
 // RCS-ID:      $Id$
-// Copyright:   (c) 1999 Vaclav Slavik & Guillermo Rodriguez Garcia
+// Copyright:   (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 #ifndef WX_PRECOMP
     #include "wx/intl.h"
     #include "wx/log.h"
 #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"
 #endif
 
 #include "wx/imaggif.h"
 #include "wx/gifdecod.h"
-#include "wx/wfstream.h"
+#include "wx/stream.h"
+#include "wx/anidecod.h" // wxImageArray
+
+#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];
+};
+
+/*static*/ wxString wxGIFHandler::ms_comment;
 
 IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler)
 
 
 IMPLEMENT_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,
+    const wxString& comment = wxEmptyString);
+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
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // wxGIFHandler
 //-----------------------------------------------------------------------------
@@ -34,7 +118,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler)
 #if wxUSE_STREAMS
 
 bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
 #if wxUSE_STREAMS
 
 bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
-                            bool verbose, int index)
+    bool verbose, int index)
 {
     wxGIFDecoder *decod;
     wxGIFErrorCode error;
 {
     wxGIFDecoder *decod;
     wxGIFErrorCode error;
@@ -68,7 +152,7 @@ bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
     if ((error == wxGIF_TRUNCATED) && verbose)
     {
         wxLogError(_("GIF: data stream seems to be truncated."));
     if ((error == wxGIF_TRUNCATED) && verbose)
     {
         wxLogError(_("GIF: data stream seems to be truncated."));
-        /* go on; image data is OK */
+        // go on; image data is OK
     }
 
     if (ok)
     }
 
     if (ok)
@@ -85,15 +169,24 @@ bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
     return ok;
 }
 
     return ok;
 }
 
-bool wxGIFHandler::SaveFile( wxImage * WXUNUSED(image),
-                             wxOutputStream& WXUNUSED(stream), bool verbose )
+bool wxGIFHandler::SaveFile(wxImage *image,
+    wxOutputStream& stream, bool verbose)
 {
 {
-    if (verbose)
-    {
-        wxLogDebug(wxT("GIF: the handler is read-only!!"));
-    }
+#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, ms_comment)
+        && wxGIFHandler_WriteTerm(&stream);
+#else
+    wxUnusedVar(image);
+    wxUnusedVar(stream);
+    wxUnusedVar(verbose);
     return false;
     return false;
+#endif
 }
 
 bool wxGIFHandler::DoCanRead( wxInputStream& stream )
 }
 
 bool wxGIFHandler::DoCanRead( wxInputStream& stream )
@@ -116,6 +209,603 @@ int wxGIFHandler::DoGetImageCount( wxInputStream& stream )
     return decod.GetFrameCount();
 }
 
     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 wxString& comment)
+{
+    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();
+    int width_even = width + ((width % 2) ? 1 : 0);
+
+    if (first)
+    {
+        ok = wxGIFHandler_WriteHeader(stream, width, height, loop,
+            pal, palCount, comment);
+    }
+
+    ok = ok && 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();
+    wxUint8 *eightBitData = new wxUint8[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, width);
+        if (!ok)
+        {
+            break;
+        }
+    }
+
+    delete [] eightBitData;
+
+    wxDELETE(m_hashTable);
+
+    return ok;
+}
+
+bool wxGIFHandler::SaveAnimation(const wxImageArray& images,
+    wxOutputStream *stream, bool verbose, int delayMilliSecs,
+    const wxString& comment)
+{
+#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,
+            comment.length() ? comment : ms_comment);
+    }
+
+    return ok && wxGIFHandler_WriteTerm(stream);
+#else
+    wxUnusedVar(images);
+    wxUnusedVar(stream);
+    wxUnusedVar(verbose);
+    wxUnusedVar(delayMilliSecs);
+    wxUnusedVar(comment);
+
+    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, newCode;
+    unsigned long newKey;
+    wxUint8 pixel;
+    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.
+        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.
+        newKey = (((unsigned long) crntCode) << 8) + pixel;
+        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
 
 #endif  // wxUSE_STREAMS
 
-#endif  // wxUSE_GIF
+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, &word, sizeof(word));
+}
+
+bool wxGIFHandler_WriteHeader(wxOutputStream *stream, int width, int height,
+    bool loop, const wxRGB *pal, int palCount, const wxString& comment)
+{
+    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);
+    }
+
+    if (comment.length())
+    {
+       ok = ok && wxGIFHandler_WriteComment(stream, comment);
+    }
+
+    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];
+
+    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] = delayMilliSecs / 10; // delay time
+    buf[5] = 0;
+    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)
+{
+    wxUint8 buf[3];
+    wxCharBuffer text(comment.mb_str());
+    size_t len = strlen(text.data());
+    len = wxMin(len, 255);
+
+    buf[0] = GIF_MARKER_EXT;
+    buf[1] = GIF_MARKER_EXT_COMMENT;
+    buf[2] = (wxUint8)len;
+
+    return wxGIFHandler_Write(stream, buf, sizeof(buf))
+        && wxGIFHandler_Write(stream, text.data(), len)
+        && 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
index 8782d95a24fe3822256b3e7bb0a7a270b7eef894..47114b52e84939284aa80c65809badc4989f560d 100644 (file)
@@ -23,6 +23,7 @@
 #ifndef WX_PRECOMP
 #endif // WX_PRECOMP
 
 #ifndef WX_PRECOMP
 #endif // WX_PRECOMP
 
+#include "wx/anidecod.h" // wxImageArray
 #include "wx/image.h"
 #include "wx/palette.h"
 #include "wx/url.h"
 #include "wx/image.h"
 #include "wx/palette.h"
 #include "wx/url.h"
@@ -70,6 +71,7 @@ private:
         CPPUNIT_TEST( SizeImage );
         CPPUNIT_TEST( CompareLoadedImage );
         CPPUNIT_TEST( CompareSavedImage );
         CPPUNIT_TEST( SizeImage );
         CPPUNIT_TEST( CompareLoadedImage );
         CPPUNIT_TEST( CompareSavedImage );
+        CPPUNIT_TEST( SaveAnimatedGIF );
     CPPUNIT_TEST_SUITE_END();
 
     void LoadFromSocketStream();
     CPPUNIT_TEST_SUITE_END();
 
     void LoadFromSocketStream();
@@ -78,6 +80,7 @@ private:
     void SizeImage();
     void CompareLoadedImage();
     void CompareSavedImage();
     void SizeImage();
     void CompareLoadedImage();
     void CompareSavedImage();
+    void SaveAnimatedGIF();
 
     DECLARE_NO_COPY_CLASS(ImageTestCase)
 };
 
     DECLARE_NO_COPY_CLASS(ImageTestCase)
 };
@@ -1089,6 +1092,47 @@ void ImageTestCase::CompareSavedImage()
 #endif
 }
 
 #endif
 }
 
+void ImageTestCase::SaveAnimatedGIF()
+{
+#if wxUSE_PALETTE
+    wxImage image("horse.gif");
+    CPPUNIT_ASSERT( image.IsOk() );
+
+    wxImageArray images;
+    images.Add(image);
+    int i;
+    for (i = 0; i < 4-1; ++i)
+    {
+        images.Add( images[i].Rotate90() );
+
+        images[i+1].SetPalette(images[0].GetPalette());
+    }
+
+    wxMemoryOutputStream memOut;
+    CPPUNIT_ASSERT( wxGIFHandler().SaveAnimation(images, &memOut) );
+
+    wxGIFHandler handler;
+    wxMemoryInputStream memIn(memOut);
+    CPPUNIT_ASSERT(memIn.IsOk());
+    const int imageCount = handler.GetImageCount(memIn);
+    CPPUNIT_ASSERT_EQUAL(4, imageCount);
+
+    for (i = 0; i < imageCount; ++i)
+    {
+        wxFileOffset pos = memIn.TellI();
+        CPPUNIT_ASSERT( handler.LoadFile(&image, memIn, true, i) );
+        memIn.SeekI(pos);
+
+        WX_ASSERT_MESSAGE
+        (
+            ("Compare test for GIF frame number %d failed", i),
+            memcmp(image.GetData(), images[i].GetData(),
+                images[i].GetWidth() * images[i].GetHeight() * 3) == 0
+        );
+    }
+#endif // #if wxUSE_PALETTE
+}
+
 #endif //wxUSE_IMAGE
 
 
 #endif //wxUSE_IMAGE