LCOV - code coverage report
Current view: top level - frmts/exr - exrdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 696 957 72.7 %
Date: 2026-06-19 21:24:00 Functions: 46 58 79.3 %

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

Generated by: LCOV version 1.14