Added support for reading comments from a GIF image.

Applied (modified) patch by troelsk. Changed comments (which are allowed per frame in an animated GIF) to be read using wxIMAGE_OPTION_GIF_COMMENT with wxImage.GetOption. Added unit tests for reading and writing GIF comments.

Closes #12843.



git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66828 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Dimitri Schoolwerth
2011-02-02 11:19:30 +00:00
parent 41857332b6
commit 15f345aad3
4 changed files with 152 additions and 29 deletions

View File

@@ -424,6 +424,7 @@ All:
- Added wxVersionInfo and various GetLibraryVersionInfo() functions (troelsk). - Added wxVersionInfo and various GetLibraryVersionInfo() functions (troelsk).
- Added wxNumberFormatter for dealing with thousands separators. - Added wxNumberFormatter for dealing with thousands separators.
- Added wxIntegerValidator<> and wxFloatingPointValidator<> validators. - Added wxIntegerValidator<> and wxFloatingPointValidator<> validators.
- Added wxIMAGE_OPTION_GIF_COMMENT to read and write GIF comments (troelsk).
Unix: Unix:

View File

@@ -1136,6 +1136,12 @@ public:
the resulting PNG file. Use this option if your application produces the resulting PNG file. Use this option if your application produces
images with small size variation. images with small size variation.
Options specific to wxGIFHandler:
@li @c wxIMAGE_OPTION_GIF_COMMENT: The comment text that is read from
or written to the GIF file. In an animated GIF each frame can have
its own comment. If there is only a comment in the first frame of
a GIF it will not be repeated in other frames.
@param name @param name
The name of the option, case-insensitive. The name of the option, case-insensitive.
@return @return

View File

@@ -40,6 +40,8 @@ enum
GIF_MARKER_EXT_APP = 0xFF GIF_MARKER_EXT_APP = 0xFF
}; };
#define GetFrame(n) ((GIFImage*)m_frames[n])
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// GIFImage // GIFImage
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@@ -61,6 +63,7 @@ public:
unsigned char *p; // bitmap unsigned char *p; // bitmap
unsigned char *pal; // palette unsigned char *pal; // palette
unsigned int ncolours; // number of colours unsigned int ncolours; // number of colours
wxString comment;
wxDECLARE_NO_COPY_CLASS(GIFImage); wxDECLARE_NO_COPY_CLASS(GIFImage);
}; };
@@ -189,6 +192,12 @@ bool wxGIFDecoder::ConvertToImage(unsigned int frame, wxImage *image) const
*(dst++) = pal[3 * (*src) + 2]; *(dst++) = pal[3 * (*src) + 2];
} }
wxString comment = GetFrame(frame)->comment;
if ( !comment.empty() )
{
image->SetOption(wxIMAGE_OPTION_GIF_COMMENT, comment);
}
return true; return true;
} }
@@ -197,9 +206,6 @@ bool wxGIFDecoder::ConvertToImage(unsigned int frame, wxImage *image) const
// Data accessors // Data accessors
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#define GetFrame(n) ((GIFImage*)m_frames[n])
// Get data for current frame // Get data for current frame
wxSize wxGIFDecoder::GetFrameSize(unsigned int frame) const wxSize wxGIFDecoder::GetFrameSize(unsigned int frame) const
@@ -672,6 +678,7 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream)
int transparent = -1; int transparent = -1;
disposal = wxANIM_UNSPECIFIED; disposal = wxANIM_UNSPECIFIED;
delay = -1; delay = -1;
wxString comment;
bool done = false; bool done = false;
while (!done) while (!done)
@@ -701,38 +708,68 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream)
done = true; done = true;
break; break;
case GIF_MARKER_EXT: case GIF_MARKER_EXT:
if (stream.GetC() == GIF_MARKER_EXT_GRAPHICS_CONTROL) switch (stream.GetC())
// graphics control extension, parse it
{ {
static const unsigned int gceSize = 6; case GIF_MARKER_EXT_GRAPHICS_CONTROL:
stream.Read(buf, gceSize);
if (stream.LastRead() != gceSize)
{ {
Destroy(); // graphics control extension, parse it
return wxGIF_INVFORMAT;
}
// read delay and convert from 1/100 of a second to ms static const unsigned int gceSize = 6;
delay = 10 * (buf[2] + 256 * buf[3]); stream.Read(buf, gceSize);
if (stream.LastRead() != gceSize)
// read transparent colour index, if used
transparent = buf[1] & 0x01 ? buf[4] : -1;
// read disposal method
disposal = (wxAnimationDisposal)(((buf[1] & 0x1C) >> 2) - 1);
}
else
// other extension, skip
{
while ((i = stream.GetC()) != 0)
{
if (stream.Eof() || (stream.LastRead() == 0) ||
stream.SeekI(i, wxFromCurrent) == wxInvalidOffset)
{ {
done = true; Destroy();
break; return wxGIF_INVFORMAT;
} }
// read delay and convert from 1/100 of a second to ms
delay = 10 * (buf[2] + 256 * buf[3]);
// read transparent colour index, if used
transparent = buf[1] & 0x01 ? buf[4] : -1;
// read disposal method
disposal = (wxAnimationDisposal)(((buf[1] & 0x1C) >> 2) - 1);
break;
} }
case GIF_MARKER_EXT_COMMENT:
{
int len = stream.GetC();
while (len)
{
if ( stream.Eof() )
{
done = true;
break;
}
wxCharBuffer charbuf(len);
stream.Read(charbuf.data(), len);
if ( (int) stream.LastRead() != len )
{
done = true;
break;
}
comment += wxConvertMB2WX(charbuf.data());
len = stream.GetC();
}
break;
}
default:
// other extension, skip
while ((i = stream.GetC()) != 0)
{
if (stream.Eof() || (stream.LastRead() == 0) ||
stream.SeekI(i, wxFromCurrent) == wxInvalidOffset)
{
done = true;
break;
}
}
break;
} }
break; break;
case GIF_MARKER_SEP: case GIF_MARKER_SEP:
@@ -751,6 +788,8 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream)
if (stream.LastRead() != idbSize) if (stream.LastRead() != idbSize)
return wxGIF_INVFORMAT; return wxGIF_INVFORMAT;
pimg->comment = comment;
comment.clear();
pimg->left = buf[0] + 256 * buf[1]; pimg->left = buf[0] + 256 * buf[1];
pimg->top = buf[2] + 256 * buf[3]; pimg->top = buf[2] + 256 * buf[3];
/* /*

View File

@@ -73,6 +73,7 @@ private:
CPPUNIT_TEST( CompareSavedImage ); CPPUNIT_TEST( CompareSavedImage );
CPPUNIT_TEST( SaveAnimatedGIF ); CPPUNIT_TEST( SaveAnimatedGIF );
CPPUNIT_TEST( ReadCorruptedTGA ); CPPUNIT_TEST( ReadCorruptedTGA );
CPPUNIT_TEST( GIFComment );
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
void LoadFromSocketStream(); void LoadFromSocketStream();
@@ -83,6 +84,7 @@ private:
void CompareSavedImage(); void CompareSavedImage();
void SaveAnimatedGIF(); void SaveAnimatedGIF();
void ReadCorruptedTGA(); void ReadCorruptedTGA();
void GIFComment();
DECLARE_NO_COPY_CLASS(ImageTestCase) DECLARE_NO_COPY_CLASS(ImageTestCase)
}; };
@@ -1171,6 +1173,81 @@ void ImageTestCase::ReadCorruptedTGA()
CPPUNIT_ASSERT( !tgaImage.LoadFile(memIn) ); CPPUNIT_ASSERT( !tgaImage.LoadFile(memIn) );
} }
static void TestGIFComment(const wxString& comment)
{
wxImage image("horse.gif");
image.SetOption(wxIMAGE_OPTION_GIF_COMMENT, comment);
wxMemoryOutputStream memOut;
CPPUNIT_ASSERT(image.SaveFile(memOut, wxBITMAP_TYPE_GIF));
wxMemoryInputStream memIn(memOut);
CPPUNIT_ASSERT( image.LoadFile(memIn) );
CPPUNIT_ASSERT_EQUAL(comment,
image.GetOption(wxIMAGE_OPTION_GIF_COMMENT));
}
void ImageTestCase::GIFComment()
{
// Test reading a comment.
wxImage image("horse.gif");
CPPUNIT_ASSERT_EQUAL(" Imported from GRADATION image: gray",
image.GetOption(wxIMAGE_OPTION_GIF_COMMENT));
// Test writing a comment and reading it back.
TestGIFComment("Giving the GIF a gifted giraffe as a gift");
// Test writing and reading a comment again but with a long comment.
TestGIFComment(wxString(wxT('a'), 256)
+ wxString(wxT('b'), 256)
+ wxString(wxT('c'), 256));
// Test writing comments in an animated GIF and reading them back.
CPPUNIT_ASSERT( image.LoadFile("horse.gif") );
wxImageArray images;
int i;
for (i = 0; i < 4; ++i)
{
if (i)
{
images.Add( images[i-1].Rotate90() );
images[i].SetPalette(images[0].GetPalette());
}
else
{
images.Add(image);
}
images[i].SetOption(wxIMAGE_OPTION_GIF_COMMENT,
wxString::Format("GIF comment for frame #%d", i+1));
}
wxMemoryOutputStream memOut;
CPPUNIT_ASSERT( wxGIFHandler().SaveAnimation(images, &memOut) );
wxGIFHandler handler;
wxMemoryInputStream memIn(memOut);
CPPUNIT_ASSERT(memIn.IsOk());
const int imageCount = handler.GetImageCount(memIn);
for (i = 0; i < imageCount; ++i)
{
wxFileOffset pos = memIn.TellI();
CPPUNIT_ASSERT( handler.LoadFile(&image, memIn, true /*verbose?*/, i) );
CPPUNIT_ASSERT_EQUAL(
wxString::Format("GIF comment for frame #%d", i+1),
image.GetOption(wxIMAGE_OPTION_GIF_COMMENT));
memIn.SeekI(pos);
}
}
#endif //wxUSE_IMAGE #endif //wxUSE_IMAGE