From: Dimitri Schoolwerth Date: Wed, 2 Feb 2011 11:19:30 +0000 (+0000) Subject: Added support for reading comments from a GIF image. X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/15f345aad37f2f499f5e631d86238325bf232e36 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 --- diff --git a/docs/changes.txt b/docs/changes.txt index 31750ae2f1..0fbb5c6b8c 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -424,6 +424,7 @@ All: - Added wxVersionInfo and various GetLibraryVersionInfo() functions (troelsk). - Added wxNumberFormatter for dealing with thousands separators. - Added wxIntegerValidator<> and wxFloatingPointValidator<> validators. +- Added wxIMAGE_OPTION_GIF_COMMENT to read and write GIF comments (troelsk). Unix: diff --git a/interface/wx/image.h b/interface/wx/image.h index e106e43061..105ee05f0f 100644 --- a/interface/wx/image.h +++ b/interface/wx/image.h @@ -1136,6 +1136,12 @@ public: the resulting PNG file. Use this option if your application produces 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 The name of the option, case-insensitive. @return diff --git a/src/common/gifdecod.cpp b/src/common/gifdecod.cpp index e8faf7d1dd..36e8ed276e 100644 --- a/src/common/gifdecod.cpp +++ b/src/common/gifdecod.cpp @@ -40,6 +40,8 @@ enum GIF_MARKER_EXT_APP = 0xFF }; +#define GetFrame(n) ((GIFImage*)m_frames[n]) + //--------------------------------------------------------------------------- // GIFImage //--------------------------------------------------------------------------- @@ -61,6 +63,7 @@ public: unsigned char *p; // bitmap unsigned char *pal; // palette unsigned int ncolours; // number of colours + wxString comment; wxDECLARE_NO_COPY_CLASS(GIFImage); }; @@ -189,6 +192,12 @@ bool wxGIFDecoder::ConvertToImage(unsigned int frame, wxImage *image) const *(dst++) = pal[3 * (*src) + 2]; } + wxString comment = GetFrame(frame)->comment; + if ( !comment.empty() ) + { + image->SetOption(wxIMAGE_OPTION_GIF_COMMENT, comment); + } + return true; } @@ -197,9 +206,6 @@ bool wxGIFDecoder::ConvertToImage(unsigned int frame, wxImage *image) const // Data accessors //--------------------------------------------------------------------------- -#define GetFrame(n) ((GIFImage*)m_frames[n]) - - // Get data for current frame wxSize wxGIFDecoder::GetFrameSize(unsigned int frame) const @@ -672,6 +678,7 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream) int transparent = -1; disposal = wxANIM_UNSPECIFIED; delay = -1; + wxString comment; bool done = false; while (!done) @@ -701,38 +708,68 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream) done = true; break; case GIF_MARKER_EXT: - if (stream.GetC() == GIF_MARKER_EXT_GRAPHICS_CONTROL) - // graphics control extension, parse it + switch (stream.GetC()) { - static const unsigned int gceSize = 6; - stream.Read(buf, gceSize); - if (stream.LastRead() != gceSize) + case GIF_MARKER_EXT_GRAPHICS_CONTROL: { - Destroy(); - return wxGIF_INVFORMAT; - } + // graphics control extension, parse it - // read delay and convert from 1/100 of a second to ms - delay = 10 * (buf[2] + 256 * buf[3]); + static const unsigned int gceSize = 6; + stream.Read(buf, gceSize); + if (stream.LastRead() != gceSize) + { + Destroy(); + return wxGIF_INVFORMAT; + } - // read transparent colour index, if used - transparent = buf[1] & 0x01 ? buf[4] : -1; + // read delay and convert from 1/100 of a second to ms + delay = 10 * (buf[2] + 256 * buf[3]); - // read disposal method - disposal = (wxAnimationDisposal)(((buf[1] & 0x1C) >> 2) - 1); - } - else - // other extension, skip - { - while ((i = stream.GetC()) != 0) + // 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: { - if (stream.Eof() || (stream.LastRead() == 0) || - stream.SeekI(i, wxFromCurrent) == wxInvalidOffset) + int len = stream.GetC(); + while (len) { - done = true; - break; + 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; case GIF_MARKER_SEP: @@ -751,6 +788,8 @@ wxGIFErrorCode wxGIFDecoder::LoadGIF(wxInputStream& stream) if (stream.LastRead() != idbSize) return wxGIF_INVFORMAT; + pimg->comment = comment; + comment.clear(); pimg->left = buf[0] + 256 * buf[1]; pimg->top = buf[2] + 256 * buf[3]; /* diff --git a/tests/image/image.cpp b/tests/image/image.cpp index 241001fcaf..958dd922a7 100644 --- a/tests/image/image.cpp +++ b/tests/image/image.cpp @@ -73,6 +73,7 @@ private: CPPUNIT_TEST( CompareSavedImage ); CPPUNIT_TEST( SaveAnimatedGIF ); CPPUNIT_TEST( ReadCorruptedTGA ); + CPPUNIT_TEST( GIFComment ); CPPUNIT_TEST_SUITE_END(); void LoadFromSocketStream(); @@ -83,6 +84,7 @@ private: void CompareSavedImage(); void SaveAnimatedGIF(); void ReadCorruptedTGA(); + void GIFComment(); DECLARE_NO_COPY_CLASS(ImageTestCase) }; @@ -1171,6 +1173,81 @@ void ImageTestCase::ReadCorruptedTGA() 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