LCOV - code coverage report
Current view: top level - frmts/exr - exrdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 690 951 72.6 %
Date: 2024-05-14 13:00:50 Functions: 45 57 78.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  EXR read/write Driver
       4             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       5             :  *
       6             :  ******************************************************************************
       7             :  * Copyright (c) 2020, Even Rouault <even.rouault at spatialys.com>
       8             :  *
       9             :  * Permission is hereby granted, free of charge, to any person obtaining a
      10             :  * copy of this software and associated documentation files (the "Software"),
      11             :  * to deal in the Software without restriction, including without limitation
      12             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      13             :  * and/or sell copies of the Software, and to permit persons to whom the
      14             :  * Software is furnished to do so, subject to the following conditions:
      15             :  *
      16             :  * The above copyright notice and this permission notice shall be included
      17             :  * in all copies or substantial portions of the Software.
      18             :  *
      19             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      20             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      21             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      22             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      23             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      24             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      25             :  * DEALINGS IN THE SOFTWARE.
      26             :  ****************************************************************************/
      27             : 
      28             : #include "gdal_pam.h"
      29             : #include "ogr_spatialref.h"
      30             : 
      31             : #include <algorithm>
      32             : #include <limits>
      33             : #include <mutex>
      34             : 
      35             : #include "openexr_headers.h"
      36             : #include "exrdrivercore.h"
      37             : 
      38             : using namespace OPENEXR_IMF_NAMESPACE;
      39             : using namespace IMATH_NAMESPACE;
      40             : 
      41             : extern "C" CPL_DLL void GDALRegister_EXR();
      42             : 
      43             : static const char *const apszCompressions[] = {
      44             :     "NONE", "RLE", "ZIPS", "ZIP", "PIZ", "PXR24", "B44", "B44A", "DWAA", "DWAB",
      45             : };
      46             : 
      47             : /************************************************************************/
      48             : /*                       GDALEXRDataset()                               */
      49             : /************************************************************************/
      50             : 
      51         182 : class GDALEXRDataset final : public GDALPamDataset
      52             : {
      53             :     friend class GDALEXRRasterBand;
      54             :     friend class GDALEXRPreviewRasterBand;
      55             :     friend class GDALEXRRGBARasterBand;
      56             : 
      57             :     // Keep stream before others, so that it is destroyed last
      58             :     std::unique_ptr<IStream> m_pIStream{};
      59             : 
      60             :     std::unique_ptr<TiledInputPart> m_pTiledIP{};
      61             :     std::unique_ptr<InputPart> m_pIP{};
      62             : 
      63             :     std::unique_ptr<MultiPartInputFile> m_pMPIF{};
      64             :     std::unique_ptr<RgbaInputFile> m_pRGBAIF{};
      65             : 
      66             :     std::vector<Rgba> m_rgbaBuffer{};
      67             :     int m_nRGBABufferLine = -1;
      68             :     int m_iPart = 0;
      69             :     int m_nDWMinX = 0;
      70             :     int m_nDWMinY = 0;
      71             :     GDALEXRDataset *m_poParent = nullptr;
      72             :     int m_iLevel = 0;
      73             :     std::vector<std::unique_ptr<GDALEXRDataset>> m_apoOvrDS{};
      74             :     OGRSpatialReference m_oSRS{};
      75             :     double m_adfGT[6] = {0, 1, 0, 0, 0, 1};
      76             :     bool m_bHasGT = false;
      77             : 
      78             :   public:
      79          91 :     GDALEXRDataset() = default;
      80             :     ~GDALEXRDataset();
      81             : 
      82             :     const OGRSpatialReference *GetSpatialRef() const override;
      83             :     CPLErr GetGeoTransform(double *adfGT) override;
      84             : 
      85             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
      86             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
      87             :                                int nBandsIn, GDALDataType eType,
      88             :                                char **papszOptions);
      89             :     static GDALDataset *CreateCopy(const char *pszFilename,
      90             :                                    GDALDataset *poSrcDS, int bStrict,
      91             :                                    char **papszOptions,
      92             :                                    GDALProgressFunc pfnProgress,
      93             :                                    void *pProgressData);
      94             : };
      95             : 
      96             : /************************************************************************/
      97             : /*                       GDALEXRRasterBand()                            */
      98             : /************************************************************************/
      99             : 
     100             : class GDALEXRRasterBand final : public GDALPamRasterBand
     101             : {
     102             :     friend class GDALEXRDataset;
     103             : 
     104             :     GDALColorInterp m_eInterp = GCI_Undefined;
     105             :     std::string m_osChannelName;
     106             : 
     107             :   protected:
     108             :     CPLErr IReadBlock(int, int, void *) override;
     109             : 
     110             :   public:
     111             :     GDALEXRRasterBand(GDALEXRDataset *poDSIn, int nBandIn,
     112             :                       const std::string &channelName, PixelType pixelType,
     113             :                       int nBlockXSizeIn, int nBlockYSizeIn);
     114             : 
     115           0 :     GDALColorInterp GetColorInterpretation() override
     116             :     {
     117           0 :         return m_eInterp;
     118             :     }
     119             : 
     120             :     int GetOverviewCount() override;
     121             :     GDALRasterBand *GetOverview(int) override;
     122             : };
     123             : 
     124             : /************************************************************************/
     125             : /*                         GDALEXRRasterBand()                          */
     126             : /************************************************************************/
     127             : 
     128         148 : GDALEXRRasterBand::GDALEXRRasterBand(GDALEXRDataset *poDSIn, int nBandIn,
     129             :                                      const std::string &channelName,
     130             :                                      PixelType pixelType, int nBlockXSizeIn,
     131         148 :                                      int nBlockYSizeIn)
     132         148 :     : m_osChannelName(channelName)
     133             : {
     134         148 :     poDS = poDSIn;
     135         148 :     nBand = nBandIn;
     136         148 :     nRasterXSize = poDSIn->GetRasterXSize();
     137         148 :     nRasterYSize = poDSIn->GetRasterYSize();
     138         148 :     nBlockXSize = nBlockXSizeIn;
     139         148 :     nBlockYSize = nBlockYSizeIn;
     140         148 :     eDataType = (pixelType == UINT) ? GDT_UInt32 : GDT_Float32;
     141         148 : }
     142             : 
     143             : /************************************************************************/
     144             : /*                          IReadBlock()                                */
     145             : /************************************************************************/
     146             : 
     147         184 : CPLErr GDALEXRRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
     148             :                                      void *pImage)
     149             : {
     150         184 :     auto poGDS = cpl::down_cast<GDALEXRDataset *>(poDS);
     151             :     try
     152             :     {
     153         184 :         FrameBuffer fb;
     154         184 :         const size_t sizeOfElt = sizeof(float);  // sizeof(uint32) as well
     155             :         const auto slice =
     156         184 :             Slice(eDataType == GDT_Float32 ? FLOAT : UINT,
     157         184 :                   static_cast<char *>(pImage) -
     158         184 :                       (poGDS->m_nDWMinX + nBlockXOff * nBlockXSize +
     159         184 :                        static_cast<size_t>(poGDS->m_nDWMinY +
     160         184 :                                            nBlockYOff * nBlockYSize) *
     161         184 :                            nBlockXSize) *
     162             :                           sizeOfElt,
     163         184 :                   sizeOfElt, sizeOfElt * nBlockXSize);
     164         184 :         fb.insert(m_osChannelName, slice);
     165             : 
     166         184 :         if (poGDS->m_pIP)
     167             :         {
     168           0 :             poGDS->m_pIP->setFrameBuffer(fb);
     169           0 :             poGDS->m_pIP->readPixels(poGDS->m_nDWMinY + nBlockYOff);
     170             :         }
     171             :         else
     172             :         {
     173         184 :             auto tiledIP = poGDS->m_poParent
     174         184 :                                ? poGDS->m_poParent->m_pTiledIP.get()
     175         183 :                                : poGDS->m_pTiledIP.get();
     176         184 :             CPLAssert(tiledIP);
     177         184 :             tiledIP->setFrameBuffer(fb);
     178         184 :             tiledIP->readTile(nBlockXOff, nBlockYOff, poGDS->m_iLevel);
     179             :         }
     180         184 :         return CE_None;
     181             :     }
     182           0 :     catch (const std::exception &e)
     183             :     {
     184           0 :         if (strstr(e.what(), "is missing"))
     185             :         {
     186           0 :             CPLDebug("EXR", "%s", e.what());
     187           0 :             memset(pImage, 0,
     188           0 :                    static_cast<size_t>(nBlockXSize) * nBlockYSize *
     189           0 :                        GDALGetDataTypeSizeBytes(eDataType));
     190           0 :             return CE_None;
     191             :         }
     192           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
     193             :     }
     194             : 
     195           0 :     return CE_Failure;
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                          GetOverviewCount()                          */
     200             : /************************************************************************/
     201             : 
     202           3 : int GDALEXRRasterBand::GetOverviewCount()
     203             : {
     204           3 :     auto poGDS = cpl::down_cast<GDALEXRDataset *>(poDS);
     205           3 :     return static_cast<int>(poGDS->m_apoOvrDS.size());
     206             : }
     207             : 
     208             : /************************************************************************/
     209             : /*                            GetOverview()                             */
     210             : /************************************************************************/
     211             : 
     212           3 : GDALRasterBand *GDALEXRRasterBand::GetOverview(int iOvr)
     213             : {
     214           3 :     if (iOvr < 0 || iOvr >= GetOverviewCount())
     215           2 :         return nullptr;
     216           1 :     auto poGDS = cpl::down_cast<GDALEXRDataset *>(poDS);
     217           1 :     return poGDS->m_apoOvrDS[iOvr]->GetRasterBand(nBand);
     218             : }
     219             : 
     220             : /************************************************************************/
     221             : /*                      GDALEXRPreviewRasterBand                        */
     222             : /************************************************************************/
     223             : 
     224             : class GDALEXRPreviewRasterBand final : public GDALPamRasterBand
     225             : {
     226             :     friend class GDALEXRDataset;
     227             : 
     228             :   protected:
     229             :     CPLErr IReadBlock(int, int, void *) override;
     230             : 
     231           0 :     GDALColorInterp GetColorInterpretation() override
     232             :     {
     233           0 :         return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
     234             :     }
     235             : 
     236             :   public:
     237             :     GDALEXRPreviewRasterBand(GDALEXRDataset *poDSIn, int nBandIn);
     238             : };
     239             : 
     240             : /************************************************************************/
     241             : /*                      GDALEXRPreviewRasterBand()                      */
     242             : /************************************************************************/
     243             : 
     244           4 : GDALEXRPreviewRasterBand::GDALEXRPreviewRasterBand(GDALEXRDataset *poDSIn,
     245           4 :                                                    int nBandIn)
     246             : {
     247           4 :     poDS = poDSIn;
     248           4 :     nBand = nBandIn;
     249           4 :     nRasterXSize = poDSIn->GetRasterXSize();
     250           4 :     nRasterYSize = poDSIn->GetRasterYSize();
     251           4 :     nBlockXSize = nRasterXSize;
     252           4 :     nBlockYSize = 1;
     253           4 :     eDataType = GDT_Byte;
     254           4 : }
     255             : 
     256             : /************************************************************************/
     257             : /*                          IReadBlock()                                */
     258             : /************************************************************************/
     259             : 
     260         200 : CPLErr GDALEXRPreviewRasterBand::IReadBlock(int, int nBlockYOff, void *pImage)
     261             : {
     262         200 :     auto poGDS = cpl::down_cast<GDALEXRDataset *>(poDS);
     263             :     try
     264             :     {
     265         200 :         const auto &header = poGDS->m_pMPIF->header(poGDS->m_iPart);
     266         200 :         const auto &preview = header.previewImage();
     267         400 :         GDALCopyWords(reinterpret_cast<const GByte *>(
     268         200 :                           preview.pixels() + nBlockYOff * nRasterXSize) +
     269         200 :                           nBand - 1,
     270             :                       GDT_Byte, 4, pImage, GDT_Byte, 1, nRasterXSize);
     271         200 :         return CE_None;
     272             :     }
     273           0 :     catch (const std::exception &e)
     274             :     {
     275           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
     276             :     }
     277             : 
     278           0 :     return CE_Failure;
     279             : }
     280             : 
     281             : /************************************************************************/
     282             : /*                      GDALEXRRGBARasterBand                        */
     283             : /************************************************************************/
     284             : 
     285             : class GDALEXRRGBARasterBand final : public GDALPamRasterBand
     286             : {
     287             :     friend class GDALEXRDataset;
     288             : 
     289             :   protected:
     290             :     CPLErr IReadBlock(int, int, void *) override;
     291             : 
     292           0 :     GDALColorInterp GetColorInterpretation() override
     293             :     {
     294           0 :         return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
     295             :     }
     296             : 
     297             :   public:
     298             :     GDALEXRRGBARasterBand(GDALEXRDataset *poDSIn, int nBandIn);
     299             : };
     300             : 
     301             : /************************************************************************/
     302             : /*                      GDALEXRRGBARasterBand()                      */
     303             : /************************************************************************/
     304             : 
     305           0 : GDALEXRRGBARasterBand::GDALEXRRGBARasterBand(GDALEXRDataset *poDSIn,
     306           0 :                                              int nBandIn)
     307             : {
     308           0 :     poDS = poDSIn;
     309           0 :     nBand = nBandIn;
     310           0 :     nRasterXSize = poDSIn->GetRasterXSize();
     311           0 :     nRasterYSize = poDSIn->GetRasterYSize();
     312           0 :     nBlockXSize = nRasterXSize;
     313           0 :     nBlockYSize = 1;
     314           0 :     eDataType = GDT_Float32;
     315           0 : }
     316             : 
     317             : /************************************************************************/
     318             : /*                          IReadBlock()                                */
     319             : /************************************************************************/
     320             : 
     321           0 : CPLErr GDALEXRRGBARasterBand::IReadBlock(int, int nBlockYOff, void *pImage)
     322             : {
     323           0 :     auto poGDS = cpl::down_cast<GDALEXRDataset *>(poDS);
     324             :     try
     325             :     {
     326           0 :         if (nBlockYOff != poGDS->m_nRGBABufferLine)
     327             :         {
     328           0 :             poGDS->m_rgbaBuffer.resize(nRasterXSize);
     329           0 :             poGDS->m_pRGBAIF->setFrameBuffer(
     330           0 :                 poGDS->m_rgbaBuffer.data() -
     331           0 :                     ((poGDS->m_nDWMinY + nBlockYOff) *
     332           0 :                          static_cast<size_t>(nRasterXSize) +
     333           0 :                      poGDS->m_nDWMinX),
     334           0 :                 1, nRasterXSize);
     335           0 :             poGDS->m_pRGBAIF->readPixels(poGDS->m_nDWMinY + nBlockYOff);
     336             :         }
     337           0 :         if (nBand == 1)
     338             :         {
     339           0 :             for (int i = 0; i < nRasterXSize; i++)
     340             :             {
     341           0 :                 static_cast<float *>(pImage)[i] = poGDS->m_rgbaBuffer[i].r;
     342             :             }
     343             :         }
     344           0 :         else if (nBand == 2)
     345             :         {
     346           0 :             for (int i = 0; i < nRasterXSize; i++)
     347             :             {
     348           0 :                 static_cast<float *>(pImage)[i] = poGDS->m_rgbaBuffer[i].g;
     349             :             }
     350             :         }
     351           0 :         else if (nBand == 3)
     352             :         {
     353           0 :             for (int i = 0; i < nRasterXSize; i++)
     354             :             {
     355           0 :                 static_cast<float *>(pImage)[i] = poGDS->m_rgbaBuffer[i].b;
     356             :             }
     357             :         }
     358             : #ifdef unused
     359             :         else
     360             :         {
     361             :             for (int i = 0; i < nRasterXSize; i++)
     362             :             {
     363             :                 static_cast<float *>(pImage)[i] = poGDS->m_rgbaBuffer[i].a;
     364             :             }
     365             :         }
     366             : #endif
     367           0 :         poGDS->m_nRGBABufferLine = nBlockYOff;
     368           0 :         return CE_None;
     369             :     }
     370           0 :     catch (const std::exception &e)
     371             :     {
     372           0 :         if (strstr(e.what(), "is missing"))
     373             :         {
     374           0 :             CPLDebug("EXR", "%s", e.what());
     375           0 :             memset(pImage, 0,
     376           0 :                    static_cast<size_t>(nBlockXSize) * nBlockYSize *
     377           0 :                        GDALGetDataTypeSizeBytes(eDataType));
     378           0 :             return CE_None;
     379             :         }
     380           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
     381             :     }
     382             : 
     383           0 :     return CE_Failure;
     384             : }
     385             : 
     386             : /************************************************************************/
     387             : /*                         ~GDALEXRDataset()                            */
     388             : /************************************************************************/
     389             : 
     390             : GDALEXRDataset::~GDALEXRDataset() = default;
     391             : 
     392             : /************************************************************************/
     393             : /*                          GetSpatialRef()                             */
     394             : /************************************************************************/
     395             : 
     396           0 : const OGRSpatialReference *GDALEXRDataset::GetSpatialRef() const
     397             : {
     398           0 :     const auto *poPamSRS = GDALPamDataset::GetSpatialRef();
     399           0 :     if (poPamSRS)
     400           0 :         return poPamSRS;
     401           0 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     402             : }
     403             : 
     404             : /************************************************************************/
     405             : /*                         GetGeoTransform()                            */
     406             : /************************************************************************/
     407             : 
     408           0 : CPLErr GDALEXRDataset::GetGeoTransform(double *adfGT)
     409             : {
     410           0 :     if (GDALPamDataset::GetGeoTransform(adfGT) == CE_None)
     411             :     {
     412           0 :         return CE_None;
     413             :     }
     414           0 :     memcpy(adfGT, m_adfGT, 6 * sizeof(double));
     415           0 :     return m_bHasGT ? CE_None : CE_Failure;
     416             : }
     417             : 
     418             : /************************************************************************/
     419             : /*                           GDALEXRIOStream                            */
     420             : /************************************************************************/
     421             : 
     422             : class GDALEXRIOStreamException final : public std::exception
     423             : {
     424             :     std::string m_msg;
     425             : 
     426             :   public:
     427          41 :     explicit GDALEXRIOStreamException(const std::string &msg) : m_msg(msg)
     428             :     {
     429          41 :     }
     430             : 
     431          10 :     const char *what() const noexcept override
     432             :     {
     433          10 :         return m_msg.c_str();
     434             :     }
     435             : };
     436             : 
     437             : #if OPENEXR_VERSION_MAJOR < 3
     438             : typedef Int64 IoInt64Type;
     439             : #else
     440             : typedef uint64_t IoInt64Type;
     441             : #endif
     442             : 
     443             : class GDALEXRIOStream final : public IStream, public OStream
     444             : {
     445             :   public:
     446         168 :     GDALEXRIOStream(VSILFILE *fp, const char *filename)
     447         168 :         : IStream(filename), OStream(filename), m_fp(fp)
     448             :     {
     449         168 :     }
     450             : 
     451         295 :     ~GDALEXRIOStream()
     452         168 :     {
     453         168 :         VSIFCloseL(m_fp);
     454         295 :     }
     455             : 
     456             :     virtual bool read(char c[/*n*/], int n) override;
     457             :     virtual void write(const char c[/*n*/], int n) override;
     458             :     virtual IoInt64Type tellg() override;
     459             : 
     460         310 :     virtual IoInt64Type tellp() override
     461             :     {
     462         310 :         return tellg();
     463             :     }
     464             : 
     465             :     virtual void seekg(IoInt64Type pos) override;
     466             : 
     467         136 :     virtual void seekp(IoInt64Type pos) override
     468             :     {
     469         136 :         return seekg(pos);
     470             :     }
     471             : 
     472             :   private:
     473             :     VSILFILE *m_fp;
     474             : };
     475             : 
     476      114977 : bool GDALEXRIOStream::read(char c[/*n*/], int n)
     477             : {
     478      114977 :     if (static_cast<int>(VSIFReadL(c, 1, n, m_fp)) != n)
     479             :     {
     480          31 :         if (VSIFEofL(m_fp))
     481             :         {
     482             :             throw GDALEXRIOStreamException(
     483          31 :                 CPLSPrintf("Unexpected end of file. Cannot read %d bytes", n));
     484             :         }
     485             :         else
     486             :         {
     487             :             throw GDALEXRIOStreamException(
     488           0 :                 CPLSPrintf("cannot read %d bytes", n));
     489             :         }
     490             :     }
     491      114946 :     return VSIFEofL(m_fp) != 0;
     492             : }
     493             : 
     494       20116 : void GDALEXRIOStream::write(const char c[/*n*/], int n)
     495             : {
     496       20116 :     if (static_cast<int>(VSIFWriteL(c, 1, n, m_fp)) != n)
     497             :     {
     498          10 :         throw GDALEXRIOStreamException(CPLSPrintf("cannot write %d bytes", n));
     499             :     }
     500       20106 : }
     501             : 
     502         430 : IoInt64Type GDALEXRIOStream::tellg()
     503             : {
     504         430 :     return static_cast<IoInt64Type>(VSIFTellL(m_fp));
     505             : }
     506             : 
     507         172 : void GDALEXRIOStream::seekg(IoInt64Type pos)
     508             : {
     509         172 :     VSIFSeekL(m_fp, static_cast<vsi_l_offset>(pos), SEEK_SET);
     510         172 : }
     511             : 
     512             : /************************************************************************/
     513             : /*                           setNumThreads()                            */
     514             : /************************************************************************/
     515             : 
     516          44 : static void setNumThreads()
     517             : {
     518             :     static std::mutex mutex;
     519          88 :     std::lock_guard<std::mutex> oLock(mutex);
     520             :     static bool bSet = false;
     521          44 :     if (!bSet)
     522             :     {
     523           3 :         bSet = true;
     524           3 :         setGlobalThreadCount(CPLGetNumCPUs());
     525             :     }
     526          44 : }
     527             : 
     528             : /************************************************************************/
     529             : /*                               Open()                                 */
     530             : /************************************************************************/
     531             : 
     532          90 : GDALDataset *GDALEXRDataset::Open(GDALOpenInfo *poOpenInfo)
     533             : {
     534          90 :     if (!EXRDriverIdentify(poOpenInfo))
     535           0 :         return nullptr;
     536          90 :     if (poOpenInfo->eAccess == GA_Update)
     537             :     {
     538           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     539             :                  "Update of existing EXR file not supported");
     540           0 :         return nullptr;
     541             :     }
     542             : 
     543         180 :     CPLString osFilename(poOpenInfo->pszFilename);
     544          90 :     int iPart = 0;
     545          90 :     bool bIsPreview = false;
     546             :     VSILFILE *fp;
     547          90 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "EXR:"))
     548             :     {
     549           1 :         bIsPreview = STARTS_WITH_CI(poOpenInfo->pszFilename, "EXR:PREVIEW:");
     550           1 :         const char *pszPartPos =
     551           1 :             bIsPreview ? poOpenInfo->pszFilename + strlen("EXR:PREVIEW:")
     552           0 :                        : poOpenInfo->pszFilename + strlen("EXR:");
     553           1 :         const char *pszNextColumn = strchr(pszPartPos, ':');
     554           1 :         if (pszNextColumn == nullptr)
     555           0 :             return nullptr;
     556           1 :         iPart = atoi(pszPartPos);
     557           1 :         if (iPart <= 0)
     558           0 :             return nullptr;
     559           1 :         osFilename = pszNextColumn + 1;
     560           1 :         fp = VSIFOpenL(osFilename, "rb");
     561           1 :         if (fp == nullptr)
     562           0 :             return nullptr;
     563             :     }
     564             :     else
     565             :     {
     566          89 :         fp = poOpenInfo->fpL;
     567          89 :         poOpenInfo->fpL = nullptr;
     568             :     }
     569             : 
     570             :     try
     571             :     {
     572         180 :         auto poDS = std::make_unique<GDALEXRDataset>();
     573          90 :         poDS->m_pIStream.reset(new GDALEXRIOStream(fp, osFilename));
     574          90 :         poDS->m_pMPIF.reset(new MultiPartInputFile(*poDS->m_pIStream));
     575          90 :         if (iPart > 0 && iPart > poDS->m_pMPIF->parts())
     576           0 :             return nullptr;
     577             : 
     578          90 :         if (iPart > 0 || poDS->m_pMPIF->parts() == 1)
     579             :         {
     580          90 :             iPart = iPart > 0 ? iPart - 1 : 0;
     581          90 :             poDS->m_iPart = iPart;
     582             : 
     583          90 :             const auto &header = poDS->m_pMPIF->header(iPart);
     584          90 :             if (bIsPreview)
     585             :             {
     586           1 :                 if (!header.hasPreviewImage())
     587           0 :                     return nullptr;
     588           5 :                 for (int i = 1; i <= 4; i++)
     589             :                 {
     590           4 :                     const auto &preview = header.previewImage();
     591           4 :                     poDS->nRasterXSize = preview.width();
     592           4 :                     poDS->nRasterYSize = preview.height();
     593           8 :                     poDS->SetBand(i,
     594           4 :                                   new GDALEXRPreviewRasterBand(poDS.get(), i));
     595             :                 }
     596           1 :                 return poDS.release();
     597             :             }
     598             : 
     599          89 :             const auto &dataWindow = header.dataWindow();
     600          89 :             poDS->m_nDWMinX = dataWindow.min.x;
     601          89 :             poDS->m_nDWMinY = dataWindow.min.y;
     602          89 :             poDS->nRasterXSize = 1 + dataWindow.max.x - dataWindow.min.x;
     603          89 :             poDS->nRasterYSize = 1 + dataWindow.max.y - dataWindow.min.y;
     604          89 :             const auto &channels = header.channels();
     605          89 :             int i = 0;
     606          89 :             bool BGR = true;
     607          89 :             bool ABGR = true;
     608          89 :             bool BYRYY = true;
     609          89 :             PixelType samePixelType = NUM_PIXELTYPES;
     610         234 :             for (auto iter = channels.begin(); iter != channels.end();
     611         145 :                  ++iter, ++i)
     612             :             {
     613         145 :                 const Channel &channel = iter.channel();
     614         290 :                 const std::string name(iter.name());
     615         145 :                 if (i == 0)
     616          89 :                     samePixelType = channel.type;
     617          56 :                 else if (samePixelType != channel.type)
     618             :                 {
     619           0 :                     ABGR = false;
     620           0 :                     BGR = false;
     621             :                 }
     622             : 
     623         145 :                 if (i == 0 && name != "B")
     624          84 :                     BGR = false;
     625          61 :                 else if (i == 1 && name != "G")
     626          21 :                     BGR = false;
     627          40 :                 else if (i == 2 && name != "R")
     628          19 :                     BGR = false;
     629             : 
     630         145 :                 if (i == 0 && name != "A")
     631          89 :                     ABGR = false;
     632          56 :                 else if (i == 1 && name != "B")
     633          26 :                     ABGR = false;
     634          30 :                 else if (i == 2 && name != "G")
     635          24 :                     ABGR = false;
     636           6 :                 else if (i == 3 && name != "R")
     637           4 :                     ABGR = false;
     638             : 
     639         145 :                 if (i == 0 && name != "BY")
     640          89 :                     BYRYY = false;
     641          56 :                 else if (i == 1 && name != "RY")
     642          26 :                     BYRYY = false;
     643          30 :                 else if (i == 2 && name != "Y")
     644          24 :                     BYRYY = false;
     645             :             }
     646          89 :             BGR &= (i == 3);
     647          89 :             ABGR &= (i == 4);
     648          89 :             BYRYY &= iPart == 0 && (i == 3);
     649          89 :             int nBlockXSize = poDS->nRasterXSize;
     650          89 :             int nBlockYSize = 1;
     651          89 :             if (header.hasTileDescription())
     652             :             {
     653          89 :                 const auto &tileDesc = header.tileDescription();
     654          89 :                 nBlockXSize = tileDesc.xSize;
     655          89 :                 nBlockYSize = tileDesc.ySize;
     656         178 :                 poDS->m_pTiledIP.reset(
     657          89 :                     new TiledInputPart(*poDS->m_pMPIF, iPart));
     658             :             }
     659           0 :             else if (BYRYY)
     660             :             {
     661           0 :                 poDS->m_pIStream->seekg(0);
     662           0 :                 poDS->m_pRGBAIF.reset(new RgbaInputFile(*poDS->m_pIStream));
     663             :             }
     664             :             else
     665             :             {
     666           0 :                 poDS->m_pIP.reset(new InputPart(*poDS->m_pMPIF, iPart));
     667             :             }
     668          89 :             if (BYRYY)
     669             :             {
     670           0 :                 for (i = 1; i <= 3; i++)
     671             :                 {
     672           0 :                     poDS->SetBand(i, new GDALEXRRGBARasterBand(poDS.get(), i));
     673             :                 }
     674           0 :                 poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     675           0 :                 poDS->SetMetadataItem("SOURCE_COLOR_SPACE", "YCbCr",
     676           0 :                                       "IMAGE_STRUCTURE");
     677             :             }
     678          89 :             else if (BGR || ABGR)
     679             :             {
     680           5 :                 const int nBands = i;
     681           5 :                 i = 0;
     682          20 :                 for (auto iter = channels.begin(); iter != channels.end();
     683          15 :                      ++iter, ++i)
     684             :                 {
     685             :                     auto poBand = new GDALEXRRasterBand(
     686          15 :                         poDS.get(), nBands - i, iter.name(), samePixelType,
     687          15 :                         nBlockXSize, nBlockYSize);
     688          15 :                     poBand->m_eInterp = static_cast<GDALColorInterp>(
     689          15 :                         GCI_RedBand + nBands - 1 - i);
     690          15 :                     poDS->SetBand(nBands - i, poBand);
     691           5 :                 }
     692             :             }
     693             :             else
     694             :             {
     695          84 :                 i = 0;
     696         214 :                 for (auto iter = channels.begin(); iter != channels.end();
     697         130 :                      ++iter, ++i)
     698             :                 {
     699         130 :                     const Channel &channel = iter.channel();
     700             :                     auto poBand = new GDALEXRRasterBand(
     701         130 :                         poDS.get(), i + 1, iter.name(), channel.type,
     702         130 :                         nBlockXSize, nBlockYSize);
     703         260 :                     const std::string name(iter.name());
     704         130 :                     if (name != CPLSPrintf("Band%d", i + 1))
     705           0 :                         poBand->SetDescription(name.c_str());
     706         130 :                     if (name == "B")
     707           0 :                         poBand->m_eInterp = GCI_BlueBand;
     708         130 :                     else if (name == "G")
     709           0 :                         poBand->m_eInterp = GCI_GreenBand;
     710         130 :                     else if (name == "R")
     711           0 :                         poBand->m_eInterp = GCI_RedBand;
     712         130 :                     else if (name == "A")
     713           0 :                         poBand->m_eInterp = GCI_AlphaBand;
     714         130 :                     else if (name == "Y")
     715           0 :                         poBand->m_eInterp = GCI_GrayIndex;
     716         130 :                     poDS->SetBand(i + 1, poBand);
     717             :                 }
     718             :             }
     719             : 
     720         178 :             if (poDS->m_pTiledIP && !BYRYY &&
     721             :                 // Not completely clear on tiling & overviews would work
     722             :                 // on dataWindow.min != 0, so exclude that for now
     723         178 :                 dataWindow.min.x == 0 && dataWindow.min.y == 0)
     724             :             {
     725          89 :                 int nLevels = std::min(poDS->m_pTiledIP->numXLevels(),
     726         178 :                                        poDS->m_pTiledIP->numYLevels());
     727          90 :                 for (int iLevel = 1; iLevel < nLevels; iLevel++)
     728             :                 {
     729           2 :                     const int nOvrWidth = poDS->m_pTiledIP->levelWidth(iLevel);
     730             :                     const int nOvrHeight =
     731           2 :                         poDS->m_pTiledIP->levelHeight(iLevel);
     732           2 :                     if (nOvrWidth < 128 && nOvrHeight < 128)
     733             :                     {
     734           1 :                         break;
     735             :                     }
     736           2 :                     auto poOvrDS = std::make_unique<GDALEXRDataset>();
     737             :                     // coverity[escape]
     738           1 :                     poOvrDS->m_poParent = poDS.get();
     739           1 :                     poOvrDS->m_iLevel = iLevel;
     740           1 :                     poOvrDS->nRasterXSize = nOvrWidth;
     741           1 :                     poOvrDS->nRasterYSize = nOvrHeight;
     742           1 :                     poDS->m_apoOvrDS.push_back(std::move(poOvrDS));
     743           1 :                     i = 0;
     744           4 :                     for (auto iter = channels.begin(); iter != channels.end();
     745           3 :                          ++iter, ++i)
     746             :                     {
     747           3 :                         const Channel &channel = iter.channel();
     748             :                         auto poBand = new GDALEXRRasterBand(
     749           3 :                             poDS->m_apoOvrDS.back().get(), i + 1, iter.name(),
     750           3 :                             channel.type, nBlockXSize, nBlockYSize);
     751           3 :                         poDS->m_apoOvrDS.back()->SetBand(i + 1, poBand);
     752             :                     }
     753             :                 }
     754             :             }
     755             : 
     756        1169 :             for (auto iter = header.begin(); iter != header.end(); ++iter)
     757             :             {
     758        1080 :                 const Attribute *attr = &iter.attribute();
     759             :                 const StringAttribute *stringAttr =
     760        1080 :                     dynamic_cast<const StringAttribute *>(attr);
     761             :                 const M33dAttribute *m33DAttr =
     762        1080 :                     dynamic_cast<const M33dAttribute *>(attr);
     763        1080 :                 if (stringAttr && strcmp(iter.name(), "gdal:crsWkt") == 0)
     764             :                 {
     765          78 :                     poDS->m_oSRS.SetAxisMappingStrategy(
     766             :                         OAMS_TRADITIONAL_GIS_ORDER);
     767          78 :                     poDS->m_oSRS.importFromWkt(stringAttr->value().c_str());
     768             :                 }
     769        1080 :                 else if (m33DAttr &&
     770          78 :                          strcmp(iter.name(), "gdal:geoTransform") == 0)
     771             :                 {
     772          78 :                     poDS->m_bHasGT = true;
     773          78 :                     poDS->m_adfGT[0] = m33DAttr->value()[0][2];
     774          78 :                     poDS->m_adfGT[1] = m33DAttr->value()[0][0];
     775          78 :                     poDS->m_adfGT[2] = m33DAttr->value()[0][1];
     776          78 :                     poDS->m_adfGT[3] = m33DAttr->value()[1][2];
     777          78 :                     poDS->m_adfGT[4] = m33DAttr->value()[1][0];
     778          78 :                     poDS->m_adfGT[5] = m33DAttr->value()[1][1];
     779             :                 }
     780         924 :                 else if (stringAttr && STARTS_WITH(iter.name(), "gdal:"))
     781             :                 {
     782          64 :                     poDS->SetMetadataItem(iter.name() + strlen("gdal:"),
     783          32 :                                           stringAttr->value().c_str());
     784             :                 }
     785         892 :                 else if (stringAttr && strcmp(iter.name(), "type") != 0)
     786             :                 {
     787           0 :                     poDS->SetMetadataItem(iter.name(),
     788           0 :                                           stringAttr->value().c_str());
     789             :                 }
     790             :             }
     791             : 
     792          89 :             const auto &compression = header.compression();
     793          89 :             if (compression == NO_COMPRESSION)
     794             :             {
     795             :                 // nothing
     796             :             }
     797          89 :             else if (compression < CPL_ARRAYSIZE(apszCompressions))
     798             :             {
     799          89 :                 poDS->SetMetadataItem("COMPRESSION",
     800          89 :                                       apszCompressions[compression],
     801          89 :                                       "IMAGE_STRUCTURE");
     802             :             }
     803             :             else
     804             :             {
     805           0 :                 CPLDebug("EXR", "Unknown compression method: %d", compression);
     806             :             }
     807             : 
     808          89 :             if (header.hasPreviewImage())
     809             :             {
     810           2 :                 CPLStringList aosSubDS;
     811             :                 aosSubDS.SetNameValue("SUBDATASET_1_NAME",
     812             :                                       CPLSPrintf("EXR:PREVIEW:%d:%s", iPart + 1,
     813           1 :                                                  osFilename.c_str()));
     814           1 :                 aosSubDS.SetNameValue("SUBDATASET_1_DESC", "Preview image");
     815           1 :                 poDS->SetMetadata(aosSubDS.List(), "SUBDATASETS");
     816             :             }
     817             :         }
     818             :         else
     819             :         {
     820           0 :             CPLStringList aosSubDS;
     821           0 :             for (int i = 0; i < poDS->m_pMPIF->parts(); i++)
     822             :             {
     823           0 :                 const auto &header = poDS->m_pMPIF->header(i);
     824             :                 aosSubDS.SetNameValue(
     825             :                     CPLSPrintf("SUBDATASET_%d_NAME", i + 1),
     826           0 :                     CPLSPrintf("EXR:%d:%s", i + 1, poOpenInfo->pszFilename));
     827             :                 aosSubDS.SetNameValue(CPLSPrintf("SUBDATASET_%d_DESC", i + 1),
     828           0 :                                       header.name().c_str());
     829             :             }
     830           0 :             poDS->SetMetadata(aosSubDS.List(), "SUBDATASETS");
     831             :         }
     832             : 
     833          89 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
     834             : 
     835             :         // Initialize any PAM information.
     836          89 :         poDS->SetDescription(poOpenInfo->pszFilename);
     837          89 :         poDS->TryLoadXML();
     838             : 
     839          89 :         return poDS.release();
     840             :     }
     841           0 :     catch (const std::exception &e)
     842             :     {
     843           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
     844           0 :         return nullptr;
     845             :     }
     846             : }
     847             : 
     848             : /************************************************************************/
     849             : /*                          getPixelType()                              */
     850             : /************************************************************************/
     851             : 
     852          81 : static PixelType getPixelType(GDALDataType eSrcDT, char **papszOptions)
     853             : {
     854          81 :     PixelType pixelType =
     855         125 :         (eSrcDT == GDT_Byte) ? HALF
     856          41 :         : (eSrcDT == GDT_Int16 || eSrcDT == GDT_UInt16 || eSrcDT == GDT_UInt32)
     857          85 :             ? UINT
     858             :             : FLOAT;
     859             :     const char *pszPixelType =
     860          81 :         CSLFetchNameValueDef(papszOptions, "PIXEL_TYPE", "");
     861          81 :     if (EQUAL(pszPixelType, "HALF"))
     862           1 :         pixelType = HALF;
     863          80 :     else if (EQUAL(pszPixelType, "FLOAT"))
     864           1 :         pixelType = FLOAT;
     865          79 :     else if (EQUAL(pszPixelType, "UINT"))
     866           1 :         pixelType = UINT;
     867          81 :     return pixelType;
     868             : }
     869             : 
     870          72 : static void WriteSRSInHeader(Header &header, const OGRSpatialReference *poSRS)
     871             : {
     872          72 :     char *pszWKT = nullptr;
     873          72 :     const char *apszOptions[] = {"FORMAT=WKT2_2018", nullptr};
     874          72 :     poSRS->exportToWkt(&pszWKT, apszOptions);
     875          72 :     if (pszWKT)
     876             :     {
     877          72 :         header.insert("gdal:crsWkt", StringAttribute(pszWKT));
     878          72 :         CPLFree(pszWKT);
     879             :     }
     880          72 : }
     881             : 
     882          72 : static void WriteGeoTransformInHeader(Header &header, const double *padfGT)
     883             : {
     884          72 :     M33d gt;
     885          72 :     gt[0][0] = padfGT[1];
     886          72 :     gt[0][1] = padfGT[2];
     887          72 :     gt[0][2] = padfGT[0];
     888          72 :     gt[1][0] = padfGT[4];
     889          72 :     gt[1][1] = padfGT[5];
     890          72 :     gt[1][2] = padfGT[3];
     891          72 :     gt[2][0] = 0;
     892          72 :     gt[2][1] = 0;
     893          72 :     gt[2][2] = 1;
     894          72 :     header.insert("gdal:geoTransform", M33dAttribute(gt));
     895          72 : }
     896             : 
     897          78 : static void WriteMetadataInHeader(Header &header, CSLConstList papszMD)
     898             : {
     899         119 :     for (CSLConstList papszIter = papszMD; papszIter && *papszIter; ++papszIter)
     900             :     {
     901          41 :         char *pszKey = nullptr;
     902          41 :         const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
     903          41 :         if (pszKey && pszValue)
     904             :         {
     905          16 :             header.insert((std::string("gdal:") + pszKey).c_str(),
     906          32 :                           StringAttribute(pszValue));
     907             :         }
     908          41 :         CPLFree(pszKey);
     909             :     }
     910          78 : }
     911             : 
     912          78 : static void FillHeaderFromDataset(Header &header, GDALDataset *poDS)
     913             : {
     914          78 :     const auto poSRS = poDS->GetSpatialRef();
     915          78 :     if (poSRS)
     916             :     {
     917          72 :         WriteSRSInHeader(header, poSRS);
     918             :     }
     919             : 
     920             :     double adfGT[6];
     921          78 :     if (poDS->GetGeoTransform(adfGT) == CE_None)
     922             :     {
     923          72 :         WriteGeoTransformInHeader(header, adfGT);
     924             :     }
     925             : 
     926          78 :     WriteMetadataInHeader(header, poDS->GetMetadata());
     927          78 : }
     928             : 
     929          78 : static void FillHeaderFromOptions(Header &header, CSLConstList papszOptions)
     930             : {
     931             :     const char *pszDWACompressLevel =
     932          78 :         CSLFetchNameValue(papszOptions, "DWA_COMPRESSION_LEVEL");
     933          78 :     if (pszDWACompressLevel)
     934             :     {
     935           1 :         header.insert(
     936             :             "dwaCompressionLevel",
     937           2 :             FloatAttribute(static_cast<float>(CPLAtof(pszDWACompressLevel))));
     938             :     }
     939          78 : }
     940             : 
     941             : /************************************************************************/
     942             : /*                             CreateCopy()                             */
     943             : /************************************************************************/
     944             : 
     945          45 : GDALDataset *GDALEXRDataset::CreateCopy(const char *pszFilename,
     946             :                                         GDALDataset *poSrcDS, int,
     947             :                                         char **papszOptions,
     948             :                                         GDALProgressFunc pfnProgress,
     949             :                                         void *pProgressData)
     950             : {
     951          45 :     const int nBands = poSrcDS->GetRasterCount();
     952          45 :     const int nXSize = poSrcDS->GetRasterXSize();
     953          45 :     const int nYSize = poSrcDS->GetRasterYSize();
     954          45 :     if (nBands == 0)
     955           1 :         return nullptr;
     956             : 
     957          44 :     bool bRGB_or_RGBA = false;
     958          44 :     if ((nBands == 3 || nBands == 4))
     959             :     {
     960           7 :         bRGB_or_RGBA = true;
     961          29 :         for (int iBand = 0; iBand < nBands; iBand++)
     962             :         {
     963          22 :             bRGB_or_RGBA &=
     964          22 :                 (poSrcDS->GetRasterBand(iBand + 1)->GetColorInterpretation() ==
     965          22 :                  GCI_RedBand + iBand);
     966             :         }
     967             :     }
     968             : 
     969             :     const bool bPreview =
     970          45 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "PREVIEW", "NO")) &&
     971           1 :         (nXSize > 100 || nYSize > 100);
     972          44 :     const GDALDataType eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
     973          44 :     if (bPreview && !(bRGB_or_RGBA && eSrcDT == GDT_Byte))
     974             :     {
     975           0 :         CPLError(
     976             :             CE_Failure, CPLE_NotSupported,
     977             :             "Preview creation only supported on RGB/RGBA images of type Byte");
     978           0 :         return nullptr;
     979             :     }
     980          44 :     const PixelType pixelType = getPixelType(eSrcDT, papszOptions);
     981             :     const bool bRescaleDiv255 =
     982          49 :         pixelType == HALF && bRGB_or_RGBA && eSrcDT == GDT_Byte &&
     983           5 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "AUTO_RESCALE", "YES"));
     984             : 
     985          44 :     setNumThreads();
     986             : 
     987          88 :     CPLString osTmpOvrFile;
     988             :     try
     989             :     {
     990          44 :         VSILFILE *fp = VSIFOpenL(pszFilename, "wb+");
     991          44 :         if (fp == nullptr)
     992             :         {
     993           3 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
     994           3 :             return nullptr;
     995             :         }
     996          51 :         GDALEXRIOStream ostream(fp, pszFilename);
     997             : 
     998          51 :         std::vector<std::string> channelNames;
     999          41 :         if (bRGB_or_RGBA)
    1000             :         {
    1001           5 :             channelNames.push_back("R");
    1002           5 :             channelNames.push_back("G");
    1003           5 :             channelNames.push_back("B");
    1004           5 :             if (nBands == 4)
    1005             :             {
    1006           0 :                 channelNames.push_back("A");
    1007             :             }
    1008             :         }
    1009             :         else
    1010             :         {
    1011          82 :             for (int iBand = 0; iBand < nBands; iBand++)
    1012             :             {
    1013          46 :                 channelNames.push_back(CPLSPrintf("Band%d", iBand + 1));
    1014             :             }
    1015             :         }
    1016             : 
    1017          51 :         Header header(nXSize, nYSize);
    1018             : 
    1019          41 :         if (bPreview)
    1020             :         {
    1021           1 :             const int previewWidth = 100;
    1022             :             const int previewHeight = std::max(
    1023           2 :                 1, static_cast<int>(static_cast<GIntBig>(previewWidth) *
    1024           1 :                                     nYSize / nXSize));
    1025           2 :             std::vector<PreviewRgba> pixels(previewWidth * previewHeight);
    1026           1 :             if (poSrcDS->RasterIO(GF_Read, 0, 0, nXSize, nYSize, &pixels[0],
    1027             :                                   previewWidth, previewHeight, GDT_Byte, nBands,
    1028             :                                   nullptr, 4, 4 * previewWidth, 1,
    1029           1 :                                   nullptr) == CE_None)
    1030             :             {
    1031           1 :                 header.setPreviewImage(
    1032           2 :                     PreviewImage(previewWidth, previewHeight, &pixels[0]));
    1033             :             }
    1034             :         }
    1035             : 
    1036          41 :         FillHeaderFromDataset(header, poSrcDS);
    1037             : 
    1038             :         const char *pszCompress =
    1039          41 :             CSLFetchNameValueDef(papszOptions, "COMPRESS", "");
    1040          41 :         if (pszCompress[0] != '\0')
    1041             :         {
    1042           2 :             bool bFound = false;
    1043          12 :             for (size_t i = 0; i < CPL_ARRAYSIZE(apszCompressions); i++)
    1044             :             {
    1045          12 :                 if (EQUAL(pszCompress, apszCompressions[i]))
    1046             :                 {
    1047           2 :                     bFound = true;
    1048           2 :                     header.compression() = static_cast<Compression>(i);
    1049           2 :                     break;
    1050             :                 }
    1051             :             }
    1052           2 :             if (!bFound)
    1053             :             {
    1054           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Unknown compression %s",
    1055             :                          pszCompress);
    1056           0 :                 return nullptr;
    1057             :             }
    1058             :         }
    1059             : 
    1060          41 :         FillHeaderFromOptions(header, papszOptions);
    1061             : 
    1062          51 :         std::vector<half> bufferHalf;
    1063          51 :         std::vector<float> bufferFloat;
    1064          51 :         std::vector<GUInt32> bufferUInt;
    1065          41 :         const size_t pixelTypeSize = (pixelType == HALF) ? 2 : 4;
    1066          41 :         const GDALDataType eDT = (pixelType == UINT) ? GDT_UInt32 : GDT_Float32;
    1067          41 :         const GSpacing nDTSize = GDALGetDataTypeSizeBytes(eDT);
    1068             : 
    1069             :         const bool bTiled =
    1070          41 :             CPLTestBool(CSLFetchNameValueDef(papszOptions, "TILED", "YES"));
    1071             : 
    1072             :         int nChunkXSize;
    1073             :         int nChunkYSize;
    1074             :         const int nBlockXSize =
    1075          41 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "256"));
    1076             :         const int nBlockYSize =
    1077          41 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "256"));
    1078          41 :         if (nBlockXSize <= 8 || nBlockYSize <= 8 || nBlockXSize >= 8192 ||
    1079             :             nBlockYSize >= 8192)
    1080             :         {
    1081           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
    1082           0 :             return nullptr;
    1083             :         }
    1084          41 :         constexpr int MAX_BUFFER_SIZE = 10 * 1024 * 1024;
    1085             : 
    1086             :         const bool bBuildOvr =
    1087          41 :             CPLTestBool(CSLFetchNameValueDef(papszOptions, "OVERVIEWS", "NO"));
    1088          41 :         if (bBuildOvr && !bTiled)
    1089             :         {
    1090           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1091             :                      "Overviews only supported on tiled images");
    1092           0 :             return nullptr;
    1093             :         }
    1094             : 
    1095          41 :         if (bTiled)
    1096             :         {
    1097          41 :             header.setType(TILEDIMAGE);
    1098          41 :             header.setTileDescription(TileDescription(
    1099             :                 nBlockXSize, nBlockYSize, bBuildOvr ? MIPMAP_LEVELS : ONE_LEVEL,
    1100             :                 ROUND_UP));
    1101          41 :             nChunkYSize = nBlockYSize;
    1102          41 :             nChunkXSize =
    1103             :                 std::min(std::max(nBlockXSize,
    1104          41 :                                   static_cast<int>(
    1105          41 :                                       MAX_BUFFER_SIZE /
    1106          41 :                                       (pixelTypeSize * nBands * nBlockYSize) /
    1107          41 :                                       nBlockXSize * nBlockXSize)),
    1108          41 :                          nXSize);
    1109             :         }
    1110             :         else
    1111             :         {
    1112           0 :             header.setType(SCANLINEIMAGE);
    1113           0 :             nChunkXSize = nXSize;
    1114           0 :             nChunkYSize = std::min(
    1115           0 :                 std::max(1,
    1116           0 :                          static_cast<int>(MAX_BUFFER_SIZE /
    1117           0 :                                           (pixelTypeSize * nBands * nXSize))),
    1118           0 :                 nYSize);
    1119             :         }
    1120             :         char *sliceBuffer;
    1121          41 :         if (pixelType == UINT)
    1122             :         {
    1123           6 :             bufferUInt.resize(static_cast<size_t>(nBands) * nChunkXSize *
    1124           6 :                               nChunkYSize);
    1125           6 :             sliceBuffer = reinterpret_cast<char *>(bufferUInt.data());
    1126             :         }
    1127             :         else
    1128             :         {
    1129          35 :             bufferFloat.resize(static_cast<size_t>(nBands) * nChunkXSize *
    1130          35 :                                nChunkYSize);
    1131          35 :             if (pixelType == HALF)
    1132             :             {
    1133          25 :                 bufferHalf.resize(static_cast<size_t>(nBands) * nChunkXSize *
    1134          25 :                                   nChunkYSize);
    1135          25 :                 sliceBuffer = reinterpret_cast<char *>(bufferHalf.data());
    1136             :             }
    1137             :             else
    1138             :             {
    1139          10 :                 sliceBuffer = reinterpret_cast<char *>(bufferFloat.data());
    1140             :             }
    1141             :         }
    1142             : 
    1143         102 :         for (const auto &channelName : channelNames)
    1144             :         {
    1145          61 :             header.channels().insert(channelName, Channel(pixelType));
    1146             :         }
    1147             : 
    1148          41 :         MultiPartOutputFile mpof(ostream, &header, 1);
    1149          31 :         if (bTiled)
    1150             :         {
    1151          31 :             TiledOutputPart op(mpof, 0);
    1152             : 
    1153          31 :             if (bBuildOvr)
    1154             :             {
    1155           1 :                 if (nBlockXSize != nBlockYSize)
    1156             :                 {
    1157           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    1158             :                              "Overview building only works if "
    1159             :                              "BLOCKXSIZE=BLOCKYSIZE");
    1160           0 :                     return nullptr;
    1161             :                 }
    1162           2 :                 if (nBlockXSize < 64 || nBlockXSize > 4096 ||
    1163           1 :                     !CPLIsPowerOfTwo(nBlockXSize))
    1164             :                 {
    1165           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    1166             :                              "Overview building only works if "
    1167             :                              "BLOCKXSIZE=BLOCKYSIZE is a power of 2 "
    1168             :                              "between 64 and 4096.");
    1169           0 :                     return nullptr;
    1170             :                 }
    1171             :             }
    1172             : 
    1173             :             const auto writeTiles =
    1174          40 :                 [nChunkXSize, nChunkYSize, nBlockXSize, nBlockYSize, nBands,
    1175             :                  pixelType, pixelTypeSize, sliceBuffer, eDT, nDTSize,
    1176             :                  bRescaleDiv255, &channelNames, &op, &bufferFloat, &bufferHalf,
    1177             :                  &bufferUInt](GDALDataset *l_poDS, int iLevel,
    1178             :                               GDALProgressFunc l_pfnProgress,
    1179     8644620 :                               void *l_pProgressData)
    1180             :             {
    1181          40 :                 const int l_nXSize = l_poDS->GetRasterXSize();
    1182          40 :                 const int l_nYSize = l_poDS->GetRasterYSize();
    1183          40 :                 const int nXBlocks = DIV_ROUND_UP(l_nXSize, nBlockXSize);
    1184          40 :                 const int nYBlocks = DIV_ROUND_UP(l_nYSize, nBlockYSize);
    1185          87 :                 for (int y = 0; y < l_nYSize; y += nChunkYSize)
    1186             :                 {
    1187             :                     const int nLinesToRead =
    1188          47 :                         std::min(nChunkYSize, l_nYSize - y);
    1189          94 :                     for (int x = 0; x < l_nXSize; x += nChunkXSize)
    1190             :                     {
    1191             :                         const int nColsToRead =
    1192          47 :                             std::min(nChunkXSize, l_nXSize - x);
    1193          47 :                         FrameBuffer fb;
    1194         144 :                         for (int iBand = 0; iBand < nBands; iBand++)
    1195             :                         {
    1196             :                             const auto slice = Slice(
    1197             :                                 pixelType,
    1198             :                                 sliceBuffer +
    1199          97 :                                     iBand * pixelTypeSize * nChunkXSize *
    1200          97 :                                         nChunkYSize -
    1201          97 :                                     (x * pixelTypeSize +
    1202          97 :                                      y * pixelTypeSize * nChunkXSize),
    1203          97 :                                 pixelTypeSize, pixelTypeSize * nChunkXSize);
    1204          97 :                             fb.insert(channelNames[iBand], slice);
    1205             :                         }
    1206          47 :                         if (l_poDS->RasterIO(
    1207             :                                 GF_Read, x, y, nColsToRead, nLinesToRead,
    1208          47 :                                 !bufferFloat.empty()
    1209          41 :                                     ? reinterpret_cast<GByte *>(&bufferFloat[0])
    1210           6 :                                     : reinterpret_cast<GByte *>(&bufferUInt[0]),
    1211             :                                 nColsToRead, nLinesToRead, eDT, nBands, nullptr,
    1212          47 :                                 nDTSize, nDTSize * nChunkXSize,
    1213          47 :                                 nDTSize * nChunkXSize * nChunkYSize,
    1214          47 :                                 nullptr) != CE_None)
    1215             :                         {
    1216           0 :                             return false;
    1217             :                         }
    1218          47 :                         if (pixelType == HALF)
    1219             :                         {
    1220          31 :                             const size_t nPixelsInBuffer =
    1221          31 :                                 static_cast<size_t>(nChunkXSize) * nChunkYSize *
    1222          31 :                                 nBands;
    1223          31 :                             if (bRescaleDiv255)
    1224             :                             {
    1225     3955220 :                                 for (size_t i = 0; i < nPixelsInBuffer; i++)
    1226             :                                 {
    1227     3955200 :                                     bufferHalf[i] = bufferFloat[i] / 255.0f;
    1228             :                                 }
    1229             :                             }
    1230             :                             else
    1231             :                             {
    1232      366692 :                                 for (size_t i = 0; i < nPixelsInBuffer; i++)
    1233             :                                 {
    1234      366680 :                                     bufferHalf[i] = bufferFloat[i];
    1235             :                                 }
    1236             :                             }
    1237             :                         }
    1238          47 :                         op.setFrameBuffer(fb);
    1239          47 :                         const int blockXMax =
    1240          47 :                             (x + nColsToRead - 1) / nBlockXSize;
    1241          47 :                         const int blockYMax =
    1242          47 :                             (y + nLinesToRead - 1) / nBlockYSize;
    1243          47 :                         op.writeTiles(x / nBlockXSize, blockXMax,
    1244             :                                       y / nBlockYSize, blockYMax, iLevel);
    1245          94 :                         if (l_pfnProgress &&
    1246          47 :                             !l_pfnProgress(
    1247          47 :                                 (static_cast<double>(blockYMax) * nXBlocks +
    1248          47 :                                  blockXMax + 1) /
    1249             :                                     nXBlocks / nYBlocks,
    1250             :                                 "", l_pProgressData))
    1251             :                         {
    1252           0 :                             return false;
    1253             :                         }
    1254             :                     }
    1255             :                 }
    1256          40 :                 return true;
    1257          31 :             };
    1258             : 
    1259             :             struct ScaledProgressReleaser
    1260             :             {
    1261           0 :                 void operator()(void *progress) const
    1262             :                 {
    1263           0 :                     GDALDestroyScaledProgress(progress);
    1264           0 :                 }
    1265             :             };
    1266             : 
    1267             :             using ScaledProgressUniquePtr =
    1268             :                 std::unique_ptr<void, ScaledProgressReleaser>;
    1269           0 :             ScaledProgressUniquePtr progress;
    1270             : 
    1271             :             // Write full resolution imagery
    1272          31 :             if (bBuildOvr)
    1273           1 :                 progress.reset(GDALCreateScaledProgress(0, 0.5, pfnProgress,
    1274             :                                                         pProgressData));
    1275             :             else
    1276          30 :                 progress.reset(
    1277             :                     GDALCreateScaledProgress(0, 1, pfnProgress, pProgressData));
    1278          31 :             if (!writeTiles(poSrcDS, 0, GDALScaledProgress, progress.get()))
    1279             :             {
    1280           0 :                 if (!osTmpOvrFile.empty())
    1281           0 :                     VSIUnlink(osTmpOvrFile);
    1282           0 :                 return nullptr;
    1283             :             }
    1284             : 
    1285          31 :             if (bBuildOvr)
    1286             :             {
    1287             :                 // First build overviews in a temporary GTiff file
    1288           1 :                 GDALDefaultOverviews oOvr;
    1289           1 :                 oOvr.Initialize(poSrcDS);
    1290           1 :                 std::vector<int> anOvrFactors;
    1291          10 :                 for (int i = 1; i < op.numLevels(); i++)
    1292           9 :                     anOvrFactors.push_back(1 << i);
    1293           1 :                 std::vector<int> anBands;
    1294           4 :                 for (int iBand = 0; iBand < nBands; iBand++)
    1295           3 :                     anBands.push_back(iBand + 1);
    1296             :                 CPLConfigOptionSetter oSetter("GDAL_TIFF_OVR_BLOCKSIZE",
    1297             :                                               CPLSPrintf("%d", nBlockXSize),
    1298           1 :                                               false);
    1299             :                 const CPLString osTmpOvrFileRadix(
    1300           1 :                     CPLSPrintf("%s_tmp", pszFilename));
    1301           1 :                 osTmpOvrFile = osTmpOvrFileRadix + ".ovr";
    1302           1 :                 progress.reset(GDALCreateScaledProgress(0.5, 0.8, pfnProgress,
    1303             :                                                         pProgressData));
    1304           2 :                 if (oOvr.BuildOverviews(
    1305             :                         osTmpOvrFileRadix,
    1306             :                         CSLFetchNameValueDef(papszOptions,
    1307             :                                              "OVERVIEW_RESAMPLING", "CUBIC"),
    1308           1 :                         static_cast<int>(anOvrFactors.size()), &anOvrFactors[0],
    1309           1 :                         nBands, &anBands[0], GDALScaledProgress, progress.get(),
    1310           1 :                         nullptr) != CE_None)
    1311             :                 {
    1312           0 :                     VSIUnlink(osTmpOvrFile);
    1313           0 :                     return nullptr;
    1314             :                 }
    1315             : 
    1316             :                 // Transfer overviews from temporary file to main image
    1317             :                 std::unique_ptr<GDALDataset> poOvrDS(
    1318           1 :                     GDALDataset::Open(osTmpOvrFile));
    1319           1 :                 if (!poOvrDS)
    1320           0 :                     return nullptr;
    1321             :                 const int nOvrs =
    1322           1 :                     1 + poOvrDS->GetRasterBand(1)->GetOverviewCount();
    1323          10 :                 for (int i = 0; i < nOvrs; i++)
    1324             :                 {
    1325           9 :                     auto poThisOvrDS = (i == 0) ? poOvrDS.get()
    1326           8 :                                                 : poOvrDS->GetRasterBand(1)
    1327           8 :                                                       ->GetOverview(i - 1)
    1328           8 :                                                       ->GetDataset();
    1329           9 :                     CPLAssert(poThisOvrDS);
    1330           9 :                     if (i == 0)
    1331           1 :                         progress.reset(GDALCreateScaledProgress(
    1332             :                             0.8, nOvrs == 1 ? 1.0 : 0.9, pfnProgress,
    1333             :                             pProgressData));
    1334           8 :                     else if (i == 1)
    1335           1 :                         progress.reset(GDALCreateScaledProgress(
    1336             :                             0.9, nOvrs == 2 ? 1.0 : 0.95, pfnProgress,
    1337             :                             pProgressData));
    1338             :                     else
    1339           7 :                         progress.reset(GDALCreateScaledProgress(
    1340           7 :                             0.95 + 0.05 * (i - 2) / (nOvrs - 2),
    1341           7 :                             0.95 + 0.05 * (i - 2 + 1) / (nOvrs - 2),
    1342             :                             pfnProgress, pProgressData));
    1343           9 :                     if (!writeTiles(poThisOvrDS, i + 1, GDALScaledProgress,
    1344             :                                     progress.get()))
    1345             :                     {
    1346           0 :                         poOvrDS.reset();
    1347           0 :                         VSIUnlink(osTmpOvrFile);
    1348           0 :                         return nullptr;
    1349             :                     }
    1350             :                 }
    1351             : 
    1352           1 :                 poOvrDS.reset();
    1353           1 :                 VSIUnlink(osTmpOvrFile);
    1354             :             }
    1355             :         }
    1356             :         else
    1357             :         {
    1358           0 :             OutputPart op(mpof, 0);
    1359             : 
    1360           0 :             for (int y = 0; y < nYSize; y += nChunkYSize)
    1361             :             {
    1362           0 :                 FrameBuffer fb;
    1363           0 :                 const int nLinesToRead = std::min(nChunkYSize, nYSize - y);
    1364           0 :                 for (int iBand = 0; iBand < nBands; iBand++)
    1365             :                 {
    1366             :                     const auto slice = Slice(
    1367             :                         pixelType,
    1368             :                         sliceBuffer +
    1369           0 :                             iBand * pixelTypeSize * nXSize * nLinesToRead -
    1370           0 :                             y * pixelTypeSize * nXSize,
    1371           0 :                         pixelTypeSize, pixelTypeSize * nXSize);
    1372           0 :                     fb.insert(channelNames[iBand], slice);
    1373             :                 }
    1374           0 :                 if (poSrcDS->RasterIO(
    1375             :                         GF_Read, 0, y, nXSize, nLinesToRead,
    1376           0 :                         !bufferFloat.empty()
    1377           0 :                             ? reinterpret_cast<GByte *>(&bufferFloat[0])
    1378           0 :                             : reinterpret_cast<GByte *>(&bufferUInt[0]),
    1379             :                         nXSize, nLinesToRead, eDT, nBands, nullptr, nDTSize,
    1380           0 :                         nDTSize * nXSize, nDTSize * nXSize * nLinesToRead,
    1381           0 :                         nullptr) != CE_None)
    1382             :                 {
    1383           0 :                     return nullptr;
    1384             :                 }
    1385           0 :                 if (pixelType == HALF)
    1386             :                 {
    1387           0 :                     for (size_t i = 0; i < static_cast<size_t>(nXSize) *
    1388           0 :                                                nLinesToRead * nBands;
    1389             :                          i++)
    1390             :                     {
    1391             :                         // cppcheck-suppress unreadVariable
    1392           0 :                         bufferHalf[i] = bufferFloat[i];
    1393             :                     }
    1394             :                 }
    1395           0 :                 op.setFrameBuffer(fb);
    1396           0 :                 op.writePixels(nLinesToRead);
    1397           0 :                 if (pfnProgress &&
    1398           0 :                     !pfnProgress(static_cast<double>(y + nLinesToRead) / nYSize,
    1399             :                                  "", pProgressData))
    1400             :                 {
    1401           0 :                     return nullptr;
    1402             :                 }
    1403             :             }
    1404             :         }
    1405             :     }
    1406          10 :     catch (const std::exception &e)
    1407             :     {
    1408          10 :         if (!osTmpOvrFile.empty())
    1409           0 :             VSIUnlink(osTmpOvrFile);
    1410          10 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
    1411          10 :         return nullptr;
    1412             :     }
    1413          62 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    1414          31 :     return GDALEXRDataset::Open(&oOpenInfo);
    1415             : }
    1416             : 
    1417             : /************************************************************************/
    1418             : /*                         GDALEXRWritableDataset                       */
    1419             : /************************************************************************/
    1420             : 
    1421             : class GDALEXRWritableDataset final : public GDALPamDataset
    1422             : {
    1423             :     friend class GDALEXRDataset;
    1424             :     friend class GDALEXRWritableRasterBand;
    1425             : 
    1426             :     PixelType m_pixelType = HALF;
    1427             :     int m_nBlockXSize = 0;
    1428             :     int m_nBlockYSize = 0;
    1429             : 
    1430             :     // Keep stream before others, so that it is destroyed last
    1431             :     std::unique_ptr<OStream> m_pOStream{};
    1432             : 
    1433             :     std::unique_ptr<TiledOutputPart> m_pTOP{};
    1434             :     std::unique_ptr<MultiPartOutputFile> m_pMPOF{};
    1435             : 
    1436             :     std::vector<std::string> m_channelNames{};
    1437             : 
    1438             :     bool m_bTriedWritingHeader = false;
    1439             :     std::vector<half> m_bufferHalf{};
    1440             :     std::vector<float> m_bufferFloat{};
    1441             :     std::vector<GUInt32> m_bufferUInt{};
    1442             :     size_t m_nBufferEltSize = 0;
    1443             :     char *m_pSliceBuffer = nullptr;
    1444             : 
    1445             :     OGRSpatialReference m_oSRS{};
    1446             :     double m_adfGT[6] = {0, 1, 0, 0, 0, 1};
    1447             :     bool m_bHasGT = false;
    1448             : 
    1449             :     CPLStringList m_aosMetadata{};
    1450             : 
    1451             :     std::vector<bool> m_abWrittenBlocks{};
    1452             :     size_t m_nXBlocks = 0;
    1453             : 
    1454             :     bool m_bRescaleDiv255 = false;
    1455             : 
    1456             :     Header m_header;
    1457             : 
    1458             :     void WriteHeader();
    1459             : 
    1460             :   public:
    1461          37 :     GDALEXRWritableDataset(int nXSize, int nYSize) : m_header(nXSize, nYSize)
    1462             :     {
    1463          37 :         nRasterXSize = nXSize;
    1464          37 :         nRasterYSize = nYSize;
    1465          37 :     }
    1466             : 
    1467             :     ~GDALEXRWritableDataset() override;
    1468             : 
    1469             :     CPLErr SetGeoTransform(double *adfGT) override;
    1470             :     CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
    1471             : 
    1472             :     const OGRSpatialReference *GetSpatialRef() const override;
    1473             :     CPLErr GetGeoTransform(double *adfGT) override;
    1474             : 
    1475             :     CPLErr SetMetadata(char **, const char * = "") override;
    1476             :     CPLErr SetMetadataItem(const char *, const char *,
    1477             :                            const char * = "") override;
    1478             : 
    1479             :     char **GetMetadata(const char *pszDomain = "") override;
    1480             :     const char *GetMetadataItem(const char *pszName,
    1481             :                                 const char *pszDomain = "") override;
    1482             : };
    1483             : 
    1484             : /************************************************************************/
    1485             : /*                       ~GDALEXRWritableDataset()                      */
    1486             : /************************************************************************/
    1487             : 
    1488          74 : GDALEXRWritableDataset::~GDALEXRWritableDataset()
    1489             : {
    1490          37 :     WriteHeader();
    1491          37 :     FlushCache(true);
    1492          74 : }
    1493             : 
    1494             : /************************************************************************/
    1495             : /*                            SetGeoTransform()                         */
    1496             : /************************************************************************/
    1497             : 
    1498          31 : CPLErr GDALEXRWritableDataset::SetGeoTransform(double *adfGT)
    1499             : {
    1500          31 :     if (m_bTriedWritingHeader)
    1501             :     {
    1502           0 :         CPLError(
    1503             :             CE_Warning, CPLE_AppDefined,
    1504             :             "SetGeoTransform() called after writing pixels. Will go to PAM");
    1505           0 :         return GDALPamDataset::SetGeoTransform(adfGT);
    1506             :     }
    1507          31 :     m_bHasGT = true;
    1508          31 :     memcpy(m_adfGT, adfGT, 6 * sizeof(double));
    1509          31 :     return CE_None;
    1510             : }
    1511             : 
    1512             : /************************************************************************/
    1513             : /*                            SetSpatialRef()                           */
    1514             : /************************************************************************/
    1515             : 
    1516          31 : CPLErr GDALEXRWritableDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    1517             : {
    1518          31 :     if (m_bTriedWritingHeader)
    1519             :     {
    1520           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1521             :                  "SetSpatialRef() called after writing pixels. Will go to PAM");
    1522           0 :         return GDALPamDataset::SetSpatialRef(poSRS);
    1523             :     }
    1524          31 :     if (poSRS)
    1525          31 :         m_oSRS = *poSRS;
    1526             :     else
    1527           0 :         m_oSRS.Clear();
    1528          31 :     return CE_None;
    1529             : }
    1530             : 
    1531             : /************************************************************************/
    1532             : /*                             SetMetadata()                            */
    1533             : /************************************************************************/
    1534             : 
    1535           0 : CPLErr GDALEXRWritableDataset::SetMetadata(char **papszMD,
    1536             :                                            const char *pszDomain)
    1537             : {
    1538           0 :     if (pszDomain == nullptr || pszDomain[0] == 0)
    1539             :     {
    1540           0 :         m_aosMetadata = CSLDuplicate(papszMD);
    1541           0 :         if (m_bTriedWritingHeader)
    1542             :         {
    1543           0 :             CPLError(
    1544             :                 CE_Warning, CPLE_AppDefined,
    1545             :                 "SetMetadata() called after writing pixels. Will go to PAM");
    1546             :         }
    1547             :         else
    1548             :         {
    1549           0 :             return CE_None;
    1550             :         }
    1551             :     }
    1552           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    1553             : }
    1554             : 
    1555             : /************************************************************************/
    1556             : /*                           SetMetadataItem()                          */
    1557             : /************************************************************************/
    1558             : 
    1559           0 : CPLErr GDALEXRWritableDataset::SetMetadataItem(const char *pszName,
    1560             :                                                const char *pszValue,
    1561             :                                                const char *pszDomain)
    1562             : {
    1563           0 :     if (pszDomain == nullptr || pszDomain[0] == 0)
    1564             :     {
    1565           0 :         m_aosMetadata.SetNameValue(pszName, pszValue);
    1566           0 :         if (m_bTriedWritingHeader)
    1567             :         {
    1568           0 :             CPLError(
    1569             :                 CE_Warning, CPLE_AppDefined,
    1570             :                 "SetMetadata() called after writing pixels. Will go to PAM");
    1571             :         }
    1572             :         else
    1573             :         {
    1574           0 :             return CE_None;
    1575             :         }
    1576             :     }
    1577           0 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    1578             : }
    1579             : 
    1580             : /************************************************************************/
    1581             : /*                             GetMetadata()                            */
    1582             : /************************************************************************/
    1583             : 
    1584          37 : char **GDALEXRWritableDataset::GetMetadata(const char *pszDomain)
    1585             : {
    1586          37 :     if (pszDomain == nullptr || pszDomain[0] == 0)
    1587             :     {
    1588          37 :         return m_aosMetadata.List();
    1589             :     }
    1590           0 :     return GDALPamDataset::GetMetadata(pszDomain);
    1591             : }
    1592             : 
    1593             : /************************************************************************/
    1594             : /*                           GetMetadataItem()                          */
    1595             : /************************************************************************/
    1596             : 
    1597           5 : const char *GDALEXRWritableDataset::GetMetadataItem(const char *pszName,
    1598             :                                                     const char *pszDomain)
    1599             : {
    1600           5 :     if (pszDomain == nullptr || pszDomain[0] == 0)
    1601             :     {
    1602           5 :         return m_aosMetadata.FetchNameValue(pszName);
    1603             :     }
    1604           0 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
    1605             : }
    1606             : 
    1607             : /************************************************************************/
    1608             : /*                          GetSpatialRef()                             */
    1609             : /************************************************************************/
    1610             : 
    1611          37 : const OGRSpatialReference *GDALEXRWritableDataset::GetSpatialRef() const
    1612             : {
    1613          37 :     const auto *poPamSRS = GDALPamDataset::GetSpatialRef();
    1614          37 :     if (poPamSRS)
    1615           0 :         return poPamSRS;
    1616          37 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1617             : }
    1618             : 
    1619             : /************************************************************************/
    1620             : /*                         GetGeoTransform()                            */
    1621             : /************************************************************************/
    1622             : 
    1623          68 : CPLErr GDALEXRWritableDataset::GetGeoTransform(double *adfGT)
    1624             : {
    1625          68 :     if (GDALPamDataset::GetGeoTransform(adfGT) == CE_None)
    1626             :     {
    1627           0 :         return CE_None;
    1628             :     }
    1629          68 :     memcpy(adfGT, m_adfGT, 6 * sizeof(double));
    1630          68 :     return m_bHasGT ? CE_None : CE_Failure;
    1631             : }
    1632             : 
    1633             : /************************************************************************/
    1634             : /*                             WriteHeader()                            */
    1635             : /************************************************************************/
    1636             : 
    1637          43 : void GDALEXRWritableDataset::WriteHeader()
    1638             : {
    1639          43 :     if (m_bTriedWritingHeader)
    1640           6 :         return;
    1641          37 :     m_bTriedWritingHeader = true;
    1642             : 
    1643             :     try
    1644             :     {
    1645          37 :         FillHeaderFromDataset(m_header, this);
    1646             : 
    1647          37 :         bool bRGB_or_RGBA = false;
    1648          37 :         if (nBands == 3 || nBands == 4)
    1649             :         {
    1650          15 :             bRGB_or_RGBA = true;
    1651          61 :             for (int i = 0; i < nBands; i++)
    1652             :             {
    1653          46 :                 bRGB_or_RGBA &=
    1654          46 :                     GetRasterBand(i + 1)->GetColorInterpretation() ==
    1655          46 :                     GCI_RedBand + i;
    1656             :             }
    1657             :         }
    1658          37 :         m_bRescaleDiv255 &= m_pixelType == HALF && bRGB_or_RGBA &&
    1659           0 :                             GetRasterBand(1)->GetRasterDataType() == GDT_Byte;
    1660             : 
    1661          37 :         if (bRGB_or_RGBA)
    1662             :         {
    1663           0 :             m_channelNames.push_back("R");
    1664           0 :             m_channelNames.push_back("G");
    1665           0 :             m_channelNames.push_back("B");
    1666           0 :             if (nBands == 4)
    1667             :             {
    1668           0 :                 m_channelNames.push_back("A");
    1669             :             }
    1670             :         }
    1671             :         else
    1672             :         {
    1673         110 :             for (int iBand = 0; iBand < nBands; iBand++)
    1674             :             {
    1675          73 :                 m_channelNames.push_back(CPLSPrintf("Band%d", iBand + 1));
    1676             :             }
    1677             :         }
    1678             : 
    1679         110 :         for (int i = 0; i < nBands; i++)
    1680             :         {
    1681          73 :             m_header.channels().insert(m_channelNames[i], Channel(m_pixelType));
    1682             :         }
    1683             : 
    1684          37 :         m_pMPOF.reset(new MultiPartOutputFile(*m_pOStream, &m_header, 1));
    1685          37 :         m_pTOP.reset(new TiledOutputPart(*m_pMPOF, 0));
    1686             : 
    1687          37 :         const size_t nElts =
    1688          37 :             static_cast<size_t>(nBands) * m_nBlockXSize * m_nBlockYSize;
    1689          37 :         if (m_pixelType == HALF)
    1690             :         {
    1691           7 :             m_bufferHalf.resize(nElts);
    1692           7 :             m_bufferFloat.resize(nElts / nBands);
    1693           7 :             m_pSliceBuffer = reinterpret_cast<char *>(&m_bufferHalf[0]);
    1694           7 :             m_nBufferEltSize = sizeof(half);
    1695             :         }
    1696          30 :         else if (m_pixelType == FLOAT)
    1697             :         {
    1698          22 :             m_bufferFloat.resize(nElts);
    1699          22 :             m_pSliceBuffer = reinterpret_cast<char *>(&m_bufferFloat[0]);
    1700          22 :             m_nBufferEltSize = sizeof(float);
    1701             :         }
    1702             :         else
    1703             :         {
    1704           8 :             m_bufferUInt.resize(nElts);
    1705           8 :             m_pSliceBuffer = reinterpret_cast<char *>(&m_bufferUInt[0]);
    1706           8 :             m_nBufferEltSize = sizeof(unsigned int);
    1707             :         }
    1708             :     }
    1709           0 :     catch (const std::exception &e)
    1710             :     {
    1711           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
    1712           0 :         m_pTOP.reset();
    1713           0 :         m_pMPOF.reset();
    1714             :     }
    1715             : }
    1716             : 
    1717             : /************************************************************************/
    1718             : /*                       GDALEXRWritableRasterBand                      */
    1719             : /************************************************************************/
    1720             : 
    1721             : class GDALEXRWritableRasterBand final : public GDALPamRasterBand
    1722             : {
    1723             :     GDALColorInterp m_eInterp = GCI_Undefined;
    1724             : 
    1725             :   protected:
    1726             :     CPLErr IReadBlock(int, int, void *) override;
    1727             :     CPLErr IWriteBlock(int, int, void *) override;
    1728             : 
    1729             :   public:
    1730             :     GDALEXRWritableRasterBand(GDALEXRWritableDataset *poDSIn, int nBandIn,
    1731             :                               GDALDataType eTypeIn);
    1732             : 
    1733           0 :     CPLErr SetColorInterpretation(GDALColorInterp eInterp) override
    1734             :     {
    1735           0 :         m_eInterp = eInterp;
    1736           0 :         return CE_None;
    1737             :     }
    1738             : 
    1739          46 :     GDALColorInterp GetColorInterpretation() override
    1740             :     {
    1741          46 :         return m_eInterp;
    1742             :     }
    1743             : };
    1744             : 
    1745             : /************************************************************************/
    1746             : /*                       GDALEXRWritableRasterBand()                    */
    1747             : /************************************************************************/
    1748             : 
    1749          73 : GDALEXRWritableRasterBand::GDALEXRWritableRasterBand(
    1750          73 :     GDALEXRWritableDataset *poDSIn, int nBandIn, GDALDataType eTypeIn)
    1751             : {
    1752          73 :     poDS = poDSIn;
    1753          73 :     nBand = nBandIn;
    1754          73 :     nRasterXSize = poDSIn->GetRasterXSize();
    1755          73 :     nRasterYSize = poDSIn->GetRasterYSize();
    1756          73 :     nBlockXSize = poDSIn->m_nBlockXSize;
    1757          73 :     nBlockYSize = poDSIn->m_nBlockYSize;
    1758          73 :     eDataType = eTypeIn;
    1759          73 : }
    1760             : 
    1761             : /************************************************************************/
    1762             : /*                           IReadBlock()                               */
    1763             : /************************************************************************/
    1764             : 
    1765           0 : CPLErr GDALEXRWritableRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    1766             :                                              void *pImage)
    1767             : {
    1768           0 :     auto poGDS = cpl::down_cast<GDALEXRWritableDataset *>(poDS);
    1769           0 :     if (!poGDS->m_abWrittenBlocks[nBlockYOff * poGDS->m_nXBlocks + nBlockXOff])
    1770             :     {
    1771           0 :         const size_t nPixelsInBlock =
    1772           0 :             static_cast<size_t>(nBlockXSize) * nBlockYSize;
    1773           0 :         memset(pImage, 0, nPixelsInBlock * GDALGetDataTypeSizeBytes(eDataType));
    1774           0 :         return CE_None;
    1775             :     }
    1776           0 :     CPLError(CE_Failure, CPLE_AppDefined,
    1777             :              "Reading blocks in a EXR dataset created by Create() is not "
    1778             :              "supported");
    1779           0 :     return CE_Failure;
    1780             : }
    1781             : 
    1782             : /************************************************************************/
    1783             : /*                           IWriteBlock()                              */
    1784             : /************************************************************************/
    1785             : 
    1786           6 : CPLErr GDALEXRWritableRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff,
    1787             :                                               void *pImage)
    1788             : {
    1789           6 :     auto poGDS = cpl::down_cast<GDALEXRWritableDataset *>(poDS);
    1790           6 :     poGDS->WriteHeader();
    1791           6 :     if (!poGDS->m_pTOP)
    1792           0 :         return CE_Failure;
    1793             : 
    1794           6 :     poGDS->m_abWrittenBlocks[nBlockYOff * poGDS->m_nXBlocks + nBlockXOff] =
    1795           6 :         true;
    1796             : 
    1797           6 :     bool bAllBlocksDirty = true;
    1798           6 :     std::vector<GDALRasterBlock *> apoBlocks;
    1799           6 :     apoBlocks.resize(poGDS->nBands);
    1800          12 :     for (int iBand = 0; iBand < poGDS->nBands; ++iBand)
    1801             :     {
    1802           6 :         if (iBand + 1 != nBand)
    1803             :         {
    1804           0 :             apoBlocks[iBand] =
    1805           0 :                 cpl::down_cast<GDALEXRWritableRasterBand *>(
    1806             :                     poGDS->GetRasterBand(iBand + 1))
    1807           0 :                     ->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
    1808             : 
    1809           0 :             if (apoBlocks[iBand] == nullptr)
    1810             :             {
    1811           0 :                 bAllBlocksDirty = false;
    1812           0 :                 break;
    1813             :             }
    1814           0 :             else if (!apoBlocks[iBand]->GetDirty())
    1815             :             {
    1816           0 :                 apoBlocks[iBand]->DropLock();
    1817           0 :                 apoBlocks[iBand] = nullptr;
    1818           0 :                 bAllBlocksDirty = false;
    1819           0 :                 break;
    1820             :             }
    1821             :         }
    1822             :         else
    1823             :         {
    1824           6 :             apoBlocks[iBand] = nullptr;
    1825             :         }
    1826             :     }
    1827           6 :     if (!bAllBlocksDirty)
    1828             :     {
    1829           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1830             :                  "For block (%d, %d), blocks for some bands are not available "
    1831             :                  "in the cache. Corresponding data will be assumed to be zero.",
    1832             :                  nBlockXOff, nBlockYOff);
    1833             :     }
    1834             : 
    1835           6 :     CPLErr eErr = CE_None;
    1836             :     try
    1837             :     {
    1838          12 :         FrameBuffer fb;
    1839           6 :         const int x = nBlockXOff * nBlockXSize;
    1840           6 :         const int y = nBlockYOff * nBlockYSize;
    1841           6 :         const size_t nPixelsInBlock =
    1842           6 :             static_cast<size_t>(nBlockXSize) * nBlockYSize;
    1843           6 :         const GDALDataType eDstDT =
    1844           6 :             poGDS->m_pixelType == UINT ? GDT_UInt32 : GDT_Float32;
    1845          12 :         for (int iBand = 0; iBand < poGDS->nBands; iBand++)
    1846             :         {
    1847           6 :             char *const dstPtr =
    1848           6 :                 poGDS->m_pSliceBuffer +
    1849           6 :                 iBand * poGDS->m_nBufferEltSize * nPixelsInBlock;
    1850             :             const auto slice = Slice(
    1851             :                 poGDS->m_pixelType,
    1852           6 :                 dstPtr - (x * poGDS->m_nBufferEltSize +
    1853           6 :                           y * poGDS->m_nBufferEltSize * nBlockXSize),
    1854           6 :                 poGDS->m_nBufferEltSize, poGDS->m_nBufferEltSize * nBlockXSize);
    1855           6 :             fb.insert(poGDS->m_channelNames[iBand], slice);
    1856             : 
    1857           6 :             const void *srcPtr = nullptr;
    1858           6 :             if (iBand + 1 == nBand)
    1859           6 :                 srcPtr = pImage;
    1860           0 :             else if (apoBlocks[iBand])
    1861           0 :                 srcPtr = apoBlocks[iBand]->GetDataRef();
    1862             :             else
    1863             :             {
    1864           0 :                 memset(poGDS->m_pSliceBuffer +
    1865           0 :                            iBand * poGDS->m_nBufferEltSize * nPixelsInBlock,
    1866           0 :                        0, nPixelsInBlock * poGDS->m_nBufferEltSize);
    1867           0 :                 continue;
    1868             :             }
    1869             : 
    1870          12 :             GDALCopyWords64(srcPtr, eDataType,
    1871             :                             GDALGetDataTypeSizeBytes(eDataType),
    1872           6 :                             poGDS->m_pixelType == HALF
    1873           2 :                                 ? static_cast<void *>(&poGDS->m_bufferFloat[0])
    1874             :                                 : static_cast<void *>(dstPtr),
    1875             :                             eDstDT, GDALGetDataTypeSizeBytes(eDstDT),
    1876             :                             static_cast<GPtrDiff_t>(nPixelsInBlock));
    1877           6 :             if (poGDS->m_pixelType == HALF)
    1878             :             {
    1879           2 :                 if (poGDS->m_bRescaleDiv255)
    1880             :                 {
    1881           0 :                     for (size_t i = 0; i < nPixelsInBlock; i++)
    1882             :                     {
    1883           0 :                         poGDS->m_bufferHalf[iBand * nPixelsInBlock + i] =
    1884           0 :                             poGDS->m_bufferFloat[i] / 255.0f;
    1885             :                     }
    1886             :                 }
    1887             :                 else
    1888             :                 {
    1889      131074 :                     for (size_t i = 0; i < nPixelsInBlock; i++)
    1890             :                     {
    1891      131072 :                         poGDS->m_bufferHalf[iBand * nPixelsInBlock + i] =
    1892      131072 :                             poGDS->m_bufferFloat[i];
    1893             :                     }
    1894             :                 }
    1895             :             }
    1896             :         }
    1897             : 
    1898           6 :         poGDS->m_pTOP->setFrameBuffer(fb);
    1899           6 :         poGDS->m_pTOP->writeTile(nBlockXOff, nBlockYOff);
    1900             :     }
    1901           0 :     catch (const std::exception &e)
    1902             :     {
    1903           0 :         CPLError(CE_Failure, CPLE_AppDefined, "OpenEXR: %s", e.what());
    1904           0 :         eErr = CE_Failure;
    1905             :     }
    1906             : 
    1907          12 :     for (int iBand = 0; iBand < poGDS->nBands; ++iBand)
    1908             :     {
    1909           6 :         if (apoBlocks[iBand])
    1910             :         {
    1911           0 :             apoBlocks[iBand]->MarkClean();
    1912           0 :             apoBlocks[iBand]->DropLock();
    1913             :         }
    1914             :     }
    1915             : 
    1916           6 :     return eErr;
    1917             : }
    1918             : 
    1919             : /************************************************************************/
    1920             : /*                            Create()                                  */
    1921             : /************************************************************************/
    1922             : 
    1923          38 : GDALDataset *GDALEXRDataset::Create(const char *pszFilename, int nXSize,
    1924             :                                     int nYSize, int nBandsIn,
    1925             :                                     GDALDataType eType, char **papszOptions)
    1926             : {
    1927          38 :     if (nBandsIn == 0)
    1928           1 :         return nullptr;
    1929          37 :     const PixelType pixelType = getPixelType(eType, papszOptions);
    1930             : 
    1931          37 :     if (!CPLTestBool(CSLFetchNameValueDef(papszOptions, "TILED", "YES")))
    1932             :     {
    1933           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1934             :                  "Create() only supports tiled mode");
    1935           0 :         return nullptr;
    1936             :     }
    1937             : 
    1938          37 :     if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "OVERVIEWS", "NO")))
    1939             :     {
    1940           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1941             :                  "Create() does not support overview creation.");
    1942           0 :         return nullptr;
    1943             :     }
    1944             : 
    1945          37 :     if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "PREVIEW", "NO")))
    1946             :     {
    1947           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1948             :                  "Create() does not support preview creation.");
    1949           0 :         return nullptr;
    1950             :     }
    1951             : 
    1952          37 :     Compression compression = ZIP_COMPRESSION;
    1953             :     const char *pszCompress =
    1954          37 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "");
    1955          37 :     if (pszCompress[0] != '\0')
    1956             :     {
    1957           1 :         bool bFound = false;
    1958           2 :         for (size_t i = 0; i < CPL_ARRAYSIZE(apszCompressions); i++)
    1959             :         {
    1960           2 :             if (EQUAL(pszCompress, apszCompressions[i]))
    1961             :             {
    1962           1 :                 bFound = true;
    1963           1 :                 compression = static_cast<Compression>(i);
    1964           1 :                 break;
    1965             :             }
    1966             :         }
    1967           1 :         if (!bFound)
    1968             :         {
    1969           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown compression %s",
    1970             :                      pszCompress);
    1971           0 :             return nullptr;
    1972             :         }
    1973             :     }
    1974             : 
    1975             :     const int nBlockXSize =
    1976          37 :         atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "256"));
    1977             :     const int nBlockYSize =
    1978          37 :         atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "256"));
    1979          37 :     if (nBlockXSize <= 8 || nBlockYSize <= 8 || nBlockXSize >= 8192 ||
    1980             :         nBlockYSize >= 8192)
    1981             :     {
    1982           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
    1983           0 :         return nullptr;
    1984             :     }
    1985             : 
    1986          37 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb+");
    1987          37 :     if (fp == nullptr)
    1988             :     {
    1989           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
    1990           0 :         return nullptr;
    1991             :     }
    1992             :     auto poDS = std::unique_ptr<GDALEXRWritableDataset>(
    1993          74 :         new GDALEXRWritableDataset(nXSize, nYSize));
    1994          37 :     poDS->m_pOStream.reset(new GDALEXRIOStream(fp, pszFilename));
    1995          37 :     poDS->eAccess = GA_Update;
    1996          37 :     poDS->m_pixelType = pixelType;
    1997          37 :     poDS->m_header.compression() = compression;
    1998          37 :     poDS->m_header.setType(TILEDIMAGE);
    1999          74 :     poDS->m_header.setTileDescription(
    2000          37 :         TileDescription(nBlockXSize, nBlockYSize));
    2001          37 :     FillHeaderFromOptions(poDS->m_header, papszOptions);
    2002          37 :     poDS->m_nBlockXSize = nBlockXSize;
    2003          37 :     poDS->m_nBlockYSize = nBlockYSize;
    2004          37 :     poDS->m_nXBlocks = static_cast<size_t>(DIV_ROUND_UP(nXSize, nBlockXSize));
    2005          37 :     const size_t nYBlocks =
    2006          37 :         static_cast<size_t>(DIV_ROUND_UP(nYSize, nBlockYSize));
    2007          37 :     if (poDS->m_nXBlocks > std::numeric_limits<size_t>::max() / nYBlocks)
    2008             :     {
    2009           0 :         return nullptr;
    2010             :     }
    2011             :     try
    2012             :     {
    2013          37 :         poDS->m_abWrittenBlocks.resize(poDS->m_nXBlocks * nYBlocks);
    2014             :     }
    2015           0 :     catch (const std::exception &e)
    2016             :     {
    2017           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
    2018           0 :         return nullptr;
    2019             :     }
    2020          74 :     poDS->m_bRescaleDiv255 =
    2021          37 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "AUTO_RESCALE", "YES"));
    2022             : 
    2023          37 :     if (nBandsIn > 1)
    2024             :     {
    2025          17 :         poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    2026             :                                            "IMAGE_STRUCTURE");
    2027             :     }
    2028         110 :     for (int i = 0; i < nBandsIn; i++)
    2029             :     {
    2030         146 :         poDS->SetBand(i + 1,
    2031          73 :                       new GDALEXRWritableRasterBand(poDS.get(), i + 1, eType));
    2032             :     }
    2033          37 :     poDS->SetDescription(pszFilename);
    2034          37 :     poDS->TryLoadXML();
    2035          37 :     return poDS.release();
    2036             : }
    2037             : 
    2038             : /************************************************************************/
    2039             : /*                          GDALRegister_EXR()                          */
    2040             : /************************************************************************/
    2041             : 
    2042           7 : void GDALRegister_EXR()
    2043             : 
    2044             : {
    2045           7 :     if (!GDAL_CHECK_VERSION("EXR driver"))
    2046           0 :         return;
    2047             : 
    2048           7 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    2049           0 :         return;
    2050             : 
    2051           7 :     GDALDriver *poDriver = new GDALDriver();
    2052           7 :     EXRDriverSetCommonMetadata(poDriver);
    2053             : 
    2054           7 :     poDriver->pfnOpen = GDALEXRDataset::Open;
    2055           7 :     poDriver->pfnCreateCopy = GDALEXRDataset::CreateCopy;
    2056           7 :     poDriver->pfnCreate = GDALEXRDataset::Create;
    2057             : 
    2058           7 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2059             : }

Generated by: LCOV version 1.14