Merge pull request #26738 from redhecker:fix

Fix bugs in GIF decoding #26738 

### Pull Request Readiness Checklist

this is related to #25691 

i solved two bugs here:

1. the decoding setting:
according to [https://www.w3.org/Graphics/GIF/spec-gif89a.txt](https://www.w3.org/Graphics/GIF/spec-gif89a.txt)

```
    DEFERRED CLEAR CODE IN LZW COMPRESSION

    There has been confusion about where clear codes can be found in the
    data stream.  As the specification says, they may appear at anytime.  There
    is not a requirement to send a clear code when the string table is full.

    It is the encoder's decision as to when the table should be cleared.  When
    the table is full, the encoder can chose to use the table as is, making no
    changes to it until the encoder chooses to clear it.  The encoder during
    this time sends out codes that are of the maximum Code Size.

    As we can see from the above, when the decoder's table is full, it must
    not change the table until a clear code is received.  The Code Size is that
    of the maximum Code Size.  Processing other than this is done normally.

    Because of a large base of decoders that do not handle the decompression in
    this manner, we ask developers of GIF encoding software to NOT implement
    this feature until at least January 1991 and later if they see that their
    particular market is not ready for it.  This will give developers of GIF
    decoding software time to implement this feature and to get it into the
    hands of their clients before the decoders start "breaking" on the new
    GIF's.  It is not required that encoders change their software to take
    advantage of the deferred clear code, but it is for decoders.
```
at first i didn't consider this case, thus leads to a bug discussed in #25691. the changes made in function lzwDecode() is aiming at solving this.

2. the fetch method of loopCount:
in the codes at https://github.com/opencv/opencv/blob/4.x/modules/imgcodecs/src/grfmt_gif.cpp#L410, if the branch is taken, 3 more bytes will be taken, leading to unpredictable behavior.

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
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Super 2025-01-11 15:34:49 +08:00 committed by GitHub
parent 1e31f8047d
commit 2c2866a7a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,6 +4,7 @@
#include "precomp.hpp" #include "precomp.hpp"
#include "grfmt_gif.hpp" #include "grfmt_gif.hpp"
#include "opencv2/core/utils/logger.hpp"
#ifdef HAVE_IMGCODEC_GIF #ifdef HAVE_IMGCODEC_GIF
namespace cv namespace cv
@ -293,11 +294,12 @@ void GifDecoder::code2pixel(Mat& img, int start, int k){
bool GifDecoder::lzwDecode() { bool GifDecoder::lzwDecode() {
// initialization // initialization
lzwMinCodeSize = m_strm.getByte(); lzwMinCodeSize = m_strm.getByte();
const int lzwMaxSize = (1 << 12); // 4096 is the maximum size of the LZW table (12 bits)
int lzwCodeSize = lzwMinCodeSize + 1; int lzwCodeSize = lzwMinCodeSize + 1;
int clearCode = 1 << lzwMinCodeSize; int clearCode = 1 << lzwMinCodeSize;
int exitCode = clearCode + 1; int exitCode = clearCode + 1;
CV_Assert(lzwCodeSize > 2 && lzwCodeSize <= 12); CV_Assert(lzwCodeSize > 2 && lzwCodeSize <= 12);
std::vector<lzwNodeD> lzwExtraTable((1 << 12) + 1); std::vector<lzwNodeD> lzwExtraTable(lzwMaxSize + 1);
int colorTableSize = clearCode; int colorTableSize = clearCode;
int lzwTableSize = exitCode; int lzwTableSize = exitCode;
@ -321,6 +323,7 @@ bool GifDecoder::lzwDecode() {
// clear code // clear code
if (!(code ^ clearCode)) { if (!(code ^ clearCode)) {
lzwExtraTable.clear(); lzwExtraTable.clear();
lzwExtraTable.resize(lzwMaxSize + 1);
// reset the code size, the same as that in the initialization part // reset the code size, the same as that in the initialization part
lzwCodeSize = lzwMinCodeSize + 1; lzwCodeSize = lzwMinCodeSize + 1;
lzwTableSize = exitCode; lzwTableSize = exitCode;
@ -341,20 +344,24 @@ bool GifDecoder::lzwDecode() {
// output code // output code
// 1. renew the lzw extra table // 1. renew the lzw extra table
if (code < colorTableSize) { // * notice that if the lzw table size is full,
lzwExtraTable[lzwTableSize].suffix = (uchar)code; // * we should use the old table until a clear code is encountered
lzwTableSize ++; if (lzwTableSize < lzwMaxSize) {
lzwExtraTable[lzwTableSize].prefix.clear(); if (code < colorTableSize) {
lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code); lzwExtraTable[lzwTableSize].suffix = (uchar)code;
lzwExtraTable[lzwTableSize].length = 2; lzwTableSize ++;
} else if (code <= lzwTableSize) { lzwExtraTable[lzwTableSize].prefix.clear();
lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0]; lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code);
lzwTableSize ++; lzwExtraTable[lzwTableSize].length = 2;
lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix; } else if (code <= lzwTableSize) {
lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix); lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0];
lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1; lzwTableSize ++;
} else { lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix;
return false; lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix);
lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1;
} else {
return false;
}
} }
// 2. output to the code stream // 2. output to the code stream
@ -368,7 +375,7 @@ bool GifDecoder::lzwDecode() {
} }
// check if the code size is full // check if the code size is full
if (lzwTableSize > (1 << 12)) { if (lzwTableSize > lzwMaxSize) {
return false; return false;
} }
@ -402,14 +409,33 @@ bool GifDecoder::getFrameCount_() {
while (type != 0x3B) { while (type != 0x3B) {
if (!(type ^ 0x21)) { if (!(type ^ 0x21)) {
// skip all kinds of the extensions // skip all kinds of the extensions
m_strm.skip(1); int extension = m_strm.getByte();
int len = m_strm.getByte(); // Application Extension need to be handled for the loop count
while (len) { if (extension == 0xFF) {
m_strm.skip(len); int len = m_strm.getByte();
len = m_strm.getByte(); while (len) {
if (len == 3 && m_strm.getByte() == 1) if (len == 3) {
{ if (m_strm.getByte() == 0x01) {
m_animation.loop_count = m_strm.getWord(); m_animation.loop_count = m_strm.getWord();
} else {
// this branch should not be reached in normal cases
m_strm.skip(2);
CV_LOG_WARNING(NULL, "found Unknown Application Extension");
}
} else {
m_strm.skip(len);
}
len = m_strm.getByte();
}
} else {
// if it does not belong to any of the extension type mentioned in the GIF Specification
if (extension != 0xF9 && extension != 0xFE && extension != 0x01) {
CV_LOG_WARNING(NULL, "found Unknown Extension Type: " + std::to_string(extension));
}
int len = m_strm.getByte();
while (len) {
m_strm.skip(len);
len = m_strm.getByte();
} }
} }
} else if (!(type ^ 0x2C)) { } else if (!(type ^ 0x2C)) {