Merge pull request #26859 from Kumataro:fix26858

imgcodecs:gif: support IMREAD_UNCHANGED and IMREAD_GRAYSCALE #26859

Close #26858 

### 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:
Kumataro 2025-02-07 01:29:54 +09:00 committed by GitHub
parent 7563cebad5
commit 01e3fe8791
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 14 deletions

View File

@ -413,13 +413,13 @@ can be saved using this function, with these exceptions:
- With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved.
- JPEG XL images with an alpha channel can be saved using this function.
To do this, create 8-bit (or 16-bit, 32-bit float) 4-channel image BGRA, where the alpha channel goes last.
Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535/1.0.
To achieve this, create an 8-bit 4-channel (CV_8UC4) / 16-bit 4-channel (CV_16UC4) / 32-bit float 4-channel (CV_32FC4) BGRA image, ensuring the alpha channel is the last component.
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255/65535/1.0.
- With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- PNG images with an alpha channel can be saved using this function. To do this, create
8-bit (or 16-bit) 4-channel image BGRA, where the alpha channel goes last. Fully transparent pixels
should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535 (see the code sample below).
- PNG images with an alpha channel can be saved using this function.
To achieve this, create an 8-bit 4-channel (CV_8UC4) / 16-bit 4-channel (CV_16UC4) BGRA image, ensuring the alpha channel is the last component.
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255/65535(see the code sample below).
- With PGM/PPM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With TIFF encoder, 8-bit unsigned (CV_8U), 8-bit signed (CV_8S),
16-bit unsigned (CV_16U), 16-bit signed (CV_16S),
@ -428,6 +428,11 @@ can be saved using this function, with these exceptions:
- Multiple images (vector of Mat) can be saved in TIFF format (see the code sample below).
- 32-bit float 3-channel (CV_32FC3) TIFF images will be saved
using the LogLuv high dynamic range encoding (4 bytes per pixel)
- With GIF encoder, 8-bit unsigned (CV_8U) images can be saved.
- GIF images with an alpha channel can be saved using this function.
To achieve this, create an 8-bit 4-channel (CV_8UC4) BGRA image, ensuring the alpha channel is the last component.
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255.
- 8-bit single-channel images (CV_8UC1) are not supported due to GIF's limitation to indexed color formats.
If the image format is not supported, the image will be converted to 8-bit unsigned (CV_8U) and saved that way.

View File

@ -14,7 +14,7 @@ namespace cv
//////////////////////////////////////////////////////////////////////
GifDecoder::GifDecoder() {
m_signature = R"(GIF)";
m_type = CV_8UC4;
m_type = CV_8UC3;
bgColor = -1;
m_buf_supported = true;
globalColorTableSize = 0;
@ -172,12 +172,17 @@ bool GifDecoder::readData(Mat &img) {
} else {
cvtColor(img_, img, COLOR_BGRA2BGR);
}
} else {
} else if (img.channels() == 4){
if (m_use_rgb) {
cvtColor(img_, img, COLOR_BGRA2RGBA);
} else {
img_.copyTo(img);
}
} else if (img.channels() == 1){
cvtColor(img_, img, COLOR_BGRA2GRAY);
} else {
CV_LOG_WARNING(NULL, cv::format("Unsupported channels: %d", img.channels()));
hasRead = false;
}
}
@ -414,6 +419,7 @@ bool GifDecoder::getFrameCount_() {
if (extension == 0xFF) {
int len = m_strm.getByte();
while (len) {
// TODO: In strictly, Application Identifier and Authentication Code should be checked.
if (len == 3) {
if (m_strm.getByte() == 0x01) {
m_animation.loop_count = m_strm.getWord();
@ -427,9 +433,28 @@ bool GifDecoder::getFrameCount_() {
}
len = m_strm.getByte();
}
} else if (extension == 0xF9) {
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.
}
m_strm.skip(2); // Delay Time
m_strm.skip(1); // Transparent Color Index
} 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) {
if (extension != 0xFE && extension != 0x01) {
CV_LOG_WARNING(NULL, "found Unknown Extension Type: " + std::to_string(extension));
}
int len = m_strm.getByte();

View File

@ -241,17 +241,17 @@ TEST(Imgcodecs_Gif, read_gif_special){
const string gif_filename2 = root + "gifsuite/special2.gif";
const string png_filename2 = root + "gifsuite/special2.png";
cv::Mat gif_img1;
ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_UNCHANGED));
ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_COLOR));
ASSERT_FALSE(gif_img1.empty());
cv::Mat png_img1;
ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_UNCHANGED));
ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_COLOR));
ASSERT_FALSE(png_img1.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img1, png_img1);
cv::Mat gif_img2;
ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_UNCHANGED));
ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_COLOR));
ASSERT_FALSE(gif_img2.empty());
cv::Mat png_img2;
ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_UNCHANGED));
ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_COLOR));
ASSERT_FALSE(png_img2.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img2, png_img2);
}
@ -351,6 +351,21 @@ TEST(Imgcodecs_Gif, write_gif_multi) {
EXPECT_EQ(0, remove(gif_filename.c_str()));
}
TEST(Imgcodecs_Gif, encode_IMREAD_GRAYSCALE) {
cv::Mat src;
cv::Mat decoded;
vector<uint8_t> buf;
vector<int> param;
bool ret = false;
src = cv::Mat(240,240,CV_8UC3,cv::Scalar(128,64,32));
EXPECT_NO_THROW(ret = imencode(".gif", src, buf, param));
EXPECT_TRUE(ret);
EXPECT_NO_THROW(decoded = imdecode(buf, cv::IMREAD_GRAYSCALE));
EXPECT_FALSE(decoded.empty());
EXPECT_EQ(decoded.channels(), 1);
}
}//opencv_test
}//namespace

View File

@ -164,6 +164,8 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
continue;
if (cn != 3 && ext == ".ppm")
continue;
if (cn == 1 && ext == ".gif")
continue;
string filename = cv::tempfile(format("%d%s", cn, ext.c_str()).c_str());
Mat img_gt(size, CV_MAKETYPE(CV_8U, cn), Scalar::all(0));
@ -179,8 +181,14 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
ASSERT_TRUE(imwrite(filename, img_gt, parameters));
Mat img = imread(filename, IMREAD_UNCHANGED);
ASSERT_FALSE(img.empty());
EXPECT_EQ(img.size(), img.size());
EXPECT_EQ(img.type(), img.type());
EXPECT_EQ(img_gt.size(), img.size());
EXPECT_EQ(img_gt.channels(), img.channels());
if (ext == ".pfm") {
EXPECT_EQ(img_gt.depth(), CV_8U);
EXPECT_EQ(img.depth(), CV_32F);
} else {
EXPECT_EQ(img_gt.depth(), img.depth());
}
EXPECT_EQ(cn, img.channels());
@ -200,6 +208,14 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
EXPECT_LT(n, 1.);
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
}
else if (ext == ".gif")
{
// GIF encoder will reduce the number of colors to 256.
// It is hard to compare image comparison by pixel unit.
double n = cvtest::norm(img, img_gt, NORM_L1);
double expected = 0.03 * img.size().area();
EXPECT_LT(n, expected);
}
else
{
double n = cvtest::norm(img, img_gt, NORM_L2);
@ -238,6 +254,9 @@ const string all_exts[] =
#ifdef HAVE_IMGCODEC_PFM
".pfm",
#endif
#ifdef HAVE_IMGCODEC_GIF
".gif",
#endif
};
vector<Size> all_sizes()