chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,744 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, animation));
|
||||
EXPECT_EQ(1000, animation.durations.back());
|
||||
|
||||
if (!hasAlpha)
|
||||
cvtColor(animation.frames[0], animation.frames[0], COLOR_BGRA2BGR);
|
||||
|
||||
animation.loop_count = 0xffff; // 0xffff is the maximum value to set.
|
||||
|
||||
// Add the first frame with a duration value of 400 milliseconds.
|
||||
int duration = 80;
|
||||
animation.durations[0] = duration * 5;
|
||||
Mat image = animation.frames[0].clone();
|
||||
putText(animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
|
||||
// Define a region of interest (ROI)
|
||||
Rect roi(2, 16, 26, 16);
|
||||
|
||||
// Modify the ROI in n iterations to simulate slight changes in animation frames.
|
||||
for (int i = 1; i < n; i++)
|
||||
{
|
||||
roi.x++;
|
||||
roi.width -= 2;
|
||||
RNG rng = theRNG();
|
||||
for (int x = roi.x; x < roi.x + roi.width; x++)
|
||||
for (int y = roi.y; y < roi.y + roi.height; y++)
|
||||
{
|
||||
if (hasAlpha)
|
||||
{
|
||||
Vec4b& pixel = image.at<Vec4b>(y, x);
|
||||
if (pixel[3] > 0)
|
||||
{
|
||||
if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||
pixel[3] -= (uchar)rng.uniform(2, 5);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec3b& pixel = image.at<Vec3b>(y, x);
|
||||
if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the duration and add the modified frame to the animation.
|
||||
duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly).
|
||||
animation.frames.push_back(image.clone());
|
||||
putText(animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
animation.durations.push_back(duration);
|
||||
}
|
||||
|
||||
// Add two identical frames with the same duration.
|
||||
if (animation.frames.size() > 1 && animation.frames.size() < 20)
|
||||
{
|
||||
animation.durations.push_back(++duration);
|
||||
animation.frames.push_back(animation.frames.back());
|
||||
animation.durations.push_back(++duration);
|
||||
animation.frames.push_back(animation.frames.back());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
TEST(Imgcodecs_Gif, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
s_animation.bgcolor = Scalar(0, 0, 0, 0); // TO DO not implemented yet.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".gif");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size();
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Check that the background color and loop count match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order
|
||||
EXPECT_EQ(l_animation.loop_count, s_animation.loop_count);
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(cvRound(s_animation.durations[i] / 10), cvRound(l_animation.durations[i] / 10));
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[16], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[17], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[18], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> webp_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_GIF
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
TEST(Imgcodecs_WebP, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||
// and the duration value for the last frame is handled by libwebp.
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Check that the background color and loop count match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order
|
||||
EXPECT_EQ(l_animation.loop_count, s_animation.loop_count);
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> webp_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwriteanimation_rgb)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||
// and the duration value for the last frame is handled by libwebp.
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0);
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_COLOR);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0);
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
|
||||
vector<Mat> webp_frames;
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count,webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwritemulti_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||
EXPECT_EQ(4, s_animation.frames[0].channels());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwritemulti_rgb)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imencode_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
// Test encoding and decoding the images in memory (without saving to disk).
|
||||
EXPECT_TRUE(imencode(".webp", s_animation.frames, buf));
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, apng_frames.size());
|
||||
}
|
||||
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
#ifdef HAVE_PNG
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
{
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
apng_frames.clear();
|
||||
// Test saving the animation frames as individual still images.
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
|
||||
// Read back the still images into a vector of Mats.
|
||||
EXPECT_TRUE(imreadmulti(output, apng_frames));
|
||||
|
||||
// Expect all frames written as multi-page image
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgba16u)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
s_animation.frames[i].convertTo(s_animation.frames[i], CV_16U, 255);
|
||||
}
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
apng_frames.clear();
|
||||
// Test saving the animation frames as individual still images.
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
|
||||
// Read back the still images into a vector of Mats.
|
||||
EXPECT_TRUE(imreadmulti(output, apng_frames));
|
||||
|
||||
// Expect all frames written as multi-page image
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgb)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
EXPECT_EQ(l_animation.frames.size(), s_animation.frames.size() - 2);
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_gray)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY);
|
||||
}
|
||||
|
||||
s_animation.bgcolor = Scalar(50, 100, 150);
|
||||
string output = cv::tempfile(".png");
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
EXPECT_EQ(Scalar(), l_animation.bgcolor);
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < l_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
EXPECT_EQ(true, imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
EXPECT_EQ(true, imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
EXPECT_EQ(imcount(output), read_frames.size());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_rgb)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < read_frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_gray)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY);
|
||||
}
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(1, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(1, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_COLOR));
|
||||
EXPECT_EQ(3, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_GRAYSCALE));
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < read_frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_bgcolor)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||
s_animation.bgcolor = Scalar(50, 100, 150); // will be written in bKGD chunk as RGB.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Check that the background color match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||
s_animation.bgcolor = Scalar();
|
||||
|
||||
output = cv::tempfile(".png");
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imencode_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
vector<Mat> read_frames;
|
||||
// Test encoding and decoding the images in memory (without saving to disk).
|
||||
EXPECT_TRUE(imencode(".png", s_animation.frames, buf));
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, read_frames));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_ImageCollection;
|
||||
|
||||
const string exts_multi[] = {
|
||||
#ifdef HAVE_AVIF
|
||||
".avif",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
".png",
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
TEST_P(Imgcodecs_ImageCollection, animations)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(GetParam().c_str());
|
||||
ASSERT_TRUE(imwritemulti(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
|
||||
{
|
||||
ImageCollection collection(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(read_frames.size(), collection.size());
|
||||
int i = 0;
|
||||
for (auto&& frame : collection)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(frame, read_frames[i], NORM_INF));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_ImageCollection,
|
||||
testing::ValuesIn(exts_multi));
|
||||
|
||||
TEST(Imgcodecs_APNG, imdecode_animation)
|
||||
{
|
||||
Animation gt_animation, mem_animation;
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, gt_animation));
|
||||
EXPECT_EQ(1000, gt_animation.durations.back());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
readFileBytes(filename, buf);
|
||||
EXPECT_TRUE(imdecodeanimation(buf, mem_animation));
|
||||
|
||||
EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size());
|
||||
EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor);
|
||||
EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count);
|
||||
for (size_t i = 0; i < gt_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF));
|
||||
EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imencode_animation)
|
||||
{
|
||||
Animation gt_animation, mem_animation;
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, gt_animation));
|
||||
EXPECT_EQ(1000, gt_animation.durations.back());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
EXPECT_TRUE(imencodeanimation(".png", gt_animation, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, mem_animation));
|
||||
|
||||
EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size());
|
||||
EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor);
|
||||
EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count);
|
||||
for (size_t i = 0; i < gt_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF));
|
||||
EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_has_hidden_frame)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/033.png";
|
||||
Animation animation1, animation2, animation3;
|
||||
|
||||
imreadanimation(filename, animation1);
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation1.frames.size());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation2));
|
||||
|
||||
EXPECT_FALSE(animation2.still_image.empty());
|
||||
EXPECT_EQ(animation1.frames.size(), animation2.frames.size());
|
||||
|
||||
animation1.frames.erase(animation1.frames.begin());
|
||||
animation1.durations.erase(animation1.durations.begin());
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation3));
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_TRUE(animation3.still_image.empty());
|
||||
EXPECT_EQ((size_t)1, animation3.frames.size());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_imread_preview)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/034.png";
|
||||
cv::Mat imread_result;
|
||||
cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(imread_result.empty());
|
||||
|
||||
Animation animation;
|
||||
ASSERT_TRUE(imreadanimation(filename, animation));
|
||||
EXPECT_FALSE(animation.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation.frames.size());
|
||||
|
||||
EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF));
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
|
||||
TEST(Imgcodecs_APNG, imread_animation_16u)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/033.png";
|
||||
|
||||
Mat img = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC4);
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 2));
|
||||
EXPECT_EQ(65535, img.at<ushort>(0, 3));
|
||||
|
||||
img = imread(filename, IMREAD_GRAYSCALE);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC1);
|
||||
EXPECT_EQ(76, img.at<uchar>(0, 0));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC3);
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 0));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 1));
|
||||
EXPECT_EQ(255, img.at<uchar>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC3);
|
||||
EXPECT_EQ(255, img.at<uchar>(0, 0));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 1));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC1);
|
||||
EXPECT_EQ(19517, img.at<ushort>(0, 0));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC3);
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC3);
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 2));
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG || HAVE_SPNG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,310 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
|
||||
namespace opencv_test {
|
||||
namespace {
|
||||
|
||||
class Imgcodecs_Avif_RoundTripSuite
|
||||
: public testing::TestWithParam<std::tuple<int, int, int, ImreadModes>> {
|
||||
protected:
|
||||
static cv::Mat modifyImage(const cv::Mat& img_original, int channels,
|
||||
int bit_depth) {
|
||||
cv::Mat img;
|
||||
if (channels == 1) {
|
||||
cv::cvtColor(img_original, img, cv::COLOR_BGR2GRAY);
|
||||
} else if (channels == 4) {
|
||||
std::vector<cv::Mat> imgs;
|
||||
cv::split(img_original, imgs);
|
||||
imgs.push_back(cv::Mat(imgs[0]));
|
||||
imgs[imgs.size() - 1] = cv::Scalar::all(128);
|
||||
cv::merge(imgs, img);
|
||||
} else {
|
||||
img = img_original.clone();
|
||||
}
|
||||
|
||||
cv::Mat img_final = img;
|
||||
// Convert image to CV_16U for some bit depths.
|
||||
if (bit_depth > 8) img.convertTo(img_final, CV_16U, 1 << (bit_depth - 8));
|
||||
|
||||
return img_final;
|
||||
}
|
||||
|
||||
void SetUp() {
|
||||
bit_depth_ = std::get<0>(GetParam());
|
||||
channels_ = std::get<1>(GetParam());
|
||||
quality_ = std::get<2>(GetParam());
|
||||
imread_mode_ = std::get<3>(GetParam());
|
||||
encoding_params_ = {cv::IMWRITE_AVIF_QUALITY, quality_,
|
||||
cv::IMWRITE_AVIF_DEPTH, bit_depth_};
|
||||
}
|
||||
|
||||
bool IsBitDepthValid() const {
|
||||
return (bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
|
||||
}
|
||||
|
||||
// Makes sure images are close enough after encode/decode roundtrip.
|
||||
void ValidateRead(const cv::Mat& img_original, const cv::Mat& img) const {
|
||||
EXPECT_EQ(img_original.size(), img.size());
|
||||
if (imread_mode_ == IMREAD_UNCHANGED) {
|
||||
ASSERT_EQ(img_original.type(), img.type());
|
||||
// Lossless.
|
||||
if (quality_ == 100) {
|
||||
EXPECT_EQ(0, cvtest::norm(img, img_original, NORM_INF));
|
||||
} else {
|
||||
const float norm = cvtest::norm(img, img_original, NORM_L2) /
|
||||
img.channels() / img.cols / img.rows /
|
||||
(1 << (bit_depth_ - 8));
|
||||
if (quality_ == 50) {
|
||||
EXPECT_LE(norm, 10);
|
||||
} else if (quality_ == 0) {
|
||||
EXPECT_LE(norm, 13);
|
||||
} else {
|
||||
EXPECT_FALSE(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int bit_depth_;
|
||||
int channels_;
|
||||
int quality_;
|
||||
int imread_mode_;
|
||||
std::vector<int> encoding_params_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Image_RoundTripSuite
|
||||
: public Imgcodecs_Avif_RoundTripSuite {
|
||||
public:
|
||||
const cv::Mat& get_img_original() {
|
||||
const Key key = {channels_, (bit_depth_ < 8) ? 8 : bit_depth_};
|
||||
return imgs_[key];
|
||||
}
|
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../cv/shared/lena.png";
|
||||
const cv::Mat img_original = cv::imread(filename);
|
||||
cv::Mat img_resized;
|
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
|
||||
for (int channels : {1, 3, 4}) {
|
||||
for (int bit_depth : {8, 10, 12}) {
|
||||
const Key key{channels, bit_depth};
|
||||
imgs_[key] = modifyImage(img_resized, channels, bit_depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int kWidth;
|
||||
static const int kHeight;
|
||||
|
||||
private:
|
||||
typedef std::tuple<int, int> Key;
|
||||
static std::map<Key, cv::Mat> imgs_;
|
||||
};
|
||||
std::map<std::tuple<int, int>, cv::Mat>
|
||||
Imgcodecs_Avif_Image_RoundTripSuite::imgs_;
|
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kWidth = 51;
|
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kHeight = 31;
|
||||
|
||||
class Imgcodecs_Avif_Image_WriteReadSuite
|
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_WriteReadSuite, imwrite_imread) {
|
||||
const cv::Mat& img_original = get_img_original();
|
||||
ASSERT_FALSE(img_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img_original, encoding_params_));
|
||||
|
||||
// Read from file.
|
||||
const cv::Mat img = cv::imread(output, imread_mode_);
|
||||
|
||||
ValidateRead(img_original, img);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_WriteReadSuite,
|
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3, 4}),
|
||||
::testing::ValuesIn({0, 50, 100}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
class Imgcodecs_Avif_Image_EncodeDecodeSuite
|
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) {
|
||||
const cv::Mat& img_original = get_img_original();
|
||||
ASSERT_FALSE(img_original.empty());
|
||||
|
||||
// Encode.
|
||||
std::vector<unsigned char> buf;
|
||||
bool result = true;
|
||||
EXPECT_NO_THROW(
|
||||
result = cv::imencode(".avif", img_original, buf, encoding_params_););
|
||||
EXPECT_TRUE(result);
|
||||
|
||||
// Read back.
|
||||
const cv::Mat img = cv::imdecode(buf, imread_mode_);
|
||||
|
||||
ValidateRead(img_original, img);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_EncodeDecodeSuite,
|
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3, 4}),
|
||||
::testing::ValuesIn({0, 50, 100}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Animation_RoundTripSuite
|
||||
: public Imgcodecs_Avif_RoundTripSuite {
|
||||
public:
|
||||
const std::vector<cv::Mat>& get_anim_original() {
|
||||
const Key key = {channels_, bit_depth_};
|
||||
return anims_[key];
|
||||
}
|
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../cv/shared/lena.png";
|
||||
const cv::Mat img_original = cv::imread(filename);
|
||||
cv::Mat img_resized;
|
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
|
||||
for (int channels : {1, 3, 4}) {
|
||||
for (int bit_depth : {8, 10, 12}) {
|
||||
const Key key{channels, bit_depth};
|
||||
const cv::Mat img = modifyImage(img_resized, channels, bit_depth);
|
||||
cv::Mat img2, img3;
|
||||
cv::flip(img, img2, 0);
|
||||
cv::flip(img, img3, -1);
|
||||
anims_[key] = {img, img2, img3};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateRead(const std::vector<cv::Mat>& anim_original,
|
||||
const std::vector<cv::Mat>& anim) const {
|
||||
ASSERT_EQ(anim_original.size(), anim.size());
|
||||
for (size_t i = 0; i < anim.size(); ++i) {
|
||||
Imgcodecs_Avif_RoundTripSuite::ValidateRead(anim_original[i], anim[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static const int kWidth;
|
||||
static const int kHeight;
|
||||
|
||||
private:
|
||||
typedef std::tuple<int, int> Key;
|
||||
static std::map<Key, std::vector<cv::Mat>> anims_;
|
||||
};
|
||||
std::map<std::tuple<int, int>, std::vector<cv::Mat>>
|
||||
Imgcodecs_Avif_Animation_RoundTripSuite::anims_;
|
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kWidth = 5;
|
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kHeight = 5;
|
||||
|
||||
class Imgcodecs_Avif_Animation_WriteReadSuite
|
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteReadSuite, encode_decode) {
|
||||
const std::vector<cv::Mat>& anim_original = get_anim_original();
|
||||
ASSERT_FALSE(anim_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
if (!IsBitDepthValid()) {
|
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
|
||||
cv::Exception);
|
||||
EXPECT_NE(0, remove(output.c_str()));
|
||||
return;
|
||||
}
|
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
|
||||
EXPECT_EQ(anim_original.size(), imcount(output));
|
||||
|
||||
// Read from file.
|
||||
std::vector<cv::Mat> anim;
|
||||
ASSERT_TRUE(cv::imreadmulti(output, anim, imread_mode_));
|
||||
|
||||
ValidateRead(anim_original, anim);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteReadSuite,
|
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
class Imgcodecs_Avif_Animation_WriteDecodeSuite
|
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteDecodeSuite, encode_decode) {
|
||||
const std::vector<cv::Mat>& anim_original = get_anim_original();
|
||||
ASSERT_FALSE(anim_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
if (!IsBitDepthValid()) {
|
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
|
||||
cv::Exception);
|
||||
EXPECT_NE(0, remove(output.c_str()));
|
||||
return;
|
||||
}
|
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
|
||||
|
||||
// Put file into buffer and read from buffer.
|
||||
std::ifstream file(output, std::ios::binary | std::ios::ate);
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<unsigned char> buf(size);
|
||||
EXPECT_TRUE(file.read(reinterpret_cast<char*>(buf.data()), size));
|
||||
file.close();
|
||||
std::vector<cv::Mat> anim;
|
||||
ASSERT_TRUE(cv::imdecodemulti(buf, imread_mode_, anim));
|
||||
|
||||
ValidateRead(anim_original, anim);
|
||||
|
||||
if (imread_mode_ == IMREAD_UNCHANGED) {
|
||||
ImageCollection collection(output, IMREAD_UNCHANGED);
|
||||
anim.clear();
|
||||
for (auto&& i : collection)
|
||||
anim.push_back(i);
|
||||
ValidateRead(anim_original, anim);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteDecodeSuite,
|
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
|
||||
#endif // HAVE_AVIF
|
||||
@@ -0,0 +1,61 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27789
|
||||
// See https://github.com/opencv/opencv/issues/23233
|
||||
TEST(Imgcodecs_BMP, encode_decode_over1GB_regression27789)
|
||||
{
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG );
|
||||
|
||||
// Create large Mat over 1GB
|
||||
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
|
||||
// 1 GiB = 1,073,741,824 bytes
|
||||
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
|
||||
|
||||
// Encode large BMP file.
|
||||
std::vector<uint8_t> buf;
|
||||
bool ret = false;
|
||||
ASSERT_NO_THROW(ret = cv::imencode(".bmp", src, buf, {}));
|
||||
ASSERT_TRUE(ret);
|
||||
|
||||
src.release(); // To reduce usage memory, it is needed.
|
||||
|
||||
// Decode large BMP file.
|
||||
cv::Mat dst;
|
||||
ASSERT_NO_THROW(dst = cv::imdecode(buf, cv::IMREAD_COLOR));
|
||||
ASSERT_FALSE(dst.empty());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_BMP, write_read_over1GB_regression27789)
|
||||
{
|
||||
// tag CV_TEST_TAG_VERYLONG applied to skip on CI. The test writes ~1GB file.
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_VERYLONG );
|
||||
string bmpFilename = cv::tempfile(".bmp"); // To remove it, test must use EXPECT_* instead of ASSERT_*.
|
||||
|
||||
// Create large Mat over 1GB
|
||||
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
|
||||
// 1 GiB = 1,073,741,824 bytes
|
||||
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
|
||||
|
||||
// Write large BMP file.
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imwrite(bmpFilename, src, {}));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Read large BMP file.
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = cv::imread(bmpFilename, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
|
||||
remove(bmpFilename.c_str());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static
|
||||
Mat generateTestImageBGR_()
|
||||
{
|
||||
Size sz(640, 480);
|
||||
Mat result(sz, CV_8UC3, Scalar::all(0));
|
||||
|
||||
const string fname = cvtest::findDataFile("../cv/shared/baboon.png");
|
||||
Mat image = imread(fname, IMREAD_COLOR);
|
||||
CV_Assert(!image.empty());
|
||||
CV_CheckEQ(image.size(), Size(512, 512), "");
|
||||
Rect roi((640-512) / 2, 0, 512, 480);
|
||||
image(Rect(0, 0, 512, 480)).copyTo(result(roi));
|
||||
result(Rect(0, 0, 5, 5)).setTo(Scalar(0, 0, 255)); // R
|
||||
result(Rect(5, 0, 5, 5)).setTo(Scalar(0, 255, 0)); // G
|
||||
result(Rect(10, 0, 5, 5)).setTo(Scalar(255, 0, 0)); // B
|
||||
result(Rect(0, 5, 5, 5)).setTo(Scalar(128, 128, 128)); // gray
|
||||
//imshow("test_image", result); waitKey();
|
||||
return result;
|
||||
}
|
||||
Mat generateTestImageBGR()
|
||||
{
|
||||
static Mat image = generateTestImageBGR_(); // initialize once
|
||||
CV_Assert(!image.empty());
|
||||
return image;
|
||||
}
|
||||
|
||||
static
|
||||
Mat generateTestImageGrayscale_()
|
||||
{
|
||||
Mat imageBGR = generateTestImageBGR();
|
||||
CV_Assert(!imageBGR.empty());
|
||||
|
||||
Mat result;
|
||||
cvtColor(imageBGR, result, COLOR_BGR2GRAY);
|
||||
return result;
|
||||
}
|
||||
Mat generateTestImageGrayscale()
|
||||
{
|
||||
static Mat image = generateTestImageGrayscale_(); // initialize once
|
||||
return image;
|
||||
}
|
||||
|
||||
void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
|
||||
{
|
||||
FILE * wfile = fopen(fname.c_str(), "rb");
|
||||
if (wfile != NULL)
|
||||
{
|
||||
fseek(wfile, 0, SEEK_END);
|
||||
size_t wfile_size = ftell(wfile);
|
||||
fseek(wfile, 0, SEEK_SET);
|
||||
|
||||
buf.resize(wfile_size);
|
||||
size_t data_size = fread(&buf[0], 1, wfile_size, wfile);
|
||||
fclose(wfile);
|
||||
|
||||
EXPECT_EQ(data_size, wfile_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
#define OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
Mat generateTestImageBGR();
|
||||
Mat generateTestImageGrayscale();
|
||||
void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf);
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
@@ -0,0 +1,711 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static Mat makeCirclesImage(Size size, int type, int nbits)
|
||||
{
|
||||
Mat img(size, type);
|
||||
img.setTo(Scalar::all(0));
|
||||
RNG& rng = theRNG();
|
||||
int maxval = (int)(1 << nbits);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = rng.uniform(0, img.cols);
|
||||
int y = rng.uniform(0, img.rows);
|
||||
int radius = rng.uniform(5, std::min(img.cols, img.rows) / 5);
|
||||
int b = rng.uniform(0, maxval);
|
||||
int g = rng.uniform(0, maxval);
|
||||
int r = rng.uniform(0, maxval);
|
||||
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
static std::vector<uchar> getSampleExifData() {
|
||||
return {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
|
||||
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
|
||||
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<uchar> getSampleXmpData() {
|
||||
return {
|
||||
'<','x',':','x','m','p','m','e','t','a',' ','x','m','l','n','s',':','x','=',
|
||||
'"','a','d','o','b','e',':','x','m','p','"','>',
|
||||
'<','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
|
||||
'O','p','e','n','C','V',
|
||||
'<','/','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
|
||||
'<','/','x',':','x','m','p','m','e','t','a','>',0
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a Minimal ICC profile data (Generated with help from ChatGPT)
|
||||
static std::vector<uchar> getSampleIccpData() {
|
||||
std::vector<uchar> iccp_data(192, 0);
|
||||
|
||||
iccp_data[3] = 192; // Profile size: 192 bytes
|
||||
|
||||
iccp_data[12] = 'm';
|
||||
iccp_data[13] = 'n';
|
||||
iccp_data[14] = 't';
|
||||
iccp_data[15] = 'r';
|
||||
|
||||
iccp_data[16] = 'R';
|
||||
iccp_data[17] = 'G';
|
||||
iccp_data[18] = 'B';
|
||||
iccp_data[19] = ' ';
|
||||
|
||||
iccp_data[20] = 'X';
|
||||
iccp_data[21] = 'Y';
|
||||
iccp_data[22] = 'Z';
|
||||
iccp_data[23] = ' ';
|
||||
|
||||
// File signature 'acsp' at offset 36 (0x24)
|
||||
iccp_data[36] = 'a';
|
||||
iccp_data[37] = 'c';
|
||||
iccp_data[38] = 's';
|
||||
iccp_data[39] = 'p';
|
||||
|
||||
// Illuminant D50 at offset 68 (0x44), example values:
|
||||
iccp_data[68] = 0x00;
|
||||
iccp_data[69] = 0x00;
|
||||
iccp_data[70] = 0xF6;
|
||||
iccp_data[71] = 0xD6; // 0.9642
|
||||
iccp_data[72] = 0x00;
|
||||
iccp_data[73] = 0x01;
|
||||
iccp_data[74] = 0x00;
|
||||
iccp_data[75] = 0x00; // 1.0
|
||||
iccp_data[76] = 0x00;
|
||||
iccp_data[77] = 0x00;
|
||||
iccp_data[78] = 0xD3;
|
||||
iccp_data[79] = 0x2D; // 0.8249
|
||||
|
||||
// Tag count at offset 128 (0x80) = 1
|
||||
iccp_data[131] = 1;
|
||||
|
||||
// Tag record at offset 132 (0x84): signature 'desc', offset 128, size 64
|
||||
iccp_data[132] = 'd';
|
||||
iccp_data[133] = 'e';
|
||||
iccp_data[134] = 's';
|
||||
iccp_data[135] = 'c';
|
||||
|
||||
iccp_data[139] = 128; // offset
|
||||
|
||||
iccp_data[143] = 64; // size
|
||||
|
||||
// Tag data 'desc' at offset 128 (start of tag data)
|
||||
// Set type 'desc' etc. here, for simplicity fill zeros
|
||||
|
||||
iccp_data[144] = 'd';
|
||||
iccp_data[145] = 'e';
|
||||
iccp_data[146] = 's';
|
||||
iccp_data[147] = 'c';
|
||||
|
||||
// ASCII string length at offset 156
|
||||
iccp_data[156] = 20; // length
|
||||
|
||||
// ASCII string "Minimal ICC Profile" starting at offset 160
|
||||
iccp_data[160] = 'M';
|
||||
iccp_data[161] = 'i';
|
||||
iccp_data[162] = 'n';
|
||||
iccp_data[163] = 'i';
|
||||
iccp_data[164] = 'm';
|
||||
iccp_data[165] = 'a';
|
||||
iccp_data[166] = 'l';
|
||||
iccp_data[167] = ' ';
|
||||
iccp_data[168] = 'I';
|
||||
iccp_data[169] = 'C';
|
||||
iccp_data[170] = 'C';
|
||||
iccp_data[171] = ' ';
|
||||
iccp_data[172] = 'P';
|
||||
iccp_data[173] = 'r';
|
||||
iccp_data[174] = 'o';
|
||||
iccp_data[175] = 'f';
|
||||
iccp_data[176] = 'i';
|
||||
iccp_data[177] = 'l';
|
||||
iccp_data[178] = 'e';
|
||||
|
||||
return iccp_data;
|
||||
}
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
static std::vector<uchar> getSampleCicpData() {
|
||||
return {
|
||||
9, // BT.2020 / BT.2100
|
||||
16, // SMPTE ST 2084 (PQ)
|
||||
0, // Identity (RGB)
|
||||
1, // Full Range
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Test to check whether the EXIF orientation tag was processed successfully or not.
|
||||
* The test uses a set of 8 images named testExifOrientation_{1 to 8}.(extension).
|
||||
* Each test image is a 10x10 square, divided into four smaller sub-squares:
|
||||
* (R corresponds to Red, G to Green, B to Blue, W to White)
|
||||
* --------- ---------
|
||||
* | R | G | | G | R |
|
||||
* |-------| - (tag 1) |-------| - (tag 2)
|
||||
* | B | W | | W | B |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | W | B | | B | W |
|
||||
* |-------| - (tag 3) |-------| - (tag 4)
|
||||
* | G | R | | R | G |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | R | B | | G | W |
|
||||
* |-------| - (tag 5) |-------| - (tag 6)
|
||||
* | G | W | | R | B |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | W | G | | B | R |
|
||||
* |-------| - (tag 7) |-------| - (tag 8)
|
||||
* | B | R | | W | G |
|
||||
* --------- ---------
|
||||
*
|
||||
*
|
||||
* Each image contains an EXIF field with an orientation tag (0x112).
|
||||
* After reading each image and applying the orientation tag,
|
||||
* the resulting image should be:
|
||||
* ---------
|
||||
* | R | G |
|
||||
* |-------|
|
||||
* | B | W |
|
||||
* ---------
|
||||
*
|
||||
* Note:
|
||||
* The flags parameter of the imread function is set as IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH.
|
||||
* Using this combination is an undocumented trick to load images similarly to the IMREAD_UNCHANGED flag,
|
||||
* preserving the alpha channel (if present) while also applying the orientation.
|
||||
*/
|
||||
|
||||
typedef testing::TestWithParam<string> Exif;
|
||||
|
||||
TEST_P(Exif, exif_orientation)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + GetParam();
|
||||
const int colorThresholdHigh = 250;
|
||||
const int colorThresholdLow = 5;
|
||||
|
||||
// Refer to the note in the explanation above.
|
||||
Mat m_img = imread(filename, IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(m_img.empty());
|
||||
|
||||
if (m_img.channels() == 3)
|
||||
{
|
||||
Vec3b vec;
|
||||
|
||||
//Checking the first quadrant (with supposed red)
|
||||
vec = m_img.at<Vec3b>(2, 2); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[2], colorThresholdHigh);
|
||||
|
||||
//Checking the second quadrant (with supposed green)
|
||||
vec = m_img.at<Vec3b>(2, 7); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[1], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
|
||||
//Checking the third quadrant (with supposed blue)
|
||||
vec = m_img.at<Vec3b>(7, 2); //some point inside the square
|
||||
EXPECT_GE(vec.val[0], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec4b vec;
|
||||
|
||||
//Checking the first quadrant (with supposed red)
|
||||
vec = m_img.at<Vec4b>(2, 2); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[2], colorThresholdHigh);
|
||||
|
||||
//Checking the second quadrant (with supposed green)
|
||||
vec = m_img.at<Vec4b>(2, 7); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[1], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
|
||||
//Checking the third quadrant (with supposed blue)
|
||||
vec = m_img.at<Vec4b>(7, 2); //some point inside the square
|
||||
EXPECT_GE(vec.val[0], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> exif_files
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
"readwrite/testExifOrientation_1.jpg",
|
||||
"readwrite/testExifOrientation_2.jpg",
|
||||
"readwrite/testExifOrientation_3.jpg",
|
||||
"readwrite/testExifOrientation_4.jpg",
|
||||
"readwrite/testExifOrientation_5.jpg",
|
||||
"readwrite/testExifOrientation_6.jpg",
|
||||
"readwrite/testExifOrientation_7.jpg",
|
||||
"readwrite/testExifOrientation_8.jpg",
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
"readwrite/testExifOrientation_1.png",
|
||||
"readwrite/testExifOrientation_2.png",
|
||||
"readwrite/testExifOrientation_3.png",
|
||||
"readwrite/testExifOrientation_4.png",
|
||||
"readwrite/testExifOrientation_5.png",
|
||||
"readwrite/testExifOrientation_6.png",
|
||||
"readwrite/testExifOrientation_7.png",
|
||||
"readwrite/testExifOrientation_8.png",
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
"readwrite/testExifOrientation_1.avif",
|
||||
"readwrite/testExifOrientation_2.avif",
|
||||
"readwrite/testExifOrientation_3.avif",
|
||||
"readwrite/testExifOrientation_4.avif",
|
||||
"readwrite/testExifOrientation_5.avif",
|
||||
"readwrite/testExifOrientation_6.avif",
|
||||
"readwrite/testExifOrientation_7.avif",
|
||||
"readwrite/testExifOrientation_8.avif",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
"readwrite/testExifOrientation_1.webp",
|
||||
"readwrite/testExifOrientation_2.webp",
|
||||
"readwrite/testExifOrientation_3.webp",
|
||||
"readwrite/testExifOrientation_4.webp",
|
||||
"readwrite/testExifOrientation_5.webp",
|
||||
"readwrite/testExifOrientation_6.webp",
|
||||
"readwrite/testExifOrientation_7.webp",
|
||||
"readwrite/testExifOrientation_8.webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif,
|
||||
testing::ValuesIn(exif_files));
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
typedef testing::TestWithParam<int> MatChannels;
|
||||
|
||||
TEST_P(MatChannels, Imgcodecs_Avif_ReadWriteWithExif)
|
||||
{
|
||||
int avif_nbits = 10;
|
||||
int avif_speed = 10;
|
||||
int avif_quality = 85;
|
||||
int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U;
|
||||
int imgtype = CV_MAKETYPE(imgdepth, GetParam());
|
||||
const string outputname = cv::tempfile(".avif");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_AVIF_DEPTH, avif_nbits,
|
||||
IMWRITE_AVIF_SPEED, avif_speed,
|
||||
IMWRITE_AVIF_QUALITY, avif_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
ASSERT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 1500);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, MatChannels,
|
||||
testing::Values(1,3,4));
|
||||
#endif // HAVE_AVIF
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
TEST(Imgcodecs_WebP, Read_Write_With_Exif_Xmp_Iccp)
|
||||
{
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const std::string outputname = cv::tempfile(".webp");
|
||||
cv::Mat img = makeCirclesImage(cv::Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData(),
|
||||
getSampleXmpData(),
|
||||
getSampleIccpData()
|
||||
};
|
||||
|
||||
int webp_quality = 101; // 101 is lossless compression
|
||||
std::vector<int> write_params = {IMWRITE_WEBP_QUALITY, webp_quality};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar>> read_metadata, read_metadata2;
|
||||
|
||||
cv::Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, cv::IMREAD_UNCHANGED);
|
||||
cv::Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, cv::IMREAD_UNCHANGED);
|
||||
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_EQ(read_metadata_types.size(), 3u);
|
||||
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata, metadata);
|
||||
|
||||
EXPECT_EQ(cv::norm(img2, img3, cv::NORM_INF), 0.0);
|
||||
|
||||
double mse = cv::norm(img, img2, cv::NORM_L2SQR) / (img.rows * img.cols);
|
||||
EXPECT_EQ(mse, 0);
|
||||
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
TEST(Imgcodecs_Jpeg, Read_Write_With_Exif)
|
||||
{
|
||||
int jpeg_quality = 95;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".jpeg");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_JPEG_QUALITY, jpeg_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 80);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif)
|
||||
{
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
ASSERT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
#else
|
||||
ASSERT_GE(read_metadata_types.size(), 0u);
|
||||
#endif
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_EQ(mse, 0); // png is lossless
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif_Xmp_Iccp_cICP)
|
||||
#else
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif_Xmp_Iccp)
|
||||
#endif
|
||||
{
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP };
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData(),
|
||||
getSampleXmpData(),
|
||||
getSampleIccpData(),
|
||||
};
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
metadata_types.push_back(IMAGE_METADATA_CICP);
|
||||
metadata.push_back(getSampleCicpData());
|
||||
#endif
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
EXPECT_EQ(metadata_types, read_metadata_types);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_EQ(metadata, read_metadata);
|
||||
#else
|
||||
ASSERT_GE(read_metadata_types.size(), 2u);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_XMP);
|
||||
EXPECT_EQ(read_metadata_types[1], IMAGE_METADATA_ICCP);
|
||||
|
||||
ASSERT_GE(read_metadata_types2.size(), 2u);
|
||||
EXPECT_EQ(read_metadata_types2[0], IMAGE_METADATA_XMP);
|
||||
EXPECT_EQ(read_metadata_types2[1], IMAGE_METADATA_ICCP);
|
||||
|
||||
ASSERT_GE(read_metadata.size(), 2u);
|
||||
EXPECT_EQ(metadata[1], read_metadata[0]);
|
||||
EXPECT_EQ(metadata[2], read_metadata[1]);
|
||||
#endif
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, Read_Exif_From_Text)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../perf/320x260.png";
|
||||
const string dst_file = cv::tempfile(".png");
|
||||
|
||||
std::vector<uchar> exif_data =
|
||||
{ 'M' , 'M' , 0, '*' , 0, 0, 0, 8, 0, 4, 1,
|
||||
26, 0, 5, 0, 0, 0, 1, 0, 0, 0, 62, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
70, 1, 40, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 49, 0, 2, 0, 0, 0, 18, 0,
|
||||
0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 1, 0, 0, 0, 96, 0, 0, 0,
|
||||
1, 80, 97, 105, 110, 116, 46, 78, 69, 84, 32, 118, 51, 46, 53, 46, 49, 48, 0
|
||||
};
|
||||
|
||||
std::vector<int> read_metadata_types;
|
||||
std::vector<std::vector<uchar> > read_metadata;
|
||||
Mat img = imreadWithMetadata(filename, read_metadata_types, read_metadata, IMREAD_GRAYSCALE);
|
||||
|
||||
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF };
|
||||
EXPECT_EQ(read_metadata_types, metadata_types);
|
||||
EXPECT_EQ(read_metadata[0], exif_data);
|
||||
}
|
||||
|
||||
static uint32_t pngCrc32(const uchar* data, size_t len)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
crc ^= data[i];
|
||||
for (int k = 0; k < 8; k++)
|
||||
crc = (crc & 1u) ? ((crc >> 1) ^ 0xEDB88320u) : (crc >> 1);
|
||||
}
|
||||
return crc ^ 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
static void pngAppendBE32(std::vector<uchar>& v, uint32_t x)
|
||||
{
|
||||
v.push_back((uchar)(x >> 24)); v.push_back((uchar)(x >> 16));
|
||||
v.push_back((uchar)(x >> 8)); v.push_back((uchar)x);
|
||||
}
|
||||
|
||||
// Regression: a PNG "Raw profile type exif" text chunk whose declared length is
|
||||
// far larger than the payload it carries must be rejected (no multi-GB
|
||||
// speculative allocation / no out-of-bounds read in ExifReader::processRawProfile)
|
||||
// while the image itself still decodes.
|
||||
TEST(Imgcodecs_Png, Read_Exif_From_Text_oversized_length_rejected)
|
||||
{
|
||||
Mat img(8, 8, CV_8UC3, Scalar(10, 20, 30));
|
||||
std::vector<uchar> png;
|
||||
ASSERT_TRUE(imencode(".png", img, png));
|
||||
ASSERT_GT(png.size(), 33u); // 8-byte signature + 25-byte IHDR chunk
|
||||
|
||||
const std::string keyword = "Raw profile type exif";
|
||||
const std::string profile = "\nexif\n999999999\n41414141\n"; // 9e8 declared, tiny payload
|
||||
std::vector<uchar> data(keyword.begin(), keyword.end());
|
||||
data.push_back(0); // keyword / text separator
|
||||
data.insert(data.end(), profile.begin(), profile.end());
|
||||
|
||||
std::vector<uchar> chunk;
|
||||
pngAppendBE32(chunk, (uint32_t)data.size());
|
||||
const char type[4] = { 't', 'E', 'X', 't' };
|
||||
chunk.insert(chunk.end(), type, type + 4);
|
||||
chunk.insert(chunk.end(), data.begin(), data.end());
|
||||
std::vector<uchar> crc_input(type, type + 4);
|
||||
crc_input.insert(crc_input.end(), data.begin(), data.end());
|
||||
pngAppendBE32(chunk, pngCrc32(crc_input.data(), crc_input.size()));
|
||||
|
||||
// splice the tEXt chunk right after IHDR (valid placement for ancillary chunks)
|
||||
png.insert(png.begin() + 33, chunk.begin(), chunk.end());
|
||||
|
||||
std::vector<int> metadata_types;
|
||||
std::vector<std::vector<uchar> > metadata;
|
||||
Mat decoded;
|
||||
ASSERT_NO_THROW(decoded = imdecodeWithMetadata(png, metadata_types, metadata, IMREAD_COLOR));
|
||||
ASSERT_FALSE(decoded.empty());
|
||||
EXPECT_EQ(decoded.rows, 8);
|
||||
EXPECT_EQ(decoded.cols, 8);
|
||||
// the malformed profile must not produce EXIF metadata
|
||||
for (size_t i = 0; i < metadata_types.size(); i++)
|
||||
EXPECT_NE(metadata_types[i], IMAGE_METADATA_EXIF);
|
||||
}
|
||||
|
||||
static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern)
|
||||
{
|
||||
size_t plen = pattern.size();
|
||||
for (size_t i = 0; i + plen <= exif_size; i++) {
|
||||
if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
typedef std::tuple<std::string, size_t, std::string, size_t, size_t, size_t> ReadExif_Sanity_Params;
|
||||
typedef testing::TestWithParam<ReadExif_Sanity_Params> ReadExif_Sanity;
|
||||
|
||||
TEST_P(ReadExif_Sanity, Check)
|
||||
{
|
||||
std::string filename = get<0>(GetParam());
|
||||
size_t exif_size = get<1>(GetParam());
|
||||
std::string pattern = get<2>(GetParam());
|
||||
size_t ploc = get<3>(GetParam());
|
||||
size_t expected_xmp_size = get<4>(GetParam());
|
||||
size_t expected_iccp_size = get<5>(GetParam());
|
||||
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
filename = root + filename;
|
||||
|
||||
std::vector<int> metadata_types, metadata_types2;
|
||||
std::vector<std::vector<uchar> > metadata, metadata2;
|
||||
Mat img = imreadWithMetadata(filename, metadata_types, metadata);
|
||||
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(".jpg", img, metadata_types, metadata, compressed);
|
||||
img = imdecodeWithMetadata(compressed, metadata_types2, metadata2);
|
||||
|
||||
EXPECT_EQ(metadata_types, metadata_types2);
|
||||
EXPECT_EQ(metadata, metadata2);
|
||||
|
||||
EXPECT_EQ(img.type(), CV_8UC3);
|
||||
ASSERT_GE(metadata_types.size(), 1u);
|
||||
EXPECT_EQ(metadata_types.size(), metadata.size());
|
||||
const Mat exif = Mat(metadata[IMAGE_METADATA_EXIF]);
|
||||
EXPECT_EQ(exif.type(), CV_8U);
|
||||
EXPECT_EQ(exif.total(), exif_size);
|
||||
ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes
|
||||
// (the header + IDF0 with at least 1 entry).
|
||||
EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M');
|
||||
EXPECT_EQ(exif.data[0], exif.data[1]);
|
||||
EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc);
|
||||
|
||||
if (metadata_types.size() > IMAGE_METADATA_XMP)
|
||||
{
|
||||
const Mat xmp = Mat(metadata[IMAGE_METADATA_XMP]);
|
||||
EXPECT_EQ(xmp.type(), CV_8U);
|
||||
EXPECT_GT(xmp.total(), 0u);
|
||||
size_t xmp_size = xmp.total() * xmp.elemSize();
|
||||
EXPECT_EQ(expected_xmp_size, xmp_size);
|
||||
}
|
||||
|
||||
if (metadata_types.size() > IMAGE_METADATA_ICCP)
|
||||
{
|
||||
const Mat iccp = Mat(metadata[IMAGE_METADATA_ICCP]);
|
||||
EXPECT_EQ(iccp.type(), CV_8U);
|
||||
EXPECT_GT(iccp.total(), 0u);
|
||||
size_t iccp_size = iccp.total() * iccp.elemSize();
|
||||
EXPECT_EQ(expected_iccp_size, iccp_size);
|
||||
}
|
||||
}
|
||||
|
||||
static const std::vector<ReadExif_Sanity_Params> exif_sanity_params
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120, 3597, 940),
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102, 505, 0),
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120, 3597, 940),
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity,
|
||||
testing::ValuesIn(exif_sanity_params));
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,375 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
//#define GENERATE_DATA
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
size_t getFileSize(const string& filename)
|
||||
{
|
||||
std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary);
|
||||
if (ifs.is_open())
|
||||
{
|
||||
ifs.seekg(0, std::ios::end);
|
||||
return (size_t)ifs.tellg();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1)
|
||||
{ // Y channels
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC1, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1,img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
// Check generated file size to ensure that it's compressed with proper options
|
||||
ASSERT_LE(396u, getFileSize(filenameOutput)); // OpenEXR 2
|
||||
ASSERT_LE( getFileSize(filenameOutput), 440u); // OpenEXR 3.2+
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC3)
|
||||
{ // RGB channels
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC3.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC3, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC7)
|
||||
{ // 0-6 channels (multispectral)
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC7.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(3, 5);
|
||||
Mat img(sz, CV_32FC7);
|
||||
img.at<cv::Vec<float, 7>>(0, 0)[0] = 101.125;
|
||||
img.at<cv::Vec<float, 7>>(2, 1)[3] = 203.500;
|
||||
img.at<cv::Vec<float, 7>>(4, 2)[6] = 305.875;
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.type(), img.type());
|
||||
EXPECT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_TRUE(img3.empty());
|
||||
const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_TRUE(img4.empty());
|
||||
}
|
||||
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_half)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1_half.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_TYPE);
|
||||
params.push_back(IMWRITE_EXR_TYPE_HALF);
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC1, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img, params));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1,img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC3_half)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC3_half.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_TYPE);
|
||||
params.push_back(IMWRITE_EXR_TYPE_HALF);
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC3, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img, params));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_PIZ)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_COMPRESSION);
|
||||
params.push_back(IMWRITE_EXR_COMPRESSION_PIZ);
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
// Check generated file size to ensure that it's compressed with proper options
|
||||
ASSERT_EQ(849u, getFileSize(filenameOutput));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
// Note: YC to GRAYSCALE (IMREAD_GRAYSCALE | IMREAD_ANYDEPTH)
|
||||
// outputs a black image,
|
||||
// as does Y to RGB (IMREAD_COLOR | IMREAD_ANYDEPTH).
|
||||
// This behavior predates adding EXR alpha support issue
|
||||
// 16115.
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_GRAYSCALE | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
// Writing Y covered by test 32FC1
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC2, img.type());
|
||||
|
||||
// Cannot test writing, 2 channel writing not supported by loadsave
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YC_changeDepth)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBY.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
const Mat img_rgb = cv::imread(filenameInput, IMREAD_COLOR_RGB);
|
||||
|
||||
ASSERT_FALSE(img_rgb.empty());
|
||||
ASSERT_EQ(CV_8UC3, img_rgb.type());
|
||||
|
||||
cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR);
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26705
|
||||
// If ALGO_HINT_ACCURATE is set, norm should be 0.
|
||||
// If ALGO_HINT_APPROX is set, norm should be 1(or 0).
|
||||
EXPECT_LE(cvtest::norm(img, img_rgb, NORM_INF),
|
||||
(cv::getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE)?0:1);
|
||||
|
||||
// Cannot test writing, EXR encoder doesn't support 8U depth
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_YCA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBYA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YC_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBY.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
// Writing YC covered by test readwrite_YCA_ignore_alpha
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_YCA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBYA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC4, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_RGBA_togreyscale)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_GRAYSCALE | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_RGBA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
// Writing RGB covered by test 32FC3
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_RGBA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC4, Scalar(0.5, 0.1, 1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0, 1));
|
||||
img(Rect(10, 20, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC4, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/26211
|
||||
// ( related with https://github.com/opencv/opencv/issues/26207 )
|
||||
TEST(Imgcodecs_EXR, imencode_regression_26207_extra)
|
||||
{
|
||||
// CV_8U is not supported depth for EXR Encoder.
|
||||
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
EXPECT_ANY_THROW(ret = imencode(".exr", src, buf));
|
||||
EXPECT_FALSE(ret);
|
||||
}
|
||||
TEST(Imgcodecs_EXR, imwrite_regression_26207_extra)
|
||||
{
|
||||
// CV_8U is not supported depth for EXR Encoder.
|
||||
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
const string filename = cv::tempfile(".exr");
|
||||
bool ret = false;
|
||||
EXPECT_ANY_THROW(ret = imwrite(filename, src));
|
||||
EXPECT_FALSE(ret);
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
|
||||
static void test_gdal_read(const string filename, bool required = true) {
|
||||
const string path = cvtest::findDataFile(filename);
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR));
|
||||
if(!required && img.empty())
|
||||
{
|
||||
throw SkipTestException("GDAL is built wihout required back-end support");
|
||||
}
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(3, img.cols);
|
||||
EXPECT_EQ(5, img.rows);
|
||||
EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
EXPECT_EQ(101.125, (img.at<Vec<float, 7>>(0, 0)[0]));
|
||||
EXPECT_EQ(203.500, (img.at<Vec<float, 7>>(2, 1)[3]));
|
||||
EXPECT_EQ(305.875, (img.at<Vec<float, 7>>(4, 2)[6]));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_envi)
|
||||
{
|
||||
test_gdal_read("../cv/gdal/envi_test.raw");
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_fits)
|
||||
{
|
||||
// .fit test is optional because GDAL may be built wihtout CFITSIO library support
|
||||
test_gdal_read("../cv/gdal/fits_test.fit", false);
|
||||
}
|
||||
|
||||
#endif // HAVE_GDAL
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,520 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
const string gifsuite_files_multi[]={
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"ccwn3p08",
|
||||
"ch1n3p04",
|
||||
"cs3n3p08",
|
||||
"cs5n3p08",
|
||||
"cs8n3p08",
|
||||
"g03n3p04",
|
||||
"g04n3p04",
|
||||
"g05n3p04",
|
||||
"g07n3p04",
|
||||
"g10n3p04",
|
||||
"g25n3p04",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"tp0n3p08",
|
||||
};
|
||||
|
||||
const string gifsuite_files_read_single[] = {
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"ccwn3p08",
|
||||
"cdfn2c08",
|
||||
"cdhn2c08",
|
||||
"cdsn2c08",
|
||||
"cdun2c08",
|
||||
"ch1n3p04",
|
||||
"cs3n3p08",
|
||||
"cs5n2c08",
|
||||
"cs5n3p08",
|
||||
"cs8n2c08",
|
||||
"cs8n3p08",
|
||||
"exif2c08",
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g10n2c08",
|
||||
"g10n3p04"
|
||||
};
|
||||
|
||||
const string gifsuite_files_read_write_suite[]={
|
||||
"g25n2c08",
|
||||
"g25n3p04",
|
||||
"s01i3p01",
|
||||
"s01n3p01",
|
||||
"s02i3p01",
|
||||
"s02n3p01",
|
||||
"s03i3p01",
|
||||
"s03n3p01",
|
||||
"s04i3p01",
|
||||
"s04n3p01",
|
||||
"s05i3p02",
|
||||
"s05n3p02",
|
||||
"s06i3p02",
|
||||
"s06n3p02",
|
||||
"s07i3p02",
|
||||
"s07n3p02",
|
||||
"s08i3p02",
|
||||
"s08n3p02",
|
||||
"s09i3p02",
|
||||
"s09n3p02",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"s33i3p04",
|
||||
"s33n3p04",
|
||||
"s34i3p04",
|
||||
"s34n3p04",
|
||||
"s35i3p04",
|
||||
"s35n3p04",
|
||||
"s36i3p04",
|
||||
"s36n3p04",
|
||||
"s37i3p04",
|
||||
"s37n3p04",
|
||||
"s38i3p04",
|
||||
"s38n3p04",
|
||||
"s39i3p04",
|
||||
"s39n3p04",
|
||||
"s40i3p04",
|
||||
"s40n3p04",
|
||||
"tp0n3p08",
|
||||
};
|
||||
|
||||
const std::pair<string,int> gifsuite_files_bgra[]={
|
||||
make_pair("gif_bgra1",53287),
|
||||
make_pair("gif_bgra2",52651),
|
||||
make_pair("gif_bgra3",54809),
|
||||
make_pair("gif_bgra4",57562),
|
||||
make_pair("gif_bgra5",56733),
|
||||
make_pair("gif_bgra6",52110),
|
||||
};
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_multi)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/gif_multi.gif";
|
||||
vector<cv::Mat> img_vec_8UC4;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(filename, img_vec_8UC4,0,20,IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(img_vec_8UC4.size(), imcount(filename));
|
||||
vector<cv::Mat> img_vec_8UC3;
|
||||
for(const auto & i : img_vec_8UC4){
|
||||
cv::Mat img_tmp;
|
||||
cvtColor(i,img_tmp,COLOR_BGRA2BGR);
|
||||
img_vec_8UC3.push_back(img_tmp);
|
||||
}
|
||||
const long unsigned int expected_size=20;
|
||||
EXPECT_EQ(img_vec_8UC3.size(),expected_size);
|
||||
for(long unsigned int i=0;i<img_vec_8UC3.size();i++){
|
||||
cv::Mat img=img_vec_8UC3[i];
|
||||
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png = imread(png_filename,IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_png);
|
||||
}
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_SingleFrame;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame, read_gif_single)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/" + GetParam() + ".gif";
|
||||
const string png_filename=root + "pngsuite/" + GetParam() + ".png";
|
||||
const long unsigned int expected_size = 1;
|
||||
|
||||
EXPECT_EQ(expected_size, imcount(filename));
|
||||
cv::Mat img_8UC4;
|
||||
ASSERT_NO_THROW(img_8UC4 = cv::imread(filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_8UC4.empty());
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3, COLOR_BGRA2BGR));
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame,
|
||||
testing::ValuesIn(gifsuite_files_read_single));
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_big){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/gif_big.gif";
|
||||
const string png_filename = root + "gifsuite/gif_big.png";
|
||||
cv::Mat img_8UC4;
|
||||
ASSERT_NO_THROW(img_8UC4 = imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_8UC4.empty());
|
||||
cv::Mat img_8UC3;
|
||||
const int expected_col=1303;
|
||||
const int expected_row=1391;
|
||||
EXPECT_EQ(expected_col, img_8UC4.cols);
|
||||
EXPECT_EQ(expected_row, img_8UC4.rows);
|
||||
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3,COLOR_BGRA2BGR));
|
||||
EXPECT_EQ(expected_col, img_8UC3.cols);
|
||||
EXPECT_EQ(expected_row, img_8UC3.rows);
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png=imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
cv::Mat img_png_8UC3;
|
||||
ASSERT_NO_THROW(cvtColor(img_png,img_png_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_EQ(img_8UC3.size, img_png_8UC3.size);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png_8UC3);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<std::pair<string,int>> Imgcodecs_Gif_GifSuite_SingleFrame_BGRA;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame_BGRA, read_gif_single_bgra){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/" + GetParam().first + ".gif";
|
||||
const string png_filename = root + "gifsuite/" + GetParam().first + ".png";
|
||||
cv::Mat gif_img;
|
||||
cv::Mat png_img;
|
||||
ASSERT_NO_THROW(gif_img = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(gif_img.empty());
|
||||
ASSERT_NO_THROW(png_img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(png_img.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img, png_img);
|
||||
int transparent_count = 0;
|
||||
for(int i=0; i<gif_img.rows; i++){
|
||||
for(int j=0; j<gif_img.cols; j++){
|
||||
cv::Vec4b pixel1 = gif_img.at<cv::Vec4b>(i,j);
|
||||
if((int)(pixel1[3]) == 0){
|
||||
transparent_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(transparent_count,GetParam().second);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame_BGRA ,
|
||||
testing::ValuesIn(gifsuite_files_bgra));
|
||||
|
||||
TEST(Imgcodecs_Gif,read_gif_multi_bgra){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/gif_multi_bgra.gif";
|
||||
vector<cv::Mat> img_vec;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(imcount(gif_filename), img_vec.size());
|
||||
const int fixed_transparent_count = 53211;
|
||||
for(auto & frame_count : img_vec){
|
||||
int transparent_count=0;
|
||||
for(int i=0; i<frame_count.rows; i++){
|
||||
for(int j=0; j<frame_count.cols; j++){
|
||||
cv::Vec4b pixel1 = frame_count.at<cv::Vec4b>(i,j);
|
||||
if((int)(pixel1[3]) == 0){
|
||||
transparent_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(fixed_transparent_count,transparent_count);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_special){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename1 = root + "gifsuite/special1.gif";
|
||||
const string png_filename1 = root + "gifsuite/special1.png";
|
||||
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_COLOR));
|
||||
ASSERT_FALSE(gif_img1.empty());
|
||||
cv::Mat png_img1;
|
||||
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_COLOR));
|
||||
ASSERT_FALSE(gif_img2.empty());
|
||||
cv::Mat png_img2;
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif,write_gif_flags){
|
||||
vector<uchar> buff;
|
||||
const int expected_rows=611;
|
||||
const int expected_cols=293;
|
||||
Mat img_gt = Mat::ones(expected_rows, expected_cols, CV_8UC3);
|
||||
const vector<int> param = { IMWRITE_GIF_QUALITY, IMWRITE_GIF_FAST_NO_DITHER, IMWRITE_GIF_DITHER, 3};
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imencode(".gif", img_gt, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img;
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_FALSE(img.empty());
|
||||
EXPECT_EQ(img.cols, expected_cols);
|
||||
EXPECT_EQ(img.rows, expected_rows);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif, write_gif_big) {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string png_filename = root + "gifsuite/gif_big.png";
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(1303, img.cols);
|
||||
EXPECT_EQ(1391, img.rows);
|
||||
ASSERT_NO_THROW(imwrite(gif_filename, img));
|
||||
cv::Mat img_gif;
|
||||
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_gif.empty());
|
||||
EXPECT_EQ(1303, img_gif.cols);
|
||||
EXPECT_EQ(1391, img_gif.rows);
|
||||
EXPECT_EQ(0, remove(gif_filename.c_str()));
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_Read_Write_Suite;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_Read_Write_Suite ,read_gif_single)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string png_filename = root + "pngsuite/"+GetParam()+".png";
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_GIF_QUALITY);
|
||||
param.push_back(8);
|
||||
param.push_back(IMWRITE_GIF_DITHER);
|
||||
param.push_back(3);
|
||||
ASSERT_NO_THROW(imwrite(gif_filename, img, param));
|
||||
cv::Mat img_gif;
|
||||
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_gif.empty());
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cv::cvtColor(img_gif, img_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img, img_8UC3);
|
||||
EXPECT_EQ(0, remove(gif_filename.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_Read_Write_Suite ,
|
||||
testing::ValuesIn(gifsuite_files_read_write_suite));
|
||||
|
||||
TEST(Imgcodecs_Gif, write_gif_multi) {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
vector<cv::Mat> img_vec;
|
||||
for (long unsigned int i = 0; i < 20; i++) {
|
||||
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
img_vec.push_back(img);
|
||||
}
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_GIF_QUALITY);
|
||||
param.push_back(8);
|
||||
param.push_back(IMWRITE_GIF_DITHER);
|
||||
param.push_back(3);
|
||||
ASSERT_NO_THROW(cv::imwritemulti(gif_filename, img_vec, param));
|
||||
vector<cv::Mat> img_vec_gif;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec_gif));
|
||||
EXPECT_EQ(img_vec.size(), img_vec_gif.size());
|
||||
for (long unsigned int i = 0; i < img_vec.size(); i++) {
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cv::cvtColor(img_vec_gif[i], img_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img_vec[i], img_8UC3);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26970
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Gif_loop_count;
|
||||
TEST_P(Imgcodecs_Gif_loop_count, imwriteanimation)
|
||||
{
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
|
||||
int loopCount = GetParam();
|
||||
cv::Animation anim(loopCount);
|
||||
|
||||
vector<cv::Mat> src;
|
||||
for(int n = 1; n <= 5 ; n ++ )
|
||||
{
|
||||
cv::Mat frame(64, 64, CV_8UC3, cv::Scalar::all(0));
|
||||
cv::putText(frame, cv::format("%d", n), cv::Point(0,64), cv::FONT_HERSHEY_PLAIN, 4.0, cv::Scalar::all(255));
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(1000 /* ms */);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
#if 0
|
||||
// To output gif image for test.
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(cv::format("gif_loop-%d.gif", loopCount), anim));
|
||||
EXPECT_TRUE(ret);
|
||||
#endif
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(gif_filename, anim));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Read raw GIF data.
|
||||
std::ifstream ifs(gif_filename);
|
||||
std::stringstream ss;
|
||||
ss << ifs.rdbuf();
|
||||
string tmp = ss.str();
|
||||
std::vector<uint8_t> buf(tmp.begin(), tmp.end());
|
||||
|
||||
std::vector<uint8_t> netscape = {0x21, 0xFF, 0x0B, 'N','E','T','S','C','A','P','E','2','.','0'};
|
||||
auto pos = std::search(buf.begin(), buf.end(), netscape.begin(), netscape.end());
|
||||
if(loopCount == 1) {
|
||||
EXPECT_EQ(pos, buf.end()) << "Netscape Application Block should not be included if Animation.loop_count == 1";
|
||||
} else {
|
||||
EXPECT_NE(pos, buf.end()) << "Netscape Application Block should be included if Animation.loop_count != 1";
|
||||
}
|
||||
|
||||
remove(gif_filename.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/,
|
||||
Imgcodecs_Gif_loop_count,
|
||||
testing::Values(
|
||||
-1,
|
||||
0, // Default, loop-forever
|
||||
1,
|
||||
2,
|
||||
65534,
|
||||
65535, // Maximum Limit
|
||||
65536
|
||||
)
|
||||
);
|
||||
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Gif_duration;
|
||||
TEST_P(Imgcodecs_Gif_duration, imwriteanimation)
|
||||
{
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
|
||||
cv::Animation anim;
|
||||
|
||||
int duration = GetParam();
|
||||
vector<cv::Mat> src;
|
||||
for(int n = 1; n <= 5 ; n ++ )
|
||||
{
|
||||
cv::Mat frame(64, 64, CV_8UC3, cv::Scalar::all(0));
|
||||
cv::putText(frame, cv::format("%d", n), cv::Point(0,64), cv::FONT_HERSHEY_PLAIN, 4.0, cv::Scalar::all(255));
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(duration /* ms */);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
#if 0
|
||||
// To output gif image for test.
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(cv::format("gif_duration-%d.gif", duration), anim));
|
||||
EXPECT_EQ(ret, ( (0 <= duration) && (duration <= 655350) ) );
|
||||
#endif
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(gif_filename, anim));
|
||||
EXPECT_EQ(ret, ( (0 <= duration) && (duration <= 655350) ) );
|
||||
|
||||
remove(gif_filename.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/,
|
||||
Imgcodecs_Gif_duration,
|
||||
testing::Values(
|
||||
-1, // Unsupported
|
||||
0, // Undefined Behaviour
|
||||
1,
|
||||
9,
|
||||
10,
|
||||
50,
|
||||
100, // 10 FPS
|
||||
1000, // 1 FPS
|
||||
655340,
|
||||
655350, // Maximum Limit
|
||||
655360 // Unsupported
|
||||
)
|
||||
);
|
||||
|
||||
}//opencv_test
|
||||
}//namespace
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,655 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
typedef tuple<string, int> File_Mode;
|
||||
typedef testing::TestWithParam<File_Mode> Imgcodecs_FileMode;
|
||||
|
||||
TEST_P(Imgcodecs_FileMode, regression)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + get<0>(GetParam());
|
||||
const int mode = get<1>(GetParam());
|
||||
|
||||
const Mat single = imread(filename, mode);
|
||||
ASSERT_FALSE(single.empty());
|
||||
|
||||
vector<Mat> pages;
|
||||
ASSERT_TRUE(imreadmulti(filename, pages, mode));
|
||||
ASSERT_FALSE(pages.empty());
|
||||
const Mat page = pages[0];
|
||||
ASSERT_FALSE(page.empty());
|
||||
|
||||
EXPECT_EQ(page.channels(), single.channels());
|
||||
EXPECT_EQ(page.depth(), single.depth());
|
||||
EXPECT_EQ(page.size().height, single.size().height);
|
||||
EXPECT_EQ(page.size().width, single.size().width);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), page, single);
|
||||
}
|
||||
|
||||
const string all_images[] =
|
||||
{
|
||||
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|
||||
|| defined(HAVE_OPENJPEG)
|
||||
"readwrite/Rome.jp2",
|
||||
"readwrite/Bretagne2.jp2",
|
||||
"readwrite/Bretagne2.jp2",
|
||||
"readwrite/Grey.jp2",
|
||||
"readwrite/Grey.jp2",
|
||||
"readwrite/balloon.j2c",
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GDCM
|
||||
"readwrite/int16-mono1.dcm",
|
||||
"readwrite/uint8-mono2.dcm",
|
||||
"readwrite/uint16-mono2.dcm",
|
||||
"readwrite/uint8-rgb.dcm",
|
||||
#endif
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
"readwrite/color_palette_alpha.png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
"readwrite/multipage.tif",
|
||||
#endif
|
||||
"readwrite/ordinary.bmp",
|
||||
"readwrite/rle8.bmp",
|
||||
#ifdef HAVE_JPEG
|
||||
"readwrite/test_1_c1.jpg",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
"readwrite/rle.hdr"
|
||||
#endif
|
||||
};
|
||||
|
||||
const int basic_modes[] =
|
||||
{
|
||||
IMREAD_UNCHANGED,
|
||||
IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR,
|
||||
IMREAD_COLOR_RGB,
|
||||
IMREAD_ANYDEPTH,
|
||||
IMREAD_ANYCOLOR
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_FileMode,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(all_images),
|
||||
testing::ValuesIn(basic_modes)));
|
||||
|
||||
// GDAL does not support "hdr", "dcm" and has problems with JPEG2000 files (jp2, j2c)
|
||||
struct notForGDAL {
|
||||
bool operator()(const string &name) const {
|
||||
const string &ext = name.substr(name.size() - 3, 3);
|
||||
return ext == "hdr" || ext == "dcm" || ext == "jp2" || ext == "j2c" ||
|
||||
name.find("rle8.bmp") != std::string::npos;
|
||||
}
|
||||
};
|
||||
|
||||
inline vector<string> gdal_images()
|
||||
{
|
||||
vector<string> res;
|
||||
std::back_insert_iterator< vector<string> > it(res);
|
||||
std::remove_copy_if(all_images, all_images + sizeof(all_images)/sizeof(all_images[0]), it, notForGDAL());
|
||||
return res;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(GDAL, Imgcodecs_FileMode,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(gdal_images()),
|
||||
testing::Values(IMREAD_LOAD_GDAL)));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
typedef tuple<string, Size> Ext_Size;
|
||||
typedef testing::TestWithParam<Ext_Size> Imgcodecs_ExtSize;
|
||||
|
||||
TEST_P(Imgcodecs_ExtSize, write_imageseq)
|
||||
{
|
||||
const string ext = get<0>(GetParam());
|
||||
const Size size = get<1>(GetParam());
|
||||
const Point2i center = Point2i(size.width / 2, size.height / 2);
|
||||
const int radius = std::min(size.height, size.width / 4);
|
||||
|
||||
for (int cn = 1; cn <= 4; cn++)
|
||||
{
|
||||
SCOPED_TRACE(format("channels %d", cn));
|
||||
std::vector<int> parameters;
|
||||
if (cn == 2)
|
||||
continue;
|
||||
if (cn == 4 && ext != ".tiff")
|
||||
continue;
|
||||
if (cn > 1 && (ext == ".pbm" || ext == ".pgm"))
|
||||
continue;
|
||||
if (cn != 3 && ext == ".ppm")
|
||||
continue;
|
||||
if (cn == 1 && ext == ".gif")
|
||||
continue;
|
||||
if (cn == 1 && ext == ".webp")
|
||||
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));
|
||||
circle(img_gt, center, radius, Scalar::all(255));
|
||||
|
||||
#if 1
|
||||
if (ext == ".pbm" || ext == ".pgm" || ext == ".ppm")
|
||||
{
|
||||
parameters.push_back(IMWRITE_PXM_BINARY);
|
||||
parameters.push_back(0);
|
||||
}
|
||||
#endif
|
||||
ASSERT_TRUE(imwrite(filename, img_gt, parameters));
|
||||
Mat img = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
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());
|
||||
|
||||
|
||||
if (ext == ".jpg")
|
||||
{
|
||||
// JPEG format does not provide 100% accuracy
|
||||
// using fuzzy image comparison
|
||||
double n = cvtest::norm(img, img_gt, NORM_L1);
|
||||
double expected = 0.07 * img.size().area();
|
||||
EXPECT_LT(n, expected);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(10, 0), img, img_gt);
|
||||
}
|
||||
else if (ext == ".pfm")
|
||||
{
|
||||
img_gt.convertTo(img_gt, CV_MAKETYPE(CV_32F, img.channels()));
|
||||
double n = cvtest::norm(img, img_gt, NORM_L2);
|
||||
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);
|
||||
EXPECT_LT(n, 1.);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
#if 0
|
||||
imshow("loaded", img);
|
||||
waitKey(0);
|
||||
#else
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
const string all_exts[] =
|
||||
{
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
".png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
".jpg",
|
||||
#endif
|
||||
".bmp",
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
".pam",
|
||||
".ppm",
|
||||
".pgm",
|
||||
".pbm",
|
||||
".pnm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
".pfm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
vector<Size> all_sizes()
|
||||
{
|
||||
vector<Size> res;
|
||||
for (int k = 1; k <= 5; ++k)
|
||||
res.push_back(Size(640 * k, 480 * k));
|
||||
return res;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(all_exts),
|
||||
testing::ValuesIn(all_sizes())));
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
typedef testing::TestWithParam<bool> Imgcodecs_pbm;
|
||||
TEST_P(Imgcodecs_pbm, write_read)
|
||||
{
|
||||
bool binary = GetParam();
|
||||
const String ext = "pbm";
|
||||
const string full_name = cv::tempfile(ext.c_str());
|
||||
|
||||
Size size(640, 480);
|
||||
const Point2i center = Point2i(size.width / 2, size.height / 2);
|
||||
const int radius = std::min(size.height, size.width / 4);
|
||||
Mat image(size, CV_8UC1, Scalar::all(0));
|
||||
circle(image, center, radius, Scalar::all(255));
|
||||
|
||||
vector<int> pbm_params;
|
||||
pbm_params.push_back(IMWRITE_PXM_BINARY);
|
||||
pbm_params.push_back(binary);
|
||||
|
||||
imwrite( full_name, image, pbm_params );
|
||||
Mat loaded = imread(full_name, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(loaded.empty());
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(loaded, image, NORM_INF));
|
||||
|
||||
FILE *f = fopen(full_name.c_str(), "rb");
|
||||
ASSERT_TRUE(f != NULL);
|
||||
ASSERT_EQ('P', getc(f));
|
||||
ASSERT_EQ('1' + (binary ? 3 : 0), getc(f));
|
||||
fclose(f);
|
||||
EXPECT_EQ(0, remove(full_name.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_pbm, testing::Bool());
|
||||
#endif
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27557
|
||||
typedef testing::TestWithParam<string> Imgcodecs_invalid_key;
|
||||
|
||||
TEST_P(Imgcodecs_invalid_key, encode_regression27557)
|
||||
{
|
||||
const string ext = GetParam();
|
||||
const int matType = ((ext == ".pbm") || (ext == ".pgm"))? CV_8UC1 : CV_8UC3;
|
||||
Mat src(100, 100, matType, Scalar(0, 255, 0));
|
||||
std::vector<uchar> buf;
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imencode(ext, src, buf, { -1, -1 }));
|
||||
EXPECT_TRUE(status);
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_invalid_key, write_regression27557)
|
||||
{
|
||||
const string ext = GetParam();
|
||||
string fname = tempfile(ext.c_str());
|
||||
|
||||
const int matType = ((ext == ".pbm") || (ext == ".pgm"))? CV_8UC1 : CV_8UC3;
|
||||
Mat src(100, 100, matType, Scalar(0, 255, 0));
|
||||
std::vector<uchar> buf;
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imwrite(fname, src, { -1, -1 }));
|
||||
EXPECT_TRUE(status);
|
||||
remove(fname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_invalid_key, testing::ValuesIn(all_exts));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_rle8)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
Mat rle = imread(root + "readwrite/rle8.bmp");
|
||||
ASSERT_FALSE(rle.empty());
|
||||
Mat ord = imread(root + "readwrite/ordinary.bmp");
|
||||
ASSERT_FALSE(ord.empty());
|
||||
EXPECT_LE(cvtest::norm(rle, ord, NORM_L2), 1.e-10);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), rle, ord);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_32bit_rgb)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_32bit_rgb.bmp";
|
||||
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, rgba_bit_mask)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_rgba_mask.bmp";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
const uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[3], 255);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_32bit_xrgb)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_32bit_xrgb.bmp";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
const uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[3], 255);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, rgba_scale)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_rgba_scale.bmp";
|
||||
|
||||
Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
ASSERT_EQ(data[1], 255);
|
||||
ASSERT_EQ(data[2], 255);
|
||||
ASSERT_EQ(data[3], 255);
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
ASSERT_EQ(data[1], 255);
|
||||
ASSERT_EQ(data[2], 255);
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC1, img.type());
|
||||
|
||||
data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<ImwriteBMPCompressionFlags> Imgcodecs_bmp_compress;
|
||||
TEST_P(Imgcodecs_bmp_compress, rgba32bpp)
|
||||
{
|
||||
const ImwriteBMPCompressionFlags comp = GetParam();
|
||||
|
||||
RNG rng = theRNG();
|
||||
Mat src(256, 256, CV_8UC4);
|
||||
rng.fill(src, RNG::UNIFORM, Scalar(0,0,0,0), Scalar(255,255,255,255));
|
||||
|
||||
vector<uint8_t> buf;
|
||||
bool ret = false;
|
||||
ASSERT_NO_THROW(ret = cv::imencode(".bmp", src, buf, {IMWRITE_BMP_COMPRESSION, static_cast<int>(comp)}));
|
||||
ASSERT_TRUE(ret);
|
||||
|
||||
ASSERT_EQ(buf[0x0e], comp == IMWRITE_BMP_COMPRESSION_RGB ? 40 : 124 ); // the size of header
|
||||
ASSERT_EQ(buf[0x0f], 0);
|
||||
ASSERT_EQ(buf[0x1c], 32); // the number of bits per pixel = 32
|
||||
ASSERT_EQ(buf[0x1d], 0);
|
||||
ASSERT_EQ(buf[0x1e], static_cast<int>(comp)); // the compression method
|
||||
ASSERT_EQ(buf[0x1f], 0);
|
||||
ASSERT_EQ(buf[0x20], 0);
|
||||
ASSERT_EQ(buf[0x21], 0);
|
||||
|
||||
Mat dst;
|
||||
ASSERT_NO_THROW(dst = cv::imdecode(buf, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(dst.empty());
|
||||
|
||||
if(comp == IMWRITE_BMP_COMPRESSION_RGB)
|
||||
{
|
||||
// If BI_RGB is used, output BMP file stores RGB image.
|
||||
ASSERT_EQ(CV_8UC3, dst.type());
|
||||
Mat srcBGR;
|
||||
cv::cvtColor(src, srcBGR, cv::COLOR_BGRA2BGR);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), srcBGR, dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If BI_BITFIELDS is used, output BMP file stores RGBA image.
|
||||
ASSERT_EQ(CV_8UC4, dst.type());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, dst);
|
||||
}
|
||||
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(All,
|
||||
Imgcodecs_bmp_compress,
|
||||
testing::Values(
|
||||
IMWRITE_BMP_COMPRESSION_RGB,
|
||||
IMWRITE_BMP_COMPRESSION_BITFIELDS));
|
||||
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
TEST(Imgcodecs_Hdr, regression)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
|
||||
string name_rle = folder + "rle.hdr";
|
||||
string name_no_rle = folder + "no_rle.hdr";
|
||||
Mat img_rle = imread(name_rle, -1);
|
||||
ASSERT_FALSE(img_rle.empty()) << "Could not open " << name_rle;
|
||||
Mat img_no_rle = imread(name_no_rle, -1);
|
||||
ASSERT_FALSE(img_no_rle.empty()) << "Could not open " << name_no_rle;
|
||||
|
||||
EXPECT_EQ(cvtest::norm(img_rle, img_no_rle, NORM_INF), 0.0);
|
||||
|
||||
string tmp_file_name = tempfile(".hdr");
|
||||
vector<int> param(2);
|
||||
param[0] = IMWRITE_HDR_COMPRESSION;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
param[1] = i;
|
||||
imwrite(tmp_file_name, img_rle, param);
|
||||
Mat written_img = imread(tmp_file_name, -1);
|
||||
EXPECT_EQ(cvtest::norm(written_img, img_rle, NORM_INF), 0.0);
|
||||
}
|
||||
remove(tmp_file_name.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Hdr, regression_imencode)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
|
||||
string name = folder + "rle.hdr";
|
||||
Mat img_ref = imread(name, -1);
|
||||
ASSERT_FALSE(img_ref.empty()) << "Could not open " << name;
|
||||
|
||||
vector<int> params(2);
|
||||
params[0] = IMWRITE_HDR_COMPRESSION;
|
||||
{
|
||||
vector<uchar> buf;
|
||||
params[1] = IMWRITE_HDR_COMPRESSION_NONE;
|
||||
imencode(".hdr", img_ref, buf, params);
|
||||
Mat img = imdecode(buf, -1);
|
||||
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
|
||||
}
|
||||
{
|
||||
vector<uchar> buf;
|
||||
params[1] = IMWRITE_HDR_COMPRESSION_RLE;
|
||||
imencode(".hdr", img_ref, buf, params);
|
||||
Mat img = imdecode(buf, -1);
|
||||
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
TEST(Imgcodecs_Pam, read_write)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "readwrite/";
|
||||
string filepath = folder + "lena.pam";
|
||||
|
||||
cv::Mat img = cv::imread(filepath);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_PAM_TUPLETYPE);
|
||||
params.push_back(IMWRITE_PAM_FORMAT_RGB);
|
||||
|
||||
string writefile = cv::tempfile(".pam");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile, img, params));
|
||||
cv::Mat reread = cv::imread(writefile);
|
||||
|
||||
string writefile_no_param = cv::tempfile(".pam");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile_no_param, img));
|
||||
cv::Mat reread_no_param = cv::imread(writefile_no_param);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(reread, reread_no_param, NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(img, reread, NORM_INF));
|
||||
|
||||
remove(writefile.c_str());
|
||||
remove(writefile_no_param.c_str());
|
||||
}
|
||||
|
||||
// Regression test: a 2-channel (GRAYSCALE_ALPHA) PAM decoded as single channel
|
||||
// used to overflow the output row in basic_conversion() (3 bytes written per
|
||||
// source pixel into a 1-channel row). Verify it decodes safely and correctly.
|
||||
TEST(Imgcodecs_Pam, decode_graya_as_gray)
|
||||
{
|
||||
const int width = 9, height = 3; // odd width to expose off-by-row overflow
|
||||
std::string header = cv::format(
|
||||
"P7\nWIDTH %d\nHEIGHT %d\nDEPTH 2\nMAXVAL 255\n"
|
||||
"TUPLTYPE GRAYSCALE_ALPHA\nENDHDR\n", width, height);
|
||||
|
||||
std::vector<uchar> buf(header.begin(), header.end());
|
||||
Mat gray_ref(height, width, CV_8UC1);
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uchar gray = (uchar)((y * width + x) * 7 + 1);
|
||||
uchar alpha = (uchar)(255 - gray);
|
||||
gray_ref.at<uchar>(y, x) = gray;
|
||||
buf.push_back(gray); // channel 0: gray
|
||||
buf.push_back(alpha); // channel 1: alpha (must be ignored)
|
||||
}
|
||||
|
||||
Mat decoded;
|
||||
ASSERT_NO_THROW(decoded = imdecode(buf, IMREAD_GRAYSCALE));
|
||||
ASSERT_FALSE(decoded.empty());
|
||||
EXPECT_EQ(width, decoded.cols);
|
||||
EXPECT_EQ(height, decoded.rows);
|
||||
EXPECT_EQ(1, decoded.channels());
|
||||
EXPECT_EQ(0, cvtest::norm(gray_ref, decoded, NORM_INF));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
TEST(Imgcodecs_Pfm, read_write)
|
||||
{
|
||||
Mat img = imread(findDataFile("readwrite/lena.pam"));
|
||||
ASSERT_FALSE(img.empty());
|
||||
img.convertTo(img, CV_32F, 1/255.0f);
|
||||
|
||||
std::vector<int> params;
|
||||
string writefile = cv::tempfile(".pfm");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile, img, params));
|
||||
cv::Mat reread = cv::imread(writefile, IMREAD_UNCHANGED);
|
||||
|
||||
string writefile_no_param = cv::tempfile(".pfm");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile_no_param, img));
|
||||
cv::Mat reread_no_param = cv::imread(writefile_no_param, IMREAD_UNCHANGED);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(reread, reread_no_param, NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(img, reread, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(writefile.c_str()));
|
||||
EXPECT_EQ(0, remove(writefile_no_param.c_str()));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Imgcodecs, write_parameter_type)
|
||||
{
|
||||
cv::Mat m(10, 10, CV_8UC1, cv::Scalar::all(0));
|
||||
cv::Mat1b m_type = cv::Mat1b::zeros(10, 10);
|
||||
string tmp_file = cv::tempfile(".bmp");
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, cv::Mat(m * 2))) << "* Failed with cv::Mat";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m * 2)) << "* Failed with cv::MatExpr";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m_type)) << "* Failed with cv::Mat_";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m_type * 2)) << "* Failed with cv::MatExpr(Mat_)";
|
||||
cv::Matx<uchar, 10, 10> matx;
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, matx)) << "* Failed with cv::Matx";
|
||||
EXPECT_EQ(0, remove(tmp_file.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs, imdecode_user_buffer)
|
||||
{
|
||||
cv::Mat encoded = cv::Mat::zeros(1, 1024, CV_8UC1);
|
||||
cv::Mat user_buffer(1, 1024, CV_8UC1);
|
||||
cv::Mat result = cv::imdecode(encoded, IMREAD_ANYCOLOR, &user_buffer);
|
||||
EXPECT_TRUE(result.empty());
|
||||
// the function does not release user-provided buffer
|
||||
EXPECT_FALSE(user_buffer.empty());
|
||||
|
||||
result = cv::imdecode(encoded, IMREAD_ANYCOLOR);
|
||||
EXPECT_TRUE(result.empty());
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
#if defined(HAVE_OPENEXR) && defined(OPENCV_IMGCODECS_ENABLE_OPENEXR_TESTS)
|
||||
#include "test_exr.impl.hpp"
|
||||
#endif
|
||||
@@ -0,0 +1,294 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
extern "C" {
|
||||
#include "jpeglib.h"
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_empty)
|
||||
{
|
||||
cv::Mat img;
|
||||
std::vector<uchar> jpegImg;
|
||||
ASSERT_THROW(cv::imencode(".jpg", img, jpegImg), cv::Exception);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_progressive_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_PROGRESSIVE);
|
||||
params.push_back(1);
|
||||
|
||||
string output_progressive = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_progressive, img, params));
|
||||
cv::Mat img_jpg_progressive = cv::imread(output_progressive);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_progressive, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_progressive.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_optimize_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_OPTIMIZE);
|
||||
params.push_back(1);
|
||||
|
||||
string output_optimized = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_optimized, img, params));
|
||||
cv::Mat img_jpg_optimized = cv::imread(output_optimized);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_optimized, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_optimized.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_rst_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_RST_INTERVAL);
|
||||
params.push_back(1);
|
||||
|
||||
string output_rst = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_rst, img, params));
|
||||
cv::Mat img_jpg_rst = cv::imread(output_rst);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_rst, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_rst.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/25274
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Jpeg_decode_cmyk;
|
||||
TEST_P(Imgcodecs_Jpeg_decode_cmyk, regression25274)
|
||||
{
|
||||
const int imread_flag = GetParam();
|
||||
|
||||
/*
|
||||
* "test_1_c4.jpg" is CMYK-JPEG.
|
||||
* $ convert test_1_c3.jpg -colorspace CMYK test_1_c4.jpg
|
||||
* $ identify test_1_c4.jpg
|
||||
* test_1_c4.jpg JPEG 480x640 480x640+0+0 8-bit CMYK 11240B 0.000u 0:00.000
|
||||
*/
|
||||
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
|
||||
string rgb_filename = string(ts.get_data_path()) + "readwrite/test_1_c3.jpg";
|
||||
cv::Mat rgb_img = cv::imread(rgb_filename, imread_flag);
|
||||
ASSERT_FALSE(rgb_img.empty());
|
||||
|
||||
string cmyk_filename = string(ts.get_data_path()) + "readwrite/test_1_c4.jpg";
|
||||
cv::Mat cmyk_img = cv::imread(cmyk_filename, imread_flag);
|
||||
ASSERT_FALSE(cmyk_img.empty());
|
||||
|
||||
EXPECT_EQ(rgb_img.size(), cmyk_img.size());
|
||||
EXPECT_EQ(rgb_img.type(), cmyk_img.type());
|
||||
|
||||
// Jpeg is lossy compression.
|
||||
// There may be small differences in decoding results by environments.
|
||||
// -> 255 * 1% = 2.55 .
|
||||
EXPECT_LE(cvtest::norm(rgb_img, cmyk_img, NORM_INF), 3); // norm() <= 3
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P( /* nothing */,
|
||||
Imgcodecs_Jpeg_decode_cmyk,
|
||||
testing::Values(cv::IMREAD_COLOR,
|
||||
cv::IMREAD_COLOR_RGB,
|
||||
cv::IMREAD_GRAYSCALE,
|
||||
cv::IMREAD_ANYCOLOR));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
static const uint32_t default_sampling_factor = static_cast<uint32_t>(0x221111);
|
||||
|
||||
static uint32_t test_jpeg_subsampling( const Mat src, const vector<int> param )
|
||||
{
|
||||
vector<uint8_t> jpeg;
|
||||
|
||||
if ( cv::imencode(".jpg", src, jpeg, param ) == false )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( src.channels() != 3 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find SOF Marker(FFC0)
|
||||
int sof_offset = 0; // not found.
|
||||
int jpeg_size = static_cast<int>( jpeg.size() );
|
||||
for ( int i = 0 ; i < jpeg_size - 1; i++ )
|
||||
{
|
||||
if ( (jpeg[i] == 0xff ) && ( jpeg[i+1] == 0xC0 ) )
|
||||
{
|
||||
sof_offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( sof_offset == 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Extract Subsampling Factor from SOF.
|
||||
return ( jpeg[sof_offset + 0x0A + 3 * 0 + 1] << 16 ) +
|
||||
( jpeg[sof_offset + 0x0A + 3 * 1 + 1] << 8 ) +
|
||||
( jpeg[sof_offset + 0x0A + 3 * 2 + 1] ) ;
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_default)
|
||||
{
|
||||
vector<int> param;
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_valid)
|
||||
{
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
const uint32_t sampling_factor_list[] = {
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_411,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_420,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_422,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_440,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_444,
|
||||
};
|
||||
const int sampling_factor_list_num = 5;
|
||||
|
||||
for ( int i = 0 ; i < sampling_factor_list_num; i ++ )
|
||||
{
|
||||
vector<int> param;
|
||||
param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR );
|
||||
param.push_back( sampling_factor_list[i] );
|
||||
EXPECT_EQ( sampling_factor_list[i], test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_invalid)
|
||||
{
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
const uint32_t sampling_factor_list[] = { // Invalid list
|
||||
0x111112,
|
||||
0x000000,
|
||||
0x001111,
|
||||
0xFF1111,
|
||||
0x141111, // 1x4,1x1,1x1 - unknown
|
||||
0x241111, // 2x4,1x1,1x1 - unknown
|
||||
0x421111, // 4x2,1x1,1x1 - unknown
|
||||
0x441111, // 4x4,1x1,1x1 - 410(libjpeg cannot handle it)
|
||||
};
|
||||
const int sampling_factor_list_num = 8;
|
||||
|
||||
for ( int i = 0 ; i < sampling_factor_list_num; i ++ )
|
||||
{
|
||||
vector<int> param;
|
||||
param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR );
|
||||
param.push_back( sampling_factor_list[i] );
|
||||
EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// See https://github.com/opencv/opencv/issues/25646
|
||||
typedef testing::TestWithParam<std::tuple<int, int>> Imgcodecs_Jpeg_encode_withLumaChromaQuality;
|
||||
|
||||
TEST_P(Imgcodecs_Jpeg_encode_withLumaChromaQuality, basic)
|
||||
{
|
||||
const int luma = get<0>(GetParam());
|
||||
const int chroma = get<1>(GetParam());
|
||||
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string fname = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
|
||||
cv::Mat src = imread(fname, cv::IMREAD_COLOR);
|
||||
ASSERT_FALSE(src.empty());
|
||||
|
||||
// Add imread RGB test
|
||||
cv::Mat src_rgb = imread(fname, cv::IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(src_rgb.empty());
|
||||
|
||||
cvtColor(src_rgb, src_rgb, COLOR_RGB2BGR);
|
||||
EXPECT_TRUE(cvtest::norm(src, src_rgb, NORM_INF) == 0);
|
||||
|
||||
std::vector<uint8_t> jpegNormal;
|
||||
ASSERT_NO_THROW(cv::imencode(".jpg", src, jpegNormal));
|
||||
|
||||
std::vector<int> param;
|
||||
param.push_back(IMWRITE_JPEG_LUMA_QUALITY);
|
||||
param.push_back(luma);
|
||||
param.push_back(IMWRITE_JPEG_CHROMA_QUALITY);
|
||||
param.push_back(chroma);
|
||||
|
||||
std::vector<uint8_t> jpegCustom;
|
||||
ASSERT_NO_THROW(cv::imencode(".jpg", src, jpegCustom, param));
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
// For jpeg7+, we can support IMWRITE_JPEG_LUMA_QUALITY and IMWRITE_JPEG_CHROMA_QUALITY.
|
||||
if( (luma == 95 /* Default Luma Quality */ ) && ( chroma == 95 /* Default Chroma Quality */))
|
||||
{
|
||||
EXPECT_EQ(jpegNormal, jpegCustom);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(jpegNormal, jpegCustom);
|
||||
}
|
||||
#else
|
||||
// For jpeg6-, we cannot support IMWRITE_JPEG_LUMA/CHROMA_QUALITY because jpeg_default_qtables() is missing.
|
||||
// - IMWRITE_JPEG_LUMA_QUALITY updates internal parameter of IMWRITE_JPEG_QUALITY.
|
||||
// - IMWRITE_JPEG_CHROMA_QUALITY updates nothing.
|
||||
if( luma == 95 /* Default Jpeg Quality */ )
|
||||
{
|
||||
EXPECT_EQ(jpegNormal, jpegCustom);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(jpegNormal, jpegCustom);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P( /* nothing */,
|
||||
Imgcodecs_Jpeg_encode_withLumaChromaQuality,
|
||||
testing::Combine(
|
||||
testing::Values(70, 95, 100), // IMWRITE_JPEG_LUMA_QUALITY
|
||||
testing::Values(70, 95, 100) )); // IMWRITE_JPEG_CHROMA_QUALITY
|
||||
|
||||
#endif // HAVE_JPEG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,364 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_JPEGXL
|
||||
|
||||
#include <jxl/version.h> // For JPEGXL_MAJOR_VERSION and JPEGXL_MINOR_VERSION
|
||||
|
||||
typedef tuple<perf::MatType, int> MatType_and_Distance;
|
||||
typedef testing::TestWithParam<MatType_and_Distance> Imgcodecs_JpegXL_MatType;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType, write_read)
|
||||
{
|
||||
const int matType = get<0>(GetParam());
|
||||
const int distanceParam = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col;
|
||||
// Jpeg XL supports lossy and lossless compressions.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th;
|
||||
|
||||
switch( CV_MAT_DEPTH(matType) )
|
||||
{
|
||||
case CV_16U:
|
||||
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
|
||||
th = 656; // = 65535 / 100;
|
||||
break;
|
||||
case CV_32F:
|
||||
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
|
||||
th = 1.0 / 100.0;
|
||||
break;
|
||||
default:
|
||||
case CV_8U:
|
||||
col = cv::Scalar(124, 76, 42, 192);
|
||||
th = 3; // = 255 / 100 (1%);
|
||||
break;
|
||||
}
|
||||
|
||||
// If increasing distanceParam, threshold should be increased.
|
||||
th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam;
|
||||
|
||||
bool ret = false;
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
Mat img_org(320, 480, matType, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(distanceParam);
|
||||
EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
|
||||
EXPECT_EQ(0, remove(tmp_fname.c_str()));
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType, encode_decode)
|
||||
{
|
||||
const int matType = get<0>(GetParam());
|
||||
const int distanceParam = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col;
|
||||
// Jpeg XL supports lossy and lossless compressions.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th;
|
||||
|
||||
// If alpha=0, libjxl modify color channels(BGR). So do not set it.
|
||||
switch( CV_MAT_DEPTH(matType) )
|
||||
{
|
||||
case CV_16U:
|
||||
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
|
||||
th = 656; // = 65535 / 100;
|
||||
break;
|
||||
case CV_32F:
|
||||
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
|
||||
th = 1.0 / 100.0;
|
||||
break;
|
||||
default:
|
||||
case CV_8U:
|
||||
col = cv::Scalar(124, 76, 42, 192);
|
||||
th = 3; // = 255 / 100 (1%);
|
||||
break;
|
||||
}
|
||||
|
||||
// If increasing distanceParam, threshold should be increased.
|
||||
th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam;
|
||||
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
Mat img_org(320, 480, matType, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(distanceParam);
|
||||
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_MatType,
|
||||
testing::Combine(
|
||||
testing::Values(
|
||||
CV_8UC1, CV_8UC3, CV_8UC4,
|
||||
CV_16UC1, CV_16UC3, CV_16UC4,
|
||||
CV_32FC1, CV_32FC3, CV_32FC4
|
||||
),
|
||||
testing::Values( // Distance
|
||||
0, // Lossless
|
||||
1, // Default
|
||||
3, // Recomended Lossy Max
|
||||
25 // Specification Max
|
||||
)
|
||||
) );
|
||||
|
||||
|
||||
typedef tuple<int, int> Effort_and_Decoding_speed;
|
||||
typedef testing::TestWithParam<Effort_and_Decoding_speed> Imgcodecs_JpegXL_Effort_DecodingSpeed;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode)
|
||||
{
|
||||
const int effort = get<0>(GetParam());
|
||||
const int speed = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col = cv::Scalar(124,76,42);
|
||||
// Jpeg XL supports lossy and lossless compression.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th = 3; // = 255 / 100 (1%);
|
||||
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
Mat img_org(320, 480, CV_8UC3, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_EFFORT);
|
||||
param.push_back(effort);
|
||||
param.push_back(IMWRITE_JPEGXL_DECODING_SPEED);
|
||||
param.push_back(speed);
|
||||
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_Effort_DecodingSpeed,
|
||||
testing::Combine(
|
||||
testing::Values( // Effort
|
||||
1, // fastest
|
||||
7, // default
|
||||
9 // slowest
|
||||
),
|
||||
testing::Values( // Decoding Speed
|
||||
0, // default, slowest, and best quality/density
|
||||
2,
|
||||
4 // fastest, at the cost of some qulity/density
|
||||
)
|
||||
) );
|
||||
|
||||
TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image)
|
||||
{
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
cv::Mat roi = src(cv::Rect(10,20,30,50));
|
||||
EXPECT_FALSE(roi.isContinuous()); // uncontinued image
|
||||
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26767
|
||||
|
||||
typedef tuple<perf::MatType, ImreadModes> MatType_and_ImreadFlag;
|
||||
typedef testing::TestWithParam<MatType_and_ImreadFlag> Imgcodecs_JpegXL_MatType_ImreadFlag;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType_ImreadFlag, all_imreadFlags)
|
||||
{
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
const int matType = get<0>(GetParam());
|
||||
const int imreadFlag = get<1>(GetParam());
|
||||
|
||||
Mat img(240, 320, matType);
|
||||
randu(img, Scalar(0, 0, 0, 255), Scalar(255, 255, 255, 255));
|
||||
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(0 /* Lossless */);
|
||||
EXPECT_NO_THROW(imwrite(tmp_fname, img, param));
|
||||
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, imreadFlag));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
switch( imreadFlag )
|
||||
{
|
||||
case IMREAD_UNCHANGED:
|
||||
EXPECT_EQ( img.type(), img_decoded.type() );
|
||||
break;
|
||||
case IMREAD_GRAYSCALE:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U );
|
||||
EXPECT_EQ( img_decoded.channels(), 1 );
|
||||
break;
|
||||
case IMREAD_COLOR:
|
||||
case IMREAD_COLOR_RGB:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U );
|
||||
EXPECT_EQ( img_decoded.channels(), 3 );
|
||||
break;
|
||||
case IMREAD_ANYDEPTH:
|
||||
EXPECT_EQ( img_decoded.depth(), img.depth() );
|
||||
EXPECT_EQ( img_decoded.channels(), 1 );
|
||||
break;
|
||||
case IMREAD_ANYCOLOR:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U ) ;
|
||||
EXPECT_EQ( img_decoded.channels(), img.channels() == 1 ? 1 : 3 ); // Alpha channel will be dropped.
|
||||
break;
|
||||
}
|
||||
remove(tmp_fname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_MatType_ImreadFlag,
|
||||
testing::Combine(
|
||||
testing::Values(
|
||||
CV_8UC1, CV_8UC3, CV_8UC4,
|
||||
CV_16UC1, CV_16UC3, CV_16UC4,
|
||||
CV_32FC1, CV_32FC3, CV_32FC4
|
||||
),
|
||||
testing::Values(
|
||||
IMREAD_UNCHANGED,
|
||||
IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR,
|
||||
IMREAD_COLOR_RGB,
|
||||
IMREAD_ANYDEPTH,
|
||||
IMREAD_ANYCOLOR
|
||||
)
|
||||
) );
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imdecode_truncated_stream)
|
||||
{
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", src, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Try to decode non-truncated image.
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buff, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
// Try to decode truncated image.
|
||||
buff.resize(buff.size() - 1 );
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buff, cv::IMREAD_COLOR));
|
||||
EXPECT_TRUE(decoded.empty());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imread_truncated_stream)
|
||||
{
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", src, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Try to decode non-truncated image.
|
||||
FILE *fp = nullptr;
|
||||
|
||||
fp = fopen(tmp_fname.c_str(), "wb");
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
fwrite(&buff[0], sizeof(uint8_t), buff.size(), fp);
|
||||
fclose(fp);
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imread(tmp_fname, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
// Try to decode truncated image.
|
||||
fp = fopen(tmp_fname.c_str(), "wb");
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
fwrite(&buff[0], sizeof(uint8_t), buff.size() - 1, fp);
|
||||
fclose(fp);
|
||||
|
||||
EXPECT_NO_THROW(decoded = cv::imread(tmp_fname, cv::IMREAD_COLOR));
|
||||
EXPECT_TRUE(decoded.empty());
|
||||
|
||||
// Delete temporary file
|
||||
remove(tmp_fname.c_str());
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27382
|
||||
TEST(Imgcodecs_JpegXL, imencode_regression27382)
|
||||
{
|
||||
cv::Mat image(1024, 1024, CV_16U);
|
||||
cv::RNG rng(1024);
|
||||
rng.fill(image, cv::RNG::NORMAL, 0, 65535);
|
||||
|
||||
std::vector<unsigned char> buffer;
|
||||
std::vector<int> params = {cv::IMWRITE_JPEGXL_DISTANCE, 0}; // lossless
|
||||
|
||||
EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params));
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
cv::Mat diff;
|
||||
cv::absdiff(image, decoded, diff);
|
||||
double max_diff = 0.0;
|
||||
cv::minMaxLoc(diff, nullptr, &max_diff);
|
||||
EXPECT_EQ(max_diff, 0 );
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imencode_regression27382_2)
|
||||
{
|
||||
cv::Mat image(1024, 1024, CV_16U);
|
||||
cv::RNG rng(1024);
|
||||
rng.fill(image, cv::RNG::NORMAL, 0, 65535);
|
||||
|
||||
std::vector<unsigned char> buffer;
|
||||
std::vector<int> params = {cv::IMWRITE_JPEGXL_QUALITY, 100}; // lossless
|
||||
|
||||
EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params));
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
cv::Mat diff;
|
||||
cv::absdiff(image, decoded, diff);
|
||||
double max_diff = 0.0;
|
||||
cv::minMaxLoc(diff, nullptr, &max_diff);
|
||||
#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10
|
||||
// Quality parameter is supported with libjxl v0.10.0 or later
|
||||
EXPECT_EQ(max_diff, 0); // Lossless
|
||||
#else
|
||||
EXPECT_NE(max_diff, 0); // Lossy
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#endif // HAVE_JPEGXL
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
CV_TEST_MAIN("highgui")
|
||||
@@ -0,0 +1,648 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/28615
|
||||
// Precision differences in 16-bit grayscale conversion between old and modern libpng versions
|
||||
#define OPENCV_IMGCODECS_PNG_EPS_DEFAULT (4)
|
||||
#ifndef OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY
|
||||
#define OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY (OPENCV_IMGCODECS_PNG_EPS_DEFAULT)
|
||||
#endif
|
||||
|
||||
TEST(Imgcodecs_Png, write_big)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/read.png";
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = imread(filename));
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(13043, img.cols);
|
||||
EXPECT_EQ(13917, img.rows);
|
||||
|
||||
vector<uchar> buff;
|
||||
bool status = false;
|
||||
ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, 1024*1024 }));
|
||||
ASSERT_TRUE(status);
|
||||
#ifdef HAVE_PNG
|
||||
EXPECT_EQ((size_t)816219, buff.size());
|
||||
#else
|
||||
EXPECT_EQ((size_t)817407, buff.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, encode)
|
||||
{
|
||||
vector<uchar> buff;
|
||||
Mat img_gt = Mat::zeros(1000, 1000, CV_8U);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_PNG_COMPRESSION);
|
||||
param.push_back(3); //default(3) 0-9.
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imencode(".png", img_gt, buff, param));
|
||||
ASSERT_TRUE(status);
|
||||
Mat img;
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang
|
||||
EXPECT_FALSE(img.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, regression_ImreadVSCvtColor)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string imgName = root + "../cv/shared/lena.png";
|
||||
Mat original_image = imread(imgName);
|
||||
Mat gray_by_codec = imread(imgName, IMREAD_GRAYSCALE);
|
||||
Mat gray_by_cvt;
|
||||
cvtColor(original_image, gray_by_cvt, COLOR_BGR2GRAY);
|
||||
|
||||
Mat diff;
|
||||
absdiff(gray_by_codec, gray_by_cvt, diff);
|
||||
EXPECT_LT(cvtest::mean(diff)[0], 1.);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(10, 0), gray_by_codec, gray_by_cvt);
|
||||
}
|
||||
|
||||
// Test OpenCV issue 3075 is solved
|
||||
TEST(Imgcodecs_Png, read_color_palette_with_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
Mat img;
|
||||
|
||||
// First Test : Read PNG with alpha, imread flag -1
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 4);
|
||||
|
||||
// pixel is red in BGRA
|
||||
EXPECT_EQ(img.at<Vec4b>(0, 0), Vec4b(0, 0, 255, 255));
|
||||
EXPECT_EQ(img.at<Vec4b>(0, 1), Vec4b(0, 0, 255, 255));
|
||||
|
||||
// Second Test : Read PNG without alpha, imread flag -1
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
// Third Test : Read PNG with alpha, imread flag 1
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in RGB
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(255, 0, 0));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(255, 0, 0));
|
||||
|
||||
// Fourth Test : Read PNG without alpha, imread flag 1
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in RGB
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(255, 0, 0));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(255, 0, 0));
|
||||
}
|
||||
|
||||
// IHDR shall be first.
|
||||
// See https://github.com/opencv/opencv/issues/27295
|
||||
TEST(Imgcodecs_Png, decode_regression27295)
|
||||
{
|
||||
vector<uchar> buff;
|
||||
Mat src = Mat::zeros(240, 180, CV_8UC3);
|
||||
vector<int> param;
|
||||
EXPECT_NO_THROW(imencode(".png", src, buff, param));
|
||||
|
||||
Mat img;
|
||||
|
||||
// If IHDR chunk found as the first chunk, output shall not be empty.
|
||||
// 8 means PNG signature length.
|
||||
// 4 means length field(uint32_t).
|
||||
EXPECT_EQ(buff[8+4+0], 'I');
|
||||
EXPECT_EQ(buff[8+4+1], 'H');
|
||||
EXPECT_EQ(buff[8+4+2], 'D');
|
||||
EXPECT_EQ(buff[8+4+3], 'R');
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_FALSE(img.empty());
|
||||
|
||||
// If Non-IHDR chunk found as the first chunk, output shall be empty.
|
||||
buff[8+4+0] = 'i'; // Not 'I'
|
||||
buff[8+4+1] = 'H';
|
||||
buff[8+4+2] = 'D';
|
||||
buff[8+4+3] = 'R';
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_TRUE(img.empty());
|
||||
|
||||
// If CgBI chunk (Apple private) found as the first chunk, output shall be empty with special message.
|
||||
buff[8+4+0] = 'C';
|
||||
buff[8+4+1] = 'g';
|
||||
buff[8+4+2] = 'B';
|
||||
buff[8+4+3] = 'I';
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_TRUE(img.empty());
|
||||
}
|
||||
|
||||
// The program must not crash even when decoding a corrupted APNG image.
|
||||
// See https://github.com/opencv/opencv/issues/27744
|
||||
#if defined(HAVE_PNG) // APNG is supported only with using libpng
|
||||
TEST(Imgcodecs_Png, decode_regression27744)
|
||||
{
|
||||
// Create APNG stream
|
||||
Animation anim;
|
||||
for(size_t i = 0 ; i < 3 ; i++) {
|
||||
Mat frame(120, 120, CV_8UC3, Scalar(0,0,0));
|
||||
putText(frame, cv::format("%d", static_cast<int>(i)), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(30);
|
||||
}
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
EXPECT_NO_THROW(ret = imencodeanimation(".png", anim, buff));
|
||||
ASSERT_TRUE(ret) << "imencodeanimation() returns false";
|
||||
|
||||
// Find IDAT chunk
|
||||
const vector<uchar> IDAT = {'I', 'D', 'A', 'T' };
|
||||
std::vector<uchar>::iterator it = std::search(buff.begin(), buff.end(), IDAT.begin(), IDAT.end());
|
||||
ASSERT_FALSE(it == buff.end()) << "IDAT chunk not found";
|
||||
|
||||
// Determine the range to test
|
||||
// APNG stream contains as { len0, len1, len2, len3, 'I', 'D', 'A' 'T', ... }
|
||||
size_t idx = std::distance(buff.begin(), it); // 'I' position
|
||||
size_t len = (buff[idx-4] << 24) + (buff[idx-3] << 16) +
|
||||
(buff[idx-2] << 8) + (buff[idx-1]); // IDAT chunk length
|
||||
idx = idx + 4; // Move to IDAT body
|
||||
|
||||
// Test
|
||||
for(size_t i = 0; i < len; i++, idx++) {
|
||||
vector<uint8_t> work = buff;
|
||||
work[idx] = static_cast<uint8_t>((static_cast<uint32_t>(work[idx]) + 1) & 0xff);
|
||||
|
||||
Mat dst;
|
||||
EXPECT_NO_THROW(dst = imdecode(work, cv::IMREAD_COLOR));
|
||||
if(dst.empty()) {
|
||||
// libpng detects some error, but the program is not crashed. Test is passed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
|
||||
// Declare matrices for ground truth in different imread flag combinations
|
||||
Mat gt_0, gt_1, gt_2, gt_3, gt_256, gt_258;
|
||||
|
||||
// Handle grayscale 8-bit and 16-bit images
|
||||
if (gt.channels() == 1)
|
||||
{
|
||||
gt.copyTo(gt_2); // For IMREAD_ANYDEPTH
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2; // For IMREAD_GRAYSCALE
|
||||
|
||||
cvtColor(gt_2, gt_3, COLOR_GRAY2BGR); // For IMREAD_COLOR | IMREAD_ANYDEPTH
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3; // For IMREAD_COLOR
|
||||
|
||||
gt_256 = gt_1; // For IMREAD_COLOR_RGB
|
||||
gt_258 = gt_3; // For IMREAD_COLOR_RGB | IMREAD_ANYDEPTH
|
||||
}
|
||||
|
||||
// Handle color images (3 or 4 channels) with 8-bit and 16-bit depth
|
||||
if (gt.channels() > 1)
|
||||
{
|
||||
// Convert to grayscale
|
||||
cvtColor(gt, gt_2, COLOR_BGRA2GRAY);
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2;
|
||||
|
||||
// Convert to 3-channel BGR
|
||||
if (gt.channels() == 3)
|
||||
gt.copyTo(gt_3);
|
||||
else
|
||||
cvtColor(gt, gt_3, COLOR_BGRA2BGR);
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3;
|
||||
|
||||
// Convert to RGB for IMREAD_COLOR_RGB variants
|
||||
cvtColor(gt_1, gt_256, COLOR_BGR2RGB);
|
||||
cvtColor(gt_3, gt_258, COLOR_BGR2RGB);
|
||||
}
|
||||
|
||||
const double epsGrayAnydepth = ((gt.depth() == CV_16U) && (gt.channels() > 1)) ? OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY: OPENCV_IMGCODECS_PNG_EPS_DEFAULT;
|
||||
|
||||
// Perform comparisons with different imread flags
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_GRAYSCALE), gt_0);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR), gt_1);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(epsGrayAnydepth, 0), imread(filename, IMREAD_ANYDEPTH), gt_2); // IMREAD_GRAYSCALE is used.
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), gt_3);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR_RGB), gt_256);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), gt_258);
|
||||
|
||||
// Uncomment this block to write out the decoded images for visual/manual inspection
|
||||
// or for regenerating expected ground truth PNGs (for example, after changing decoder logic).
|
||||
#if 0
|
||||
imwrite(filename + "_0.png", imread(filename, IMREAD_GRAYSCALE));
|
||||
imwrite(filename + "_1.png", imread(filename, IMREAD_COLOR));
|
||||
imwrite(filename + "_2.png", imread(filename, IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_3.png", imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_256.png", imread(filename, IMREAD_COLOR_RGB));
|
||||
imwrite(filename + "_258.png", imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH));
|
||||
#endif
|
||||
|
||||
// Uncomment this block to verify that saved images (from above) load identically
|
||||
// when read back with IMREAD_UNCHANGED. Helps ensure write-read symmetry.
|
||||
#if 0
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_GRAYSCALE), imread(filename + "_0.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR), imread(filename + "_1.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_ANYDEPTH), imread(filename + "_2.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), imread(filename + "_3.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB), imread(filename + "_256.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), imread(filename + "_258.png", IMREAD_UNCHANGED));
|
||||
#endif
|
||||
}
|
||||
|
||||
const string pngsuite_files[] =
|
||||
{
|
||||
"basi0g01",
|
||||
"basi0g02",
|
||||
"basi0g04",
|
||||
"basi0g08",
|
||||
"basi0g16",
|
||||
"basi2c08",
|
||||
"basi2c16",
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basi3p08",
|
||||
"basi4a08",
|
||||
"basi4a16",
|
||||
"basi6a08",
|
||||
"basi6a16",
|
||||
"basn0g01",
|
||||
"basn0g02",
|
||||
"basn0g04",
|
||||
"basn0g08",
|
||||
"basn0g16",
|
||||
"basn2c08",
|
||||
"basn2c16",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"basn3p08",
|
||||
"basn4a08",
|
||||
"basn4a16",
|
||||
"basn6a08",
|
||||
"basn6a16",
|
||||
"bgai4a08",
|
||||
"bgai4a16",
|
||||
"bgan6a08",
|
||||
"bgan6a16",
|
||||
"bgbn4a08",
|
||||
"bggn4a16",
|
||||
"bgwn6a08",
|
||||
"bgyn6a16",
|
||||
"ccwn2c08",
|
||||
"ccwn3p08",
|
||||
"cdfn2c08",
|
||||
"cdhn2c08",
|
||||
"cdsn2c08",
|
||||
"cdun2c08",
|
||||
"ch1n3p04",
|
||||
"ch2n3p08",
|
||||
"cm0n0g04",
|
||||
"cm7n0g04",
|
||||
"cm9n0g04",
|
||||
"cs3n2c16",
|
||||
"cs3n3p08",
|
||||
"cs5n2c08",
|
||||
"cs5n3p08",
|
||||
"cs8n2c08",
|
||||
"cs8n3p08",
|
||||
"ct0n0g04",
|
||||
"ct1n0g04",
|
||||
"cten0g04",
|
||||
"ctfn0g04",
|
||||
"ctgn0g04",
|
||||
"cthn0g04",
|
||||
"ctjn0g04",
|
||||
"ctzn0g04",
|
||||
"exif2c08",
|
||||
"f00n0g08",
|
||||
"f00n2c08",
|
||||
"f01n0g08",
|
||||
"f01n2c08",
|
||||
"f02n0g08",
|
||||
"f02n2c08",
|
||||
"f03n0g08",
|
||||
"f03n2c08",
|
||||
"f04n0g08",
|
||||
"f04n2c08",
|
||||
"f99n0g04",
|
||||
"g03n0g16",
|
||||
"g04n0g16",
|
||||
"g05n0g16",
|
||||
"g07n0g16",
|
||||
"g10n0g16",
|
||||
"g10n2c08",
|
||||
"g10n3p04",
|
||||
"g25n0g16",
|
||||
"oi1n0g16",
|
||||
"oi1n2c16",
|
||||
"oi2n0g16",
|
||||
"oi2n2c16",
|
||||
"oi4n0g16",
|
||||
"oi4n2c16",
|
||||
"oi9n0g16",
|
||||
"oi9n2c16",
|
||||
"pp0n2c16",
|
||||
"pp0n6a08",
|
||||
"ps1n0g08",
|
||||
"ps1n2c16",
|
||||
"ps2n0g08",
|
||||
"ps2n2c16",
|
||||
"s01i3p01",
|
||||
"s01n3p01",
|
||||
"s02i3p01",
|
||||
"s02n3p01",
|
||||
"s03i3p01",
|
||||
"s03n3p01",
|
||||
"s04i3p01",
|
||||
"s04n3p01",
|
||||
"s05i3p02",
|
||||
"s05n3p02",
|
||||
"s06i3p02",
|
||||
"s06n3p02",
|
||||
"s07i3p02",
|
||||
"s07n3p02",
|
||||
"s08i3p02",
|
||||
"s08n3p02",
|
||||
"s09i3p02",
|
||||
"s09n3p02",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"s33i3p04",
|
||||
"s33n3p04",
|
||||
"s34i3p04",
|
||||
"s34n3p04",
|
||||
"s35i3p04",
|
||||
"s35n3p04",
|
||||
"s36i3p04",
|
||||
"s36n3p04",
|
||||
"s37i3p04",
|
||||
"s37n3p04",
|
||||
"s38i3p04",
|
||||
"s38n3p04",
|
||||
"s39i3p04",
|
||||
"s39n3p04",
|
||||
"s40i3p04",
|
||||
"s40n3p04",
|
||||
"tbbn0g04",
|
||||
"tbbn2c16",
|
||||
"tbbn3p08",
|
||||
"tbgn2c16",
|
||||
"tbgn3p08",
|
||||
"tbrn2c08",
|
||||
"tbwn0g16",
|
||||
"tbwn3p08",
|
||||
"tbyn3p08",
|
||||
"tm3n3p02",
|
||||
"tp0n0g08",
|
||||
"tp0n2c08",
|
||||
"tp0n3p08",
|
||||
"tp1n3p08",
|
||||
"z00n2c08",
|
||||
"z03n2c08",
|
||||
"z06n2c08",
|
||||
"z09n2c08",
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite,
|
||||
testing::ValuesIn(pngsuite_files));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Gamma;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Gamma, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
}
|
||||
|
||||
const string pngsuite_files_gamma[] =
|
||||
{
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g25n2c08",
|
||||
"g25n3p04"
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Gamma,
|
||||
testing::ValuesIn(pngsuite_files_gamma));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Corrupted;
|
||||
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
|
||||
// Corrupted files should not be read
|
||||
EXPECT_TRUE(src.empty());
|
||||
}
|
||||
|
||||
const string pngsuite_files_corrupted[] = {
|
||||
"xc1n0g08",
|
||||
"xc9n2c08",
|
||||
"xcrn0g04",
|
||||
"xcsn0g01",
|
||||
"xd0n2c08",
|
||||
"xd3n2c08",
|
||||
"xd9n2c08",
|
||||
"xdtn0g01",
|
||||
"xhdn0g08",
|
||||
"xlfn0g04",
|
||||
"xs1n0g01",
|
||||
"xs2n0g01",
|
||||
"xs4n0g01",
|
||||
"xs7n0g01",
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Corrupted,
|
||||
testing::ValuesIn(pngsuite_files_corrupted));
|
||||
|
||||
CV_ENUM(PNGStrategy, IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY, IMWRITE_PNG_STRATEGY_RLE, IMWRITE_PNG_STRATEGY_FIXED);
|
||||
CV_ENUM(PNGFilters, IMWRITE_PNG_FILTER_NONE, IMWRITE_PNG_FILTER_SUB, IMWRITE_PNG_FILTER_UP, IMWRITE_PNG_FILTER_AVG, IMWRITE_PNG_FILTER_PAETH, IMWRITE_PNG_FAST_FILTERS, IMWRITE_PNG_ALL_FILTERS);
|
||||
|
||||
typedef testing::TestWithParam<testing::tuple<string, PNGStrategy, PNGFilters, int>> Imgcodecs_Png_Encode;
|
||||
|
||||
TEST_P(Imgcodecs_Png_Encode, params)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + get<0>(GetParam());
|
||||
|
||||
const int strategy = get<1>(GetParam());
|
||||
const int filter = get<2>(GetParam());
|
||||
const int compression_level = get<3>(GetParam());
|
||||
|
||||
std::vector<uchar> file_buf;
|
||||
readFileBytes(filename, file_buf);
|
||||
Mat src = imdecode(file_buf, IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot decode test image " << filename;
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".png", src, buf, { IMWRITE_PNG_COMPRESSION, compression_level, IMWRITE_PNG_STRATEGY, strategy, IMWRITE_PNG_FILTER, filter });
|
||||
EXPECT_EQ(buf.size(), file_buf.size());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_Png_Encode,
|
||||
testing::Values(
|
||||
make_tuple("f00n0g08.png", IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_FILTER_NONE, 6),
|
||||
make_tuple("f00n2c08.png", IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_FILTER_NONE, 6),
|
||||
make_tuple("f01n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_SUB, 6),
|
||||
make_tuple("f01n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_SUB, 6),
|
||||
make_tuple("f02n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_UP, 6),
|
||||
make_tuple("f02n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_UP, 6),
|
||||
make_tuple("f03n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_AVG, 6),
|
||||
make_tuple("f03n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_AVG, 6),
|
||||
make_tuple("f04n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_PAETH, 6),
|
||||
make_tuple("f04n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_PAETH, 6),
|
||||
make_tuple("z03n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 3),
|
||||
make_tuple("z06n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 6),
|
||||
make_tuple("z09n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 9)));
|
||||
|
||||
typedef testing::TestWithParam<testing::tuple<string, int, size_t>> Imgcodecs_Png_ImwriteFlags;
|
||||
|
||||
TEST_P(Imgcodecs_Png_ImwriteFlags, compression_level)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + get<0>(GetParam());
|
||||
|
||||
const int compression_level = get<1>(GetParam());
|
||||
const size_t compression_level_output_size = get<2>(GetParam());
|
||||
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot read test image " << filename;
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".png", src, buf, { IMWRITE_PNG_COMPRESSION, compression_level });
|
||||
EXPECT_EQ(buf.size(), compression_level_output_size);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_Png_ImwriteFlags,
|
||||
testing::Values(
|
||||
make_tuple("../perf/512x512.png", 0, 788279),
|
||||
make_tuple("../perf/512x512.png", 1, 179503),
|
||||
make_tuple("../perf/512x512.png", 2, 176007),
|
||||
make_tuple("../perf/512x512.png", 3, 170497),
|
||||
make_tuple("../perf/512x512.png", 4, 163357),
|
||||
make_tuple("../perf/512x512.png", 5, 159190),
|
||||
make_tuple("../perf/512x512.png", 6, 156621),
|
||||
make_tuple("../perf/512x512.png", 7, 155696),
|
||||
make_tuple("../perf/512x512.png", 8, 153708),
|
||||
make_tuple("../perf/512x512.png", 9, 152181)));
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27614
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Png_ZLIBBUFFER_SIZE;
|
||||
TEST_P(Imgcodecs_Png_ZLIBBUFFER_SIZE, encode_regression_27614)
|
||||
{
|
||||
Mat img(320,240,CV_8UC3,cv::Scalar(64,76,43));
|
||||
vector<uint8_t> buff;
|
||||
bool status = false;
|
||||
ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, GetParam() }));
|
||||
ASSERT_TRUE(status);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_ZLIBBUFFER_SIZE,
|
||||
testing::Values(5,
|
||||
6, // Minimum limit
|
||||
8192, // Default value
|
||||
131072, // 128 KiB
|
||||
262144, // 256 KiB
|
||||
1048576, // Maximum limit
|
||||
1048577));
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,102 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
static inline
|
||||
void PrintTo(const ImreadModes& val, std::ostream* os)
|
||||
{
|
||||
int v = val;
|
||||
if (v == IMREAD_UNCHANGED && (v & IMREAD_IGNORE_ORIENTATION) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_UNCHANGED == -1);
|
||||
*os << "IMREAD_UNCHANGED";
|
||||
return;
|
||||
}
|
||||
if ((v & IMREAD_COLOR) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_COLOR == 1);
|
||||
v &= ~IMREAD_COLOR;
|
||||
*os << "IMREAD_COLOR" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
else if ((v & IMREAD_COLOR_RGB) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_COLOR_RGB == 256);
|
||||
v &= ~IMREAD_COLOR_RGB;
|
||||
*os << "IMREAD_COLOR_RGB" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
else if ((v & IMREAD_ANYCOLOR) != 0)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Assert(IMREAD_GRAYSCALE == 0);
|
||||
*os << "IMREAD_GRAYSCALE" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_ANYDEPTH) != 0)
|
||||
{
|
||||
v &= ~IMREAD_ANYDEPTH;
|
||||
*os << "IMREAD_ANYDEPTH" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_ANYCOLOR) != 0)
|
||||
{
|
||||
v &= ~IMREAD_ANYCOLOR;
|
||||
*os << "IMREAD_ANYCOLOR" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_LOAD_GDAL) != 0)
|
||||
{
|
||||
v &= ~IMREAD_LOAD_GDAL;
|
||||
*os << "IMREAD_LOAD_GDAL" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_IGNORE_ORIENTATION) != 0)
|
||||
{
|
||||
v &= ~IMREAD_IGNORE_ORIENTATION;
|
||||
*os << "IMREAD_IGNORE_ORIENTATION" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
switch (v)
|
||||
{
|
||||
case IMREAD_UNCHANGED: return;
|
||||
case IMREAD_GRAYSCALE: return;
|
||||
case IMREAD_COLOR: return;
|
||||
case IMREAD_ANYDEPTH: return;
|
||||
case IMREAD_ANYCOLOR: return;
|
||||
case IMREAD_LOAD_GDAL: return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_2: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_2: *os << "REDUCED_2"; return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_4: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_4: *os << "REDUCED_4"; return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_8: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_8: *os << "REDUCED_8"; return;
|
||||
case IMREAD_IGNORE_ORIENTATION: return;
|
||||
case IMREAD_COLOR_RGB: return;
|
||||
} // don't use "default:" to emit compiler warnings
|
||||
*os << "IMREAD_UNKNOWN(" << (int)v << ")";
|
||||
}
|
||||
|
||||
static inline
|
||||
void PrintTo(const ImwriteBMPCompressionFlags& val, std::ostream* os)
|
||||
{
|
||||
switch(val)
|
||||
{
|
||||
case IMWRITE_BMP_COMPRESSION_RGB:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_RGB";
|
||||
break;
|
||||
case IMWRITE_BMP_COMPRESSION_BITFIELDS:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_BITFIELDS";
|
||||
break;
|
||||
default:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_UNKNOWN(" << (int)val << ")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,631 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/* < <file_name, image_size>, <imread mode, scale> > */
|
||||
typedef tuple< tuple<string, Size>, tuple<ImreadModes, int> > Imgcodecs_Resize_t;
|
||||
|
||||
typedef testing::TestWithParam< Imgcodecs_Resize_t > Imgcodecs_Resize;
|
||||
|
||||
/* resize_flag_and_dims = <imread_flag, scale>*/
|
||||
const tuple <ImreadModes, int> resize_flag_and_dims[] =
|
||||
{
|
||||
make_tuple(IMREAD_UNCHANGED, 1),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_2, 2),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_4, 4),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_8, 8),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_2, 2),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_4, 4),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_8, 8)
|
||||
};
|
||||
|
||||
const tuple<string, Size> images[] =
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
make_tuple<string, Size>("../cv/imgproc/stuff.jpg", Size(640, 480)),
|
||||
#endif
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
make_tuple<string, Size>("../cv/shared/pic1.png", Size(400, 300)),
|
||||
#endif
|
||||
make_tuple<string, Size>("../highgui/readwrite/ordinary.bmp", Size(480, 272)),
|
||||
};
|
||||
|
||||
TEST_P(Imgcodecs_Resize, imread_reduce_flags)
|
||||
{
|
||||
const string file_name = findDataFile(get<0>(get<0>(GetParam())));
|
||||
const Size imageSize = get<1>(get<0>(GetParam()));
|
||||
|
||||
const int imread_flag = get<0>(get<1>(GetParam()));
|
||||
const int scale = get<1>(get<1>(GetParam()));
|
||||
|
||||
const int cols = imageSize.width / scale;
|
||||
const int rows = imageSize.height / scale;
|
||||
{
|
||||
Mat img = imread(file_name, imread_flag);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cols, img.cols);
|
||||
EXPECT_EQ(rows, img.rows);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST_P(Imgcodecs_Resize, imdecode_reduce_flags)
|
||||
{
|
||||
const string file_name = findDataFile(get<0>(get<0>(GetParam())));
|
||||
const Size imageSize = get<1>(get<0>(GetParam()));
|
||||
|
||||
const int imread_flag = get<0>(get<1>(GetParam()));
|
||||
const int scale = get<1>(get<1>(GetParam()));
|
||||
|
||||
const int cols = imageSize.width / scale;
|
||||
const int rows = imageSize.height / scale;
|
||||
|
||||
const std::ios::openmode mode = std::ios::in | std::ios::binary;
|
||||
std::ifstream ifs(file_name.c_str(), mode);
|
||||
ASSERT_TRUE(ifs.is_open());
|
||||
|
||||
ifs.seekg(0, std::ios::end);
|
||||
const size_t sz = static_cast<size_t>(ifs.tellg());
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> content(sz);
|
||||
ifs.read((char*)content.data(), sz);
|
||||
ASSERT_FALSE(ifs.fail());
|
||||
|
||||
{
|
||||
Mat img = imdecode(Mat(content), imread_flag);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cols, img.cols);
|
||||
EXPECT_EQ(rows, img.rows);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Resize,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(images),
|
||||
testing::ValuesIn(resize_flag_and_dims)
|
||||
)
|
||||
);
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Image, read_write_bmp)
|
||||
{
|
||||
const size_t IMAGE_COUNT = 10;
|
||||
const double thresDbell = 32;
|
||||
|
||||
for (size_t i = 0; i < IMAGE_COUNT; ++i)
|
||||
{
|
||||
stringstream s; s << i;
|
||||
const string digit = s.str();
|
||||
const string src_name = TS::ptr()->get_data_path() + "../python/images/QCIF_0" + digit + ".bmp";
|
||||
const string dst_name = cv::tempfile((digit + ".bmp").c_str());
|
||||
Mat image = imread(src_name);
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
resize(image, image, Size(968, 757), 0.0, 0.0, INTER_CUBIC);
|
||||
imwrite(dst_name, image);
|
||||
Mat loaded = imread(dst_name);
|
||||
ASSERT_FALSE(loaded.empty());
|
||||
|
||||
double psnr = cvtest::PSNR(loaded, image);
|
||||
EXPECT_GT(psnr, thresDbell);
|
||||
|
||||
vector<uchar> from_file;
|
||||
|
||||
FILE *f = fopen(dst_name.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
from_file.resize((size_t)len);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
from_file.resize(fread(&from_file[0], 1, from_file.size(), f));
|
||||
fclose(f);
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".bmp", image, buf);
|
||||
ASSERT_EQ(buf, from_file);
|
||||
|
||||
Mat buf_loaded = imdecode(Mat(buf), 1);
|
||||
ASSERT_FALSE(buf_loaded.empty());
|
||||
|
||||
psnr = cvtest::PSNR(buf_loaded, image);
|
||||
EXPECT_GT(psnr, thresDbell);
|
||||
|
||||
EXPECT_EQ(0, remove(dst_name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
typedef string Ext;
|
||||
typedef testing::TestWithParam<Ext> Imgcodecs_Image;
|
||||
|
||||
const string exts[] = {
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
"png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
"tiff",
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
"jpg",
|
||||
#endif
|
||||
#ifdef HAVE_JPEGXL
|
||||
"jxl",
|
||||
#endif
|
||||
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|
||||
|| defined(HAVE_OPENJPEG)
|
||||
"jp2",
|
||||
#endif
|
||||
#if 0 /*defined HAVE_OPENEXR && !defined __APPLE__*/
|
||||
"exr",
|
||||
#endif
|
||||
"bmp",
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
"ppm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_SUNRASTER
|
||||
"ras",
|
||||
#endif
|
||||
};
|
||||
|
||||
static
|
||||
void test_image_io(const Mat& image, const std::string& fname, const std::string& ext, int imreadFlag, double psnrThreshold)
|
||||
{
|
||||
vector<uchar> buf;
|
||||
ASSERT_NO_THROW(imencode("." + ext, image, buf));
|
||||
|
||||
ASSERT_NO_THROW(imwrite(fname, image));
|
||||
|
||||
FILE *f = fopen(fname.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
cout << "File size: " << len << " bytes" << endl;
|
||||
EXPECT_GT(len, 1024) << "File is small. Test or implementation is broken";
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
EXPECT_EQ(buf, file_buf) << "imwrite() / imencode() calls must provide the same output (bit-exact)";
|
||||
|
||||
Mat buf_loaded = imdecode(Mat(buf), imreadFlag);
|
||||
EXPECT_FALSE(buf_loaded.empty());
|
||||
|
||||
if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1)
|
||||
{
|
||||
cvtColor(buf_loaded, buf_loaded, COLOR_RGB2BGR);
|
||||
}
|
||||
|
||||
Mat loaded = imread(fname, imreadFlag);
|
||||
EXPECT_FALSE(loaded.empty());
|
||||
|
||||
if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1)
|
||||
{
|
||||
cvtColor(loaded, loaded, COLOR_RGB2BGR);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, cv::norm(loaded, buf_loaded, NORM_INF)) << "imread() and imdecode() calls must provide the same result (bit-exact)";
|
||||
|
||||
double psnr = cvtest::PSNR(loaded, image);
|
||||
EXPECT_GT(psnr, psnrThreshold);
|
||||
|
||||
// not necessary due bitexact check above
|
||||
//double buf_psnr = cvtest::PSNR(buf_loaded, image);
|
||||
//EXPECT_GT(buf_psnr, psnrThreshold);
|
||||
|
||||
#if 0 // debug
|
||||
if (psnr <= psnrThreshold /*|| buf_psnr <= thresDbell*/)
|
||||
{
|
||||
cout << "File: " << fname << endl;
|
||||
imshow("origin", image);
|
||||
imshow("imread", loaded);
|
||||
imshow("imdecode", buf_loaded);
|
||||
waitKey();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_Image, read_write_BGR)
|
||||
{
|
||||
const string ext = this->GetParam();
|
||||
const string fname = cv::tempfile(ext.c_str());
|
||||
|
||||
double psnrThreshold = 100;
|
||||
if (ext == "jpg")
|
||||
psnrThreshold = 32;
|
||||
if (ext == "jxl")
|
||||
psnrThreshold = 30;
|
||||
#if defined(HAVE_JASPER)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 95;
|
||||
#elif defined(HAVE_OPENJPEG)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 35;
|
||||
#endif
|
||||
|
||||
Mat image = generateTestImageBGR();
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR, psnrThreshold));
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR_RGB, psnrThreshold));
|
||||
|
||||
EXPECT_EQ(0, remove(fname.c_str()));
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_Image, read_write_GRAYSCALE)
|
||||
{
|
||||
const string ext = this->GetParam();
|
||||
|
||||
if (false
|
||||
|| ext == "ppm" // grayscale is not implemented
|
||||
|| ext == "ras" // broken (black result)
|
||||
)
|
||||
throw SkipTestException("GRAYSCALE mode is not supported");
|
||||
|
||||
const string fname = cv::tempfile(ext.c_str());
|
||||
|
||||
double psnrThreshold = 100;
|
||||
if (ext == "jpg")
|
||||
psnrThreshold = 40;
|
||||
if (ext == "jxl")
|
||||
psnrThreshold = 40;
|
||||
#if defined(HAVE_JASPER)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 70;
|
||||
#elif defined(HAVE_OPENJPEG)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 35;
|
||||
#endif
|
||||
|
||||
Mat image = generateTestImageGrayscale();
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_GRAYSCALE, psnrThreshold));
|
||||
|
||||
EXPECT_EQ(0, remove(fname.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(imgcodecs, Imgcodecs_Image, testing::ValuesIn(exts));
|
||||
|
||||
TEST(Imgcodecs_Image, regression_9376)
|
||||
{
|
||||
String path = findDataFile("readwrite/regression_9376.bmp");
|
||||
Mat m = imread(path);
|
||||
ASSERT_FALSE(m.empty());
|
||||
EXPECT_EQ(32, m.cols);
|
||||
EXPECT_EQ(32, m.rows);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, imread_overload)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string imgName = findDataFile("../highgui/readwrite/ordinary.bmp");
|
||||
|
||||
Mat ref = imread(imgName);
|
||||
ASSERT_FALSE(ref.empty());
|
||||
{
|
||||
Mat img(ref.size(), ref.type(), Scalar::all(0)); // existing image
|
||||
void * ptr = img.data;
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
EXPECT_EQ(img.data, ptr); // no reallocation
|
||||
}
|
||||
{
|
||||
Mat img; // empty image
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
}
|
||||
{
|
||||
UMat img; // empty UMat
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Image, write_umat)
|
||||
{
|
||||
const string src_name = TS::ptr()->get_data_path() + "../python/images/baboon.bmp";
|
||||
const string dst_name = cv::tempfile(".bmp");
|
||||
|
||||
Mat image1 = imread(src_name);
|
||||
ASSERT_FALSE(image1.empty());
|
||||
|
||||
UMat image1_umat = image1.getUMat(ACCESS_RW);
|
||||
|
||||
imwrite(dst_name, image1_umat);
|
||||
|
||||
Mat image2 = imread(dst_name);
|
||||
ASSERT_FALSE(image2.empty());
|
||||
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), image1, image2);
|
||||
EXPECT_EQ(0, remove(dst_name.c_str()));
|
||||
}
|
||||
|
||||
#ifdef HAVE_TIFF
|
||||
TEST(Imgcodecs_Image, multipage_collection_size)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
EXPECT_EQ((std::size_t)6, collection.size());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
auto collectionBegin = collection.begin();
|
||||
for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin)
|
||||
{
|
||||
double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_two_iterator)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
auto firstIter = collection.begin();
|
||||
auto secondIter = collection.begin();
|
||||
|
||||
// Decode all odd pages then decode even pages -> 1, 0, 3, 2 ...
|
||||
firstIter++;
|
||||
for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) {
|
||||
Mat mat = *firstIter;
|
||||
double diff = cv::norm(mat, imread(page_files[i]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
Mat evenMat = *secondIter;
|
||||
diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_operator_plusplus)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
// operator++ test
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
auto firstIter = collection.begin();
|
||||
auto secondIter = firstIter++;
|
||||
|
||||
// firstIter points to second page, secondIter points to first page
|
||||
double diff = cv::norm(*firstIter, *secondIter, NORM_INF);
|
||||
EXPECT_NE(diff, 0.);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_backward_decoding)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
EXPECT_EQ((size_t)6, collection.size());
|
||||
|
||||
// backward decoding -> 5,4,3,2,1,0
|
||||
for(int i = (int)collection.size() - 1; i >= 0; --i)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[i]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(collection[i], ithPage, NORM_INF);
|
||||
EXPECT_EQ(diff, 0.);
|
||||
}
|
||||
|
||||
for(int i = 0; i < (int)collection.size(); ++i)
|
||||
{
|
||||
collection.releaseCache(i);
|
||||
}
|
||||
|
||||
double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF);
|
||||
EXPECT_EQ(diff, 0.);
|
||||
}
|
||||
|
||||
TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
size_t index = 0;
|
||||
for(auto &i: collection)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[index]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(i, ithPage, NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
++index;
|
||||
}
|
||||
EXPECT_EQ(index, collection.size());
|
||||
|
||||
index = 0;
|
||||
for(auto &&i: collection)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[index]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(i, ithPage, NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
++index;
|
||||
}
|
||||
EXPECT_EQ(index, collection.size());
|
||||
}
|
||||
|
||||
TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
ImageCollection imcol(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
auto it0 = imcol.begin(), it1 = it0, it2 = it0;
|
||||
vector<Mat> img(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
img[i] = *it0;
|
||||
it0->release();
|
||||
++it0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
++it2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
auto img2 = *it2;
|
||||
auto img1 = *it1;
|
||||
++it2;
|
||||
++it1;
|
||||
EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26207
|
||||
TEST(Imgcodecs, imencodemulti_regression_26207)
|
||||
{
|
||||
vector<Mat> imgs;
|
||||
const cv::Mat img(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
imgs.push_back(img);
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
|
||||
// Encode single image
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", img, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Encode multiple images
|
||||
imgs.push_back(img.clone());
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Count stored images from buffer.
|
||||
// imcount() doesn't support buffer, so encoded buffer outputs to file temporary.
|
||||
const size_t len = buf.size();
|
||||
const string filename = cv::tempfile(".tiff");
|
||||
FILE *f = fopen(filename.c_str(), "wb");
|
||||
EXPECT_NE(f, nullptr);
|
||||
EXPECT_EQ(len, fwrite(&buf[0], 1, len, f));
|
||||
fclose(f);
|
||||
|
||||
EXPECT_EQ(2, (int)imcount(filename));
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
}
|
||||
#endif
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/26211
|
||||
// ( related with https://github.com/opencv/opencv/issues/26207 )
|
||||
TEST(Imgcodecs, imencode_regression_26207_extra)
|
||||
{
|
||||
// CV_32F is not supported depth for BMP Encoder.
|
||||
// Encoded buffer contains CV_8U image which is fallbacked.
|
||||
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imencode(".bmp", src, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = imdecode(buf, IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
EXPECT_EQ(CV_8UC1, dst.type());
|
||||
}
|
||||
TEST(Imgcodecs, imwrite_regression_26207_extra)
|
||||
{
|
||||
// CV_32F is not supported depth for BMP Encoder.
|
||||
// Encoded buffer contains CV_8U image which is fallbacked.
|
||||
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
|
||||
const string filename = cv::tempfile(".bmp");
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imwrite(filename, src));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = imread(filename, IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
EXPECT_EQ(CV_8UC1, dst.type());
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Params, imwrite_regression_22752)
|
||||
{
|
||||
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
|
||||
vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_QUALITY);
|
||||
// params.push_back(100)); // Forget it.
|
||||
EXPECT_ANY_THROW(cv::imwrite("test.jpg", img, params)); // parameters size or missing JPEG codec
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Params, imencode_regression_22752)
|
||||
{
|
||||
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
|
||||
vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_QUALITY);
|
||||
// params.push_back(100)); // Forget it.
|
||||
vector<uchar> buf;
|
||||
EXPECT_ANY_THROW(cv::imencode("test.jpg", img, buf, params)); // parameters size or missing JPEG codec
|
||||
}
|
||||
|
||||
TEST(Imgcodecs, decode_over2GB)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_6GB);
|
||||
// empty buffer more than 2GB size
|
||||
std::vector<uint8_t> buf(size_t(INT_MAX) + 4096);
|
||||
cv::Mat dst;
|
||||
EXPECT_THROW(dst = cv::imdecode(buf, cv::IMREAD_COLOR), cv::Exception);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,138 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
// Regression tests for the Sun Raster decoder (grfmt_sunras.cpp).
|
||||
// These tests guard against:
|
||||
// - UBSan "load of invalid enum value" in SunRasterDecoder::readHeader()
|
||||
// (see https://github.com/opencv/opencv/issues/29150)
|
||||
// - Out-of-range SunRasType / SunRasMapType values causing UB via unchecked C-cast
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: build a minimal Sun Raster byte buffer with caller-supplied fields.
|
||||
// Sun Raster header layout (all fields big-endian, 32-bit):
|
||||
// [0] magic = 0x59a66a95
|
||||
// [4] width
|
||||
// [8] height
|
||||
// [12] depth (bits-per-pixel)
|
||||
// [16] length (image data length, may be 0 for RAS_OLD)
|
||||
// [20] ras_type (SunRasType)
|
||||
// [24] maptype (SunRasMapType)
|
||||
// [28] maplength
|
||||
// ---------------------------------------------------------------------------
|
||||
static std::vector<uint8_t> makeSunRasHeader(
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t length, uint32_t ras_type, uint32_t maptype, uint32_t maplength)
|
||||
{
|
||||
std::vector<uint8_t> buf(32);
|
||||
auto put32 = [&](size_t off, uint32_t v) {
|
||||
buf[off+0] = (v >> 24) & 0xff;
|
||||
buf[off+1] = (v >> 16) & 0xff;
|
||||
buf[off+2] = (v >> 8) & 0xff;
|
||||
buf[off+3] = (v ) & 0xff;
|
||||
};
|
||||
put32( 0, 0x59a66a95u); // magic
|
||||
put32( 4, width);
|
||||
put32( 8, height);
|
||||
put32(12, depth);
|
||||
put32(16, length);
|
||||
put32(20, ras_type);
|
||||
put32(24, maptype);
|
||||
put32(28, maplength);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Crash / UBSan regression — issue #29150
|
||||
// Feeding an invalid maptype value (34077 / 0x851d) must NOT trigger UB;
|
||||
// imdecode must return an empty Mat gracefully.
|
||||
// ---------------------------------------------------------------------------
|
||||
TEST(Imgcodecs_SunRaster, invalid_maptype_returns_empty_29150)
|
||||
{
|
||||
// Crafted header from the original bug report:
|
||||
// magic=0x59a66a95, width=0x10101, height=0x10000, depth=1, length=0x1000000
|
||||
// ras_type=0 (RAS_OLD, valid), maptype=0x851d (34077, INVALID)
|
||||
const std::vector<uint8_t> image_data = {
|
||||
0x59,0xa6,0x6a,0x95, 0x01,0x01,0x00,0x00,
|
||||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x01,
|
||||
0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x85,0x1d, 0xae,0x5b,0x8d,0xd5,
|
||||
0x9c,0x25,0x22,0x41, 0x51,0x92,0x13,0x14,0x33
|
||||
};
|
||||
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(image_data, cv::IMREAD_REDUCED_GRAYSCALE_2));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid maptype";
|
||||
}
|
||||
|
||||
// Invalid ras_type (value well outside [0,3]) must be rejected cleanly.
|
||||
TEST(Imgcodecs_SunRaster, invalid_rastype_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(/*w*/8, /*h*/8, /*depth*/8,
|
||||
/*len*/0, /*ras_type*/0xFFFF, /*maptype*/0, /*mapllen*/0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid ras_type";
|
||||
}
|
||||
|
||||
// maptype = 2 is outside [0,1] (only RMT_NONE=0, RMT_EQUAL_RGB=1 are defined).
|
||||
TEST(Imgcodecs_SunRaster, maptype_2_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(8, 8, 8, 0, /*ras_type*/0, /*maptype*/2, 0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=2";
|
||||
}
|
||||
|
||||
// maptype = UINT32_MAX is also invalid.
|
||||
TEST(Imgcodecs_SunRaster, maptype_max_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(8, 8, 8, 0, 0, 0xFFFFFFFFu, 0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=UINT_MAX";
|
||||
}
|
||||
|
||||
// A truncated buffer (shorter than the 32-byte header) must not crash.
|
||||
TEST(Imgcodecs_SunRaster, truncated_header_returns_empty)
|
||||
{
|
||||
// Only 16 bytes — header read will run out of data.
|
||||
const std::vector<uint8_t> buf = {
|
||||
0x59,0xa6,0x6a,0x95, 0x00,0x00,0x00,0x08,
|
||||
0x00,0x00,0x00,0x08, 0x00,0x00,0x00,0x08
|
||||
};
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for truncated header";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sanity: a well-formed 8-bpp grayscale Sun Raster (RMT_NONE) still decodes.
|
||||
// We build the full header + pixel data manually so the test has no file I/O.
|
||||
// ---------------------------------------------------------------------------
|
||||
TEST(Imgcodecs_SunRaster, valid_8bpp_grayscale_decodes)
|
||||
{
|
||||
const int W = 4, H = 4;
|
||||
// RAS_OLD(0), maptype=RMT_NONE(0), maplength=0
|
||||
auto buf = makeSunRasHeader(W, H, 8, W*H, 0, 0, 0);
|
||||
|
||||
// Sun Raster rows are padded to 16-bit boundary.
|
||||
// W=4: row_bytes = 4 (already even), no padding needed.
|
||||
for (int i = 0; i < W * H; ++i)
|
||||
buf.push_back(static_cast<uint8_t>(i * 16)); // arbitrary gray values
|
||||
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(result.empty()) << "imdecode must succeed for a valid 8-bpp Sun Raster";
|
||||
EXPECT_EQ(result.cols, W);
|
||||
EXPECT_EQ(result.rows, H);
|
||||
EXPECT_EQ(result.type(), CV_8UC1);
|
||||
}
|
||||
|
||||
}} // namespace opencv_test
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
static void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
|
||||
{
|
||||
FILE * wfile = fopen(fname.c_str(), "rb");
|
||||
if (wfile != NULL)
|
||||
{
|
||||
fseek(wfile, 0, SEEK_END);
|
||||
size_t wfile_size = ftell(wfile);
|
||||
fseek(wfile, 0, SEEK_SET);
|
||||
|
||||
buf.resize(wfile_size);
|
||||
|
||||
size_t data_size = fread(&buf[0], 1, wfile_size, wfile);
|
||||
|
||||
if(wfile)
|
||||
{
|
||||
fclose(wfile);
|
||||
}
|
||||
|
||||
EXPECT_EQ(data_size, wfile_size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_lossless_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
string filename = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(filename);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless
|
||||
|
||||
cv::Mat img_webp = cv::imread(output);
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
readFileBytes(output, buf);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
cv::Mat decode = cv::imdecode(buf, IMREAD_COLOR);
|
||||
ASSERT_FALSE(decode.empty());
|
||||
EXPECT_TRUE(cvtest::norm(decode, img_webp, NORM_INF) == 0);
|
||||
|
||||
cv::Mat decode_rgb = cv::imdecode(buf, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(decode_rgb.empty());
|
||||
|
||||
cvtColor(decode_rgb, decode_rgb, COLOR_RGB2BGR);
|
||||
EXPECT_TRUE(cvtest::norm(decode_rgb, img_webp, NORM_INF) == 0);
|
||||
|
||||
ASSERT_FALSE(img_webp.empty());
|
||||
|
||||
EXPECT_TRUE(cvtest::norm(img, img_webp, NORM_INF) == 0);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_lossy_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
std::string input = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
for(int q = 100; q>=0; q-=20)
|
||||
{
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_WEBP_QUALITY);
|
||||
params.push_back(MAX(q,1));
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img, params));
|
||||
cv::Mat img_webp = cv::imread(output);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
EXPECT_FALSE(img_webp.empty());
|
||||
EXPECT_EQ(3, img_webp.channels());
|
||||
EXPECT_EQ(512, img_webp.cols);
|
||||
EXPECT_EQ(512, img_webp.rows);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
std::string input = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<cv::Mat> imgs;
|
||||
cv::split(img, imgs);
|
||||
imgs.push_back(cv::Mat(imgs[0]));
|
||||
imgs[imgs.size() - 1] = cv::Scalar::all(128);
|
||||
cv::merge(imgs, img);
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img));
|
||||
cv::Mat img_webp = cv::imread(output, IMREAD_UNCHANGED);
|
||||
cv::Mat img_webp_bgr = cv::imread(output); // IMREAD_COLOR by default
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
EXPECT_FALSE(img_webp.empty());
|
||||
EXPECT_EQ(4, img_webp.channels());
|
||||
EXPECT_EQ(512, img_webp.cols);
|
||||
EXPECT_EQ(512, img_webp.rows);
|
||||
EXPECT_FALSE(img_webp_bgr.empty());
|
||||
EXPECT_EQ(3, img_webp_bgr.channels());
|
||||
EXPECT_EQ(512, img_webp_bgr.cols);
|
||||
EXPECT_EQ(512, img_webp_bgr.rows);
|
||||
}
|
||||
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/28503
|
||||
TEST(Imgcodecs_WebP, encode_decode_LOSSLESS_MODE)
|
||||
{
|
||||
cv::Mat img(cv::Size(64,4), CV_8UC4, cv::Scalar(124,64,67,0) );
|
||||
for(int ix = 0; ix < img.size().width; ix++)
|
||||
{
|
||||
img.at<Vec4b>(0, ix)[3] = 0; // Transpacency pixel
|
||||
img.at<Vec4b>(1, ix)[3] = 1;
|
||||
img.at<Vec4b>(2, ix)[3] = 254;
|
||||
img.at<Vec4b>(3, ix)[3] = 255;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> work;
|
||||
EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_ON}));
|
||||
cv::Mat img_ON = cv::imdecode(work, IMREAD_UNCHANGED);
|
||||
EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR}));
|
||||
cv::Mat img_PRESERVE_COLOR = cv::imdecode(work, IMREAD_UNCHANGED);
|
||||
|
||||
for(int ix = 0; ix < img.size().width; ix++)
|
||||
{
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(0, ix), Vec4b(0, 0, 0, 0)); // LOSSLESS_ON -> COLOR will be optimized/dropped.
|
||||
EXPECT_EQ(img_PRESERVE_COLOR.at<Vec4b>(0, ix), Vec4b(124,64,67,0)); // PRESERVE_COLOR
|
||||
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(1, ix), img_PRESERVE_COLOR.at<Vec4b>(1, ix) );
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(2, ix), img_PRESERVE_COLOR.at<Vec4b>(2, ix) );
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(3, ix), img_PRESERVE_COLOR.at<Vec4b>(3, ix) );
|
||||
}
|
||||
}
|
||||
|
||||
// Expected result categories for WebP encoding tests.
|
||||
enum ImencodeLosslessResult {
|
||||
LOSSY, // Expect lossy compression (pixel differences allowed)
|
||||
LOSSLESS, // Expect standard lossless compression (pixel values match)
|
||||
EXACT // Expect exact lossless (preserving RGB values of transparent pixels)
|
||||
};
|
||||
|
||||
typedef std::tuple<int, int, ImencodeLosslessResult> WebPModePriorityParams;
|
||||
class Imgcodecs_WebP_Mode_Priority : public testing::TestWithParam<WebPModePriorityParams> {};
|
||||
|
||||
TEST_P(Imgcodecs_WebP_Mode_Priority, encode_webp_mode_priority)
|
||||
{
|
||||
const int mode = std::get<0>(GetParam());
|
||||
const int quality = std::get<1>(GetParam());
|
||||
const ImencodeLosslessResult expected = std::get<2>(GetParam());
|
||||
|
||||
// Generate a 100x100 RGBA test image.
|
||||
// Set a transparent pixel with specific color (Blue) to verify EXACT mode.
|
||||
Mat src(100, 100, CV_8UC4, Scalar(255, 255, 255, 255));
|
||||
src.at<Vec4b>(0, 0) = Vec4b(255, 0, 0, 0); // Transparent Blue (B:255, G:0, R:0, A:0)
|
||||
|
||||
// Build the imwrite parameter vector dynamically.
|
||||
std::vector<int> params;
|
||||
if (mode != -1) {
|
||||
params.push_back(IMWRITE_WEBP_LOSSLESS_MODE);
|
||||
params.push_back(mode);
|
||||
}
|
||||
if (quality != -1) {
|
||||
params.push_back(IMWRITE_WEBP_QUALITY);
|
||||
params.push_back(quality);
|
||||
}
|
||||
|
||||
// Encode to memory and decode back.
|
||||
std::vector<uchar> buf;
|
||||
ASSERT_TRUE(imencode(".webp", src, buf, params));
|
||||
Mat dst = imdecode(buf, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(dst.empty());
|
||||
|
||||
// Validation logic
|
||||
if (expected == LOSSY) {
|
||||
// We expect some differences in lossy mode
|
||||
double diff = cv::norm(src, dst, NORM_INF);
|
||||
EXPECT_GT(diff, 0) << "Should be lossy (Quality: " << quality << ")";
|
||||
}
|
||||
else if (expected == LOSSLESS) {
|
||||
// Standard lossless: we allow the library to modify RGB values
|
||||
// of fully transparent pixels (A=0) to improve compression ratio.
|
||||
// Thus, we compare only visible pixels or check with a slightly relaxed condition.
|
||||
|
||||
// Option A: If you want to allow RGB changes on A=0:
|
||||
// We can't use cv::norm directly if A=0 pixels are modified.
|
||||
// Let's check if they are identical except for the transparent pixel.
|
||||
Mat diff;
|
||||
absdiff(src, dst, diff);
|
||||
Scalar total_diff = sum(diff);
|
||||
// If only the (0,0) pixel changed from (255,0,0,0) to (0,0,0,0),
|
||||
// total_diff will be 255.
|
||||
EXPECT_LE(total_diff[0] + total_diff[1] + total_diff[2], 255)
|
||||
<< "Standard lossless should not have significant pixel differences";
|
||||
EXPECT_EQ(src.at<Vec4b>(0,0)[3], dst.at<Vec4b>(0,0)[3]) << "Alpha must be preserved";
|
||||
}
|
||||
else if (expected == EXACT) {
|
||||
// Exact lossless: Every single bit must match, including transparent pixels.
|
||||
double diff = cv::norm(src, dst, NORM_INF);
|
||||
EXPECT_EQ(0, diff) << "Exact mode must preserve all pixel values perfectly";
|
||||
EXPECT_EQ(src.at<Vec4b>(0, 0), dst.at<Vec4b>(0, 0))
|
||||
<< "RGB values of transparent pixels must be preserved in EXACT mode";
|
||||
}
|
||||
else {
|
||||
FAIL() << "Unknown expectation type";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate human-readable test names in gtest output.
|
||||
*/
|
||||
static std::string getModeStr(int m) {
|
||||
if (m == -1) return "OMIT";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_OFF) return "OFF";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_ON) return "ON";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR) return "PRESERVE";
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static std::string getExpectStr(ImencodeLosslessResult r) {
|
||||
return (r == LOSSY) ? "LOSSY" : (r == EXACT) ? "EXACT" : "LOSSLESS";
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Imgcodecs_WebP_Mode_Priority,
|
||||
testing::Values(
|
||||
// Default (OMIT mode) cases
|
||||
WebPModePriorityParams(-1, -1, LOSSLESS),
|
||||
WebPModePriorityParams(-1, 80, LOSSY),
|
||||
WebPModePriorityParams(-1, 101, LOSSLESS),
|
||||
|
||||
// LOSSLESS_OFF (Explicitly off)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, -1, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 80, LOSSY),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 101, LOSSLESS),
|
||||
|
||||
// LOSSLESS_ON (Force lossless)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, -1, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 80, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 101, LOSSLESS),
|
||||
|
||||
// PRESERVE_COLOR (Exact lossless)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, -1, EXACT),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 80, EXACT),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 101, EXACT)
|
||||
),
|
||||
[](const testing::TestParamInfo<WebPModePriorityParams>& info_) {
|
||||
std::string mode = getModeStr(std::get<0>(info_.param));
|
||||
int q = std::get<1>(info_.param);
|
||||
std::string q_str = (q == -1) ? "omit" : std::to_string(q);
|
||||
return mode + "_q" + q_str + "_" + getExpectStr(std::get<2>(info_.param));
|
||||
}
|
||||
);
|
||||
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
}} // namespace
|
||||
Reference in New Issue
Block a user