mirror of
https://github.com/opencv/opencv.git
synced 2025-08-06 14:36:36 +08:00
Merge pull request #26930 from Kumataro:fix26924
Imgcodecs: gif: support Disposal Method #26930 Close https://github.com/opencv/opencv/issues/26924 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
43551b72d7
commit
a63ede6b1d
@ -24,7 +24,6 @@ GifDecoder::GifDecoder() {
|
||||
hasRead = false;
|
||||
hasTransparentColor = false;
|
||||
transparentColor = 0;
|
||||
opMode = GRFMT_GIF_Nothing;
|
||||
top = 0, left = 0, width = 0, height = 0;
|
||||
depth = 8;
|
||||
idx = 0;
|
||||
@ -66,6 +65,8 @@ bool GifDecoder::readHeader() {
|
||||
for (int i = 0; i < 3 * globalColorTableSize; i++) {
|
||||
globalColorTable[i] = (uchar)m_strm.getByte();
|
||||
}
|
||||
CV_CheckGE(bgColor, 0, "bgColor should be >= 0");
|
||||
CV_CheckLT(bgColor, globalColorTableSize, "bgColor should be < globalColorTableSize");
|
||||
}
|
||||
|
||||
// get the frame count
|
||||
@ -81,7 +82,8 @@ bool GifDecoder::readData(Mat &img) {
|
||||
return true;
|
||||
}
|
||||
|
||||
readExtensions();
|
||||
const GifDisposeMethod disposalMethod = readExtensions();
|
||||
|
||||
// Image separator
|
||||
CV_Assert(!(m_strm.getByte()^0x2C));
|
||||
left = m_strm.getWord();
|
||||
@ -93,38 +95,50 @@ bool GifDecoder::readData(Mat &img) {
|
||||
imgCodeStream.resize(width * height);
|
||||
Mat img_;
|
||||
|
||||
switch (opMode) {
|
||||
case GifOpMode::GRFMT_GIF_PreviousImage:
|
||||
if (lastImage.empty()){
|
||||
img_ = Mat::zeros(m_height, m_width, CV_8UC4);
|
||||
} else {
|
||||
img_ = lastImage;
|
||||
if (lastImage.empty())
|
||||
{
|
||||
Scalar background(0.0, 0.0, 0.0, 0.0);
|
||||
if (bgColor < globalColorTableSize)
|
||||
{
|
||||
background = Scalar( globalColorTable[bgColor * 3 + 2], // B
|
||||
globalColorTable[bgColor * 3 + 1], // G
|
||||
globalColorTable[bgColor * 3 + 0], // R
|
||||
0); // A
|
||||
}
|
||||
img_ = Mat(m_height, m_width, CV_8UC4, background);
|
||||
} else {
|
||||
img_ = lastImage;
|
||||
}
|
||||
lastImage.release();
|
||||
|
||||
Mat restore;
|
||||
switch(disposalMethod)
|
||||
{
|
||||
case GIF_DISPOSE_NA:
|
||||
case GIF_DISPOSE_NONE:
|
||||
// Do nothing
|
||||
break;
|
||||
case GIF_DISPOSE_RESTORE_BACKGROUND:
|
||||
if (bgColor < globalColorTableSize)
|
||||
{
|
||||
const Scalar background = Scalar( globalColorTable[bgColor * 3 + 2], // B
|
||||
globalColorTable[bgColor * 3 + 1], // G
|
||||
globalColorTable[bgColor * 3 + 0], // R
|
||||
0); // A
|
||||
restore = Mat(width, height, CV_8UC4, background);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_LOG_WARNING(NULL, cv::format("bgColor(%d) is out of globalColorTableSize(%d)", bgColor, globalColorTableSize));
|
||||
}
|
||||
break;
|
||||
case GifOpMode::GRFMT_GIF_Background:
|
||||
// background color is valid iff global color table exists
|
||||
CV_Assert(globalColorTableSize > 0);
|
||||
if (hasTransparentColor && transparentColor == bgColor) {
|
||||
img_ = Mat(m_height, m_width, CV_8UC4,
|
||||
Scalar(globalColorTable[bgColor * 3 + 2],
|
||||
globalColorTable[bgColor * 3 + 1],
|
||||
globalColorTable[bgColor * 3], 0));
|
||||
} else {
|
||||
img_ = Mat(m_height, m_width, CV_8UC4,
|
||||
Scalar(globalColorTable[bgColor * 3 + 2],
|
||||
globalColorTable[bgColor * 3 + 1],
|
||||
globalColorTable[bgColor * 3], 255));
|
||||
}
|
||||
break;
|
||||
case GifOpMode::GRFMT_GIF_Nothing:
|
||||
case GifOpMode::GRFMT_GIF_Cover:
|
||||
// default value
|
||||
img_ = Mat::zeros(m_height, m_width, CV_8UC4);
|
||||
case GIF_DISPOSE_RESTORE_PREVIOUS:
|
||||
restore = Mat(img_, cv::Rect(left,top,width,height)).clone();
|
||||
break;
|
||||
default:
|
||||
CV_Assert(false);
|
||||
break;
|
||||
}
|
||||
lastImage.release();
|
||||
|
||||
auto flags = (uchar)m_strm.getByte();
|
||||
if (flags & 0x80) {
|
||||
@ -189,6 +203,13 @@ bool GifDecoder::readData(Mat &img) {
|
||||
// release the memory
|
||||
img_.release();
|
||||
|
||||
// update lastImage to dispose current frame.
|
||||
if(!restore.empty())
|
||||
{
|
||||
Mat roi = Mat(lastImage, cv::Rect(left,top,width,height));
|
||||
restore.copyTo(roi);
|
||||
}
|
||||
|
||||
return hasRead;
|
||||
}
|
||||
|
||||
@ -212,8 +233,9 @@ bool GifDecoder::nextPage() {
|
||||
}
|
||||
}
|
||||
|
||||
void GifDecoder::readExtensions() {
|
||||
GifDisposeMethod GifDecoder::readExtensions() {
|
||||
uchar len;
|
||||
GifDisposeMethod disposalMethod = GifDisposeMethod::GIF_DISPOSE_NA;
|
||||
while (!(m_strm.getByte() ^ 0x21)) {
|
||||
auto extensionType = (uchar)m_strm.getByte();
|
||||
|
||||
@ -221,13 +243,19 @@ void GifDecoder::readExtensions() {
|
||||
// the scope of this extension is the next image or plain text extension
|
||||
if (!(extensionType ^ 0xF9)) {
|
||||
hasTransparentColor = false;
|
||||
opMode = GifOpMode::GRFMT_GIF_Nothing;// default value
|
||||
len = (uchar)m_strm.getByte();
|
||||
CV_Assert(len == 4);
|
||||
auto flags = (uchar)m_strm.getByte();
|
||||
const uint8_t packedFields = (uchar)m_strm.getByte();
|
||||
|
||||
const uint8_t dm = (packedFields >> GIF_DISPOSE_METHOD_SHIFT) & GIF_DISPOSE_METHOD_MASK;
|
||||
CV_CheckLE(dm, GIF_DISPOSE_MAX, "Unsupported Dispose Method");
|
||||
disposalMethod = static_cast<GifDisposeMethod>(dm);
|
||||
|
||||
const uint8_t transColorFlag = packedFields & GIF_TRANS_COLOR_FLAG_MASK;
|
||||
CV_CheckLE(transColorFlag, GIF_TRANSPARENT_INDEX_MAX, "Unsupported Transparent Color Flag");
|
||||
hasTransparentColor = (transColorFlag == GIF_TRANSPARENT_INDEX_GIVEN);
|
||||
|
||||
m_animation.durations.push_back(m_strm.getWord() * 10); // delay time
|
||||
opMode = (GifOpMode)((flags & 0x1C) >> 2);
|
||||
hasTransparentColor = flags & 0x01;
|
||||
transparentColor = (uchar)m_strm.getByte();
|
||||
}
|
||||
|
||||
@ -240,6 +268,8 @@ void GifDecoder::readExtensions() {
|
||||
}
|
||||
// roll back to the block identifier
|
||||
m_strm.setPos(m_strm.getPos() - 1);
|
||||
|
||||
return disposalMethod;
|
||||
}
|
||||
|
||||
void GifDecoder::code2pixel(Mat& img, int start, int k){
|
||||
@ -247,23 +277,6 @@ void GifDecoder::code2pixel(Mat& img, int start, int k){
|
||||
for (int j = 0; j < width; j++) {
|
||||
uchar colorIdx = imgCodeStream[idx++];
|
||||
if (hasTransparentColor && colorIdx == transparentColor) {
|
||||
if (opMode != GifOpMode::GRFMT_GIF_PreviousImage) {
|
||||
if (colorIdx < localColorTableSize) {
|
||||
img.at<Vec4b>(top + i, left + j) =
|
||||
Vec4b(localColorTable[colorIdx * 3 + 2], // B
|
||||
localColorTable[colorIdx * 3 + 1], // G
|
||||
localColorTable[colorIdx * 3], // R
|
||||
0); // A
|
||||
} else if (colorIdx < globalColorTableSize) {
|
||||
img.at<Vec4b>(top + i, left + j) =
|
||||
Vec4b(globalColorTable[colorIdx * 3 + 2], // B
|
||||
globalColorTable[colorIdx * 3 + 1], // G
|
||||
globalColorTable[colorIdx * 3], // R
|
||||
0); // A
|
||||
} else {
|
||||
img.at<Vec4b>(top + i, left + j) = Vec4b(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (colorIdx < localColorTableSize) {
|
||||
@ -437,14 +450,10 @@ bool GifDecoder::getFrameCount_() {
|
||||
int len = m_strm.getByte();
|
||||
while (len) {
|
||||
if (len == 4) {
|
||||
int packedFields = m_strm.getByte();
|
||||
// 3 bit : Reserved
|
||||
// 3 bit : Disposal Method
|
||||
// 1 bit : User Input Flag
|
||||
// 1 bit : Transparent Color Flag
|
||||
if ( (packedFields & 0x01)== 0x01) {
|
||||
m_type = CV_8UC4; // Transparent Index is given.
|
||||
}
|
||||
const uint8_t packedFields = static_cast<uint8_t>(m_strm.getByte()); // Packed Fields
|
||||
const uint8_t transColorFlag = packedFields & GIF_TRANS_COLOR_FLAG_MASK;
|
||||
CV_CheckLE(transColorFlag, GIF_TRANSPARENT_INDEX_MAX, "Unsupported Transparent Color Flag");
|
||||
m_type = (transColorFlag == GIF_TRANSPARENT_INDEX_GIVEN) ? CV_8UC4 : CV_8UC3;
|
||||
m_strm.skip(2); // Delay Time
|
||||
m_strm.skip(1); // Transparent Color Index
|
||||
} else {
|
||||
@ -518,7 +527,6 @@ GifEncoder::GifEncoder() {
|
||||
m_height = 0, m_width = 0;
|
||||
width = 0, height = 0, top = 0, left = 0;
|
||||
m_buf_supported = true;
|
||||
opMode = GRFMT_GIF_Cover;
|
||||
transparentColor = 0; // index of the transparent color, default 0. currently it is a constant number
|
||||
transparentRGB = Vec3b(0, 0, 0); // the transparent color, default black
|
||||
lzwMaxCodeSize = 12; // the maximum code size, default 12. currently it is a constant number
|
||||
@ -665,11 +673,9 @@ bool GifEncoder::writeFrame(const Mat &img) {
|
||||
strm.putByte(0x21); // extension introducer
|
||||
strm.putByte(0xF9); // graphic control label
|
||||
strm.putByte(0x04); // block size, fixed number
|
||||
// flag is a packed field, and the first 3 bits are reserved
|
||||
uchar flag = opMode << 2;
|
||||
if (criticalTransparency)
|
||||
flag |= 1;
|
||||
strm.putByte(flag);
|
||||
const int gcePackedFields = static_cast<int>(GIF_DISPOSE_RESTORE_PREVIOUS << GIF_DISPOSE_METHOD_SHIFT) |
|
||||
static_cast<int>(criticalTransparency ? GIF_TRANSPARENT_INDEX_GIVEN : GIF_TRANSPARENT_INDEX_NOT_GIVEN);
|
||||
strm.putByte(gcePackedFields);
|
||||
strm.putWord(frameDelay);
|
||||
strm.putByte(transparentColor);
|
||||
strm.putByte(0x00); // end of the extension
|
||||
@ -680,7 +686,7 @@ bool GifEncoder::writeFrame(const Mat &img) {
|
||||
strm.putWord(top);
|
||||
strm.putWord(width);
|
||||
strm.putWord(height);
|
||||
flag = localColorTableSize > 0 ? 0x80 : 0x00;
|
||||
uint8_t flag = localColorTableSize > 0 ? 0x80 : 0x00;
|
||||
if (localColorTableSize > 0) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
getColorTable(img_vec, false);
|
||||
|
@ -11,14 +11,35 @@
|
||||
namespace cv
|
||||
{
|
||||
|
||||
enum GifOpMode
|
||||
{
|
||||
GRFMT_GIF_Nothing = 0,
|
||||
GRFMT_GIF_PreviousImage = 1,
|
||||
GRFMT_GIF_Background = 2,
|
||||
GRFMT_GIF_Cover = 3
|
||||
// See https://www.w3.org/Graphics/GIF/spec-gif89a.txt
|
||||
// 23. Graphic Control Extension.
|
||||
// <Packed Fields>
|
||||
// Reserved : 3 bits
|
||||
// Disposal Method : 3 bits
|
||||
// User Input Flag : 1 bit
|
||||
// Transparent Color Flag : 1 bit
|
||||
constexpr int GIF_DISPOSE_METHOD_SHIFT = 2;
|
||||
constexpr int GIF_DISPOSE_METHOD_MASK = 7; // 0b111
|
||||
constexpr int GIF_TRANS_COLOR_FLAG_MASK = 1; // 0b1
|
||||
|
||||
enum GifDisposeMethod {
|
||||
GIF_DISPOSE_NA = 0,
|
||||
GIF_DISPOSE_NONE = 1,
|
||||
GIF_DISPOSE_RESTORE_BACKGROUND = 2,
|
||||
GIF_DISPOSE_RESTORE_PREVIOUS = 3,
|
||||
// 4-7 are reserved/undefined.
|
||||
|
||||
GIF_DISPOSE_MAX = GIF_DISPOSE_RESTORE_PREVIOUS,
|
||||
};
|
||||
|
||||
enum GifTransparentColorFlag {
|
||||
GIF_TRANSPARENT_INDEX_NOT_GIVEN = 0,
|
||||
GIF_TRANSPARENT_INDEX_GIVEN = 1,
|
||||
|
||||
GIF_TRANSPARENT_INDEX_MAX = GIF_TRANSPARENT_INDEX_GIVEN,
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//// GIF Decoder ////
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -43,7 +64,6 @@ protected:
|
||||
int depth;
|
||||
int idx;
|
||||
|
||||
GifOpMode opMode;
|
||||
bool hasTransparentColor;
|
||||
uchar transparentColor;
|
||||
int top, left, width, height;
|
||||
@ -66,7 +86,7 @@ protected:
|
||||
std::vector<uchar> prefix;
|
||||
};
|
||||
|
||||
void readExtensions();
|
||||
GifDisposeMethod readExtensions();
|
||||
void code2pixel(Mat& img, int start, int k);
|
||||
bool lzwDecode();
|
||||
bool getFrameCount_();
|
||||
|
@ -366,6 +366,54 @@ TEST(Imgcodecs_Gif, encode_IMREAD_GRAYSCALE) {
|
||||
EXPECT_EQ(decoded.channels(), 1);
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26924
|
||||
TEST(Imgcodecs_Gif, decode_disposal_method)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/disposalMethod.gif";
|
||||
cv::Animation anim;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imreadanimation(filename, anim, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
/* [Detail of this test]
|
||||
* disposalMethod.gif has 5 frames to draw 8x8 rectangles with each color index, offsets and disposal method.
|
||||
* frame 1 draws {1, ... ,1} rectangle at (1,1) with DisposalMethod = 0.
|
||||
* frame 2 draws {2, ... ,2} rectangle at (2,2) with DisposalMethod = 3.
|
||||
* frame 3 draws {3, ... ,3} rectangle at (3,3) with DisposalMethod = 1.
|
||||
* frame 4 draws {4, ... ,4} rectangle at (4,4) with DisposalMethod = 2.
|
||||
* frame 5 draws {5, ... ,5} rectangle at (5,5) with DisposalMethod = 1.
|
||||
*
|
||||
* To convenience to test, color[N] in the color table has RGB(32*N, some, some).
|
||||
* color[0] = RGB(0,0,0) (background color).
|
||||
* color[1] = RGB(32,0,0)
|
||||
* color[2] = RGB(64,0,255)
|
||||
* color[3] = RGB(96,255,0)
|
||||
* color[4] = RGB(128,128,128)
|
||||
* color[5] = RGB(160,255,255)
|
||||
*/
|
||||
const int refIds[5][6] =
|
||||
{// { 0, 0, 0, 0, 0, 0} 0 is background color.
|
||||
{ 0, 1, 1, 1, 1, 1}, // 1 is to be not disposed.
|
||||
{ 0, 1, 2, 2, 2, 2}, // 2 is to be restored to previous.
|
||||
{ 0, 1, 1, 3, 3, 3}, // 3 is to be left in place.
|
||||
{ 0, 1, 1, 3, 4, 4}, // 4 is to be restored to the background color.
|
||||
{ 0, 1, 1, 3, 0, 5}, // 5 is to be left in place.
|
||||
};
|
||||
|
||||
for(int i = 0 ; i < 5; i++)
|
||||
{
|
||||
cv::Mat frame = anim.frames[i];
|
||||
EXPECT_FALSE(frame.empty());
|
||||
EXPECT_EQ(frame.type(), CV_8UC4);
|
||||
for(int j = 0; j < 6; j ++ )
|
||||
{
|
||||
const cv::Scalar p = frame.at<Vec4b>(j,j);
|
||||
EXPECT_EQ( p[2], refIds[i][j] * 32 ) << " i = " << i << " j = " << j << " pixels = " << p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//opencv_test
|
||||
}//namespace
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user