From cc22a73d0fb88925617245bd03ed71b9bf358e9c Mon Sep 17 00:00:00 2001 From: Rachel A Date: Mon, 1 Mar 2021 10:57:22 -0800 Subject: [PATCH] EXR alpha support for 4 channel reading and writing. Issue https://github.com/opencv/opencv/issues/16115. --- modules/imgcodecs/src/grfmt_exr.cpp | 180 +++++++++++++++++++------------ modules/imgcodecs/src/grfmt_exr.hpp | 4 +- modules/imgcodecs/test/test_exr.impl.hpp | 158 ++++++++++++++++++++++++++- 3 files changed, 268 insertions(+), 74 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index 1eceb4f..9667b8c 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -84,12 +84,13 @@ ExrDecoder::ExrDecoder() { m_signature = "\x76\x2f\x31\x01"; m_file = 0; - m_red = m_green = m_blue = 0; + m_red = m_green = m_blue = m_alpha = 0; m_type = ((Imf::PixelType)0); m_iscolor = false; m_bit_depth = 0; m_isfloat = false; m_ischroma = false; + m_hasalpha = false; m_native_depth = false; } @@ -113,7 +114,7 @@ void ExrDecoder::close() int ExrDecoder::type() const { - return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), m_iscolor ? 3 : 1); + return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), ((m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1)); } @@ -141,6 +142,11 @@ bool ExrDecoder::readHeader() m_red = channels.findChannel( "R" ); m_green = channels.findChannel( "G" ); m_blue = channels.findChannel( "B" ); + m_alpha = channels.findChannel( "A" ); + + if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios + m_hasalpha = true; + if( m_red || m_green || m_blue ) { m_iscolor = true; @@ -178,7 +184,8 @@ bool ExrDecoder::readHeader() bool ExrDecoder::readData( Mat& img ) { m_native_depth = CV_MAT_DEPTH(type()) == img.depth(); - bool color = img.channels() > 1; + bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario + bool alphasupported = ( img.channels() % 2 == 0 ); // even number of channels indicates alpha int channels = 0; uchar* data = img.ptr(); size_t step = img.step; @@ -187,18 +194,22 @@ bool ExrDecoder::readData( Mat& img ) bool rgbtogray = ( !m_ischroma && m_iscolor && !color ); bool result = true; FrameBuffer frame; - int xsample[3] = {1, 1, 1}; + const int defaultchannels = 3; + int xsample[defaultchannels] = {1, 1, 1}; char *buffer; - size_t xstep = 0; + CV_Assert(m_type == FLOAT); + const size_t floatsize = sizeof(float); + size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth size_t ystep = 0; - - xstep = m_native_depth ? 4 : 1; + const int channelstoread = ( (m_iscolor && alphasupported) ? 4 : + ( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img + size_t xStride = floatsize * channelstoread; AutoBuffer copy_buffer; if( !justcopy ) { - copy_buffer.allocate(sizeof(float) * m_width * 3); + copy_buffer.allocate(floatsize * m_width * defaultchannels); buffer = copy_buffer.data(); ystep = 0; } @@ -215,49 +226,49 @@ bool ExrDecoder::readData( Mat& img ) if( m_blue ) { frame.insert( "BY", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, - 12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); - xsample[0] = m_blue->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep, + xStride, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); + xsample[0] = m_blue->xSampling; } else { frame.insert( "BY", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep, + xStride, ystep, 1, 1, 0.0 )); } if( m_green ) { frame.insert( "Y", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, - 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); - xsample[1] = m_green->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize, + xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); + xsample[1] = m_green->xSampling; } else { frame.insert( "Y", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize, + xStride, ystep, 1, 1, 0.0 )); } if( m_red ) { frame.insert( "RY", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, - 12, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); - xsample[2] = m_red->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2), + xStride, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); + xsample[2] = m_red->xSampling; } else { frame.insert( "RY", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2), + xStride, ystep, 1, 1, 0.0 )); } } else { frame.insert( "Y", Slice( m_type, - buffer - m_datawindow.min.x * 4 - m_datawindow.min.y * ystep, - 4, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); - xsample[0] = m_green->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep, + xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); + xsample[0] = m_green->xSampling; } } else @@ -265,67 +276,85 @@ bool ExrDecoder::readData( Mat& img ) if( m_blue ) { frame.insert( "B", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, - 12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); - xsample[0] = m_blue->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep, + xStride, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); + xsample[0] = m_blue->xSampling; } else { frame.insert( "B", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep, + xStride, ystep, 1, 1, 0.0 )); } if( m_green ) { frame.insert( "G", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, - 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); - xsample[1] = m_green->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize, + xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); + xsample[1] = m_green->xSampling; } else { frame.insert( "G", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize, + xStride, ystep, 1, 1, 0.0 )); } if( m_red ) { frame.insert( "R", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, - 12, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); - xsample[2] = m_red->ySampling; + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2), + xStride, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); + xsample[2] = m_red->xSampling; } else { frame.insert( "R", Slice( m_type, - buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, - 12, ystep, 1, 1, 0.0 )); + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2), + xStride, ystep, 1, 1, 0.0 )); } } + if( justcopy && m_hasalpha && alphasupported ) + { // alpha preserved only in justcopy scenario where alpha is desired (alphasupported) + // and present in original file (m_hasalpha) + CV_Assert(channelstoread == img.channels()); + int offset = (channelstoread - 1) * floatsize; + frame.insert( "A", Slice( m_type, + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + offset, + xStride, ystep, m_alpha->xSampling, m_alpha->ySampling, 0.0 )); + } + for (FrameBuffer::Iterator it = frame.begin(); it != frame.end(); it++) { channels++; } + CV_Assert(channels == channelstoread); + + if( (channels != channelstoread) || (!justcopy && channels > defaultchannels) ) + { // safety checking what ought to be true here + close(); + return false; + } + m_file->setFrameBuffer( frame ); if( justcopy ) { m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y ); - if( color ) + if( m_iscolor ) { if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSample( data, 3, step / xstep, xsample[0], m_blue->ySampling ); + UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data + xstep, 3, step / xstep, xsample[1], m_green->ySampling ); + UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSample( data + 2 * xstep, 3, step / xstep, xsample[2], m_red->ySampling ); + UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); } else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data, 1, step / xstep, xsample[0], m_green->ySampling ); + UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); if( chromatorgb ) - ChromaToBGR( (float *)data, m_height, step / xstep ); + ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); } else { @@ -347,7 +376,7 @@ bool ExrDecoder::readData( Mat& img ) else { if( chromatorgb ) - ChromaToBGR( (float *)buffer, 1, step ); + ChromaToBGR( (float *)buffer, 1, defaultchannels, step ); if( m_type == FLOAT ) { @@ -372,11 +401,11 @@ bool ExrDecoder::readData( Mat& img ) if( color ) { if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSampleY( data, 3, step / xstep, m_blue->ySampling ); + UpSampleY( data, defaultchannels, step / xstep, m_blue->ySampling ); if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSampleY( data + xstep, 3, step / xstep, m_green->ySampling ); + UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling ); if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSampleY( data + 2 * xstep, 3, step / xstep, m_red->ySampling ); + UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_red->ySampling ); } else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) UpSampleY( data, 1, step / xstep, m_green->ySampling ); @@ -457,7 +486,7 @@ void ExrDecoder::UpSampleY( uchar *data, int xstep, int ystep, int ysample ) /** // algorithm from ImfRgbaYca.cpp */ -void ExrDecoder::ChromaToBGR( float *data, int numlines, int step ) +void ExrDecoder::ChromaToBGR( float *data, int numlines, int xstep, int ystep ) { for( int y = 0; y < numlines; y++ ) { @@ -466,15 +495,15 @@ void ExrDecoder::ChromaToBGR( float *data, int numlines, int step ) double b, Y, r; if( m_type == FLOAT ) { - b = data[y * step + x * 3]; - Y = data[y * step + x * 3 + 1]; - r = data[y * step + x * 3 + 2]; + b = data[y * ystep + x * xstep]; + Y = data[y * ystep + x * xstep + 1]; + r = data[y * ystep + x * xstep + 2]; } else { - b = ((unsigned *)data)[y * step + x * 3]; - Y = ((unsigned *)data)[y * step + x * 3 + 1]; - r = ((unsigned *)data)[y * step + x * 3 + 2]; + b = ((unsigned *)data)[y * ystep + x * xstep]; + Y = ((unsigned *)data)[y * ystep + x * xstep + 1]; + r = ((unsigned *)data)[y * ystep + x * xstep + 2]; } r = (r + 1) * Y; b = (b + 1) * Y; @@ -482,18 +511,18 @@ void ExrDecoder::ChromaToBGR( float *data, int numlines, int step ) if( m_type == FLOAT ) { - data[y * step + x * 3] = (float)b; - data[y * step + x * 3 + 1] = (float)Y; - data[y * step + x * 3 + 2] = (float)r; + data[y * ystep + x * xstep] = (float)b; + data[y * ystep + x * xstep + 1] = (float)Y; + data[y * ystep + x * xstep + 2] = (float)r; } else { int t = cvRound(b); - ((unsigned *)data)[y * step + x * 3 + 0] = (unsigned)MAX(t, 0); + ((unsigned *)data)[y * ystep + x * xstep + 0] = (unsigned)MAX(t, 0); t = cvRound(Y); - ((unsigned *)data)[y * step + x * 3 + 1] = (unsigned)MAX(t, 0); + ((unsigned *)data)[y * ystep + x * xstep + 1] = (unsigned)MAX(t, 0); t = cvRound(r); - ((unsigned *)data)[y * step + x * 3 + 2] = (unsigned)MAX(t, 0); + ((unsigned *)data)[y * ystep + x * xstep + 2] = (unsigned)MAX(t, 0); } } } @@ -571,7 +600,6 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) int depth = img.depth(); CV_Assert( depth == CV_32F ); int channels = img.channels(); - CV_Assert( channels == 3 || channels == 1 ); bool result = false; Header header( width, height ); Imf::PixelType type = FLOAT; @@ -594,7 +622,7 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) } } - if( channels == 3 ) + if( channels == 3 || channels == 4 ) { header.channels().insert( "R", Channel( type ) ); header.channels().insert( "G", Channel( type ) ); @@ -607,6 +635,11 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) //printf("gray\n"); } + if( channels % 2 == 0 ) + { // even number of channels indicates Alpha + header.channels().insert( "A", Channel( type ) ); + } + OutputFile file( m_filename.c_str(), header ); FrameBuffer frame; @@ -629,14 +662,19 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) size = 4; } - if( channels == 3 ) + if( channels == 3 || channels == 4 ) { - frame.insert( "B", Slice( type, buffer, size * 3, bufferstep )); - frame.insert( "G", Slice( type, buffer + size, size * 3, bufferstep )); - frame.insert( "R", Slice( type, buffer + size * 2, size * 3, bufferstep )); + frame.insert( "B", Slice( type, buffer, size * channels, bufferstep )); + frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep )); + frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep )); } else - frame.insert( "Y", Slice( type, buffer, size, bufferstep )); + frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep )); + + if( channels % 2 == 0 ) + { // even channel count indicates Alpha channel + frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep )); + } file.setFrameBuffer( frame ); diff --git a/modules/imgcodecs/src/grfmt_exr.hpp b/modules/imgcodecs/src/grfmt_exr.hpp index ec08028..99acd77 100644 --- a/modules/imgcodecs/src/grfmt_exr.hpp +++ b/modules/imgcodecs/src/grfmt_exr.hpp @@ -81,7 +81,7 @@ protected: void UpSample( uchar *data, int xstep, int ystep, int xsample, int ysample ); void UpSampleX( float *data, int xstep, int xsample ); void UpSampleY( uchar *data, int xstep, int ystep, int ysample ); - void ChromaToBGR( float *data, int numlines, int step ); + void ChromaToBGR( float *data, int numlines, int xstep, int ystep ); void RGBToGray( float *in, float *out ); InputFile *m_file; @@ -91,11 +91,13 @@ protected: const Channel *m_red; const Channel *m_green; const Channel *m_blue; + const Channel *m_alpha; Chromaticities m_chroma; int m_bit_depth; bool m_native_depth; bool m_iscolor; bool m_isfloat; + bool m_hasalpha; private: ExrDecoder(const ExrDecoder &); // copy disabled diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index 1f78a8f..ae5af53 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -7,7 +7,7 @@ namespace opencv_test { namespace { 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"); @@ -31,7 +31,7 @@ TEST(Imgcodecs_EXR, readWrite_32FC1) } 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"); @@ -113,5 +113,159 @@ TEST(Imgcodecs_EXR, readWrite_32FC3_half) 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 behavoir 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 suppported 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()); + + // 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())); +} }} // namespace -- 2.7.4