- 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:
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
GIF_MARKER_EXT_APP = 0xFF
};
+#define GetFrame(n) ((GIFImage*)m_frames[n])
+
//---------------------------------------------------------------------------
// GIFImage
//---------------------------------------------------------------------------
unsigned char *p; // bitmap
unsigned char *pal; // palette
unsigned int ncolours; // number of colours
+ wxString comment;
wxDECLARE_NO_COPY_CLASS(GIFImage);
};
*(dst++) = pal[3 * (*src) + 2];
}
+ wxString comment = GetFrame(frame)->comment;
+ if ( !comment.empty() )
+ {
+ image->SetOption(wxIMAGE_OPTION_GIF_COMMENT, comment);
+ }
+
return true;
}
// Data accessors
//---------------------------------------------------------------------------
-#define GetFrame(n) ((GIFImage*)m_frames[n])
-
-
// Get data for current frame
wxSize wxGIFDecoder::GetFrameSize(unsigned int frame) const
int transparent = -1;
disposal = wxANIM_UNSPECIFIED;
delay = -1;
+ wxString comment;
bool done = false;
while (!done)
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:
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];
/*
CPPUNIT_TEST( CompareSavedImage );
CPPUNIT_TEST( SaveAnimatedGIF );
CPPUNIT_TEST( ReadCorruptedTGA );
+ CPPUNIT_TEST( GIFComment );
CPPUNIT_TEST_SUITE_END();
void LoadFromSocketStream();
void CompareSavedImage();
void SaveAnimatedGIF();
void ReadCorruptedTGA();
+ void GIFComment();
DECLARE_NO_COPY_CLASS(ImageTestCase)
};
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