Added GIF and animated GIF saving support.

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
This commit is contained in:
Dimitri Schoolwerth
2011-01-19 12:28:31 +00:00
parent 818bc81a8b
commit 77b83d0a0f
6 changed files with 798 additions and 18 deletions

View File

@@ -457,6 +457,7 @@ All (GUI):
- Added wxArtProvider returning higher quality icons from Tango project. - Added wxArtProvider returning higher quality icons from Tango project.
- wxPropertyGrid: Added "HasAlpha" attribute for wxColourProperty. - wxPropertyGrid: Added "HasAlpha" attribute for wxColourProperty.
- Added support for saving PNG files with palette (troelsk). - Added support for saving PNG files with palette (troelsk).
- Added support for saving as GIF and animated GIF (troelsk).
GTK: GTK:

View 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/stream.h"
#include "wx/image.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

View File

@@ -1,9 +1,9 @@
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Name: wx/imaggif.h // Name: wx/imaggif.h
// Purpose: wxImage GIF handler // 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
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -19,6 +19,10 @@
#if wxUSE_GIF #if wxUSE_GIF
struct wxRGB;
struct GifHashTableType;
class WXDLLIMPEXP_FWD_CORE wxImageArray; // anidecod.h
class WXDLLIMPEXP_CORE wxGIFHandler : public wxImageHandler class WXDLLIMPEXP_CORE wxGIFHandler : public wxImageHandler
{ {
public: public:
@@ -28,6 +32,7 @@ public:
m_extension = wxT("gif"); m_extension = wxT("gif");
m_type = wxBITMAP_TYPE_GIF; m_type = wxBITMAP_TYPE_GIF;
m_mime = wxT("image/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, virtual bool SaveFile(wxImage *image, wxOutputStream& stream,
bool verbose=true); bool verbose=true);
// Save animated gif
bool SaveAnimation(const wxImageArray& images, wxOutputStream *stream,
bool verbose = true, int delayMilliSecs = 1000,
const wxString& comment = wxEmptyString);
protected: protected:
virtual int DoGetImageCount(wxInputStream& stream); virtual int DoGetImageCount(wxInputStream& stream);
virtual bool DoCanRead(wxInputStream& stream); virtual bool DoCanRead(wxInputStream& stream);
#endif // wxUSE_STREAMS
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: private:
DECLARE_DYNAMIC_CLASS(wxGIFHandler) DECLARE_DYNAMIC_CLASS(wxGIFHandler)
}; };

View File

@@ -334,7 +334,7 @@ const unsigned char wxIMAGE_ALPHA_OPAQUE = 0xff;
- wxBMPHandler: For loading (including alpha support) and saving, always installed. - wxBMPHandler: For loading (including alpha support) and saving, always installed.
- wxPNGHandler: For loading and saving. Includes alpha support. - wxPNGHandler: For loading and saving. Includes alpha support.
- wxJPEGHandler: For loading and saving. - 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). - wxPCXHandler: For loading and saving (see below).
- wxPNMHandler: For loading and saving (see below). - wxPNMHandler: For loading and saving (see below).
- wxTIFFHandler: For loading (including alpha support) and saving. - 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. Loading PNMs only works for ASCII or raw RGB images.
When saving in PNM format, wxPNMHandler will always save as raw RGB. 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} @library{wxcore}
@category{gdi} @category{gdi}

View File

@@ -1,9 +1,9 @@
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Name: src/common/imaggif.cpp // Name: src/common/imaggif.cpp
// Purpose: wxGIFHandler // 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
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -19,14 +19,98 @@
#ifndef WX_PRECOMP #ifndef WX_PRECOMP
#include "wx/intl.h" #include "wx/intl.h"
#include "wx/log.h" #include "wx/log.h"
#include "wx/palette.h"
#include "wx/utils.h"
#endif #endif
#include "wx/imaggif.h" #include "wx/imaggif.h"
#include "wx/gifdecod.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
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@@ -68,7 +152,7 @@ bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
if ((error == wxGIF_TRUNCATED) && verbose) if ((error == wxGIF_TRUNCATED) && verbose)
{ {
wxLogError(_("GIF: data stream seems to be truncated.")); 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), bool wxGIFHandler::SaveFile(wxImage *image,
wxOutputStream& WXUNUSED(stream), bool verbose ) wxOutputStream& stream, bool verbose)
{ {
if (verbose) #if wxUSE_PALETTE
{ wxRGB pal[256];
wxLogDebug(wxT("GIF: the handler is read-only!!")); 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

View File

@@ -23,6 +23,7 @@
#ifndef WX_PRECOMP #ifndef WX_PRECOMP
#endif // WX_PRECOMP #endif // WX_PRECOMP
#include "wx/anidecod.h" // wxImageArray
#include "wx/image.h" #include "wx/image.h"
#include "wx/palette.h" #include "wx/palette.h"
#include "wx/url.h" #include "wx/url.h"
@@ -70,6 +71,7 @@ private:
CPPUNIT_TEST( SizeImage ); CPPUNIT_TEST( SizeImage );
CPPUNIT_TEST( CompareLoadedImage ); CPPUNIT_TEST( CompareLoadedImage );
CPPUNIT_TEST( CompareSavedImage ); CPPUNIT_TEST( CompareSavedImage );
CPPUNIT_TEST( SaveAnimatedGIF );
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
void LoadFromSocketStream(); void LoadFromSocketStream();
@@ -78,6 +80,7 @@ private:
void SizeImage(); void SizeImage();
void CompareLoadedImage(); void CompareLoadedImage();
void CompareSavedImage(); 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