0
0
mirror of https://github.com/opencv/opencv.git synced 2026-01-18 17:21:42 +01:00

Merge pull request #27113 from sturkmen72:spng-CV_16U

Fixing imread() function 16 bit reading png problem with libspng #27113

The purpose of the PR was to load bit-exact compatible results with libspng and libpng. To test this, `Imgcodecs_Png_PngSuite `was improved. Files containing gamma correction were moved to a separate test called `Imgcodecs_Png_PngSuite_Gamma `because the logic created for the other files did not apply to those with gamma correction. As a result, libspng now works in bit-exact compatibility with libpng. The code can be refactored later.

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Suleyman TURKMEN
2025-06-22 15:43:09 +03:00
committed by GitHub
parent 1950c4dbb9
commit afe5b226b4
4 changed files with 314 additions and 231 deletions

View File

@@ -15,6 +15,10 @@ import java.util.List;
public class ImgcodecsTest extends OpenCVTestCase {
public void testAnimation() {
if (!Imgcodecs.haveImageWriter("*.apng")) {
return;
}
Mat src = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_REDUCED_COLOR_4);
assertFalse(src.empty());

View File

@@ -861,7 +861,7 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_
PngEncoder::PngEncoder()
{
m_description = "Portable Network Graphics files (*.png)";
m_description = "Portable Network Graphics files (*.png;*.apng)";
m_buf_supported = true;
op_zstream1.zalloc = NULL;
op_zstream2.zalloc = NULL;

View File

@@ -31,18 +31,18 @@
* with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation,
* slightly modified versions are implemented in the below of this page.
*/
void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int _swap_rb);
void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step,
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int _swap_rb);
cv::Size size, int ncn, int _swap_rb);
void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
ushort *gray, int gray_step,
cv::Size size, int ncn, int _swap_rb);
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int ncn, int _swap_rb);
namespace cv
{
@@ -109,7 +109,7 @@ int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t siz
bool SPngDecoder::readHeader()
{
volatile bool result = false;
bool result = false;
close();
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
@@ -136,40 +136,36 @@ bool SPngDecoder::readHeader()
if (!m_buf.empty() || m_f)
{
struct spng_ihdr ihdr;
int ret = spng_get_ihdr(ctx, &ihdr);
if (ret == SPNG_OK)
if (spng_get_ihdr(ctx, &ihdr) == SPNG_OK)
{
m_width = static_cast<int>(ihdr.width);
m_height = static_cast<int>(ihdr.height);
m_color_type = ihdr.color_type;
m_bit_depth = ihdr.bit_depth;
if (ihdr.bit_depth <= 8 || ihdr.bit_depth == 16)
int num_trans;
switch (ihdr.color_type)
{
int num_trans;
switch (ihdr.color_type)
{
case SPNG_COLOR_TYPE_TRUECOLOR:
case SPNG_COLOR_TYPE_INDEXED:
struct spng_trns trns;
num_trans = !spng_get_trns(ctx, &trns);
if (num_trans > 0)
m_type = CV_8UC4;
else
m_type = CV_8UC3;
break;
case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA:
case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
case SPNG_COLOR_TYPE_TRUECOLOR:
case SPNG_COLOR_TYPE_INDEXED:
struct spng_trns trns;
num_trans = !spng_get_trns(ctx, &trns);
if (num_trans > 0)
m_type = CV_8UC4;
break;
default:
m_type = CV_8UC1;
}
if (ihdr.bit_depth == 16)
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
result = true;
else
m_type = CV_8UC3;
break;
case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA:
case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
m_type = CV_8UC4;
break;
default:
m_type = CV_8UC1;
}
if (ihdr.bit_depth == 16)
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
result = true;
}
}
@@ -178,98 +174,86 @@ bool SPngDecoder::readHeader()
bool SPngDecoder::readData(Mat &img)
{
volatile bool result = false;
bool color = img.channels() > 1;
struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx;
bool result = false;
if (m_ctx && m_width && m_height)
{
int fmt = SPNG_FMT_PNG;
struct spng_ctx* png_ptr = (struct spng_ctx*)m_ctx;
bool color = img.channels() > 1;
int fmt = img.channels() == 4 ? m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8 : SPNG_FMT_PNG;
int decode_flags = img.channels() == 4 ? SPNG_DECODE_TRNS : 0;
struct spng_trns trns;
int have_trns = spng_get_trns((struct spng_ctx *)m_ctx, &trns);
int decode_flags = 0;
if (have_trns == SPNG_OK)
{
decode_flags = SPNG_DECODE_TRNS;
}
if (img.channels() == 4)
{
if (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR ||
m_color_type == SPNG_COLOR_TYPE_INDEXED ||
m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE)
fmt = m_bit_depth == 16 ? SPNG_FMT_GA16 : SPNG_FMT_GA8;
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA)
{
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
}
else
fmt = SPNG_FMT_RGBA8;
}
if (img.channels() == 3)
if (img.type() == CV_8UC3)
{
fmt = SPNG_FMT_RGB8;
if ((m_color_type == SPNG_COLOR_TYPE_GRAYSCALE || m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) &&
m_bit_depth == 16)
fmt = SPNG_FMT_RGB8;
else if (m_bit_depth == 16)
fmt = SPNG_FMT_PNG;
}
else if (img.channels() == 1)
{
if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8)
fmt = SPNG_FMT_G8;
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth == 16)
{
if (img.depth() == CV_8U || img.depth() == CV_8S)
{
fmt = SPNG_FMT_RGB8;
}
else
{
fmt = SPNG_FMT_PNG;
}
}
else if (m_color_type == SPNG_COLOR_TYPE_INDEXED ||
m_color_type == SPNG_COLOR_TYPE_TRUECOLOR)
{
if (img.depth() == CV_8U || img.depth() == CV_8S)
{
fmt = SPNG_FMT_RGB8;
}
else
{
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8;
}
}
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || fmt == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)
{
if (img.depth() == CV_8U || img.depth() == CV_8S)
{
fmt = SPNG_FMT_RGB8;
}
else
{
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
}
}
else
fmt = SPNG_FMT_RGB8;
fmt = img.depth() == CV_16U ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8;
}
if (fmt == SPNG_FMT_PNG && m_bit_depth == 16 && m_color_type >= SPNG_COLOR_TYPE_GRAYSCALE_ALPHA)
{
Mat tmp(m_height, m_width, CV_16UC4);
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
return false;
cvtColor(tmp, img, m_use_rgb ? COLOR_RGBA2RGB : COLOR_RGBA2BGR);
return true;
}
struct spng_ihdr ihdr;
spng_get_ihdr(png_ptr, &ihdr);
size_t image_width, image_size = 0;
int ret = spng_decoded_image_size(png_ptr, fmt, &image_size);
struct spng_ihdr ihdr;
spng_get_ihdr(png_ptr, &ihdr);
if (ret == SPNG_OK)
{
image_width = image_size / m_height;
if (!color && fmt == SPNG_FMT_RGB8 && m_bit_depth == 16 && (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA))
{
Mat tmp(m_height, m_width, CV_16UC4);
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
return false;
spngCvt_BGRA2Gray_16u28u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
img.data, (int)img.step1(), Size(m_width, m_height), 4, 2);
return true;
}
if (!color && ihdr.interlace_method && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA16))
{
if (fmt == SPNG_FMT_RGBA16)
{
Mat tmp(m_height, m_width, CV_16UC4);
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), fmt, 0))
return false;
spngCvt_BGRA2Gray_16u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
reinterpret_cast<ushort*>(img.data), (int)img.step1(), Size(m_width, m_height), 4, 2);
return true;
}
else
{
Mat tmp(m_height, m_width, CV_8UC3);
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, fmt, 0))
return false;
spngCvt_BGRA2Gray_8u_CnC1R(tmp.data, (int)tmp.step1(), img.data, (int)img.step1(), Size(m_width, m_height), 3, 2);
return true;
}
}
if (fmt == SPNG_FMT_PNG && img.elemSize() * m_width / 3 == image_width)
{
Mat tmp(m_height, m_width, CV_16U);
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, SPNG_FMT_PNG, 0))
return false;
cvtColor(tmp, img, COLOR_GRAY2BGR);
return true;
}
ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags);
if (ret == SPNG_OK)
{
@@ -279,88 +263,46 @@ bool SPngDecoder::readData(Mat &img)
// decode image then convert to grayscale
if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16))
{
if (ihdr.interlace_method == 0)
AutoBuffer<unsigned char> buffer;
buffer.allocate(image_width);
if (fmt == SPNG_FMT_RGB8)
{
AutoBuffer<unsigned char> buffer;
buffer.allocate(image_width);
if (fmt == SPNG_FMT_RGB8)
do
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGR2Gray_8u_C3C1R(
buffer.data(),
0,
img.data + row_info.row_num * img.step,
0, Size(m_width, 1), 2);
} while (ret == SPNG_OK);
}
else if (fmt == SPNG_FMT_RGBA8)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGRA2Gray_8u_C4C1R(
buffer.data(),
0,
img.data + row_info.row_num * img.step,
0, Size(m_width, 1), 2);
} while (ret == SPNG_OK);
}
else if (fmt == SPNG_FMT_RGBA16)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGRA2Gray_16u_CnC1R(
reinterpret_cast<const ushort *>(buffer.data()), 0,
reinterpret_cast<ushort *>(img.data + row_info.row_num * img.step),
0, Size(m_width, 1),
4, 2);
} while (ret == SPNG_OK);
}
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 3, 2);
} while (ret == SPNG_OK);
}
else
else if (fmt == SPNG_FMT_RGBA8)
{
AutoBuffer<unsigned char> imageBuffer(image_size);
ret = spng_decode_image(png_ptr, imageBuffer.data(), image_size, fmt, 0);
int step = m_width * img.channels();
if (fmt == SPNG_FMT_RGB8)
do
{
spngCvt_BGR2Gray_8u_C3C1R(
imageBuffer.data(),
step,
img.data,
step, Size(m_width, m_height), 2);
}
else if (fmt == SPNG_FMT_RGBA8)
{
spngCvt_BGRA2Gray_8u_C4C1R(
imageBuffer.data(),
step,
img.data,
step, Size(m_width, m_height), 2);
}
else if (fmt == SPNG_FMT_RGBA16)
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 4, 2);
} while (ret == SPNG_OK);
}
else if (fmt == SPNG_FMT_RGBA16)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
spngCvt_BGRA2Gray_16u_CnC1R(
reinterpret_cast<const ushort *>(imageBuffer.data()), step / 3,
reinterpret_cast<ushort *>(img.data),
step / 3, Size(m_width, m_height),
4, 2);
}
reinterpret_cast<const ushort*>(buffer.data()), 0,
reinterpret_cast<ushort*>(img.data + row_info.row_num * img.step),
0, Size(m_width, 1), 4, 2);
} while (ret == SPNG_OK);
}
}
else if (color)
@@ -383,9 +325,8 @@ bool SPngDecoder::readData(Mat &img)
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0 && !m_use_rgb)
{
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort *>(buffer[row_info.row_num]), 0,
reinterpret_cast<ushort *>(buffer[row_info.row_num]), 0,
Size(m_width, 1));
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
}
} while (ret == SPNG_OK);
if (ihdr.interlace_method && !m_use_rgb)
@@ -414,6 +355,8 @@ bool SPngDecoder::readData(Mat &img)
}
else if (fmt == SPNG_FMT_PNG)
{
AutoBuffer<unsigned char> bufcn4;
bufcn4.allocate(image_width);
do
{
ret = spng_get_row_info(png_ptr, &row_info);
@@ -421,16 +364,17 @@ bool SPngDecoder::readData(Mat &img)
break;
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0 && !m_use_rgb)
{
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort *>(buffer[row_info.row_num]), 0,
reinterpret_cast<ushort *>(buffer[row_info.row_num]), 0, Size(m_width, 1));
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
}
} while (ret == SPNG_OK);
if (ihdr.interlace_method && !m_use_rgb)
{
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort *>(img.data), step,
reinterpret_cast<ushort *>(img.data), step, Size(m_width, m_height));
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(img.data), step,
reinterpret_cast<ushort*>(img.data), step, Size(m_width, m_height));
}
}
else
@@ -454,7 +398,6 @@ bool SPngDecoder::readData(Mat &img)
}
}
else
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
@@ -462,8 +405,8 @@ bool SPngDecoder::readData(Mat &img)
break;
ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width);
} while (ret == SPNG_OK);
}
}
if (ret == SPNG_EOI)
@@ -687,45 +630,32 @@ bool SPngEncoder::write(const Mat &img, const std::vector<int> &params)
}
void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int _swap_rb)
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int ncn, int _swap_rb)
{
int i;
for (; size.height--; gray += gray_step)
{
double cBGR0 = 0.1140441895;
double cBGR2 = 0.2989807129;
if (_swap_rb)
std::swap(cBGR0, cBGR2);
for (i = 0; i < size.width; i++, bgr += 3)
{
int t = static_cast<int>(cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]);
gray[i] = (uchar)t;
}
bgr += bgr_step - size.width * 3;
}
}
void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step,
uchar *gray, int gray_step,
cv::Size size, int _swap_rb)
{
for (; size.height--; gray += gray_step)
{
double cBGR0 = 0.1140441895;
double cBGR1 = 0.5869750977;
double cBGR2 = 0.2989807129;
int cBGR0 = 3737;
int cBGR1 = 19234;
int cBGR2 = 9797;
if (_swap_rb)
std::swap(cBGR0, cBGR2);
for (int i = 0; i < size.width; i++, bgra += 4)
for (i = 0; i < size.width; i++, bgr += ncn)
{
gray[i] = cv::saturate_cast<uchar>(cBGR0 * bgra[0] + cBGR1 * bgra[1] + cBGR2 * bgra[2]);
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
{
gray[i] = (uchar)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]) >> 15);
}
else
{
gray[i] = bgr[0];
}
}
bgra += rgba_step - size.width * 4;
bgr += bgr_step - size.width * ncn;
}
}
@@ -735,15 +665,43 @@ void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
{
for (; size.height--; gray += gray_step)
{
double cBGR0 = 0.1140441895;
double cBGR1 = 0.5869750977;
double cBGR2 = 0.2989807129;
int cBGR0 = 3737;
int cBGR1 = 19234;
int cBGR2 = 9797;
if (_swap_rb)
std::swap(cBGR0, cBGR2);
for (int i = 0; i < size.width; i++, bgr += ncn)
{
gray[i] = (ushort)(cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]);
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
{
gray[i] = (ushort)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15);
}
else
{
gray[i] = bgr[0];
}
}
bgr += bgr_step - size.width * ncn;
}
}
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, int ncn, int _swap_rb)
{
int cBGR0 = 3737;
int cBGR1 = 19234;
int cBGR2 = 9797;
if (_swap_rb)
std::swap(cBGR0, cBGR2);
for (; size.height--; gray += gray_step)
{
for (int i = 0; i < size.width; i++, bgr += ncn)
{
gray[i] = static_cast<uchar>(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8);
}
bgr += bgr_step - size.width * ncn;

View File

@@ -150,19 +150,107 @@ TEST(Imgcodecs_Png, decode_regression27295)
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";
FileStorage fs(xml_filename, FileStorage::READ);
EXPECT_TRUE(fs.isOpened());
// 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);
}
// 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(4, 0), imread(filename, IMREAD_ANYDEPTH), gt_2);
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[] =
@@ -243,23 +331,13 @@ const string pngsuite_files[] =
"f04n2c08",
"f99n0g04",
"g03n0g16",
"g03n2c08",
"g03n3p04",
"g04n0g16",
"g04n2c08",
"g04n3p04",
"g05n0g16",
"g05n2c08",
"g05n3p04",
"g07n0g16",
"g07n2c08",
"g07n3p04",
"g10n0g16",
"g10n2c08",
"g10n3p04",
"g25n0g16",
"g25n2c08",
"g25n3p04",
"oi1n0g16",
"oi1n2c16",
"oi2n0g16",
@@ -333,6 +411,49 @@ const string pngsuite_files[] =
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)