Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imgcodecs: tiff: Support to encode for CV_32S with compression params #23433

Merged
merged 5 commits into from Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 65 additions & 7 deletions modules/imgcodecs/src/grfmt_tiff.cpp
Expand Up @@ -63,6 +63,9 @@ using namespace tiff_dummy_namespace;
namespace cv
{

// to extend cvtColor() to support CV_8S, CV_16S, CV_32S and CV_64F.
static void extend_cvtColor( InputArray _src, OutputArray _dst, int code );

#define CV_TIFF_CHECK_CALL(call) \
if (0 == (call)) { \
CV_LOG_WARNING(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed " #call); \
Expand Down Expand Up @@ -1039,9 +1042,9 @@ bool TiffDecoder::readData( Mat& img )
Rect roi_tile(0, 0, tile_width, tile_height);
Rect roi_img(x, img_y, tile_width, tile_height);
if (!m_hdr && ncn == 3)
cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR);
extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR);
else if (!m_hdr && ncn == 4)
cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA);
extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA);
else
m_tile(roi_tile).copyTo(img(roi_img));
break;
Expand Down Expand Up @@ -1279,13 +1282,17 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
break;
}

case CV_32F:
sample_format = SAMPLEFORMAT_IEEEFP;
/* FALLTHRU */
case CV_32S:
{
bitsPerChannel = 32;
sample_format = SAMPLEFORMAT_INT;
break;
}
case CV_32F:
{
bitsPerChannel = 32;
page_compression = COMPRESSION_NONE;
sample_format = SAMPLEFORMAT_IEEEFP;
break;
}
case CV_64F:
Expand Down Expand Up @@ -1356,13 +1363,13 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect

case 3:
{
cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB);
extend_cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB);
break;
}

case 4:
{
cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA);
extend_cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA);
break;
}

Expand Down Expand Up @@ -1424,6 +1431,57 @@ bool TiffEncoder::write( const Mat& img, const std::vector<int>& params)
return writeLibTiff(img_vec, params);
}

static void extend_cvtColor( InputArray _src, OutputArray _dst, int code )
asmorkalov marked this conversation as resolved.
Show resolved Hide resolved
{
CV_Assert( !_src.empty() );
CV_Assert( _src.dims() == 2 );

// This function extend_cvtColor reorders the src channels with only thg limited condition.
// Otherwise, it calls cvtColor.

const int stype = _src.type();
if(!
(
(
( stype == CV_8SC3 ) || ( stype == CV_8SC4 ) ||
( stype == CV_16SC3 ) || ( stype == CV_16SC4 ) ||
( stype == CV_32SC3 ) || ( stype == CV_32SC4 ) ||
( stype == CV_64FC3 ) || ( stype == CV_64FC4 )
)
&&
(
( code == COLOR_BGR2RGB ) || ( code == COLOR_BGRA2RGBA )
)
)
)
{
cvtColor( _src, _dst, code );
return;
}

Mat src = _src.getMat();

// cv::mixChannels requires the output arrays to be pre-allocated before calling the function.
_dst.create( _src.size(), stype );
Mat dst = _dst.getMat();

// BGR to RGB or BGRA to RGBA
// src[0] -> dst[2]
// src[1] -> dst[1]
// src[2] -> dst[0]
// src[3] -> dst[3] if src has alpha channel.
std::vector<int> fromTo;
fromTo.push_back(0); fromTo.push_back(2);
fromTo.push_back(1); fromTo.push_back(1);
fromTo.push_back(2); fromTo.push_back(0);
if ( code == COLOR_BGRA2RGBA )
{
fromTo.push_back(3); fromTo.push_back(3);
}

cv::mixChannels( src, dst, fromTo );
}

} // namespace

#endif
95 changes: 95 additions & 0 deletions modules/imgcodecs/test/test_tiff.cpp
Expand Up @@ -14,6 +14,38 @@ namespace opencv_test { namespace {
#define int64 int64_hack_
#include "tiff.h"

// Re-define Mat type as enum for showing on Google Test.
enum CV_ddtCn{
_CV_8UC1 = CV_8UC1, _CV_8UC3 = CV_8UC3, _CV_8UC4 = CV_8UC4,
_CV_8SC1 = CV_8SC1, _CV_8SC3 = CV_8SC3, _CV_8SC4 = CV_8SC4,
_CV_16UC1 = CV_16UC1, _CV_16UC3 = CV_16UC3, _CV_16UC4 = CV_16UC4,
_CV_16SC1 = CV_16SC1, _CV_16SC3 = CV_16SC3, _CV_16SC4 = CV_16SC4,
_CV_32SC1 = CV_32SC1, _CV_32SC3 = CV_32SC3, _CV_32SC4 = CV_32SC4,
_CV_16FC1 = CV_16FC1, _CV_16FC3 = CV_16FC3, _CV_16FC4 = CV_16FC4,
_CV_32FC1 = CV_32FC1, _CV_32FC3 = CV_32FC3, _CV_32FC4 = CV_32FC4,
_CV_64FC1 = CV_64FC1, _CV_64FC3 = CV_64FC3, _CV_64FC4 = CV_64FC4,
};

static inline
void PrintTo(const CV_ddtCn& val, std::ostream* os)
{
const int val_type = static_cast<int>(val);

switch ( CV_MAT_DEPTH(val_type) )
{
case CV_8U : *os << "CV_8U" ; break;
case CV_16U : *os << "CV_16U" ; break;
case CV_8S : *os << "CV_8S" ; break;
case CV_16S : *os << "CV_16S" ; break;
case CV_32S : *os << "CV_32S" ; break;
case CV_16F : *os << "CV_16F" ; break;
case CV_32F : *os << "CV_32F" ; break;
case CV_64F : *os << "CV_64F" ; break;
default : *os << "CV_???" ; break;
}
*os << "C" << CV_MAT_CN(val_type);
}

#ifdef __ANDROID__
// Test disabled as it uses a lot of memory.
// It is killed with SIGKILL by out of memory killer.
Expand Down Expand Up @@ -840,6 +872,69 @@ TEST(Imgcodecs_Tiff, readWrite_predictor)
}
}

// See https://github.com/opencv/opencv/issues/23416

typedef std::pair<CV_ddtCn,bool> Imgcodes_Tiff_TypeAndComp;
typedef testing::TestWithParam< Imgcodes_Tiff_TypeAndComp > Imgcodecs_Tiff_Types;

TEST_P(Imgcodecs_Tiff_Types, readWrite_alltypes)
{
const int mat_types = static_cast<int>(get<0>(GetParam()));
const bool isCompAvailable = get<1>(GetParam());

// Create a test image.
const Mat src = cv::Mat::zeros( 120, 160, mat_types );
{
// Add noise to test compression.
cv::Mat roi = cv::Mat(src, cv::Rect(0, 0, src.cols, src.rows/2));
cv::randu(roi, cv::Scalar(0), cv::Scalar(256));
}

// Try to encode/decode the test image with LZW compression.
std::vector<uchar> bufLZW;
{
std::vector<int> params;
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(COMPRESSION_LZW);
ASSERT_NO_THROW(cv::imencode(".tiff", src, bufLZW, params));

Mat dstLZW;
ASSERT_NO_THROW(cv::imdecode( bufLZW, IMREAD_UNCHANGED, &dstLZW));
ASSERT_EQ(dstLZW.type(), src.type());
ASSERT_EQ(dstLZW.size(), src.size());
ASSERT_LE(cvtest::norm(dstLZW, src, NORM_INF | NORM_RELATIVE), 1e-3);
}

// Try to encode/decode the test image with RAW.
std::vector<uchar> bufRAW;
{
std::vector<int> params;
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(COMPRESSION_NONE);
ASSERT_NO_THROW(cv::imencode(".tiff", src, bufRAW, params));

Mat dstRAW;
ASSERT_NO_THROW(cv::imdecode( bufRAW, IMREAD_UNCHANGED, &dstRAW));
ASSERT_EQ(dstRAW.type(), src.type());
ASSERT_EQ(dstRAW.size(), src.size());
ASSERT_LE(cvtest::norm(dstRAW, src, NORM_INF | NORM_RELATIVE), 1e-3);
}

// Compare LZW and RAW streams.
EXPECT_EQ(bufLZW == bufRAW, !isCompAvailable);
}

Imgcodes_Tiff_TypeAndComp all_types[] = {
{ _CV_8UC1, true }, { _CV_8UC3, true }, { _CV_8UC4, true },
{ _CV_8SC1, true }, { _CV_8SC3, true }, { _CV_8SC4, true },
{ _CV_16UC1, true }, { _CV_16UC3, true }, { _CV_16UC4, true },
{ _CV_16SC1, true }, { _CV_16SC3, true }, { _CV_16SC4, true },
{ _CV_32SC1, true }, { _CV_32SC3, true }, { _CV_32SC4, true },
{ _CV_32FC1, false }, { _CV_32FC3, false }, { _CV_32FC4, false }, // No compression
{ _CV_64FC1, false }, { _CV_64FC3, false }, { _CV_64FC4, false } // No compression
};

INSTANTIATE_TEST_CASE_P(AllTypes, Imgcodecs_Tiff_Types, testing::ValuesIn(all_types));

//==================================================================================================

Expand Down