diff --git a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java index 1f5de6a2ef..91066eb93d 100644 --- a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java +++ b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java @@ -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()); diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 3941961b56..20653cf3c1 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -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; diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index 9804c7a8ae..acf2f0d55d 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -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(ihdr.width); m_height = static_cast(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(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(tmp.data), (int)tmp.step1(), + reinterpret_cast(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 buffer; + buffer.allocate(image_width); + if (fmt == SPNG_FMT_RGB8) { - AutoBuffer 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(buffer.data()), 0, - reinterpret_cast(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 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(imageBuffer.data()), step / 3, - reinterpret_cast(img.data), - step / 3, Size(m_width, m_height), - 4, 2); - } + reinterpret_cast(buffer.data()), 0, + reinterpret_cast(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(buffer[row_info.row_num]), 0, - reinterpret_cast(buffer[row_info.row_num]), 0, - Size(m_width, 1)); + icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(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 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(buffer[row_info.row_num]), 0, - reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(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(img.data), step, - reinterpret_cast(img.data), step, Size(m_width, m_height)); + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, + reinterpret_cast(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 ¶ms) } -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(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(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(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8); } bgr += bgr_step - size.width * ncn; diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index f271950a5b..583039202a 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -150,19 +150,107 @@ TEST(Imgcodecs_Png, decode_regression27295) typedef testing::TestWithParam 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 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 Imgcodecs_Png_PngSuite_Corrupted; TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode)