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:
@@ -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:
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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];
|
||||||
/*
|
/*
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user