From 71fe90312101c157702525e10296a4dcd4a15f3f Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 12 Mar 2025 19:06:01 +0100 Subject: [PATCH] Merge pull request #27040 from vrabaud:png_leak Make sure there are enough channels to check for opacity #27040 ### 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 - [ ] 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 --- modules/imgcodecs/src/grfmt_png.cpp | 102 +++++++++++++++------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index dd5ac49a63..ffcc0f29be 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -42,6 +42,8 @@ #include "precomp.hpp" +#include + #ifdef HAVE_PNG /****************************************************************************************\ @@ -328,6 +330,8 @@ bool PngDecoder::readHeader() delay_den = png_get_uint_16(&chunk.p[30]); dop = chunk.p[32]; bop = chunk.p[33]; + if (dop > 2 || bop > 1) + return false; } if (id == id_PLTE || id == id_tRNS) @@ -480,6 +484,11 @@ bool PngDecoder::readData( Mat& img ) { return false; } + // Asking for blend over with no alpha is invalid. + if (bop == 1 && img.channels() != 4) + { + return false; + } memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); return true; @@ -607,6 +616,15 @@ bool PngDecoder::nextPage() { void PngDecoder::compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img) { + const size_t elem_size = img.elemSize(); + if (_bop == 0) { + // Overwrite mode: copy source row directly to destination + for(uint32_t j = 0; j < h; ++j) { + std::memcpy(rows_dst[j + y] + x * elem_size,rows_src[j], w * elem_size); + } + return; + } + int channels = img.channels(); if (img.depth() == CV_16U) cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) { @@ -614,30 +632,24 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect uint16_t* sp = reinterpret_cast(rows_src[j]); uint16_t* dp = reinterpret_cast(rows_dst[j + y]) + x * channels; - if (_bop == 0) { - // Overwrite mode: copy source row directly to destination - memcpy(dp, sp, w * channels * sizeof(uint16_t)); - } - else { - // Blending mode - for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (sp[3] == 65535) { // Fully opaque in 16-bit (max value) - memcpy(dp, sp, channels * sizeof(uint16_t)); + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (channels < 4 || sp[3] == 65535) { // Fully opaque in 16-bit (max value) + memcpy(dp, sp, elem_size); + } + else if (sp[3] != 0) { // Partially transparent + if (dp[3] != 0) { // Both source and destination have alpha + uint32_t u = sp[3] * 65535; // 16-bit max + uint32_t v = (65535 - sp[3]) * dp[3]; + uint32_t al = u + v; + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 65535); // Alpha } - else if (sp[3] != 0) { // Partially transparent - if (dp[3] != 0) { // Both source and destination have alpha - uint32_t u = sp[3] * 65535; // 16-bit max - uint32_t v = (65535 - sp[3]) * dp[3]; - uint32_t al = u + v; - dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red - dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green - dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue - dp[3] = static_cast(al / 65535); // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, channels * sizeof(uint16_t)); - } + else { + // If destination alpha is 0, copy source pixel + memcpy(dp, sp, elem_size); } } } @@ -649,32 +661,26 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect unsigned char* sp = rows_src[j]; unsigned char* dp = rows_dst[j + y] + x * channels; - if (_bop == 0) { - // Overwrite mode: copy source row directly to destination - memcpy(dp, sp, w * channels); - } - else { - // Blending mode - for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (sp[3] == 255) { - // Fully opaque: copy source pixel directly - memcpy(dp, sp, channels); + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (channels < 4 || sp[3] == 255) { + // Fully opaque: copy source pixel directly + memcpy(dp, sp, elem_size); + } + else if (sp[3] != 0) { + // Alpha blending + if (dp[3] != 0) { + int u = sp[3] * 255; + int v = (255 - sp[3]) * dp[3]; + int al = u + v; + dp[0] = (sp[0] * u + dp[0] * v) / al; // Red + dp[1] = (sp[1] * u + dp[1] * v) / al; // Green + dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue + dp[3] = al / 255; // Alpha } - else if (sp[3] != 0) { - // Alpha blending - if (dp[3] != 0) { - int u = sp[3] * 255; - int v = (255 - sp[3]) * dp[3]; - int al = u + v; - dp[0] = (sp[0] * u + dp[0] * v) / al; // Red - dp[1] = (sp[1] * u + dp[1] * v) / al; // Green - dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue - dp[3] = al / 255; // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, channels); - } + else { + // If destination alpha is 0, copy source pixel + memcpy(dp, sp, elem_size); } } }