]> 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 support for saving as GIF and animated GIF (troelsk).
 
 GTK:
 
index 86970ae771894c72618d7e92bcda732eb17065bb..15b3adfe11fe535c25f85e6a2955e30ee50f7a2a 100644 (file)
@@ -12,7 +12,7 @@
 
 #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"
@@ -76,6 +76,6 @@ private:
 };
 
 
-#endif  // wxUSE_STREAMS && wxUSE_ICO_CUR
+#endif  // wxUSE_STREAMS && (wxUSE_ICO_CUR || wxUSE_GIF)
 
 #endif  // _WX_ANIDECOD_H
index 602c2e3233c37bb0e9c1473ac99c77f4acad7aa7..103e934fb6043ee0e5b1956e140d7b34c7c0fe68 100644 (file)
@@ -1,9 +1,9 @@
 /////////////////////////////////////////////////////////////////////////////
 // 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$
-// Copyright:   (c) Guillermo Rodriguez Garcia
+// Copyright:   (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 
 #if wxUSE_GIF
 
+struct wxRGB;
+struct GifHashTableType;
+class WXDLLIMPEXP_FWD_CORE wxImageArray; // anidecod.h
+
 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_hashTable = NULL;
     }
 
 #if wxUSE_STREAMS
@@ -36,11 +41,49 @@ public:
     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);
+
+    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
+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)
 };
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.
-    - 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.
@@ -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.
 
+    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}
index fdb9a4971d1af78c4521d8d47c9fe2bb64c1d961..b0c4399d9c803aab81b9aaa04c3402ce882a10c4 100644 (file)
@@ -1,9 +1,9 @@
 /////////////////////////////////////////////////////////////////////////////
 // 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$
-// Copyright:   (c) 1999 Vaclav Slavik & Guillermo Rodriguez Garcia
+// Copyright:   (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 #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/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)
 
+//----------------------------------------------------------------------------
+// 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
 //-----------------------------------------------------------------------------
@@ -34,7 +118,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler)
 #if wxUSE_STREAMS
 
 bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
-                            bool verbose, int index)
+    bool verbose, int index)
 {
     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."));
-        /* go on; image data is OK */
+        // go on; image data is OK
     }
 
     if (ok)
@@ -85,15 +169,24 @@ bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
     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;
+#endif
 }
 
 bool wxGIFHandler::DoCanRead( wxInputStream& stream )
@@ -116,6 +209,603 @@ int wxGIFHandler::DoGetImageCount( wxInputStream& stream )
     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_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
 
+#include "wx/anidecod.h" // wxImageArray
 #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( SaveAnimatedGIF );
     CPPUNIT_TEST_SUITE_END();
 
     void LoadFromSocketStream();
@@ -78,6 +80,7 @@ private:
     void SizeImage();
     void CompareLoadedImage();
     void CompareSavedImage();
+    void SaveAnimatedGIF();
 
     DECLARE_NO_COPY_CLASS(ImageTestCase)
 };
@@ -1089,6 +1092,47 @@ void ImageTestCase::CompareSavedImage()
 #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