EXR alpha support for 4 channel reading and writing. Issue https://github.com/opencv...
authorRachel A <aldridge.r.a@gmail.com>
Mon, 1 Mar 2021 18:57:22 +0000 (10:57 -0800)
committerRachel A <aldridge.r.a@gmail.com>
Tue, 2 Mar 2021 19:49:56 +0000 (11:49 -0800)
modules/imgcodecs/src/grfmt_exr.cpp
modules/imgcodecs/src/grfmt_exr.hpp
modules/imgcodecs/test/test_exr.impl.hpp

index 1eceb4f..9667b8c 100644 (file)
@@ -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<char> 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<int>& 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<int>& 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<int>& 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<int>& 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 );
 
index ec08028..99acd77 100644 (file)
@@ -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
index 1f78a8f..ae5af53 100644 (file)
@@ -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