LCOV - code coverage report
Current view: top level - frmts/jpegxl - jpegxl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1141 1458 78.3 %
Date: 2025-10-01 17:07:58 Functions: 25 25 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  JPEG-XL Driver
       4             :  * Purpose:  Implement GDAL JPEG-XL Support based on libjxl
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_error.h"
      14             : #include "cpl_multiproc.h"
      15             : #include "gdal_frmts.h"
      16             : #include "gdalexif.h"
      17             : #include "gdaljp2metadata.h"
      18             : #include "gdaljp2abstractdataset.h"
      19             : #include "gdalorienteddataset.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cassert>
      23             : #include <cstdlib>
      24             : #include <limits>
      25             : 
      26             : #include "jxl_headers.h"
      27             : 
      28             : #include "jpegxldrivercore.h"
      29             : 
      30             : namespace
      31             : {
      32             : struct VSILFileReleaser
      33             : {
      34          69 :     void operator()(VSILFILE *fp)
      35             :     {
      36          69 :         if (fp)
      37          69 :             VSIFCloseL(fp);
      38          69 :     }
      39             : };
      40             : }  // namespace
      41             : 
      42             : constexpr float MIN_DISTANCE = 0.01f;
      43             : 
      44             : /************************************************************************/
      45             : /*                        JPEGXLDataset                                 */
      46             : /************************************************************************/
      47             : 
      48             : class JPEGXLDataset final : public GDALJP2AbstractDataset
      49             : {
      50             :     friend class JPEGXLRasterBand;
      51             : 
      52             :     VSILFILE *m_fp = nullptr;
      53             :     JxlDecoderPtr m_decoder{};
      54             : #ifdef HAVE_JXL_THREADS
      55             :     JxlResizableParallelRunnerPtr m_parallelRunner{};
      56             : #endif
      57             :     bool m_bDecodingFailed = false;
      58             :     std::vector<GByte> m_abyImage{};
      59             :     std::vector<std::vector<GByte>> m_abyExtraChannels{};
      60             :     std::vector<GByte> m_abyInputData{};
      61             :     int m_nBits = 0;
      62             :     int m_nNonAlphaExtraChannels = 0;
      63             : #ifdef HAVE_JXL_BOX_API
      64             :     std::string m_osXMP{};
      65             :     char *m_apszXMP[2] = {nullptr, nullptr};
      66             :     std::vector<GByte> m_abyEXIFBox{};
      67             :     CPLStringList m_aosEXIFMetadata{};
      68             :     bool m_bHasJPEGReconstructionData = false;
      69             :     std::string m_osJPEGData{};
      70             : #endif
      71             : 
      72             :     bool Open(GDALOpenInfo *poOpenInfo);
      73             : 
      74             :     void GetDecodedImage(void *pabyOutputData, size_t nOutputDataSize);
      75             : 
      76             :   protected:
      77             :     CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
      78             :                      GDALDataType, int, BANDMAP_TYPE, GSpacing, GSpacing,
      79             :                      GSpacing, GDALRasterIOExtraArg *psExtraArg) override;
      80             : 
      81             :   public:
      82             :     ~JPEGXLDataset() override;
      83             : 
      84             :     CPLErr Close() override;
      85             : 
      86             :     char **GetMetadataDomainList() override;
      87             :     char **GetMetadata(const char *pszDomain) override;
      88             :     const char *GetMetadataItem(const char *pszName,
      89             :                                 const char *pszDomain) override;
      90             : 
      91             :     CPLStringList GetCompressionFormats(int nXOff, int nYOff, int nXSize,
      92             :                                         int nYSize, int nBandCount,
      93             :                                         const int *panBandList) override;
      94             :     CPLErr ReadCompressedData(const char *pszFormat, int nXOff, int nYOff,
      95             :                               int nXSize, int nYSize, int nBandCount,
      96             :                               const int *panBandList, void **ppBuffer,
      97             :                               size_t *pnBufferSize,
      98             :                               char **ppszDetailedFormat) override;
      99             : 
     100             :     const std::vector<GByte> &GetDecodedImage();
     101             : 
     102             :     static int Identify(GDALOpenInfo *poOpenInfo);
     103             :     static GDALPamDataset *OpenStaticPAM(GDALOpenInfo *poOpenInfo);
     104             :     static GDALDataset *OpenStatic(GDALOpenInfo *poOpenInfo);
     105             :     static GDALDataset *CreateCopy(const char *pszFilename,
     106             :                                    GDALDataset *poSrcDS, int bStrict,
     107             :                                    char **papszOptions,
     108             :                                    GDALProgressFunc pfnProgress,
     109             :                                    void *pProgressData);
     110             : };
     111             : 
     112             : /************************************************************************/
     113             : /*                      JPEGXLRasterBand                                */
     114             : /************************************************************************/
     115             : 
     116             : class JPEGXLRasterBand final : public GDALPamRasterBand
     117             : {
     118             :   protected:
     119             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
     120             : 
     121             :     CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
     122             :                      GDALDataType, GSpacing, GSpacing,
     123             :                      GDALRasterIOExtraArg *psExtraArg) override;
     124             : 
     125             :   public:
     126             :     JPEGXLRasterBand(JPEGXLDataset *poDSIn, int nBandIn,
     127             :                      GDALDataType eDataTypeIn, int nBitsPerSample,
     128             :                      GDALColorInterp eInterp);
     129             : };
     130             : 
     131             : /************************************************************************/
     132             : /*                         ~JPEGXLDataset()                             */
     133             : /************************************************************************/
     134             : 
     135         681 : JPEGXLDataset::~JPEGXLDataset()
     136             : {
     137         340 :     JPEGXLDataset::Close();
     138         680 : }
     139             : 
     140             : /************************************************************************/
     141             : /*                                Close()                               */
     142             : /************************************************************************/
     143             : 
     144         501 : CPLErr JPEGXLDataset::Close()
     145             : {
     146         501 :     CPLErr eErr = CE_None;
     147             : 
     148         501 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     149             :     {
     150         340 :         eErr = JPEGXLDataset::FlushCache(true);
     151             : 
     152         340 :         if (m_fp != nullptr && VSIFCloseL(m_fp) != 0)
     153           0 :             eErr = CE_Failure;
     154         340 :         m_fp = nullptr;
     155             : 
     156         340 :         eErr = GDAL::Combine(eErr, GDALPamDataset::Close());
     157             :     }
     158         502 :     return eErr;
     159             : }
     160             : 
     161             : /************************************************************************/
     162             : /*                         JPEGXLRasterBand()                           */
     163             : /************************************************************************/
     164             : 
     165         835 : JPEGXLRasterBand::JPEGXLRasterBand(JPEGXLDataset *poDSIn, int nBandIn,
     166             :                                    GDALDataType eDataTypeIn, int nBitsPerSample,
     167         835 :                                    GDALColorInterp eInterp)
     168             : {
     169         835 :     poDS = poDSIn;
     170         835 :     nBand = nBandIn;
     171         835 :     eDataType = eDataTypeIn;
     172         835 :     nRasterXSize = poDS->GetRasterXSize();
     173         836 :     nRasterYSize = poDS->GetRasterYSize();
     174         836 :     nBlockXSize = poDS->GetRasterXSize();
     175         836 :     nBlockYSize = 1;
     176         836 :     SetColorInterpretation(eInterp);
     177         839 :     if ((eDataType == GDT_Byte && nBitsPerSample < 8) ||
     178         837 :         (eDataType == GDT_UInt16 && nBitsPerSample < 16))
     179             :     {
     180           4 :         SetMetadataItem("NBITS", CPLSPrintf("%d", nBitsPerSample),
     181             :                         "IMAGE_STRUCTURE");
     182             :     }
     183         839 : }
     184             : 
     185             : /************************************************************************/
     186             : /*                             IReadBlock()                             */
     187             : /************************************************************************/
     188             : 
     189       35191 : CPLErr JPEGXLRasterBand::IReadBlock(int /*nBlockXOff*/, int nBlockYOff,
     190             :                                     void *pData)
     191             : {
     192       35191 :     auto poGDS = cpl::down_cast<JPEGXLDataset *>(poDS);
     193             : 
     194       35191 :     const auto &abyDecodedImage = poGDS->GetDecodedImage();
     195       35191 :     if (abyDecodedImage.empty())
     196             :     {
     197           0 :         return CE_Failure;
     198             :     }
     199             : 
     200       35191 :     const auto nDataSize = GDALGetDataTypeSizeBytes(eDataType);
     201       35191 :     const int nNonExtraBands = poGDS->nBands - poGDS->m_nNonAlphaExtraChannels;
     202       35191 :     if (nBand <= nNonExtraBands)
     203             :     {
     204       24545 :         GDALCopyWords(abyDecodedImage.data() +
     205       24545 :                           ((nBand - 1) + static_cast<size_t>(nBlockYOff) *
     206       24545 :                                              nRasterXSize * nNonExtraBands) *
     207       24545 :                               nDataSize,
     208             :                       eDataType, nDataSize * nNonExtraBands, pData, eDataType,
     209             :                       nDataSize, nRasterXSize);
     210             :     }
     211             :     else
     212             :     {
     213       10646 :         const uint32_t nIndex = nBand - 1 - nNonExtraBands;
     214       21292 :         memcpy(pData,
     215       10646 :                poGDS->m_abyExtraChannels[nIndex].data() +
     216       10646 :                    static_cast<size_t>(nBlockYOff) * nRasterXSize * nDataSize,
     217       10646 :                nRasterXSize * nDataSize);
     218             :     }
     219             : 
     220       35191 :     return CE_None;
     221             : }
     222             : 
     223             : /************************************************************************/
     224             : /*                         Identify()                                   */
     225             : /************************************************************************/
     226             : 
     227       45041 : int JPEGXLDataset::Identify(GDALOpenInfo *poOpenInfo)
     228             : {
     229       45041 :     if (poOpenInfo->fpL == nullptr)
     230       42456 :         return false;
     231             : 
     232        2585 :     if (poOpenInfo->IsExtensionEqualToCI("jxl"))
     233         185 :         return true;
     234             : 
     235             :     // See
     236             :     // https://github.com/libjxl/libjxl/blob/c98f133f3f5e456caaa2ba00bc920e923b713abc/lib/jxl/decode.cc#L107-L138
     237             : 
     238             :     // JPEG XL codestream
     239        2403 :     if (poOpenInfo->nHeaderBytes >= 2 && poOpenInfo->pabyHeader[0] == 0xff &&
     240         578 :         poOpenInfo->pabyHeader[1] == 0x0a)
     241             :     {
     242             :         // Two bytes is not enough to reliably identify, so let's try to decode
     243             :         // basic info
     244         745 :         auto decoder = JxlDecoderMake(nullptr);
     245         371 :         if (!decoder)
     246           0 :             return false;
     247             :         JxlDecoderStatus status =
     248         371 :             JxlDecoderSubscribeEvents(decoder.get(), JXL_DEC_BASIC_INFO);
     249         371 :         if (status != JXL_DEC_SUCCESS)
     250             :         {
     251           0 :             return false;
     252             :         }
     253             : 
     254         373 :         status = JxlDecoderSetInput(decoder.get(), poOpenInfo->pabyHeader,
     255         371 :                                     poOpenInfo->nHeaderBytes);
     256         372 :         if (status != JXL_DEC_SUCCESS)
     257             :         {
     258           0 :             return false;
     259             :         }
     260             : 
     261         372 :         status = JxlDecoderProcessInput(decoder.get());
     262         372 :         if (status != JXL_DEC_BASIC_INFO)
     263             :         {
     264           0 :             return false;
     265             :         }
     266             : 
     267         372 :         return true;
     268             :     }
     269             : 
     270        2030 :     return IsJPEGXLContainer(poOpenInfo);
     271             : }
     272             : 
     273             : /************************************************************************/
     274             : /*                             Open()                                   */
     275             : /************************************************************************/
     276             : 
     277         338 : bool JPEGXLDataset::Open(GDALOpenInfo *poOpenInfo)
     278             : {
     279         338 :     m_decoder = JxlDecoderMake(nullptr);
     280         341 :     if (!m_decoder)
     281             :     {
     282           0 :         CPLError(CE_Failure, CPLE_AppDefined, "JxlDecoderMake() failed");
     283           0 :         return false;
     284             :     }
     285             : 
     286             : #ifdef HAVE_JXL_THREADS
     287         341 :     m_parallelRunner = JxlResizableParallelRunnerMake(nullptr);
     288         339 :     if (!m_parallelRunner)
     289             :     {
     290           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     291             :                  "JxlResizableParallelRunnerMake() failed");
     292           0 :         return false;
     293             :     }
     294             : 
     295         341 :     if (JxlDecoderSetParallelRunner(m_decoder.get(), JxlResizableParallelRunner,
     296         340 :                                     m_parallelRunner.get()) != JXL_DEC_SUCCESS)
     297             :     {
     298           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     299             :                  "JxlDecoderSetParallelRunner() failed");
     300           0 :         return false;
     301             :     }
     302             : #endif
     303             : 
     304             :     JxlDecoderStatus status =
     305         340 :         JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO |
     306             : #ifdef HAVE_JXL_BOX_API
     307             :                                                        JXL_DEC_BOX |
     308             : #endif
     309             :                                                        JXL_DEC_COLOR_ENCODING);
     310         340 :     if (status != JXL_DEC_SUCCESS)
     311             :     {
     312           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     313             :                  "JxlDecoderSubscribeEvents() failed");
     314           0 :         return false;
     315             :     }
     316             : 
     317             :     JxlBasicInfo info;
     318         340 :     memset(&info, 0, sizeof(info));
     319         340 :     bool bGotInfo = false;
     320             : 
     321             :     // Steal file handle
     322         340 :     m_fp = poOpenInfo->fpL;
     323         340 :     poOpenInfo->fpL = nullptr;
     324         340 :     VSIFSeekL(m_fp, 0, SEEK_SET);
     325             : 
     326         339 :     m_abyInputData.resize(1024 * 1024);
     327             : 
     328             : #ifdef HAVE_JXL_BOX_API
     329         339 :     JxlDecoderSetDecompressBoxes(m_decoder.get(), TRUE);
     330         676 :     std::vector<GByte> abyBoxBuffer(1024 * 1024);
     331         678 :     std::string osCurrentBox;
     332         680 :     std::vector<GByte> abyJumbBoxBuffer;
     333          63 :     const auto ProcessCurrentBox = [&]()
     334             :     {
     335             :         const size_t nRemainingBytesInBuffer =
     336          63 :             JxlDecoderReleaseBoxBuffer(m_decoder.get());
     337          63 :         CPLAssert(nRemainingBytesInBuffer < abyBoxBuffer.size());
     338          63 :         if (osCurrentBox == "xml " && m_osXMP.empty())
     339             :         {
     340           4 :             std::string osXML(reinterpret_cast<char *>(abyBoxBuffer.data()),
     341           8 :                               abyBoxBuffer.size() - nRemainingBytesInBuffer);
     342           4 :             if (osXML.compare(0, strlen("<?xpacket"), "<?xpacket") == 0)
     343             :             {
     344           4 :                 m_osXMP = std::move(osXML);
     345             :             }
     346             :         }
     347          59 :         else if (osCurrentBox == "Exif" && m_aosEXIFMetadata.empty())
     348             :         {
     349          18 :             const size_t nSize = abyBoxBuffer.size() - nRemainingBytesInBuffer;
     350             :             // The first 4 bytes are at 0, before the TIFF EXIF file content
     351          18 :             if (nSize > 12 && abyBoxBuffer[0] == 0 && abyBoxBuffer[1] == 0 &&
     352          54 :                 abyBoxBuffer[2] == 0 && abyBoxBuffer[3] == 0 &&
     353          18 :                 (abyBoxBuffer[4] == 0x4d  // TIFF_BIGENDIAN
     354          18 :                  || abyBoxBuffer[4] == 0x49 /* TIFF_LITTLEENDIAN */))
     355             :             {
     356           0 :                 m_abyEXIFBox.insert(m_abyEXIFBox.end(), abyBoxBuffer.data() + 4,
     357          18 :                                     abyBoxBuffer.data() + nSize);
     358             : #ifdef CPL_LSB
     359          18 :                 const bool bSwab = abyBoxBuffer[4] == 0x4d;
     360             : #else
     361             :                 const bool bSwab = abyBoxBuffer[4] == 0x49;
     362             : #endif
     363          18 :                 constexpr int nTIFFHEADER = 0;
     364             :                 uint32_t nTiffDirStart;
     365          18 :                 memcpy(&nTiffDirStart, abyBoxBuffer.data() + 8,
     366             :                        sizeof(uint32_t));
     367          18 :                 if (bSwab)
     368             :                 {
     369           0 :                     CPL_LSBPTR32(&nTiffDirStart);
     370             :                 }
     371             :                 VSILFILE *fpEXIF =
     372          18 :                     VSIFileFromMemBuffer(nullptr, abyBoxBuffer.data() + 4,
     373          18 :                                          abyBoxBuffer.size() - 4, false);
     374          18 :                 int nExifOffset = 0;
     375          18 :                 int nInterOffset = 0;
     376          18 :                 int nGPSOffset = 0;
     377          18 :                 char **papszEXIFMetadata = nullptr;
     378          18 :                 EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nTiffDirStart,
     379             :                                     bSwab, nTIFFHEADER, nExifOffset,
     380             :                                     nInterOffset, nGPSOffset);
     381             : 
     382          18 :                 if (nExifOffset > 0)
     383             :                 {
     384           9 :                     EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nExifOffset,
     385             :                                         bSwab, nTIFFHEADER, nExifOffset,
     386             :                                         nInterOffset, nGPSOffset);
     387             :                 }
     388          18 :                 if (nInterOffset > 0)
     389             :                 {
     390           0 :                     EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nInterOffset,
     391             :                                         bSwab, nTIFFHEADER, nExifOffset,
     392             :                                         nInterOffset, nGPSOffset);
     393             :                 }
     394          18 :                 if (nGPSOffset > 0)
     395             :                 {
     396           9 :                     EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nGPSOffset,
     397             :                                         bSwab, nTIFFHEADER, nExifOffset,
     398             :                                         nInterOffset, nGPSOffset);
     399             :                 }
     400          18 :                 VSIFCloseL(fpEXIF);
     401          18 :                 m_aosEXIFMetadata.Assign(papszEXIFMetadata,
     402          18 :                                          /*takeOwnership=*/true);
     403             :             }
     404             :         }
     405          41 :         else if (osCurrentBox == "jumb")
     406             :         {
     407          41 :             abyJumbBoxBuffer = abyBoxBuffer;
     408             :         }
     409          63 :         osCurrentBox.clear();
     410          63 :     };
     411             : 
     412             :     // Process input to get boxes and basic info
     413         341 :     const uint64_t nMaxBoxBufferSize = std::strtoull(
     414             :         CPLGetConfigOption("GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE", "100000000"),
     415             :         nullptr, 10);
     416             : #endif
     417             : 
     418         341 :     int l_nBands = 0;
     419         341 :     GDALDataType eDT = GDT_Unknown;
     420             : 
     421             :     while (true)
     422             :     {
     423        1884 :         status = JxlDecoderProcessInput(m_decoder.get());
     424             : 
     425             : #ifdef HAVE_JXL_BOX_API
     426        2741 :         if ((status == JXL_DEC_SUCCESS || status == JXL_DEC_BOX) &&
     427         856 :             !osCurrentBox.empty())
     428             :         {
     429             :             try
     430             :             {
     431          63 :                 ProcessCurrentBox();
     432             :             }
     433           0 :             catch (const std::exception &)
     434             :             {
     435           0 :                 CPLError(CE_Warning, CPLE_OutOfMemory,
     436             :                          "Not enough memory to read box '%s'",
     437             :                          osCurrentBox.c_str());
     438             :             }
     439             :         }
     440             : #endif
     441             : 
     442        1886 :         if (status == JXL_DEC_SUCCESS)
     443             :         {
     444         339 :             break;
     445             :         }
     446        1547 :         else if (status == JXL_DEC_NEED_MORE_INPUT)
     447             :         {
     448         341 :             JxlDecoderReleaseInput(m_decoder.get());
     449             : 
     450         341 :             const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
     451             :                                            m_abyInputData.size(), m_fp);
     452         340 :             if (nRead == 0)
     453             :             {
     454             : #ifdef HAVE_JXL_BOX_API
     455           0 :                 JxlDecoderCloseInput(m_decoder.get());
     456             : #endif
     457           0 :                 break;
     458             :             }
     459         340 :             if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
     460         340 :                                    nRead) != JXL_DEC_SUCCESS)
     461             :             {
     462           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     463             :                          "JxlDecoderSetInput() failed");
     464           0 :                 return false;
     465             :             }
     466             : #ifdef HAVE_JXL_BOX_API
     467         340 :             if (nRead < m_abyInputData.size())
     468             :             {
     469         341 :                 JxlDecoderCloseInput(m_decoder.get());
     470             :             }
     471             : #endif
     472             :         }
     473        1206 :         else if (status == JXL_DEC_BASIC_INFO)
     474             :         {
     475         339 :             bGotInfo = true;
     476         339 :             status = JxlDecoderGetBasicInfo(m_decoder.get(), &info);
     477         338 :             if (status != JXL_DEC_SUCCESS)
     478             :             {
     479           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     480             :                          "JxlDecoderGetBasicInfo() failed");
     481           0 :                 return false;
     482             :             }
     483             : 
     484         338 :             if (info.xsize > static_cast<uint32_t>(INT_MAX) ||
     485         341 :                 info.ysize > static_cast<uint32_t>(INT_MAX))
     486             :             {
     487           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too big raster");
     488           0 :                 return false;
     489             :             }
     490             : 
     491         339 :             nRasterXSize = static_cast<int>(info.xsize);
     492         339 :             nRasterYSize = static_cast<int>(info.ysize);
     493             : 
     494         339 :             m_nBits = info.bits_per_sample;
     495         339 :             if (info.exponent_bits_per_sample == 0)
     496             :             {
     497         322 :                 if (info.bits_per_sample <= 8)
     498         282 :                     eDT = GDT_Byte;
     499          40 :                 else if (info.bits_per_sample <= 16)
     500          41 :                     eDT = GDT_UInt16;
     501             :             }
     502          17 :             else if (info.exponent_bits_per_sample == 5)
     503             :             {
     504             :                 // Float16
     505           2 :                 CPLDebug("JXL", "16-bit floating point data");
     506           2 :                 eDT = GDT_Float32;
     507             :             }
     508          15 :             else if (info.exponent_bits_per_sample == 8)
     509             :             {
     510          14 :                 eDT = GDT_Float32;
     511             :             }
     512         339 :             if (eDT == GDT_Unknown)
     513             :             {
     514           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Unhandled data type");
     515           0 :                 return false;
     516             :             }
     517             : 
     518         339 :             l_nBands = static_cast<int>(info.num_color_channels) +
     519         339 :                        static_cast<int>(info.num_extra_channels);
     520         339 :             if (info.num_extra_channels == 1 &&
     521          61 :                 (info.num_color_channels == 1 ||
     522          36 :                  info.num_color_channels == 3) &&
     523          61 :                 info.alpha_bits != 0)
     524             :             {
     525          53 :                 m_nNonAlphaExtraChannels = 0;
     526             :             }
     527             :             else
     528             :             {
     529         286 :                 m_nNonAlphaExtraChannels =
     530         286 :                     static_cast<int>(info.num_extra_channels);
     531             :             }
     532             :         }
     533             : #ifdef HAVE_JXL_BOX_API
     534         867 :         else if (status == JXL_DEC_BOX)
     535             :         {
     536         517 :             osCurrentBox.clear();
     537         517 :             JxlBoxType type = {0};
     538         517 :             if (JxlDecoderGetBoxType(m_decoder.get(), type,
     539         517 :                                      /* decompressed = */ TRUE) !=
     540             :                 JXL_DEC_SUCCESS)
     541             :             {
     542           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     543             :                          "JxlDecoderGetBoxType() failed");
     544           0 :                 continue;
     545             :             }
     546         517 :             char szType[5] = {0};
     547         517 :             memcpy(szType, type, sizeof(type));
     548             :             // CPLDebug("JPEGXL", "box: %s", szType);
     549         517 :             if (strcmp(szType, "xml ") == 0 || strcmp(szType, "Exif") == 0 ||
     550         494 :                 strcmp(szType, "jumb") == 0)
     551             :             {
     552          64 :                 uint64_t nRawSize = 0;
     553          64 :                 JxlDecoderGetBoxSizeRaw(m_decoder.get(), &nRawSize);
     554          64 :                 if (nRawSize > nMaxBoxBufferSize)
     555             :                 {
     556           0 :                     CPLError(
     557             :                         CE_Warning, CPLE_OutOfMemory,
     558             :                         "Reading a '%s' box involves at least " CPL_FRMT_GUIB
     559             :                         " bytes, "
     560             :                         "but the current limitation of the "
     561             :                         "GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE "
     562             :                         "configuration option is " CPL_FRMT_GUIB " bytes",
     563             :                         szType, static_cast<GUIntBig>(nRawSize),
     564             :                         static_cast<GUIntBig>(nMaxBoxBufferSize));
     565           0 :                     continue;
     566             :                 }
     567          64 :                 if (nRawSize > abyBoxBuffer.size())
     568             :                 {
     569           0 :                     if (nRawSize > std::numeric_limits<size_t>::max() / 2)
     570             :                     {
     571           0 :                         CPLError(CE_Warning, CPLE_OutOfMemory,
     572             :                                  "Not enough memory to read box '%s'", szType);
     573           0 :                         continue;
     574             :                     }
     575             :                     try
     576             :                     {
     577           0 :                         abyBoxBuffer.clear();
     578           0 :                         abyBoxBuffer.resize(static_cast<size_t>(nRawSize));
     579             :                     }
     580           0 :                     catch (const std::exception &)
     581             :                     {
     582           0 :                         abyBoxBuffer.resize(1024 * 1024);
     583           0 :                         CPLError(CE_Warning, CPLE_OutOfMemory,
     584             :                                  "Not enough memory to read box '%s'", szType);
     585           0 :                         continue;
     586             :                     }
     587             :                 }
     588             : 
     589          64 :                 if (JxlDecoderSetBoxBuffer(m_decoder.get(), abyBoxBuffer.data(),
     590          64 :                                            abyBoxBuffer.size()) !=
     591             :                     JXL_DEC_SUCCESS)
     592             :                 {
     593           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     594             :                              "JxlDecoderSetBoxBuffer() failed");
     595           0 :                     continue;
     596             :                 }
     597          64 :                 osCurrentBox = szType;
     598             :             }
     599         453 :             else if (strcmp(szType, "jbrd") == 0)
     600             :             {
     601          18 :                 m_bHasJPEGReconstructionData = true;
     602             :             }
     603             :         }
     604             : #endif
     605         350 :         else if (status == JXL_DEC_COLOR_ENCODING)
     606             :         {
     607             : #ifdef HAVE_JxlDecoderDefaultPixelFormat
     608             :             JxlPixelFormat format = {
     609             :                 static_cast<uint32_t>(nBands),
     610             :                 eDT == GDT_Byte     ? JXL_TYPE_UINT8
     611             :                 : eDT == GDT_UInt16 ? JXL_TYPE_UINT16
     612             :                                     : JXL_TYPE_FLOAT,
     613             :                 JXL_NATIVE_ENDIAN, 0 /* alignment */
     614             :             };
     615             : #endif
     616             : 
     617         341 :             bool bIsDefaultColorEncoding = false;
     618             :             JxlColorEncoding color_encoding;
     619             : 
     620             :             // Check if the color profile is the default one we set on creation.
     621             :             // If so, do not expose it as ICC color profile
     622         341 :             if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile(
     623         341 :                                        m_decoder.get(),
     624             : #ifdef HAVE_JxlDecoderDefaultPixelFormat
     625             :                                        &format,
     626             : #endif
     627             :                                        JXL_COLOR_PROFILE_TARGET_DATA,
     628             :                                        &color_encoding))
     629             :             {
     630             :                 JxlColorEncoding default_color_encoding;
     631         339 :                 JxlColorEncodingSetToSRGB(&default_color_encoding,
     632         339 :                                           info.num_color_channels ==
     633             :                                               1 /*is_gray*/);
     634             : 
     635         339 :                 bIsDefaultColorEncoding =
     636         339 :                     color_encoding.color_space ==
     637         678 :                         default_color_encoding.color_space &&
     638         339 :                     color_encoding.white_point ==
     639         339 :                         default_color_encoding.white_point &&
     640         339 :                     color_encoding.white_point_xy[0] ==
     641         339 :                         default_color_encoding.white_point_xy[0] &&
     642         339 :                     color_encoding.white_point_xy[1] ==
     643         339 :                         default_color_encoding.white_point_xy[1] &&
     644         339 :                     (color_encoding.color_space == JXL_COLOR_SPACE_GRAY ||
     645         159 :                      color_encoding.color_space == JXL_COLOR_SPACE_XYB ||
     646         159 :                      (color_encoding.primaries ==
     647         159 :                           default_color_encoding.primaries &&
     648         159 :                       color_encoding.primaries_red_xy[0] ==
     649         159 :                           default_color_encoding.primaries_red_xy[0] &&
     650         159 :                       color_encoding.primaries_red_xy[1] ==
     651         159 :                           default_color_encoding.primaries_red_xy[1] &&
     652         159 :                       color_encoding.primaries_green_xy[0] ==
     653         159 :                           default_color_encoding.primaries_green_xy[0] &&
     654         159 :                       color_encoding.primaries_green_xy[1] ==
     655         159 :                           default_color_encoding.primaries_green_xy[1] &&
     656         159 :                       color_encoding.primaries_blue_xy[0] ==
     657         159 :                           default_color_encoding.primaries_blue_xy[0] &&
     658         159 :                       color_encoding.primaries_blue_xy[1] ==
     659         159 :                           default_color_encoding.primaries_blue_xy[1])) &&
     660         339 :                     color_encoding.transfer_function ==
     661         339 :                         default_color_encoding.transfer_function &&
     662        1015 :                     color_encoding.gamma == default_color_encoding.gamma &&
     663         337 :                     color_encoding.rendering_intent ==
     664         337 :                         default_color_encoding.rendering_intent;
     665             :             }
     666             : 
     667         341 :             if (!bIsDefaultColorEncoding)
     668             :             {
     669           4 :                 size_t icc_size = 0;
     670           4 :                 if (JXL_DEC_SUCCESS ==
     671           4 :                     JxlDecoderGetICCProfileSize(m_decoder.get(),
     672             : #ifdef HAVE_JxlDecoderDefaultPixelFormat
     673             :                                                 &format,
     674             : #endif
     675             :                                                 JXL_COLOR_PROFILE_TARGET_DATA,
     676             :                                                 &icc_size))
     677             :                 {
     678           8 :                     std::vector<GByte> icc(icc_size);
     679           8 :                     if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsICCProfile(
     680           4 :                                                m_decoder.get(),
     681             : #ifdef HAVE_JxlDecoderDefaultPixelFormat
     682             :                                                &format,
     683             : #endif
     684             :                                                JXL_COLOR_PROFILE_TARGET_DATA,
     685             :                                                icc.data(), icc_size))
     686             :                     {
     687             :                         // Escape the profile.
     688           4 :                         char *pszBase64Profile = CPLBase64Encode(
     689           4 :                             static_cast<int>(icc.size()), icc.data());
     690             : 
     691             :                         // Set ICC profile metadata.
     692           4 :                         SetMetadataItem("SOURCE_ICC_PROFILE", pszBase64Profile,
     693           4 :                                         "COLOR_PROFILE");
     694             : 
     695           4 :                         CPLFree(pszBase64Profile);
     696             :                     }
     697             :                 }
     698             :             }
     699             :         }
     700             : #ifdef HAVE_JXL_BOX_API
     701           9 :         else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT)
     702             :         {
     703             :             // Grow abyBoxBuffer if it is too small
     704             :             const size_t nRemainingBytesInBuffer =
     705           7 :                 JxlDecoderReleaseBoxBuffer(m_decoder.get());
     706             :             const size_t nBytesUsed =
     707           7 :                 abyBoxBuffer.size() - nRemainingBytesInBuffer;
     708           7 :             if (abyBoxBuffer.size() > std::numeric_limits<size_t>::max() / 2)
     709             :             {
     710           0 :                 CPLError(CE_Warning, CPLE_OutOfMemory,
     711             :                          "Not enough memory to read box '%s'",
     712             :                          osCurrentBox.c_str());
     713           0 :                 osCurrentBox.clear();
     714           0 :                 continue;
     715             :             }
     716           7 :             const size_t nNewBoxBufferSize = abyBoxBuffer.size() * 2;
     717           7 :             if (nNewBoxBufferSize > nMaxBoxBufferSize)
     718             :             {
     719           1 :                 CPLError(CE_Warning, CPLE_OutOfMemory,
     720             :                          "Reading a '%s' box involves at least " CPL_FRMT_GUIB
     721             :                          " bytes, "
     722             :                          "but the current limitation of the "
     723             :                          "GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE "
     724             :                          "configuration option is " CPL_FRMT_GUIB " bytes",
     725             :                          osCurrentBox.c_str(),
     726             :                          static_cast<GUIntBig>(nNewBoxBufferSize),
     727             :                          static_cast<GUIntBig>(nMaxBoxBufferSize));
     728           1 :                 osCurrentBox.clear();
     729           1 :                 continue;
     730             :             }
     731             :             try
     732             :             {
     733           6 :                 abyBoxBuffer.resize(nNewBoxBufferSize);
     734             :             }
     735           0 :             catch (const std::exception &)
     736             :             {
     737           0 :                 CPLError(CE_Warning, CPLE_OutOfMemory,
     738             :                          "Not enough memory to read box '%s'",
     739             :                          osCurrentBox.c_str());
     740           0 :                 osCurrentBox.clear();
     741           0 :                 continue;
     742             :             }
     743           6 :             if (JxlDecoderSetBoxBuffer(
     744           6 :                     m_decoder.get(), abyBoxBuffer.data() + nBytesUsed,
     745          12 :                     abyBoxBuffer.size() - nBytesUsed) != JXL_DEC_SUCCESS)
     746             :             {
     747           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     748             :                          "JxlDecoderSetBoxBuffer() failed");
     749           0 :                 osCurrentBox.clear();
     750           0 :                 continue;
     751             :             }
     752             :         }
     753             : #endif
     754             :         else
     755             :         {
     756           2 :             CPLError(CE_Warning, CPLE_AppDefined, "Unexpected event: %d",
     757             :                      status);
     758           0 :             break;
     759             :         }
     760        1543 :     }
     761             : 
     762         339 :     JxlDecoderReleaseInput(m_decoder.get());
     763             : 
     764             : #ifdef HAVE_JXL_BOX_API
     765             :     // Load georeferencing from jumb box or from worldfile sidecar.
     766         340 :     if (!abyJumbBoxBuffer.empty())
     767             :     {
     768          41 :         VSILFILE *fpJUMB = VSIFileFromMemBuffer(
     769          41 :             nullptr, abyJumbBoxBuffer.data(), abyJumbBoxBuffer.size(), false);
     770          41 :         LoadJP2Metadata(poOpenInfo, nullptr, fpJUMB);
     771          41 :         VSIFCloseL(fpJUMB);
     772             :     }
     773             : #else
     774             :     if (IsJPEGXLContainer(poOpenInfo))
     775             :     {
     776             :         // A JPEGXL container can be explored with the JPEG2000 box reading
     777             :         // logic
     778             :         VSIFSeekL(m_fp, 12, SEEK_SET);
     779             :         poOpenInfo->fpL = m_fp;
     780             :         LoadJP2Metadata(poOpenInfo);
     781             :         poOpenInfo->fpL = nullptr;
     782             :     }
     783             : #endif
     784             :     else
     785             :     {
     786             :         // Only try to read worldfile
     787         298 :         VSILFILE *fpDummy = VSIFileFromMemBuffer(nullptr, nullptr, 0, false);
     788         296 :         LoadJP2Metadata(poOpenInfo, nullptr, fpDummy);
     789         299 :         VSIFCloseL(fpDummy);
     790             :     }
     791             : 
     792         339 :     if (!bGotInfo)
     793             :     {
     794           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Did not get basic info");
     795           0 :         return false;
     796             :     }
     797             : 
     798         339 :     GDALDataset::SetMetadataItem("COMPRESSION_REVERSIBILITY",
     799         339 :                                  info.uses_original_profile
     800             : #ifdef HAVE_JXL_BOX_API
     801         314 :                                          && !m_bHasJPEGReconstructionData
     802             : #endif
     803             :                                      ? "LOSSLESS (possibly)"
     804             :                                      : "LOSSY",
     805             :                                  "IMAGE_STRUCTURE");
     806             : #ifdef HAVE_JXL_BOX_API
     807         338 :     if (m_bHasJPEGReconstructionData)
     808             :     {
     809          18 :         GDALDataset::SetMetadataItem("ORIGINAL_COMPRESSION", "JPEG",
     810             :                                      "IMAGE_STRUCTURE");
     811             :     }
     812             : #endif
     813             : 
     814             : #ifdef HAVE_JXL_THREADS
     815             :     const char *pszNumThreads =
     816         338 :         CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
     817         682 :     uint32_t nMaxThreads = static_cast<uint32_t>(
     818         341 :         EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
     819           0 :                                          : atoi(pszNumThreads));
     820         341 :     if (nMaxThreads > 1024)
     821           0 :         nMaxThreads = 1024;  // to please Coverity
     822             : 
     823             :     const uint32_t nThreads = std::min(
     824             :         nMaxThreads,
     825         341 :         JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
     826         339 :     CPLDebug("JPEGXL", "Using %u threads", nThreads);
     827         341 :     JxlResizableParallelRunnerSetThreads(m_parallelRunner.get(), nThreads);
     828             : #endif
     829             : 
     830             :     // Instantiate bands
     831         338 :     const int nNonExtraBands = l_nBands - m_nNonAlphaExtraChannels;
     832        1172 :     for (int i = 1; i <= l_nBands; i++)
     833             :     {
     834         831 :         GDALColorInterp eInterp = GCI_Undefined;
     835         831 :         if (info.num_color_channels == 1)
     836             :         {
     837         278 :             if (i == 1 && l_nBands <= 2)
     838         160 :                 eInterp = GCI_GrayIndex;
     839         118 :             else if (i == 2 && info.num_extra_channels == 1 &&
     840          25 :                      info.alpha_bits != 0)
     841          17 :                 eInterp = GCI_AlphaBand;
     842             :         }
     843         553 :         else if (info.num_color_channels == 3)
     844             :         {
     845         553 :             if (i <= 3)
     846         475 :                 eInterp = static_cast<GDALColorInterp>(GCI_RedBand + (i - 1));
     847          78 :             else if (i == 4 && info.num_extra_channels == 1 &&
     848          36 :                      info.alpha_bits != 0)
     849          36 :                 eInterp = GCI_AlphaBand;
     850             :         }
     851        1664 :         std::string osBandName;
     852             : 
     853         831 :         if (i - 1 >= nNonExtraBands)
     854             :         {
     855             :             JxlExtraChannelInfo sExtraInfo;
     856         123 :             memset(&sExtraInfo, 0, sizeof(sExtraInfo));
     857         123 :             const size_t nIndex = static_cast<size_t>(i - 1 - nNonExtraBands);
     858         123 :             if (JxlDecoderGetExtraChannelInfo(m_decoder.get(), nIndex,
     859         123 :                                               &sExtraInfo) == JXL_DEC_SUCCESS)
     860             :             {
     861         123 :                 switch (sExtraInfo.type)
     862             :                 {
     863          21 :                     case JXL_CHANNEL_ALPHA:
     864          21 :                         eInterp = GCI_AlphaBand;
     865          21 :                         break;
     866           0 :                     case JXL_CHANNEL_DEPTH:
     867           0 :                         osBandName = "Depth channel";
     868           0 :                         break;
     869           0 :                     case JXL_CHANNEL_SPOT_COLOR:
     870           0 :                         osBandName = "Spot color channel";
     871           0 :                         break;
     872           0 :                     case JXL_CHANNEL_SELECTION_MASK:
     873           0 :                         osBandName = "Selection mask channel";
     874           0 :                         break;
     875           0 :                     case JXL_CHANNEL_BLACK:
     876           0 :                         osBandName = "Black channel";
     877           0 :                         break;
     878           0 :                     case JXL_CHANNEL_CFA:
     879           0 :                         osBandName = "CFA channel";
     880           0 :                         break;
     881           0 :                     case JXL_CHANNEL_THERMAL:
     882           0 :                         osBandName = "Thermal channel";
     883           0 :                         break;
     884         102 :                     case JXL_CHANNEL_RESERVED0:
     885             :                     case JXL_CHANNEL_RESERVED1:
     886             :                     case JXL_CHANNEL_RESERVED2:
     887             :                     case JXL_CHANNEL_RESERVED3:
     888             :                     case JXL_CHANNEL_RESERVED4:
     889             :                     case JXL_CHANNEL_RESERVED5:
     890             :                     case JXL_CHANNEL_RESERVED6:
     891             :                     case JXL_CHANNEL_RESERVED7:
     892             :                     case JXL_CHANNEL_UNKNOWN:
     893             :                     case JXL_CHANNEL_OPTIONAL:
     894         102 :                         break;
     895             :                 }
     896             : 
     897         123 :                 if (sExtraInfo.name_length > 0)
     898             :                 {
     899         172 :                     std::string osName;
     900          86 :                     osName.resize(sExtraInfo.name_length);
     901          86 :                     if (JxlDecoderGetExtraChannelName(
     902          86 :                             m_decoder.get(), nIndex, &osName[0],
     903         258 :                             osName.size() + 1) == JXL_DEC_SUCCESS &&
     904          86 :                         osName != CPLSPrintf("Band %d", i))
     905             :                     {
     906           2 :                         osBandName = std::move(osName);
     907             :                     }
     908             :                 }
     909             :             }
     910             :         }
     911             : 
     912             :         auto poBand = new JPEGXLRasterBand(
     913         831 :             this, i, eDT, static_cast<int>(info.bits_per_sample), eInterp);
     914         839 :         SetBand(i, poBand);
     915         833 :         if (!osBandName.empty())
     916           2 :             poBand->SetDescription(osBandName.c_str());
     917             :     }
     918             : 
     919         341 :     if (l_nBands > 1)
     920             :     {
     921         205 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     922             :     }
     923             : 
     924             :     // Initialize any PAM information.
     925         339 :     SetDescription(poOpenInfo->pszFilename);
     926         338 :     TryLoadXML(poOpenInfo->GetSiblingFiles());
     927         676 :     oOvManager.Initialize(this, poOpenInfo->pszFilename,
     928         336 :                           poOpenInfo->GetSiblingFiles());
     929             : 
     930         339 :     nPamFlags &= ~GPF_DIRTY;
     931             : 
     932         339 :     return true;
     933             : }
     934             : 
     935             : /************************************************************************/
     936             : /*                        GetDecodedImage()                             */
     937             : /************************************************************************/
     938             : 
     939       35208 : const std::vector<GByte> &JPEGXLDataset::GetDecodedImage()
     940             : {
     941       35208 :     if (m_bDecodingFailed || !m_abyImage.empty())
     942       35122 :         return m_abyImage;
     943             : 
     944          86 :     const auto eDT = GetRasterBand(1)->GetRasterDataType();
     945          86 :     const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
     946          86 :     assert(nDataSize > 0);
     947          86 :     const int nNonExtraBands = nBands - m_nNonAlphaExtraChannels;
     948          86 :     if (static_cast<size_t>(nRasterXSize) > std::numeric_limits<size_t>::max() /
     949          86 :                                                 nRasterYSize / nDataSize /
     950          86 :                                                 nNonExtraBands)
     951             :     {
     952           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     953             :                  "Image too big for architecture");
     954           0 :         m_bDecodingFailed = true;
     955           0 :         return m_abyImage;
     956             :     }
     957             : 
     958             :     try
     959             :     {
     960          86 :         m_abyImage.resize(static_cast<size_t>(nRasterXSize) * nRasterYSize *
     961          86 :                           nNonExtraBands * nDataSize);
     962             :     }
     963           0 :     catch (const std::exception &e)
     964             :     {
     965           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     966           0 :                  "Cannot allocate image buffer: %s", e.what());
     967           0 :         m_bDecodingFailed = true;
     968           0 :         return m_abyImage;
     969             :     }
     970             : 
     971          86 :     m_abyExtraChannels.resize(m_nNonAlphaExtraChannels);
     972         162 :     for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
     973             :     {
     974             :         try
     975             :         {
     976          76 :             m_abyExtraChannels[i].resize(static_cast<size_t>(nRasterXSize) *
     977          76 :                                          nRasterYSize * nDataSize);
     978             :         }
     979           0 :         catch (const std::exception &e)
     980             :         {
     981           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
     982           0 :                      "Cannot allocate image buffer: %s", e.what());
     983           0 :             m_bDecodingFailed = true;
     984           0 :             return m_abyImage;
     985             :         }
     986             :     }
     987             : 
     988          86 :     GetDecodedImage(m_abyImage.data(), m_abyImage.size());
     989             : 
     990          86 :     if (m_bDecodingFailed)
     991           0 :         m_abyImage.clear();
     992             : 
     993          86 :     return m_abyImage;
     994             : }
     995             : 
     996             : /************************************************************************/
     997             : /*                      GetMetadataDomainList()                         */
     998             : /************************************************************************/
     999             : 
    1000           3 : char **JPEGXLDataset::GetMetadataDomainList()
    1001             : {
    1002           3 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    1003           3 :                                    TRUE, "xml:XMP", "EXIF", nullptr);
    1004             : }
    1005             : 
    1006             : /************************************************************************/
    1007             : /*                            GetMetadata()                             */
    1008             : /************************************************************************/
    1009             : 
    1010         210 : char **JPEGXLDataset::GetMetadata(const char *pszDomain)
    1011             : {
    1012             : #ifdef HAVE_JXL_BOX_API
    1013         210 :     if (pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP") && !m_osXMP.empty())
    1014             :     {
    1015           3 :         m_apszXMP[0] = &m_osXMP[0];
    1016           3 :         return m_apszXMP;
    1017             :     }
    1018             : 
    1019         221 :     if (pszDomain != nullptr && EQUAL(pszDomain, "EXIF") &&
    1020          14 :         !m_aosEXIFMetadata.empty())
    1021             :     {
    1022          10 :         return m_aosEXIFMetadata.List();
    1023             :     }
    1024             : #endif
    1025         197 :     return GDALPamDataset::GetMetadata(pszDomain);
    1026             : }
    1027             : 
    1028             : /************************************************************************/
    1029             : /*                       GetCompressionFormats()                        */
    1030             : /************************************************************************/
    1031             : 
    1032           2 : CPLStringList JPEGXLDataset::GetCompressionFormats(int nXOff, int nYOff,
    1033             :                                                    int nXSize, int nYSize,
    1034             :                                                    int nBandCount,
    1035             :                                                    const int *panBandList)
    1036             : {
    1037           2 :     CPLStringList aosRet;
    1038           2 :     if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1039           4 :         nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
    1040             :     {
    1041           2 :         aosRet.AddString("JXL");
    1042             : #ifdef HAVE_JXL_BOX_API
    1043           2 :         if (m_bHasJPEGReconstructionData)
    1044           1 :             aosRet.AddString("JPEG");
    1045             : #endif
    1046             :     }
    1047           2 :     return aosRet;
    1048             : }
    1049             : 
    1050             : /************************************************************************/
    1051             : /*                       ReadCompressedData()                           */
    1052             : /************************************************************************/
    1053             : 
    1054          16 : CPLErr JPEGXLDataset::ReadCompressedData(const char *pszFormat, int nXOff,
    1055             :                                          int nYOff, int nXSize, int nYSize,
    1056             :                                          int nBandCount, const int *panBandList,
    1057             :                                          void **ppBuffer, size_t *pnBufferSize,
    1058             :                                          char **ppszDetailedFormat)
    1059             : {
    1060          16 :     if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1061          32 :         nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
    1062             :     {
    1063          16 :         const CPLStringList aosTokens(CSLTokenizeString2(pszFormat, ";", 0));
    1064          16 :         if (aosTokens.size() != 1)
    1065           0 :             return CE_Failure;
    1066             : 
    1067          16 :         if (EQUAL(aosTokens[0], "JXL"))
    1068             :         {
    1069           7 :             if (ppszDetailedFormat)
    1070           1 :                 *ppszDetailedFormat = VSIStrdup("JXL");
    1071           7 :             VSIFSeekL(m_fp, 0, SEEK_END);
    1072           7 :             const auto nFileSize = VSIFTellL(m_fp);
    1073           7 :             if (nFileSize > std::numeric_limits<size_t>::max() / 2)
    1074           0 :                 return CE_Failure;
    1075           7 :             auto nSize = static_cast<size_t>(nFileSize);
    1076           7 :             if (ppBuffer)
    1077             :             {
    1078           6 :                 if (pnBufferSize == nullptr)
    1079           1 :                     return CE_Failure;
    1080           5 :                 bool bFreeOnError = false;
    1081           5 :                 if (*ppBuffer)
    1082             :                 {
    1083           3 :                     if (*pnBufferSize < nSize)
    1084           1 :                         return CE_Failure;
    1085             :                 }
    1086             :                 else
    1087             :                 {
    1088           2 :                     *ppBuffer = VSI_MALLOC_VERBOSE(nSize);
    1089           2 :                     if (*ppBuffer == nullptr)
    1090           0 :                         return CE_Failure;
    1091           2 :                     bFreeOnError = true;
    1092             :                 }
    1093           4 :                 VSIFSeekL(m_fp, 0, SEEK_SET);
    1094           4 :                 if (VSIFReadL(*ppBuffer, nSize, 1, m_fp) != 1)
    1095             :                 {
    1096           0 :                     if (bFreeOnError)
    1097             :                     {
    1098           0 :                         VSIFree(*ppBuffer);
    1099           0 :                         *ppBuffer = nullptr;
    1100             :                     }
    1101           0 :                     return CE_Failure;
    1102             :                 }
    1103             : 
    1104           4 :                 size_t nPos = 0;
    1105           4 :                 GByte *pabyData = static_cast<GByte *>(*ppBuffer);
    1106          25 :                 while (nSize - nPos >= 8)
    1107             :                 {
    1108             :                     uint32_t nBoxSize;
    1109          21 :                     memcpy(&nBoxSize, pabyData + nPos, 4);
    1110          21 :                     CPL_MSBPTR32(&nBoxSize);
    1111          21 :                     if (nBoxSize < 8 || nBoxSize > nSize - nPos)
    1112             :                         break;
    1113          21 :                     char szBoxName[5] = {0, 0, 0, 0, 0};
    1114          21 :                     memcpy(szBoxName, pabyData + nPos + 4, 4);
    1115          21 :                     if (memcmp(szBoxName, "Exif", 4) == 0 ||
    1116          21 :                         memcmp(szBoxName, "xml ", 4) == 0 ||
    1117          21 :                         memcmp(szBoxName, "jumb", 4) == 0)
    1118             :                     {
    1119           4 :                         CPLDebug("JPEGXL",
    1120             :                                  "Remove existing %s box from "
    1121             :                                  "source compressed data",
    1122             :                                  szBoxName);
    1123           4 :                         memmove(pabyData + nPos, pabyData + nPos + nBoxSize,
    1124           4 :                                 nSize - (nPos + nBoxSize));
    1125           4 :                         nSize -= nBoxSize;
    1126             :                     }
    1127             :                     else
    1128             :                     {
    1129          17 :                         nPos += nBoxSize;
    1130             :                     }
    1131             :                 }
    1132             :             }
    1133           5 :             if (pnBufferSize)
    1134           5 :                 *pnBufferSize = nSize;
    1135           5 :             return CE_None;
    1136             :         }
    1137             : 
    1138             : #ifdef HAVE_JXL_BOX_API
    1139           9 :         if (m_bHasJPEGReconstructionData && EQUAL(aosTokens[0], "JPEG"))
    1140             :         {
    1141           9 :             auto decoder = JxlDecoderMake(nullptr);
    1142           9 :             if (!decoder)
    1143             :             {
    1144           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1145             :                          "JxlDecoderMake() failed");
    1146           0 :                 return CE_Failure;
    1147             :             }
    1148           9 :             auto status = JxlDecoderSubscribeEvents(
    1149             :                 decoder.get(), JXL_DEC_BASIC_INFO |
    1150             :                                    JXL_DEC_JPEG_RECONSTRUCTION |
    1151             :                                    JXL_DEC_FULL_IMAGE);
    1152           9 :             if (status != JXL_DEC_SUCCESS)
    1153             :             {
    1154           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1155             :                          "JxlDecoderSubscribeEvents() failed");
    1156           0 :                 return CE_Failure;
    1157             :             }
    1158             : 
    1159           9 :             VSIFSeekL(m_fp, 0, SEEK_SET);
    1160             :             try
    1161             :             {
    1162           9 :                 std::vector<GByte> jpeg_bytes;
    1163           9 :                 std::vector<GByte> jpeg_data_chunk(16 * 1024);
    1164             : 
    1165           9 :                 bool bJPEGReconstruction = false;
    1166             :                 while (true)
    1167             :                 {
    1168          45 :                     status = JxlDecoderProcessInput(decoder.get());
    1169          45 :                     if (status == JXL_DEC_SUCCESS)
    1170             :                     {
    1171           9 :                         break;
    1172             :                     }
    1173          36 :                     else if (status == JXL_DEC_NEED_MORE_INPUT)
    1174             :                     {
    1175           9 :                         JxlDecoderReleaseInput(decoder.get());
    1176             : 
    1177             :                         const size_t nRead =
    1178           9 :                             VSIFReadL(m_abyInputData.data(), 1,
    1179             :                                       m_abyInputData.size(), m_fp);
    1180           9 :                         if (nRead == 0)
    1181             :                         {
    1182           0 :                             break;
    1183             :                         }
    1184           9 :                         if (JxlDecoderSetInput(decoder.get(),
    1185           9 :                                                m_abyInputData.data(),
    1186           9 :                                                nRead) != JXL_DEC_SUCCESS)
    1187             :                         {
    1188           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1189             :                                      "JxlDecoderSetInput() failed");
    1190           0 :                             return CE_Failure;
    1191             :                         }
    1192             :                     }
    1193          27 :                     else if (status == JXL_DEC_JPEG_RECONSTRUCTION)
    1194             :                     {
    1195           9 :                         bJPEGReconstruction = true;
    1196             :                         // Decoding to JPEG.
    1197           9 :                         if (JXL_DEC_SUCCESS !=
    1198           9 :                             JxlDecoderSetJPEGBuffer(decoder.get(),
    1199             :                                                     jpeg_data_chunk.data(),
    1200             :                                                     jpeg_data_chunk.size()))
    1201             :                         {
    1202           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
    1203             :                                      "Decoder failed to set JPEG Buffer\n");
    1204           0 :                             return CE_Failure;
    1205             :                         }
    1206             :                     }
    1207          18 :                     else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT)
    1208             :                     {
    1209             :                         // Decoded a chunk to JPEG.
    1210             :                         size_t used_jpeg_output =
    1211           0 :                             jpeg_data_chunk.size() -
    1212           0 :                             JxlDecoderReleaseJPEGBuffer(decoder.get());
    1213             :                         jpeg_bytes.insert(
    1214           0 :                             jpeg_bytes.end(), jpeg_data_chunk.data(),
    1215           0 :                             jpeg_data_chunk.data() + used_jpeg_output);
    1216           0 :                         if (used_jpeg_output == 0)
    1217             :                         {
    1218             :                             // Chunk is too small.
    1219           0 :                             jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
    1220             :                         }
    1221           0 :                         if (JXL_DEC_SUCCESS !=
    1222           0 :                             JxlDecoderSetJPEGBuffer(decoder.get(),
    1223             :                                                     jpeg_data_chunk.data(),
    1224             :                                                     jpeg_data_chunk.size()))
    1225             :                         {
    1226           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
    1227             :                                      "Decoder failed to set JPEG Buffer\n");
    1228           0 :                             return CE_Failure;
    1229             :                         }
    1230             :                     }
    1231          18 :                     else if (status == JXL_DEC_BASIC_INFO ||
    1232             :                              status == JXL_DEC_FULL_IMAGE)
    1233             :                     {
    1234             :                         // do nothing
    1235             :                     }
    1236             :                     else
    1237             :                     {
    1238           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1239             :                                  "Unexpected event: %d", status);
    1240           0 :                         break;
    1241             :                     }
    1242          36 :                 }
    1243           9 :                 if (bJPEGReconstruction)
    1244             :                 {
    1245             :                     size_t used_jpeg_output =
    1246           9 :                         jpeg_data_chunk.size() -
    1247           9 :                         JxlDecoderReleaseJPEGBuffer(decoder.get());
    1248           9 :                     jpeg_bytes.insert(jpeg_bytes.end(), jpeg_data_chunk.data(),
    1249           9 :                                       jpeg_data_chunk.data() +
    1250          18 :                                           used_jpeg_output);
    1251             :                 }
    1252             : 
    1253           9 :                 JxlDecoderReleaseInput(decoder.get());
    1254             : 
    1255          18 :                 if (!jpeg_bytes.empty() &&
    1256           9 :                     jpeg_bytes.size() < static_cast<size_t>(INT_MAX))
    1257             :                 {
    1258           9 :                     constexpr GByte EXIF_SIGNATURE[] = {'E', 'x',  'i',
    1259             :                                                         'f', '\0', '\0'};
    1260           9 :                     constexpr char APP1_XMP_SIGNATURE[] =
    1261             :                         "http://ns.adobe.com/xap/1.0/";
    1262           9 :                     size_t nChunkLoc = 2;
    1263          60 :                     while (nChunkLoc + 4 <= jpeg_bytes.size())
    1264             :                     {
    1265         120 :                         if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
    1266          60 :                             jpeg_bytes[nChunkLoc + 1] == 0xDA)
    1267             :                         {
    1268           9 :                             break;
    1269             :                         }
    1270          51 :                         if (jpeg_bytes[nChunkLoc + 0] != 0xFF)
    1271           0 :                             break;
    1272             :                         const int nChunkLength =
    1273          51 :                             jpeg_bytes[nChunkLoc + 2] * 256 +
    1274          51 :                             jpeg_bytes[nChunkLoc + 3];
    1275         102 :                         if (nChunkLength < 2 ||
    1276          51 :                             static_cast<size_t>(nChunkLength) >
    1277          51 :                                 jpeg_bytes.size() - (nChunkLoc + 2))
    1278           0 :                             break;
    1279          51 :                         if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
    1280          51 :                             jpeg_bytes[nChunkLoc + 1] == 0xE1 &&
    1281           0 :                             nChunkLoc + 4 + sizeof(EXIF_SIGNATURE) <=
    1282         102 :                                 jpeg_bytes.size() &&
    1283           0 :                             memcmp(jpeg_bytes.data() + nChunkLoc + 4,
    1284             :                                    EXIF_SIGNATURE, sizeof(EXIF_SIGNATURE)) == 0)
    1285             :                         {
    1286           0 :                             CPLDebug("JPEGXL", "Remove existing EXIF from "
    1287             :                                                "source compressed data");
    1288           0 :                             jpeg_bytes.erase(jpeg_bytes.begin() + nChunkLoc,
    1289           0 :                                              jpeg_bytes.begin() + nChunkLoc +
    1290           0 :                                                  2 + nChunkLength);
    1291           0 :                             continue;
    1292             :                         }
    1293          51 :                         else if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
    1294          51 :                                  jpeg_bytes[nChunkLoc + 1] == 0xE1 &&
    1295           0 :                                  nChunkLoc + 4 + sizeof(APP1_XMP_SIGNATURE) <=
    1296         102 :                                      jpeg_bytes.size() &&
    1297           0 :                                  memcmp(jpeg_bytes.data() + nChunkLoc + 4,
    1298             :                                         APP1_XMP_SIGNATURE,
    1299             :                                         sizeof(APP1_XMP_SIGNATURE)) == 0)
    1300             :                         {
    1301           0 :                             CPLDebug("JPEGXL", "Remove existing XMP from "
    1302             :                                                "source compressed data");
    1303           0 :                             jpeg_bytes.erase(jpeg_bytes.begin() + nChunkLoc,
    1304           0 :                                              jpeg_bytes.begin() + nChunkLoc +
    1305           0 :                                                  2 + nChunkLength);
    1306           0 :                             continue;
    1307             :                         }
    1308          51 :                         nChunkLoc += 2 + nChunkLength;
    1309             :                     }
    1310             : 
    1311           9 :                     if (ppszDetailedFormat)
    1312             :                     {
    1313           1 :                         *ppszDetailedFormat =
    1314           2 :                             VSIStrdup(GDALGetCompressionFormatForJPEG(
    1315           1 :                                           jpeg_bytes.data(), jpeg_bytes.size())
    1316             :                                           .c_str());
    1317             :                     }
    1318             : 
    1319           9 :                     const auto nSize = jpeg_bytes.size();
    1320           9 :                     if (ppBuffer)
    1321             :                     {
    1322           8 :                         if (*ppBuffer)
    1323             :                         {
    1324           4 :                             if (pnBufferSize == nullptr)
    1325           1 :                                 return CE_Failure;
    1326           3 :                             if (*pnBufferSize < nSize)
    1327           1 :                                 return CE_Failure;
    1328             :                         }
    1329             :                         else
    1330             :                         {
    1331           4 :                             *ppBuffer = VSI_MALLOC_VERBOSE(nSize);
    1332           4 :                             if (*ppBuffer == nullptr)
    1333           0 :                                 return CE_Failure;
    1334             :                         }
    1335           6 :                         memcpy(*ppBuffer, jpeg_bytes.data(), nSize);
    1336             :                     }
    1337           7 :                     if (pnBufferSize)
    1338           7 :                         *pnBufferSize = nSize;
    1339             : 
    1340           7 :                     return CE_None;
    1341             :                 }
    1342             :             }
    1343           0 :             catch (const std::exception &)
    1344             :             {
    1345             :             }
    1346             :         }
    1347             : #endif
    1348             :     }
    1349           0 :     return CE_Failure;
    1350             : }
    1351             : 
    1352             : /************************************************************************/
    1353             : /*                          GetMetadataItem()                           */
    1354             : /************************************************************************/
    1355             : 
    1356         206 : const char *JPEGXLDataset::GetMetadataItem(const char *pszName,
    1357             :                                            const char *pszDomain)
    1358             : {
    1359             : #ifdef HAVE_JXL_BOX_API
    1360         214 :     if (pszDomain != nullptr && EQUAL(pszDomain, "EXIF") &&
    1361           8 :         !m_aosEXIFMetadata.empty())
    1362             :     {
    1363           8 :         return m_aosEXIFMetadata.FetchNameValue(pszName);
    1364             :     }
    1365             : #endif
    1366             : 
    1367         198 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
    1368             : }
    1369             : 
    1370             : /************************************************************************/
    1371             : /*                        GetDecodedImage()                             */
    1372             : /************************************************************************/
    1373             : 
    1374         255 : void JPEGXLDataset::GetDecodedImage(void *pabyOutputData,
    1375             :                                     size_t nOutputDataSize)
    1376             : {
    1377         255 :     JxlDecoderRewind(m_decoder.get());
    1378         255 :     VSIFSeekL(m_fp, 0, SEEK_SET);
    1379             : 
    1380         255 :     if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_FULL_IMAGE) !=
    1381             :         JXL_DEC_SUCCESS)
    1382             :     {
    1383           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1384             :                  "JxlDecoderSubscribeEvents() failed");
    1385           0 :         return;
    1386             :     }
    1387             : 
    1388         255 :     const auto eDT = GetRasterBand(1)->GetRasterDataType();
    1389             :     while (true)
    1390             :     {
    1391        1019 :         const JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get());
    1392        1018 :         if (status == JXL_DEC_ERROR)
    1393             :         {
    1394           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Decoding error");
    1395           0 :             m_bDecodingFailed = true;
    1396           0 :             break;
    1397             :         }
    1398        1018 :         else if (status == JXL_DEC_NEED_MORE_INPUT)
    1399             :         {
    1400         255 :             JxlDecoderReleaseInput(m_decoder.get());
    1401             : 
    1402         255 :             const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
    1403             :                                            m_abyInputData.size(), m_fp);
    1404         255 :             if (nRead == 0)
    1405             :             {
    1406           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1407             :                          "Decoder expected more input, but no more available");
    1408           0 :                 m_bDecodingFailed = true;
    1409           0 :                 break;
    1410             :             }
    1411         255 :             if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
    1412         255 :                                    nRead) != JXL_DEC_SUCCESS)
    1413             :             {
    1414           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1415             :                          "JxlDecoderSetInput() failed");
    1416           0 :                 m_bDecodingFailed = true;
    1417           0 :                 break;
    1418             :             }
    1419             :         }
    1420         763 :         else if (status == JXL_DEC_SUCCESS)
    1421             :         {
    1422         254 :             break;
    1423             :         }
    1424         509 :         else if (status == JXL_DEC_FULL_IMAGE)
    1425             :         {
    1426             :             // ok
    1427             :         }
    1428         255 :         else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
    1429             :         {
    1430         255 :             JxlPixelFormat format = {
    1431         255 :                 static_cast<uint32_t>(nBands - m_nNonAlphaExtraChannels),
    1432         255 :                 eDT == GDT_Byte     ? JXL_TYPE_UINT8
    1433          40 :                 : eDT == GDT_UInt16 ? JXL_TYPE_UINT16
    1434             :                                     : JXL_TYPE_FLOAT,
    1435             :                 JXL_NATIVE_ENDIAN, 0 /* alignment */
    1436         255 :             };
    1437             : 
    1438             :             size_t buffer_size;
    1439         255 :             if (JxlDecoderImageOutBufferSize(m_decoder.get(), &format,
    1440         255 :                                              &buffer_size) != JXL_DEC_SUCCESS)
    1441             :             {
    1442           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1443             :                          "JxlDecoderImageOutBufferSize failed()");
    1444           0 :                 m_bDecodingFailed = true;
    1445           0 :                 break;
    1446             :             }
    1447         255 :             if (buffer_size != nOutputDataSize)
    1448             :             {
    1449           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1450             :                          "JxlDecoderImageOutBufferSize returned an unexpected "
    1451             :                          "buffer_size");
    1452           0 :                 m_bDecodingFailed = true;
    1453           0 :                 break;
    1454             :             }
    1455             : 
    1456             :             // It could be interesting to use JxlDecoderSetImageOutCallback()
    1457             :             // to do progressive decoding, but at the time of writing, libjxl
    1458             :             // seems to just call the callback when all the image is
    1459             :             // decompressed
    1460         255 :             if (JxlDecoderSetImageOutBuffer(m_decoder.get(), &format,
    1461             :                                             pabyOutputData,
    1462         255 :                                             nOutputDataSize) != JXL_DEC_SUCCESS)
    1463             :             {
    1464           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1465             :                          "JxlDecoderSetImageOutBuffer failed()");
    1466           0 :                 m_bDecodingFailed = true;
    1467           0 :                 break;
    1468             :             }
    1469             : 
    1470         255 :             format.num_channels = 1;
    1471         331 :             for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
    1472             :             {
    1473          76 :                 if (JxlDecoderExtraChannelBufferSize(m_decoder.get(), &format,
    1474             :                                                      &buffer_size,
    1475          76 :                                                      i) != JXL_DEC_SUCCESS)
    1476             :                 {
    1477           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1478             :                              "JxlDecoderExtraChannelBufferSize failed()");
    1479           0 :                     m_bDecodingFailed = true;
    1480           0 :                     break;
    1481             :                 }
    1482          76 :                 if (buffer_size != m_abyExtraChannels[i].size())
    1483             :                 {
    1484           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1485             :                              "JxlDecoderExtraChannelBufferSize returned an "
    1486             :                              "unexpected buffer_size");
    1487           0 :                     m_bDecodingFailed = true;
    1488           0 :                     break;
    1489             :                 }
    1490         152 :                 if (JxlDecoderSetExtraChannelBuffer(
    1491          76 :                         m_decoder.get(), &format, m_abyExtraChannels[i].data(),
    1492         152 :                         m_abyExtraChannels[i].size(), i) != JXL_DEC_SUCCESS)
    1493             :                 {
    1494           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1495             :                              "JxlDecoderSetExtraChannelBuffer failed()");
    1496           0 :                     m_bDecodingFailed = true;
    1497           0 :                     break;
    1498             :                 }
    1499             :             }
    1500         255 :             if (m_bDecodingFailed)
    1501             :             {
    1502           0 :                 break;
    1503             :             }
    1504             :         }
    1505             :         else
    1506             :         {
    1507           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1508             :                      "Unexpected decoder state: %d", status);
    1509             :         }
    1510         764 :     }
    1511             : 
    1512             :     // Rescale from 8-bits/16-bits
    1513         254 :     if (m_nBits < GDALGetDataTypeSizeBits(eDT))
    1514             :     {
    1515           7 :         const auto Rescale = [this, eDT](void *pBuffer, int nChannels)
    1516             :         {
    1517           4 :             const size_t nSamples =
    1518           4 :                 static_cast<size_t>(nRasterXSize) * nRasterYSize * nChannels;
    1519           4 :             const int nMaxVal = (1 << m_nBits) - 1;
    1520           4 :             if (eDT == GDT_Byte)
    1521             :             {
    1522           1 :                 const int nHalfMaxWidth = 127;
    1523           1 :                 GByte *panData = static_cast<GByte *>(pBuffer);
    1524         401 :                 for (size_t i = 0; i < nSamples; ++i)
    1525             :                 {
    1526         400 :                     panData[i] = static_cast<GByte>(
    1527         400 :                         (panData[i] * nMaxVal + nHalfMaxWidth) / 255);
    1528             :                 }
    1529             :             }
    1530           3 :             else if (eDT == GDT_UInt16)
    1531             :             {
    1532           1 :                 const int nHalfMaxWidth = 32767;
    1533           1 :                 uint16_t *panData = static_cast<uint16_t *>(pBuffer);
    1534         401 :                 for (size_t i = 0; i < nSamples; ++i)
    1535             :                 {
    1536         400 :                     panData[i] = static_cast<uint16_t>(
    1537         400 :                         (panData[i] * nMaxVal + nHalfMaxWidth) / 65535);
    1538             :                 }
    1539             :             }
    1540           8 :         };
    1541             : 
    1542           4 :         Rescale(pabyOutputData, nBands - m_nNonAlphaExtraChannels);
    1543           4 :         for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
    1544             :         {
    1545           0 :             Rescale(m_abyExtraChannels[i].data(), 1);
    1546             :         }
    1547             :     }
    1548             : }
    1549             : 
    1550             : /************************************************************************/
    1551             : /*                             IRasterIO()                              */
    1552             : /************************************************************************/
    1553             : 
    1554         187 : CPLErr JPEGXLDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1555             :                                 int nXSize, int nYSize, void *pData,
    1556             :                                 int nBufXSize, int nBufYSize,
    1557             :                                 GDALDataType eBufType, int nBandCount,
    1558             :                                 BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1559             :                                 GSpacing nLineSpace, GSpacing nBandSpace,
    1560             :                                 GDALRasterIOExtraArg *psExtraArg)
    1561             : 
    1562             : {
    1563         186 :     const auto AreSequentialBands = [](const int *panItems, int nItems)
    1564             :     {
    1565         545 :         for (int i = 0; i < nItems; i++)
    1566             :         {
    1567         364 :             if (panItems[i] != i + 1)
    1568           5 :                 return false;
    1569             :         }
    1570         181 :         return true;
    1571             :     };
    1572             : 
    1573         187 :     if (eRWFlag == GF_Read && nXOff == 0 && nYOff == 0 &&
    1574         187 :         nXSize == nRasterXSize && nYSize == nRasterYSize &&
    1575         186 :         nBufXSize == nXSize && nBufYSize == nYSize)
    1576             :     {
    1577             :         // Get the full image in a pixel-interleaved way
    1578         186 :         if (m_bDecodingFailed)
    1579           0 :             return CE_Failure;
    1580             : 
    1581         186 :         CPLDebug("JPEGXL", "Using optimized IRasterIO() code path");
    1582             : 
    1583         186 :         const auto nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    1584         186 :         const bool bIsPixelInterleaveBuffer =
    1585          13 :             ((nBandSpace == 0 && nBandCount == 1) ||
    1586         173 :              nBandSpace == nBufTypeSize) &&
    1587         549 :             nPixelSpace == static_cast<GSpacing>(nBufTypeSize) * nBandCount &&
    1588         177 :             nLineSpace == nPixelSpace * nRasterXSize;
    1589             : 
    1590         186 :         const auto eNativeDT = GetRasterBand(1)->GetRasterDataType();
    1591         186 :         const auto nNativeDataSize = GDALGetDataTypeSizeBytes(eNativeDT);
    1592             :         const bool bIsBandSequential =
    1593         186 :             AreSequentialBands(panBandMap, nBandCount);
    1594         186 :         if (eBufType == eNativeDT && bIsBandSequential &&
    1595         176 :             nBandCount == nBands && m_nNonAlphaExtraChannels == 0 &&
    1596             :             bIsPixelInterleaveBuffer)
    1597             :         {
    1598             :             // We can directly use the user output buffer
    1599         169 :             GetDecodedImage(pData, static_cast<size_t>(nRasterXSize) *
    1600         169 :                                        nRasterYSize * nBands * nNativeDataSize);
    1601         168 :             return m_bDecodingFailed ? CE_Failure : CE_None;
    1602             :         }
    1603             : 
    1604          17 :         const auto &abyDecodedImage = GetDecodedImage();
    1605          17 :         if (abyDecodedImage.empty())
    1606             :         {
    1607           0 :             return CE_Failure;
    1608             :         }
    1609          17 :         const int nNonExtraBands = nBands - m_nNonAlphaExtraChannels;
    1610          17 :         if (bIsPixelInterleaveBuffer && bIsBandSequential &&
    1611             :             nBandCount == nNonExtraBands)
    1612             :         {
    1613           3 :             GDALCopyWords64(abyDecodedImage.data(), eNativeDT, nNativeDataSize,
    1614             :                             pData, eBufType, nBufTypeSize,
    1615           3 :                             static_cast<GPtrDiff_t>(nRasterXSize) *
    1616           3 :                                 nRasterYSize * nBandCount);
    1617             :         }
    1618             :         else
    1619             :         {
    1620          53 :             for (int iBand = 0; iBand < nBandCount; iBand++)
    1621             :             {
    1622          39 :                 const int iSrcBand = panBandMap[iBand] - 1;
    1623          39 :                 if (iSrcBand < nNonExtraBands)
    1624             :                 {
    1625        1162 :                     for (int iY = 0; iY < nRasterYSize; iY++)
    1626             :                     {
    1627        1144 :                         const GByte *pSrc = abyDecodedImage.data() +
    1628        1144 :                                             (static_cast<size_t>(iY) *
    1629        1144 :                                                  nRasterXSize * nNonExtraBands +
    1630        1144 :                                              iSrcBand) *
    1631        1144 :                                                 nNativeDataSize;
    1632        1144 :                         GByte *pDst = static_cast<GByte *>(pData) +
    1633        1144 :                                       iY * nLineSpace + iBand * nBandSpace;
    1634        1144 :                         GDALCopyWords(pSrc, eNativeDT,
    1635             :                                       nNativeDataSize * nNonExtraBands, pDst,
    1636             :                                       eBufType, static_cast<int>(nPixelSpace),
    1637             :                                       nRasterXSize);
    1638             :                     }
    1639             :                 }
    1640             :                 else
    1641             :                 {
    1642         891 :                     for (int iY = 0; iY < nRasterYSize; iY++)
    1643             :                     {
    1644             :                         const GByte *pSrc =
    1645         870 :                             m_abyExtraChannels[iSrcBand - nNonExtraBands]
    1646         870 :                                 .data() +
    1647         870 :                             static_cast<size_t>(iY) * nRasterXSize *
    1648         870 :                                 nNativeDataSize;
    1649         870 :                         GByte *pDst = static_cast<GByte *>(pData) +
    1650         870 :                                       iY * nLineSpace + iBand * nBandSpace;
    1651         870 :                         GDALCopyWords(pSrc, eNativeDT, nNativeDataSize, pDst,
    1652             :                                       eBufType, static_cast<int>(nPixelSpace),
    1653             :                                       nRasterXSize);
    1654             :                     }
    1655             :                 }
    1656             :             }
    1657             :         }
    1658          17 :         return CE_None;
    1659             :     }
    1660             : 
    1661           1 :     return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1662             :                                      pData, nBufXSize, nBufYSize, eBufType,
    1663             :                                      nBandCount, panBandMap, nPixelSpace,
    1664           1 :                                      nLineSpace, nBandSpace, psExtraArg);
    1665             : }
    1666             : 
    1667             : /************************************************************************/
    1668             : /*                             IRasterIO()                              */
    1669             : /************************************************************************/
    1670             : 
    1671       35607 : CPLErr JPEGXLRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1672             :                                    int nXSize, int nYSize, void *pData,
    1673             :                                    int nBufXSize, int nBufYSize,
    1674             :                                    GDALDataType eBufType, GSpacing nPixelSpace,
    1675             :                                    GSpacing nLineSpace,
    1676             :                                    GDALRasterIOExtraArg *psExtraArg)
    1677             : 
    1678             : {
    1679       35607 :     if (eRWFlag == GF_Read && nXOff == 0 && nYOff == 0 &&
    1680         229 :         nXSize == nRasterXSize && nYSize == nRasterYSize &&
    1681           4 :         nBufXSize == nXSize && nBufYSize == nYSize)
    1682             :     {
    1683           4 :         return cpl::down_cast<JPEGXLDataset *>(poDS)->IRasterIO(
    1684             :             GF_Read, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1685           4 :             eBufType, 1, &nBand, nPixelSpace, nLineSpace, 0, psExtraArg);
    1686             :     }
    1687             : 
    1688       35603 :     return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1689             :                                         pData, nBufXSize, nBufYSize, eBufType,
    1690       35603 :                                         nPixelSpace, nLineSpace, psExtraArg);
    1691             : }
    1692             : 
    1693             : /************************************************************************/
    1694             : /*                          OpenStaticPAM()                             */
    1695             : /************************************************************************/
    1696             : 
    1697         341 : GDALPamDataset *JPEGXLDataset::OpenStaticPAM(GDALOpenInfo *poOpenInfo)
    1698             : {
    1699         341 :     if (!Identify(poOpenInfo))
    1700           0 :         return nullptr;
    1701             : 
    1702         677 :     auto poDS = std::make_unique<JPEGXLDataset>();
    1703         338 :     if (!poDS->Open(poOpenInfo))
    1704           0 :         return nullptr;
    1705             : 
    1706         336 :     return poDS.release();
    1707             : }
    1708             : 
    1709             : /************************************************************************/
    1710             : /*                          OpenStatic()                                */
    1711             : /************************************************************************/
    1712             : 
    1713         282 : GDALDataset *JPEGXLDataset::OpenStatic(GDALOpenInfo *poOpenInfo)
    1714             : {
    1715         282 :     GDALDataset *poDS = OpenStaticPAM(poOpenInfo);
    1716             : 
    1717             : #ifdef HAVE_JXL_BOX_API
    1718         554 :     if (poDS &&
    1719         275 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "APPLY_ORIENTATION", false))
    1720             :     {
    1721             :         const char *pszOrientation =
    1722           8 :             poDS->GetMetadataItem("EXIF_Orientation", "EXIF");
    1723           8 :         if (pszOrientation && !EQUAL(pszOrientation, "1"))
    1724             :         {
    1725           7 :             int nOrientation = atoi(pszOrientation);
    1726           7 :             if (nOrientation >= 2 && nOrientation <= 8)
    1727             :             {
    1728          14 :                 std::unique_ptr<GDALDataset> poOriDS(poDS);
    1729             :                 auto poOrientedDS = std::make_unique<GDALOrientedDataset>(
    1730           7 :                     std::move(poOriDS),
    1731          14 :                     static_cast<GDALOrientedDataset::Origin>(nOrientation));
    1732           7 :                 poDS = poOrientedDS.release();
    1733             :             }
    1734             :         }
    1735             :     }
    1736             : #endif
    1737             : 
    1738         277 :     return poDS;
    1739             : }
    1740             : 
    1741             : /************************************************************************/
    1742             : /*                              CreateCopy()                            */
    1743             : /************************************************************************/
    1744             : 
    1745          93 : GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename,
    1746             :                                        GDALDataset *poSrcDS, int /*bStrict*/,
    1747             :                                        char **papszOptions,
    1748             :                                        GDALProgressFunc pfnProgress,
    1749             :                                        void *pProgressData)
    1750             : 
    1751             : {
    1752         186 :     if (poSrcDS->GetRasterXSize() <= 0 || poSrcDS->GetRasterYSize() <= 0 ||
    1753          93 :         poSrcDS->GetRasterCount() == 0)
    1754             :     {
    1755           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid source dataset");
    1756           2 :         return nullptr;
    1757             :     }
    1758             : 
    1759             :     // Look for EXIF metadata first in the EXIF metadata domain, and fallback
    1760             :     // to main domain.
    1761             :     const bool bWriteExifMetadata =
    1762          91 :         CPLFetchBool(papszOptions, "WRITE_EXIF_METADATA", true);
    1763          91 :     char **papszEXIF = poSrcDS->GetMetadata("EXIF");
    1764          91 :     bool bEXIFFromMainDomain = false;
    1765          91 :     if (papszEXIF == nullptr && bWriteExifMetadata)
    1766             :     {
    1767          90 :         char **papszMetadata = poSrcDS->GetMetadata();
    1768         130 :         for (CSLConstList papszIter = papszMetadata; papszIter && *papszIter;
    1769             :              ++papszIter)
    1770             :         {
    1771          45 :             if (STARTS_WITH(*papszIter, "EXIF_"))
    1772             :             {
    1773           5 :                 papszEXIF = papszMetadata;
    1774           5 :                 bEXIFFromMainDomain = true;
    1775           5 :                 break;
    1776             :             }
    1777             :         }
    1778             :     }
    1779             : 
    1780             :     // Write "xml " box with xml:XMP metadata
    1781          91 :     const bool bWriteXMP = CPLFetchBool(papszOptions, "WRITE_XMP", true);
    1782          91 :     char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
    1783             : 
    1784          91 :     const bool bWriteGeoJP2 = CPLFetchBool(papszOptions, "WRITE_GEOJP2", true);
    1785          91 :     GDALGeoTransform gt;
    1786          91 :     const bool bHasGeoTransform = poSrcDS->GetGeoTransform(gt) == CE_None;
    1787          91 :     const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
    1788          91 :     const int nGCPCount = poSrcDS->GetGCPCount();
    1789          91 :     char **papszRPCMD = poSrcDS->GetMetadata("RPC");
    1790          91 :     std::unique_ptr<GDALJP2Box> poJUMBFBox;
    1791          91 :     if (bWriteGeoJP2 &&
    1792          51 :         (poSRS != nullptr || bHasGeoTransform || nGCPCount || papszRPCMD))
    1793             :     {
    1794          80 :         GDALJP2Metadata oJP2Metadata;
    1795          40 :         if (poSRS)
    1796          40 :             oJP2Metadata.SetSpatialRef(poSRS);
    1797          40 :         if (bHasGeoTransform)
    1798          40 :             oJP2Metadata.SetGeoTransform(gt);
    1799          40 :         if (nGCPCount)
    1800             :         {
    1801           0 :             const OGRSpatialReference *poSRSGCP = poSrcDS->GetGCPSpatialRef();
    1802           0 :             if (poSRSGCP)
    1803           0 :                 oJP2Metadata.SetSpatialRef(poSRSGCP);
    1804           0 :             oJP2Metadata.SetGCPs(nGCPCount, poSrcDS->GetGCPs());
    1805             :         }
    1806          40 :         if (papszRPCMD)
    1807           0 :             oJP2Metadata.SetRPCMD(papszRPCMD);
    1808             : 
    1809             :         const char *pszAreaOfPoint =
    1810          40 :             poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
    1811          40 :         oJP2Metadata.bPixelIsPoint =
    1812          40 :             pszAreaOfPoint && EQUAL(pszAreaOfPoint, GDALMD_AOP_POINT);
    1813             : 
    1814          40 :         std::unique_ptr<GDALJP2Box> poJP2GeoTIFF;
    1815          40 :         poJP2GeoTIFF.reset(oJP2Metadata.CreateJP2GeoTIFF());
    1816          40 :         if (poJP2GeoTIFF)
    1817             :         {
    1818             :             // Per JUMBF spec: UUID Content Type. The JUMBF box contains exactly
    1819             :             // one UUID box
    1820          40 :             const GByte abyUUIDTypeUUID[16] = {
    1821             :                 0x75, 0x75, 0x69, 0x64, 0x00, 0x11, 0x00, 0x10,
    1822             :                 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71};
    1823          40 :             std::unique_ptr<GDALJP2Box> poJUMBFDescrBox;
    1824          40 :             poJUMBFDescrBox.reset(GDALJP2Box::CreateJUMBFDescriptionBox(
    1825             :                 abyUUIDTypeUUID, "GeoJP2 box"));
    1826          40 :             const GDALJP2Box *poJP2GeoTIFFConst = poJP2GeoTIFF.get();
    1827          40 :             poJUMBFBox.reset(GDALJP2Box::CreateJUMBFBox(poJUMBFDescrBox.get(),
    1828             :                                                         1, &poJP2GeoTIFFConst));
    1829             :         }
    1830             :     }
    1831             : 
    1832          91 :     constexpr int DEFAULT_EFFORT = 5;
    1833          91 :     const int nEffort = atoi(CSLFetchNameValueDef(
    1834             :         papszOptions, "EFFORT", CPLSPrintf("%d", DEFAULT_EFFORT)));
    1835          91 :     const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE");
    1836          91 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    1837             :     const char *pszAlphaDistance =
    1838          91 :         CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE");
    1839             :     const char *pszLossLessCopy =
    1840          91 :         CSLFetchNameValueDef(papszOptions, "LOSSLESS_COPY", "AUTO");
    1841          85 :     if ((EQUAL(pszLossLessCopy, "AUTO") && !pszDistance && !pszQuality &&
    1842         183 :          !pszAlphaDistance && nEffort == DEFAULT_EFFORT) ||
    1843          19 :         (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
    1844             :     {
    1845          76 :         void *pJPEGXLContent = nullptr;
    1846          76 :         size_t nJPEGXLContent = 0;
    1847             :         const bool bSrcIsJXL =
    1848          76 :             (poSrcDS->ReadCompressedData(
    1849             :                  "JXL", 0, 0, poSrcDS->GetRasterXSize(),
    1850             :                  poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr,
    1851          76 :                  &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None);
    1852          76 :         if (bSrcIsJXL && (pszDistance || pszQuality || pszAlphaDistance ||
    1853             :                           nEffort != DEFAULT_EFFORT))
    1854             :         {
    1855           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1856             :                      "LOSSLESS_COPY=YES not supported when EFFORT, QUALITY, "
    1857             :                      "DISTANCE or ALPHA_DISTANCE are specified");
    1858           2 :             return nullptr;
    1859             :         }
    1860          76 :         else if (bSrcIsJXL)
    1861             :         {
    1862           2 :             CPLDebug("JPEGXL", "Lossless copy from source dataset");
    1863             :             GByte abySizeAndBoxName[8];
    1864           2 :             std::vector<GByte> abyData;
    1865           2 :             bool bFallbackToGeneral = false;
    1866             :             try
    1867             :             {
    1868           2 :                 abyData.assign(static_cast<const GByte *>(pJPEGXLContent),
    1869           2 :                                static_cast<const GByte *>(pJPEGXLContent) +
    1870           2 :                                    nJPEGXLContent);
    1871             : 
    1872           2 :                 size_t nInsertPos = 0;
    1873           3 :                 if (abyData.size() >= 2 && abyData[0] == 0xff &&
    1874           1 :                     abyData[1] == 0x0a)
    1875             :                 {
    1876             :                     // If we get a "naked" codestream, insert it into a
    1877             :                     // ISOBMFF-based container
    1878           1 :                     constexpr const GByte abyJXLContainerSignatureAndFtypBox[] =
    1879             :                         {0x00, 0x00, 0x00, 0x0C, 'J',  'X',  'L',  ' ',
    1880             :                          0x0D, 0x0A, 0x87, 0x0A, 0x00, 0x00, 0x00, 0x14,
    1881             :                          'f',  't',  'y',  'p',  'j',  'x',  'l',  ' ',
    1882             :                          0,    0,    0,    0,    'j',  'x',  'l',  ' '};
    1883             :                     uint32_t nBoxSize =
    1884           1 :                         static_cast<uint32_t>(8 + abyData.size());
    1885             :                     abyData.insert(
    1886           0 :                         abyData.begin(), abyJXLContainerSignatureAndFtypBox,
    1887             :                         abyJXLContainerSignatureAndFtypBox +
    1888           1 :                             sizeof(abyJXLContainerSignatureAndFtypBox));
    1889           1 :                     CPL_MSBPTR32(&nBoxSize);
    1890           1 :                     memcpy(abySizeAndBoxName, &nBoxSize, 4);
    1891           1 :                     memcpy(abySizeAndBoxName + 4, "jxlc", 4);
    1892           1 :                     nInsertPos = sizeof(abyJXLContainerSignatureAndFtypBox);
    1893             :                     abyData.insert(
    1894           1 :                         abyData.begin() + nInsertPos, abySizeAndBoxName,
    1895           2 :                         abySizeAndBoxName + sizeof(abySizeAndBoxName));
    1896             :                 }
    1897             :                 else
    1898             :                 {
    1899           1 :                     size_t nPos = 0;
    1900           1 :                     const GByte *pabyData = abyData.data();
    1901           4 :                     while (nPos + 8 <= abyData.size())
    1902             :                     {
    1903             :                         uint32_t nBoxSize;
    1904           4 :                         memcpy(&nBoxSize, pabyData + nPos, 4);
    1905           4 :                         CPL_MSBPTR32(&nBoxSize);
    1906           4 :                         if (nBoxSize < 8 || nBoxSize > abyData.size() - nPos)
    1907           1 :                             break;
    1908           4 :                         char szBoxName[5] = {0, 0, 0, 0, 0};
    1909           4 :                         memcpy(szBoxName, pabyData + nPos + 4, 4);
    1910           4 :                         if (memcmp(szBoxName, "jxlp", 4) == 0 ||
    1911           3 :                             memcmp(szBoxName, "jxlc", 4) == 0)
    1912             :                         {
    1913           1 :                             nInsertPos = nPos;
    1914           1 :                             break;
    1915             :                         }
    1916             :                         else
    1917             :                         {
    1918           3 :                             nPos += nBoxSize;
    1919             :                         }
    1920             :                     }
    1921             :                 }
    1922             : 
    1923             :                 // Write "Exif" box with EXIF metadata
    1924           2 :                 if (papszEXIF && bWriteExifMetadata)
    1925             :                 {
    1926           0 :                     if (nInsertPos)
    1927             :                     {
    1928           0 :                         GUInt32 nMarkerSize = 0;
    1929             :                         GByte *pabyEXIF =
    1930           0 :                             EXIFCreate(papszEXIF, nullptr, 0, 0, 0,  // overview
    1931             :                                        &nMarkerSize);
    1932           0 :                         CPLAssert(nMarkerSize > 6 &&
    1933             :                                   memcmp(pabyEXIF, "Exif\0\0", 6) == 0);
    1934             :                         // Add 4 leading bytes at 0
    1935           0 :                         std::vector<GByte> abyEXIF(4 + nMarkerSize - 6);
    1936           0 :                         memcpy(&abyEXIF[4], pabyEXIF + 6, nMarkerSize - 6);
    1937           0 :                         CPLFree(pabyEXIF);
    1938             : 
    1939             :                         uint32_t nBoxSize =
    1940           0 :                             static_cast<uint32_t>(8 + abyEXIF.size());
    1941           0 :                         CPL_MSBPTR32(&nBoxSize);
    1942           0 :                         memcpy(abySizeAndBoxName, &nBoxSize, 4);
    1943           0 :                         memcpy(abySizeAndBoxName + 4, "Exif", 4);
    1944             :                         abyData.insert(
    1945           0 :                             abyData.begin() + nInsertPos, abySizeAndBoxName,
    1946           0 :                             abySizeAndBoxName + sizeof(abySizeAndBoxName));
    1947           0 :                         abyData.insert(abyData.begin() + nInsertPos + 8,
    1948             :                                        abyEXIF.data(),
    1949           0 :                                        abyEXIF.data() + abyEXIF.size());
    1950           0 :                         nInsertPos += 8 + abyEXIF.size();
    1951             :                     }
    1952             :                     else
    1953             :                     {
    1954             :                         // shouldn't happen
    1955           0 :                         CPLDebug("JPEGX", "Cannot add Exif box to codestream");
    1956           0 :                         bFallbackToGeneral = true;
    1957             :                     }
    1958             :                 }
    1959             : 
    1960           2 :                 if (papszXMP && papszXMP[0] && bWriteXMP)
    1961             :                 {
    1962           0 :                     if (nInsertPos)
    1963             :                     {
    1964           0 :                         const size_t nXMPLen = strlen(papszXMP[0]);
    1965           0 :                         uint32_t nBoxSize = static_cast<uint32_t>(8 + nXMPLen);
    1966           0 :                         CPL_MSBPTR32(&nBoxSize);
    1967           0 :                         memcpy(abySizeAndBoxName, &nBoxSize, 4);
    1968           0 :                         memcpy(abySizeAndBoxName + 4, "xml ", 4);
    1969             :                         abyData.insert(
    1970           0 :                             abyData.begin() + nInsertPos, abySizeAndBoxName,
    1971           0 :                             abySizeAndBoxName + sizeof(abySizeAndBoxName));
    1972           0 :                         abyData.insert(abyData.begin() + nInsertPos + 8,
    1973             :                                        reinterpret_cast<GByte *>(papszXMP[0]),
    1974           0 :                                        reinterpret_cast<GByte *>(papszXMP[0]) +
    1975           0 :                                            nXMPLen);
    1976           0 :                         nInsertPos += 8 + nXMPLen;
    1977             :                     }
    1978             :                     else
    1979             :                     {
    1980             :                         // shouldn't happen
    1981           0 :                         CPLDebug("JPEGX", "Cannot add XMP box to codestream");
    1982           0 :                         bFallbackToGeneral = true;
    1983             :                     }
    1984             :                 }
    1985             : 
    1986             :                 // Write GeoJP2 box in a JUMBF box from georeferencing information
    1987           2 :                 if (poJUMBFBox)
    1988             :                 {
    1989           2 :                     if (nInsertPos)
    1990             :                     {
    1991             :                         const size_t nDataLen =
    1992           2 :                             static_cast<size_t>(poJUMBFBox->GetBoxLength());
    1993           2 :                         uint32_t nBoxSize = static_cast<uint32_t>(8 + nDataLen);
    1994           2 :                         CPL_MSBPTR32(&nBoxSize);
    1995           2 :                         memcpy(abySizeAndBoxName, &nBoxSize, 4);
    1996           2 :                         memcpy(abySizeAndBoxName + 4, "jumb", 4);
    1997             :                         abyData.insert(
    1998           2 :                             abyData.begin() + nInsertPos, abySizeAndBoxName,
    1999           4 :                             abySizeAndBoxName + sizeof(abySizeAndBoxName));
    2000           2 :                         GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
    2001           2 :                         abyData.insert(abyData.begin() + nInsertPos + 8,
    2002           4 :                                        pabyBoxData, pabyBoxData + nDataLen);
    2003           2 :                         VSIFree(pabyBoxData);
    2004           2 :                         nInsertPos += 8 + nDataLen;
    2005             :                     }
    2006             :                     else
    2007             :                     {
    2008             :                         // shouldn't happen
    2009           0 :                         CPLDebug("JPEGX",
    2010             :                                  "Cannot add JUMBF GeoJP2 box to codestream");
    2011           0 :                         bFallbackToGeneral = true;
    2012             :                     }
    2013             :                 }
    2014             : 
    2015           2 :                 CPL_IGNORE_RET_VAL(nInsertPos);
    2016             :             }
    2017           0 :             catch (const std::exception &)
    2018             :             {
    2019           0 :                 abyData.clear();
    2020             :             }
    2021           2 :             VSIFree(pJPEGXLContent);
    2022             : 
    2023           2 :             if (!bFallbackToGeneral && !abyData.empty())
    2024             :             {
    2025           2 :                 VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
    2026           2 :                 if (fpImage == nullptr)
    2027             :                 {
    2028           0 :                     CPLError(CE_Failure, CPLE_OpenFailed,
    2029             :                              "Unable to create jpeg file %s.", pszFilename);
    2030             : 
    2031           0 :                     return nullptr;
    2032             :                 }
    2033           4 :                 if (VSIFWriteL(abyData.data(), 1, abyData.size(), fpImage) !=
    2034           2 :                     abyData.size())
    2035             :                 {
    2036           0 :                     CPLError(CE_Failure, CPLE_FileIO,
    2037           0 :                              "Failure writing data: %s", VSIStrerror(errno));
    2038           0 :                     VSIFCloseL(fpImage);
    2039           0 :                     return nullptr;
    2040             :                 }
    2041           2 :                 if (VSIFCloseL(fpImage) != 0)
    2042             :                 {
    2043           0 :                     CPLError(CE_Failure, CPLE_FileIO,
    2044           0 :                              "Failure writing data: %s", VSIStrerror(errno));
    2045           0 :                     return nullptr;
    2046             :                 }
    2047             : 
    2048           2 :                 pfnProgress(1.0, nullptr, pProgressData);
    2049             : 
    2050             :                 // Re-open file and clone missing info to PAM
    2051           2 :                 GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    2052           2 :                 auto poDS = OpenStaticPAM(&oOpenInfo);
    2053           2 :                 if (poDS)
    2054             :                 {
    2055             :                     // Do not create a .aux.xml file just for AREA_OR_POINT=Area
    2056             :                     const char *pszAreaOfPoint =
    2057           2 :                         poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
    2058           2 :                     if (pszAreaOfPoint &&
    2059           1 :                         EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
    2060             :                     {
    2061           1 :                         poDS->SetMetadataItem(GDALMD_AREA_OR_POINT,
    2062           1 :                                               GDALMD_AOP_AREA);
    2063           1 :                         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    2064             :                     }
    2065             : 
    2066             :                     // When copying from JPEG, expose the EXIF metadata in the main domain,
    2067             :                     // so that PAM doesn't copy it.
    2068           2 :                     if (bEXIFFromMainDomain)
    2069             :                     {
    2070           0 :                         for (CSLConstList papszIter = papszEXIF;
    2071           0 :                              papszIter && *papszIter; ++papszIter)
    2072             :                         {
    2073           0 :                             if (STARTS_WITH(*papszIter, "EXIF_"))
    2074             :                             {
    2075           0 :                                 char *pszKey = nullptr;
    2076             :                                 const char *pszValue =
    2077           0 :                                     CPLParseNameValue(*papszIter, &pszKey);
    2078           0 :                                 if (pszKey && pszValue)
    2079             :                                 {
    2080           0 :                                     poDS->SetMetadataItem(pszKey, pszValue);
    2081             :                                 }
    2082           0 :                                 CPLFree(pszKey);
    2083             :                             }
    2084             :                         }
    2085           0 :                         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    2086             :                     }
    2087             : 
    2088           2 :                     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    2089             :                 }
    2090             : 
    2091           2 :                 return poDS;
    2092             :             }
    2093             :         }
    2094             :     }
    2095             : 
    2096          89 :     JxlPixelFormat format = {0, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    2097          89 :     const auto eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    2098          89 :     switch (eDT)
    2099             :     {
    2100          65 :         case GDT_Byte:
    2101          65 :             format.data_type = JXL_TYPE_UINT8;
    2102          65 :             break;
    2103          13 :         case GDT_UInt16:
    2104          13 :             format.data_type = JXL_TYPE_UINT16;
    2105          13 :             break;
    2106           2 :         case GDT_Float32:
    2107           2 :             format.data_type = JXL_TYPE_FLOAT;
    2108           2 :             break;
    2109           9 :         default:
    2110           9 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type");
    2111           9 :             return nullptr;
    2112             :     }
    2113             : 
    2114          80 :     const char *pszLossLess = CSLFetchNameValue(papszOptions, "LOSSLESS");
    2115             : 
    2116          73 :     const bool bLossless = (pszLossLess == nullptr && pszDistance == nullptr &&
    2117         160 :                             pszQuality == nullptr) ||
    2118           7 :                            (pszLossLess != nullptr && CPLTestBool(pszLossLess));
    2119          80 :     if (pszLossLess == nullptr &&
    2120          67 :         (pszDistance != nullptr || pszQuality != nullptr))
    2121             :     {
    2122           8 :         CPLDebug("JPEGXL", "Using lossy mode");
    2123             :     }
    2124          80 :     if ((pszLossLess != nullptr && bLossless) && pszDistance != nullptr)
    2125             :     {
    2126           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    2127             :                  "DISTANCE and LOSSLESS=YES are mutually exclusive");
    2128           1 :         return nullptr;
    2129             :     }
    2130          79 :     if ((pszLossLess != nullptr && bLossless) && pszAlphaDistance != nullptr)
    2131             :     {
    2132           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    2133             :                  "ALPHA_DISTANCE and LOSSLESS=YES are mutually exclusive");
    2134           1 :         return nullptr;
    2135             :     }
    2136          78 :     if ((pszLossLess != nullptr && bLossless) && pszQuality != nullptr)
    2137             :     {
    2138           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    2139             :                  "QUALITY and LOSSLESS=YES are mutually exclusive");
    2140           1 :         return nullptr;
    2141             :     }
    2142          77 :     if (pszDistance != nullptr && pszQuality != nullptr)
    2143             :     {
    2144           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    2145             :                  "QUALITY and DISTANCE are mutually exclusive");
    2146           1 :         return nullptr;
    2147             :     }
    2148             : 
    2149          76 :     float fDistance = 0.0f;
    2150          76 :     float fAlphaDistance = -1.0;
    2151          76 :     if (!bLossless)
    2152             :     {
    2153          10 :         fDistance =
    2154          10 :             pszDistance ? static_cast<float>(CPLAtof(pszDistance)) : 1.0f;
    2155          10 :         if (pszQuality != nullptr)
    2156             :         {
    2157           2 :             const double quality = CPLAtof(pszQuality);
    2158             :             // Quality settings roughly match libjpeg qualities.
    2159             :             // Formulas taken from cjxl.cc
    2160           2 :             if (quality >= 100)
    2161             :             {
    2162           1 :                 fDistance = 0;
    2163             :             }
    2164           1 :             else if (quality >= 30)
    2165             :             {
    2166           0 :                 fDistance = static_cast<float>(0.1 + (100 - quality) * 0.09);
    2167             :             }
    2168             :             else
    2169             :             {
    2170           1 :                 fDistance =
    2171           1 :                     static_cast<float>(53.0 / 3000.0 * quality * quality -
    2172           1 :                                        23.0 / 20.0 * quality + 25.0);
    2173             :             }
    2174             :         }
    2175          10 :         if (fDistance >= 0.0f && fDistance < MIN_DISTANCE)
    2176           2 :             fDistance = MIN_DISTANCE;
    2177             : 
    2178          10 :         if (pszAlphaDistance)
    2179             :         {
    2180           1 :             fAlphaDistance = static_cast<float>(CPLAtof(pszAlphaDistance));
    2181           1 :             if (fAlphaDistance > 0.0f && fAlphaDistance < MIN_DISTANCE)
    2182           0 :                 fAlphaDistance = MIN_DISTANCE;
    2183             :         }
    2184             :     }
    2185             : 
    2186          76 :     const bool bAlphaDistanceSameAsMainChannel =
    2187          77 :         (fAlphaDistance < 0.0f) ||
    2188           0 :         ((bLossless && fAlphaDistance == 0.0f) ||
    2189           1 :          (!bLossless && fAlphaDistance == fDistance));
    2190             : #ifndef HAVE_JxlEncoderSetExtraChannelDistance
    2191             :     if (!bAlphaDistanceSameAsMainChannel)
    2192             :     {
    2193             :         CPLError(CE_Warning, CPLE_NotSupported,
    2194             :                  "ALPHA_DISTANCE ignored due to "
    2195             :                  "JxlEncoderSetExtraChannelDistance() not being "
    2196             :                  "available. Please upgrade libjxl to > 0.8.1");
    2197             :     }
    2198             : #endif
    2199             : 
    2200         152 :     auto encoder = JxlEncoderMake(nullptr);
    2201          76 :     if (!encoder)
    2202             :     {
    2203           0 :         CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderMake() failed");
    2204           0 :         return nullptr;
    2205             :     }
    2206             : 
    2207          76 :     const char *pszNBits = CSLFetchNameValue(papszOptions, "NBITS");
    2208          76 :     if (pszNBits == nullptr)
    2209         148 :         pszNBits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
    2210          74 :             "NBITS", "IMAGE_STRUCTURE");
    2211             :     const int nBits =
    2212          76 :         ((eDT == GDT_Byte || eDT == GDT_UInt16) && pszNBits != nullptr)
    2213          78 :             ? atoi(pszNBits)
    2214          74 :             : GDALGetDataTypeSizeBits(eDT);
    2215             : 
    2216             :     JxlBasicInfo basic_info;
    2217          76 :     JxlEncoderInitBasicInfo(&basic_info);
    2218          76 :     basic_info.xsize = poSrcDS->GetRasterXSize();
    2219          76 :     basic_info.ysize = poSrcDS->GetRasterYSize();
    2220          76 :     basic_info.bits_per_sample = nBits;
    2221          76 :     basic_info.orientation = JXL_ORIENT_IDENTITY;
    2222          76 :     if (format.data_type == JXL_TYPE_FLOAT)
    2223             :     {
    2224           2 :         basic_info.exponent_bits_per_sample = 8;
    2225             :     }
    2226             : 
    2227          76 :     const int nSrcBands = poSrcDS->GetRasterCount();
    2228             : 
    2229          76 :     bool bHasInterleavedAlphaBand = false;
    2230          76 :     if (nSrcBands == 1)
    2231             :     {
    2232          28 :         basic_info.num_color_channels = 1;
    2233             :     }
    2234          48 :     else if (nSrcBands == 2)
    2235             :     {
    2236           8 :         basic_info.num_color_channels = 1;
    2237           8 :         basic_info.num_extra_channels = 1;
    2238           8 :         if (poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
    2239           8 :                 GCI_AlphaBand &&
    2240             :             bAlphaDistanceSameAsMainChannel)
    2241             :         {
    2242           5 :             bHasInterleavedAlphaBand = true;
    2243           5 :             basic_info.alpha_bits = basic_info.bits_per_sample;
    2244           5 :             basic_info.alpha_exponent_bits =
    2245           5 :                 basic_info.exponent_bits_per_sample;
    2246             :         }
    2247             :     }
    2248             :     else /* if( nSrcBands >= 3 ) */
    2249             :     {
    2250          40 :         if (poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
    2251          35 :                 GCI_RedBand &&
    2252          35 :             poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
    2253          75 :                 GCI_GreenBand &&
    2254          35 :             poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand)
    2255             :         {
    2256          33 :             basic_info.num_color_channels = 3;
    2257          33 :             basic_info.num_extra_channels = nSrcBands - 3;
    2258          52 :             if (nSrcBands >= 4 &&
    2259          19 :                 poSrcDS->GetRasterBand(4)->GetColorInterpretation() ==
    2260          52 :                     GCI_AlphaBand &&
    2261             :                 bAlphaDistanceSameAsMainChannel)
    2262             :             {
    2263          14 :                 bHasInterleavedAlphaBand = true;
    2264          14 :                 basic_info.alpha_bits = basic_info.bits_per_sample;
    2265          14 :                 basic_info.alpha_exponent_bits =
    2266          14 :                     basic_info.exponent_bits_per_sample;
    2267             :             }
    2268             :         }
    2269             :         else
    2270             :         {
    2271           7 :             basic_info.num_color_channels = 1;
    2272           7 :             basic_info.num_extra_channels = nSrcBands - 1;
    2273             :         }
    2274             :     }
    2275             : 
    2276          76 :     const int nBaseChannels = static_cast<int>(
    2277          76 :         basic_info.num_color_channels + (bHasInterleavedAlphaBand ? 1 : 0));
    2278          76 :     format.num_channels = nBaseChannels;
    2279             : 
    2280             : #ifndef HAVE_JxlEncoderInitExtraChannelInfo
    2281             :     if (basic_info.num_extra_channels != (bHasInterleavedAlphaBand ? 1 : 0))
    2282             :     {
    2283             :         CPLError(CE_Failure, CPLE_AppDefined,
    2284             :                  "This version of libjxl does not support "
    2285             :                  "creating non-alpha extra channels.");
    2286             :         return nullptr;
    2287             :     }
    2288             : #endif
    2289             : 
    2290             : #ifdef HAVE_JXL_THREADS
    2291         152 :     auto parallelRunner = JxlResizableParallelRunnerMake(nullptr);
    2292          76 :     if (!parallelRunner)
    2293             :     {
    2294           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2295             :                  "JxlResizableParallelRunnerMake() failed");
    2296           0 :         return nullptr;
    2297             :     }
    2298             : 
    2299          76 :     const char *pszNumThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
    2300          76 :     if (pszNumThreads == nullptr)
    2301          76 :         pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
    2302         152 :     uint32_t nMaxThreads = static_cast<uint32_t>(
    2303          76 :         EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
    2304           0 :                                          : atoi(pszNumThreads));
    2305          76 :     if (nMaxThreads > 1024)
    2306           0 :         nMaxThreads = 1024;  // to please Coverity
    2307             : 
    2308             :     const uint32_t nThreads =
    2309         228 :         std::min(nMaxThreads, JxlResizableParallelRunnerSuggestThreads(
    2310          76 :                                   basic_info.xsize, basic_info.ysize));
    2311          76 :     CPLDebug("JPEGXL", "Using %u threads", nThreads);
    2312          76 :     JxlResizableParallelRunnerSetThreads(parallelRunner.get(), nThreads);
    2313             : 
    2314          76 :     if (JxlEncoderSetParallelRunner(encoder.get(), JxlResizableParallelRunner,
    2315          76 :                                     parallelRunner.get()) != JXL_ENC_SUCCESS)
    2316             :     {
    2317           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2318             :                  "JxlEncoderSetParallelRunner() failed");
    2319           0 :         return nullptr;
    2320             :     }
    2321             : #endif
    2322             : 
    2323             : #ifdef HAVE_JxlEncoderFrameSettingsCreate
    2324             :     JxlEncoderFrameSettings *opts =
    2325          76 :         JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
    2326             : #else
    2327             :     JxlEncoderOptions *opts = JxlEncoderOptionsCreate(encoder.get(), nullptr);
    2328             : #endif
    2329          76 :     if (opts == nullptr)
    2330             :     {
    2331           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2332             :                  "JxlEncoderFrameSettingsCreate() failed");
    2333           0 :         return nullptr;
    2334             :     }
    2335             : 
    2336             : #ifdef HAVE_JxlEncoderSetCodestreamLevel
    2337          76 :     if (poSrcDS->GetRasterXSize() > 262144 ||
    2338         152 :         poSrcDS->GetRasterYSize() > 262144 ||
    2339          76 :         poSrcDS->GetRasterXSize() > 268435456 / poSrcDS->GetRasterYSize())
    2340             :     {
    2341           0 :         JxlEncoderSetCodestreamLevel(encoder.get(), 10);
    2342             :     }
    2343             : #endif
    2344             : 
    2345          76 :     if (bLossless)
    2346             :     {
    2347             : #ifdef HAVE_JxlEncoderSetCodestreamLevel
    2348          66 :         if (nBits > 12)
    2349             :         {
    2350          14 :             JxlEncoderSetCodestreamLevel(encoder.get(), 10);
    2351             :         }
    2352             : #endif
    2353             : 
    2354             : #ifdef HAVE_JxlEncoderSetFrameLossless
    2355          66 :         JxlEncoderSetFrameLossless(opts, TRUE);
    2356             : #else
    2357             :         JxlEncoderOptionsSetLossless(opts, TRUE);
    2358             : #endif
    2359          66 :         basic_info.uses_original_profile = JXL_TRUE;
    2360             :     }
    2361             :     else
    2362             :     {
    2363             : #ifdef HAVE_JxlEncoderSetFrameDistance
    2364          10 :         if (JxlEncoderSetFrameDistance(opts, fDistance) != JXL_ENC_SUCCESS)
    2365             : #else
    2366             :         if (JxlEncoderOptionsSetDistance(opts, fDistance) != JXL_ENC_SUCCESS)
    2367             : #endif
    2368             :         {
    2369           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2370             :                      "JxlEncoderSetFrameDistance() failed");
    2371           1 :             return nullptr;
    2372             :         }
    2373             :     }
    2374             : 
    2375             : #ifdef HAVE_JxlEncoderFrameSettingsSetOption
    2376          75 :     if (JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT,
    2377          75 :                                          nEffort) != JXL_ENC_SUCCESS)
    2378             : #else
    2379             :     if (JxlEncoderOptionsSetEffort(opts, nEffort) != JXL_ENC_SUCCESS)
    2380             : #endif
    2381             :     {
    2382           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2383             :                  "JxlEncoderFrameSettingsSetOption() failed");
    2384           1 :         return nullptr;
    2385             :     }
    2386             : 
    2387         148 :     std::vector<GByte> abyJPEG;
    2388          74 :     void *pJPEGContent = nullptr;
    2389          74 :     size_t nJPEGContent = 0;
    2390          74 :     char *pszDetailedFormat = nullptr;
    2391             :     // If the source dataset is a JPEG file or compatible of it, try to
    2392             :     // losslessly add it
    2393         146 :     if ((EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy)) &&
    2394          72 :         poSrcDS->ReadCompressedData(
    2395             :             "JPEG", 0, 0, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(),
    2396             :             poSrcDS->GetRasterCount(), nullptr, &pJPEGContent, &nJPEGContent,
    2397          72 :             &pszDetailedFormat) == CE_None)
    2398             :     {
    2399           5 :         CPLAssert(pszDetailedFormat != nullptr);
    2400             :         const CPLStringList aosTokens(
    2401          10 :             CSLTokenizeString2(pszDetailedFormat, ";", 0));
    2402           5 :         VSIFree(pszDetailedFormat);
    2403           5 :         const char *pszBitDepth = aosTokens.FetchNameValueDef("bit_depth", "");
    2404           5 :         if (pJPEGContent && !EQUAL(pszBitDepth, "8"))
    2405             :         {
    2406           0 :             CPLDebug(
    2407             :                 "JPEGXL",
    2408             :                 "Unsupported bit_depth=%s for lossless transcoding from JPEG",
    2409             :                 pszBitDepth);
    2410           0 :             VSIFree(pJPEGContent);
    2411           0 :             pJPEGContent = nullptr;
    2412             :         }
    2413             :         const char *pszColorspace =
    2414           5 :             aosTokens.FetchNameValueDef("colorspace", "");
    2415           5 :         if (pJPEGContent && !EQUAL(pszColorspace, "unknown") &&
    2416           4 :             !EQUAL(pszColorspace, "RGB") && !EQUAL(pszColorspace, "YCbCr"))
    2417             :         {
    2418           0 :             CPLDebug(
    2419             :                 "JPEGXL",
    2420             :                 "Unsupported colorspace=%s for lossless transcoding from JPEG",
    2421             :                 pszColorspace);
    2422           0 :             VSIFree(pJPEGContent);
    2423           0 :             pJPEGContent = nullptr;
    2424             :         }
    2425           5 :         const char *pszSOF = aosTokens.FetchNameValueDef("frame_type", "");
    2426           5 :         if (pJPEGContent && !EQUAL(pszSOF, "SOF0_baseline") &&
    2427           0 :             !EQUAL(pszSOF, "SOF1_extended_sequential") &&
    2428           0 :             !EQUAL(pszSOF, "SOF2_progressive_huffman"))
    2429             :         {
    2430           0 :             CPLDebug(
    2431             :                 "JPEGXL",
    2432             :                 "Unsupported frame_type=%s for lossless transcoding from JPEG",
    2433             :                 pszSOF);
    2434           0 :             VSIFree(pJPEGContent);
    2435           0 :             pJPEGContent = nullptr;
    2436             :         }
    2437             :     }
    2438          74 :     if (pJPEGContent)
    2439             :     {
    2440             :         try
    2441             :         {
    2442           5 :             abyJPEG.reserve(nJPEGContent);
    2443           0 :             abyJPEG.insert(abyJPEG.end(), static_cast<GByte *>(pJPEGContent),
    2444           5 :                            static_cast<GByte *>(pJPEGContent) + nJPEGContent);
    2445           5 :             VSIFree(pJPEGContent);
    2446             : 
    2447          10 :             std::vector<GByte> abyJPEGMod;
    2448           5 :             abyJPEGMod.reserve(abyJPEG.size());
    2449             : 
    2450             :             // Append Start Of Image marker (0xff 0xd8)
    2451           5 :             abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin(),
    2452          10 :                               abyJPEG.begin() + 2);
    2453             : 
    2454             :             // Rework JPEG data to remove APP (except APP0) and COM
    2455             :             // markers as it confuses libjxl, when trying to
    2456             :             // reconstruct a JPEG file
    2457           5 :             size_t i = 2;
    2458          10 :             while (i + 1 < abyJPEG.size())
    2459             :             {
    2460          10 :                 if (abyJPEG[i] != 0xFF)
    2461             :                 {
    2462             :                     // Not a valid tag (shouldn't happen)
    2463           0 :                     abyJPEGMod.clear();
    2464           0 :                     break;
    2465             :                 }
    2466             : 
    2467             :                 // Stop when encountering a marker that is not a APP
    2468             :                 // or COM marker
    2469          10 :                 const bool bIsCOM = abyJPEG[i + 1] == 0xFE;
    2470          10 :                 if ((abyJPEG[i + 1] & 0xF0) != 0xE0 && !bIsCOM)
    2471             :                 {
    2472             :                     // Append all markers until end
    2473           5 :                     abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i,
    2474          10 :                                       abyJPEG.end());
    2475           5 :                     break;
    2476             :                 }
    2477           5 :                 const bool bIsAPP0 = abyJPEG[i + 1] == 0xE0;
    2478             : 
    2479             :                 // Skip marker ID
    2480           5 :                 i += 2;
    2481             :                 // Check we can read chunk length
    2482           5 :                 if (i + 1 >= abyJPEG.size())
    2483             :                 {
    2484             :                     // Truncated JPEG file
    2485           0 :                     abyJPEGMod.clear();
    2486           0 :                     break;
    2487             :                 }
    2488           5 :                 const int nChunkLength = abyJPEG[i] * 256 + abyJPEG[i + 1];
    2489           5 :                 if ((bIsCOM || bIsAPP0) && i + nChunkLength <= abyJPEG.size())
    2490             :                 {
    2491             :                     // Append COM or APP0 marker
    2492          10 :                     abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i - 2,
    2493          15 :                                       abyJPEG.begin() + i + nChunkLength);
    2494             :                 }
    2495           5 :                 i += nChunkLength;
    2496             :             }
    2497           5 :             abyJPEG = std::move(abyJPEGMod);
    2498             :         }
    2499           0 :         catch (const std::exception &)
    2500             :         {
    2501             :         }
    2502             :     }
    2503          83 :     if (abyJPEG.empty() && !bLossless &&
    2504           9 :         (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
    2505             :     {
    2506           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2507             :                  "LOSSLESS_COPY=YES requested but not possible");
    2508           1 :         return nullptr;
    2509             :     }
    2510             : 
    2511             :     const char *pszICCProfile =
    2512          73 :         CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
    2513          73 :     if (pszICCProfile == nullptr)
    2514             :     {
    2515             :         pszICCProfile =
    2516          73 :             poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
    2517             :     }
    2518          73 :     if (pszICCProfile && pszICCProfile[0] != '\0')
    2519             :     {
    2520           1 :         basic_info.uses_original_profile = JXL_TRUE;
    2521             :     }
    2522             : 
    2523          73 :     if (abyJPEG.empty())
    2524             :     {
    2525          68 :         if (JXL_ENC_SUCCESS !=
    2526          68 :             JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
    2527             :         {
    2528           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2529             :                      "JxlEncoderSetBasicInfo() failed");
    2530           0 :             return nullptr;
    2531             :         }
    2532             : 
    2533          68 :         if (pszICCProfile && pszICCProfile[0] != '\0')
    2534             :         {
    2535           1 :             char *pEmbedBuffer = CPLStrdup(pszICCProfile);
    2536             :             GInt32 nEmbedLen =
    2537           1 :                 CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pEmbedBuffer));
    2538           1 :             if (JXL_ENC_SUCCESS !=
    2539           1 :                 JxlEncoderSetICCProfile(encoder.get(),
    2540             :                                         reinterpret_cast<GByte *>(pEmbedBuffer),
    2541             :                                         nEmbedLen))
    2542             :             {
    2543           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2544             :                          "JxlEncoderSetICCProfile() failed");
    2545           0 :                 CPLFree(pEmbedBuffer);
    2546           0 :                 return nullptr;
    2547             :             }
    2548           1 :             CPLFree(pEmbedBuffer);
    2549             :         }
    2550             :         else
    2551             :         {
    2552             :             JxlColorEncoding color_encoding;
    2553          67 :             JxlColorEncodingSetToSRGB(&color_encoding,
    2554          67 :                                       basic_info.num_color_channels ==
    2555             :                                           1 /*is_gray*/);
    2556          67 :             if (JXL_ENC_SUCCESS !=
    2557          67 :                 JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
    2558             :             {
    2559           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2560             :                          "JxlEncoderSetColorEncoding() failed");
    2561           0 :                 return nullptr;
    2562             :             }
    2563             :         }
    2564             :     }
    2565             : 
    2566             : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
    2567          73 :     if (abyJPEG.empty() && basic_info.num_extra_channels > 0)
    2568             :     {
    2569          33 :         if (basic_info.num_extra_channels >= 5)
    2570           0 :             JxlEncoderSetCodestreamLevel(encoder.get(), 10);
    2571             : 
    2572          71 :         for (int i = (bHasInterleavedAlphaBand ? 1 : 0);
    2573          71 :              i < static_cast<int>(basic_info.num_extra_channels); ++i)
    2574             :         {
    2575          38 :             const int nBand =
    2576          38 :                 static_cast<int>(1 + basic_info.num_color_channels + i);
    2577          38 :             const auto poBand = poSrcDS->GetRasterBand(nBand);
    2578             :             JxlExtraChannelInfo extra_channel_info;
    2579             :             const JxlExtraChannelType channelType =
    2580          38 :                 poBand->GetColorInterpretation() == GCI_AlphaBand
    2581          38 :                     ? JXL_CHANNEL_ALPHA
    2582          38 :                     : JXL_CHANNEL_OPTIONAL;
    2583          38 :             JxlEncoderInitExtraChannelInfo(channelType, &extra_channel_info);
    2584          38 :             extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
    2585          38 :             extra_channel_info.exponent_bits_per_sample =
    2586          38 :                 basic_info.exponent_bits_per_sample;
    2587             : 
    2588          38 :             const uint32_t nIndex = static_cast<uint32_t>(i);
    2589          38 :             if (JXL_ENC_SUCCESS !=
    2590          38 :                 JxlEncoderSetExtraChannelInfo(encoder.get(), nIndex,
    2591             :                                               &extra_channel_info))
    2592             :             {
    2593           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2594             :                          "JxlEncoderSetExtraChannelInfo() failed");
    2595           0 :                 return nullptr;
    2596             :             }
    2597          38 :             std::string osChannelName(CPLSPrintf("Band %d", nBand));
    2598          38 :             const char *pszDescription = poBand->GetDescription();
    2599          38 :             if (pszDescription && pszDescription[0] != '\0')
    2600           1 :                 osChannelName = pszDescription;
    2601          38 :             if (JXL_ENC_SUCCESS !=
    2602          76 :                 JxlEncoderSetExtraChannelName(encoder.get(), nIndex,
    2603          38 :                                               osChannelName.data(),
    2604             :                                               osChannelName.size()))
    2605             :             {
    2606           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2607             :                          "JxlEncoderSetExtraChannelName() failed");
    2608           0 :                 return nullptr;
    2609             :             }
    2610             : #if HAVE_JxlEncoderSetExtraChannelDistance
    2611          38 :             if (channelType == JXL_CHANNEL_ALPHA && fAlphaDistance >= 0.0f)
    2612             :             {
    2613           1 :                 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(
    2614             :                                            opts, nIndex, fAlphaDistance))
    2615             :                 {
    2616           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2617             :                              "JxlEncoderSetExtraChannelDistance failed");
    2618           0 :                     return nullptr;
    2619             :                 }
    2620             :             }
    2621          37 :             else if (!bLossless)
    2622             :             {
    2623             :                 // By default libjxl applies lossless encoding for extra channels
    2624           4 :                 if (JXL_ENC_SUCCESS !=
    2625           4 :                     JxlEncoderSetExtraChannelDistance(opts, nIndex, fDistance))
    2626             :                 {
    2627           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2628             :                              "JxlEncoderSetExtraChannelDistance failed");
    2629           0 :                     return nullptr;
    2630             :                 }
    2631             :             }
    2632             : #endif
    2633             :         }
    2634             :     }
    2635             : #endif
    2636             : 
    2637             : #ifdef HAVE_JXL_BOX_API
    2638             :     const bool bCompressBox =
    2639          73 :         CPLFetchBool(papszOptions, "COMPRESS_BOXES", false);
    2640             : 
    2641          73 :     if (papszXMP && papszXMP[0] && bWriteXMP)
    2642             :     {
    2643           2 :         JxlEncoderUseBoxes(encoder.get());
    2644             : 
    2645           2 :         const char *pszXMP = papszXMP[0];
    2646           2 :         if (JxlEncoderAddBox(encoder.get(), "xml ",
    2647             :                              reinterpret_cast<const uint8_t *>(pszXMP),
    2648           2 :                              strlen(pszXMP), bCompressBox) != JXL_ENC_SUCCESS)
    2649             :         {
    2650           0 :             CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
    2651           0 :             return nullptr;
    2652             :         }
    2653             :     }
    2654             : 
    2655             :     // Write "Exif" box with EXIF metadata
    2656          73 :     if (papszEXIF && bWriteExifMetadata)
    2657             :     {
    2658           6 :         GUInt32 nMarkerSize = 0;
    2659           6 :         GByte *pabyEXIF = EXIFCreate(papszEXIF, nullptr, 0, 0, 0,  // overview
    2660             :                                      &nMarkerSize);
    2661           6 :         CPLAssert(nMarkerSize > 6 && memcmp(pabyEXIF, "Exif\0\0", 6) == 0);
    2662             :         // Add 4 leading bytes at 0
    2663           6 :         std::vector<GByte> abyEXIF(4 + nMarkerSize - 6);
    2664           6 :         memcpy(&abyEXIF[4], pabyEXIF + 6, nMarkerSize - 6);
    2665           6 :         CPLFree(pabyEXIF);
    2666             : 
    2667           6 :         JxlEncoderUseBoxes(encoder.get());
    2668           6 :         if (JxlEncoderAddBox(encoder.get(), "Exif", abyEXIF.data(),
    2669           6 :                              abyEXIF.size(), bCompressBox) != JXL_ENC_SUCCESS)
    2670             :         {
    2671           0 :             CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
    2672           0 :             return nullptr;
    2673             :         }
    2674             :     }
    2675             : 
    2676             :     // Write GeoJP2 box in a JUMBF box from georeferencing information
    2677          73 :     if (poJUMBFBox)
    2678             :     {
    2679          30 :         GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
    2680          30 :         JxlEncoderUseBoxes(encoder.get());
    2681          30 :         if (JxlEncoderAddBox(encoder.get(), "jumb", pabyBoxData,
    2682          30 :                              static_cast<size_t>(poJUMBFBox->GetBoxLength()),
    2683          30 :                              bCompressBox) != JXL_ENC_SUCCESS)
    2684             :         {
    2685           0 :             VSIFree(pabyBoxData);
    2686           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2687             :                      "JxlEncoderAddBox() failed for jumb");
    2688           0 :             return nullptr;
    2689             :         }
    2690          30 :         VSIFree(pabyBoxData);
    2691             :     }
    2692             : #endif
    2693             : 
    2694             :     auto fp = std::unique_ptr<VSILFILE, VSILFileReleaser>(
    2695         146 :         VSIFOpenL(pszFilename, "wb"));
    2696          73 :     if (!fp)
    2697             :     {
    2698           4 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s: %s", pszFilename,
    2699           4 :                  VSIStrerror(errno));
    2700           4 :         return nullptr;
    2701             :     }
    2702             : 
    2703          69 :     int nPamMask = GCIF_PAM_DEFAULT;
    2704             : 
    2705          69 :     if (!abyJPEG.empty())
    2706             :     {
    2707             : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
    2708             :         const bool bHasMaskBand =
    2709          10 :             basic_info.num_extra_channels == 0 &&
    2710           5 :             poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET;
    2711           5 :         if (bHasMaskBand)
    2712             :         {
    2713           1 :             nPamMask &= ~GCIF_MASK;
    2714             : 
    2715           1 :             basic_info.alpha_bits = basic_info.bits_per_sample;
    2716           1 :             basic_info.num_extra_channels = 1;
    2717           1 :             if (JXL_ENC_SUCCESS !=
    2718           1 :                 JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
    2719             :             {
    2720           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2721             :                          "JxlEncoderSetBasicInfo() failed");
    2722           0 :                 return nullptr;
    2723             :             }
    2724             : 
    2725             :             JxlExtraChannelInfo extra_channel_info;
    2726           1 :             JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA,
    2727             :                                            &extra_channel_info);
    2728           1 :             extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
    2729           1 :             extra_channel_info.exponent_bits_per_sample =
    2730           1 :                 basic_info.exponent_bits_per_sample;
    2731             : 
    2732           1 :             if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(
    2733             :                                        encoder.get(), 0, &extra_channel_info))
    2734             :             {
    2735           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2736             :                          "JxlEncoderSetExtraChannelInfo() failed");
    2737           0 :                 return nullptr;
    2738             :             }
    2739             :         }
    2740             : #endif
    2741             : 
    2742           5 :         CPLDebug("JPEGXL", "Adding JPEG frame");
    2743           5 :         JxlEncoderStoreJPEGMetadata(encoder.get(), true);
    2744           5 :         if (JxlEncoderAddJPEGFrame(opts, abyJPEG.data(), abyJPEG.size()) !=
    2745             :             JXL_ENC_SUCCESS)
    2746             :         {
    2747           1 :             if (EQUAL(pszLossLessCopy, "AUTO"))
    2748             :             {
    2749             :                 // could happen with a file with arithmetic encoding for example
    2750           1 :                 CPLDebug("JPEGXL",
    2751             :                          "JxlEncoderAddJPEGFrame() framed. "
    2752             :                          "Perhaps unsupported JPEG formulation for libjxl. "
    2753             :                          "Retrying with normal code path");
    2754           2 :                 CPLStringList aosOptions(papszOptions);
    2755           1 :                 aosOptions.SetNameValue("LOSSLESS_COPY", "NO");
    2756             :                 CPLConfigOptionSetter oSetter("GDAL_ERROR_ON_LIBJPEG_WARNING",
    2757           2 :                                               "YES", true);
    2758           1 :                 return CreateCopy(pszFilename, poSrcDS, FALSE,
    2759             :                                   aosOptions.List(), pfnProgress,
    2760           1 :                                   pProgressData);
    2761             :             }
    2762             :             else
    2763             :             {
    2764           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2765             :                          "JxlEncoderAddJPEGFrame() failed");
    2766           0 :                 return nullptr;
    2767             :             }
    2768             :         }
    2769             : 
    2770             : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
    2771           4 :         if (bHasMaskBand)
    2772             :         {
    2773             :             JxlColorEncoding color_encoding;
    2774           1 :             JxlColorEncodingSetToSRGB(&color_encoding,
    2775           1 :                                       basic_info.num_color_channels ==
    2776             :                                           1 /*is_gray*/);
    2777             :             // libjxl until commit
    2778             :             // https://github.com/libjxl/libjxl/commits/c70c9d0bdc03f77d6bd8d9c3c56d4dac1b9b1652
    2779             :             // needs JxlEncoderSetColorEncoding()
    2780             :             // But post it (308b5f1eed81becac506569080e4490cc486660c,
    2781             :             // "Use chunked frame adapter instead of image bundle in
    2782             :             // EncodeFrame. (#2983)"), this errors out.
    2783           1 :             CPL_IGNORE_RET_VAL(
    2784           1 :                 JxlEncoderSetColorEncoding(encoder.get(), &color_encoding));
    2785             : 
    2786           1 :             const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
    2787           2 :             if (nDataSize <= 0 ||
    2788           1 :                 static_cast<size_t>(poSrcDS->GetRasterXSize()) >
    2789           1 :                     std::numeric_limits<size_t>::max() /
    2790           1 :                         poSrcDS->GetRasterYSize() / nDataSize)
    2791             :             {
    2792           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    2793             :                          "Image too big for architecture");
    2794           0 :                 return nullptr;
    2795             :             }
    2796             :             const size_t nInputDataSize =
    2797           1 :                 static_cast<size_t>(poSrcDS->GetRasterXSize()) *
    2798           1 :                 poSrcDS->GetRasterYSize() * nDataSize;
    2799             : 
    2800           1 :             std::vector<GByte> abyInputData;
    2801             :             try
    2802             :             {
    2803           1 :                 abyInputData.resize(nInputDataSize);
    2804             :             }
    2805           0 :             catch (const std::exception &e)
    2806             :             {
    2807           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    2808           0 :                          "Cannot allocate image buffer: %s", e.what());
    2809           0 :                 return nullptr;
    2810             :             }
    2811             : 
    2812           1 :             format.num_channels = 1;
    2813           2 :             if (poSrcDS->GetRasterBand(1)->GetMaskBand()->RasterIO(
    2814             :                     GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
    2815           1 :                     poSrcDS->GetRasterYSize(), abyInputData.data(),
    2816             :                     poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
    2817           1 :                     0, 0, nullptr) != CE_None)
    2818             :             {
    2819           0 :                 return nullptr;
    2820             :             }
    2821           1 :             if (JxlEncoderSetExtraChannelBuffer(
    2822           1 :                     opts, &format, abyInputData.data(),
    2823           1 :                     static_cast<size_t>(poSrcDS->GetRasterXSize()) *
    2824           1 :                         poSrcDS->GetRasterYSize() * nDataSize,
    2825           1 :                     0) != JXL_ENC_SUCCESS)
    2826             :             {
    2827           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2828             :                          "JxlEncoderSetExtraChannelBuffer() failed");
    2829           0 :                 return nullptr;
    2830             :             }
    2831             :         }
    2832             : #endif
    2833             :     }
    2834             :     else
    2835             :     {
    2836          64 :         const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
    2837             : 
    2838         128 :         if (nDataSize <= 0 || static_cast<size_t>(poSrcDS->GetRasterXSize()) >
    2839          64 :                                   std::numeric_limits<size_t>::max() /
    2840          64 :                                       poSrcDS->GetRasterYSize() /
    2841          64 :                                       nBaseChannels / nDataSize)
    2842             :         {
    2843           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    2844             :                      "Image too big for architecture");
    2845           1 :             return nullptr;
    2846             :         }
    2847             :         const size_t nInputDataSize =
    2848          64 :             static_cast<size_t>(poSrcDS->GetRasterXSize()) *
    2849          64 :             poSrcDS->GetRasterYSize() * nBaseChannels * nDataSize;
    2850             : 
    2851          64 :         std::vector<GByte> abyInputData;
    2852             :         try
    2853             :         {
    2854          64 :             abyInputData.resize(nInputDataSize);
    2855             :         }
    2856           0 :         catch (const std::exception &e)
    2857             :         {
    2858           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    2859           0 :                      "Cannot allocate image buffer: %s", e.what());
    2860           0 :             return nullptr;
    2861             :         }
    2862             : 
    2863         128 :         if (poSrcDS->RasterIO(
    2864             :                 GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
    2865          64 :                 poSrcDS->GetRasterYSize(), abyInputData.data(),
    2866             :                 poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
    2867          64 :                 nBaseChannels, nullptr, nDataSize * nBaseChannels,
    2868          64 :                 nDataSize * nBaseChannels * poSrcDS->GetRasterXSize(),
    2869          64 :                 nDataSize, nullptr) != CE_None)
    2870             :         {
    2871           1 :             return nullptr;
    2872             :         }
    2873             : 
    2874         307 :         const auto Rescale = [eDT, nBits, poSrcDS](void *pBuffer, int nChannels)
    2875             :         {
    2876             :             // Rescale to 8-bits/16-bits
    2877         101 :             if ((eDT == GDT_Byte && nBits < 8) ||
    2878          23 :                 (eDT == GDT_UInt16 && nBits < 16))
    2879             :             {
    2880             :                 const size_t nSamples =
    2881           2 :                     static_cast<size_t>(poSrcDS->GetRasterXSize()) *
    2882           2 :                     poSrcDS->GetRasterYSize() * nChannels;
    2883           2 :                 const int nMaxVal = (1 << nBits) - 1;
    2884           2 :                 const int nMavValHalf = nMaxVal / 2;
    2885           2 :                 if (eDT == GDT_Byte)
    2886             :                 {
    2887           1 :                     uint8_t *panData = static_cast<uint8_t *>(pBuffer);
    2888         401 :                     for (size_t i = 0; i < nSamples; ++i)
    2889             :                     {
    2890         400 :                         panData[i] = static_cast<GByte>(
    2891         400 :                             (std::min(static_cast<int>(panData[i]), nMaxVal) *
    2892         400 :                                  255 +
    2893         400 :                              nMavValHalf) /
    2894         400 :                             nMaxVal);
    2895             :                     }
    2896             :                 }
    2897           1 :                 else if (eDT == GDT_UInt16)
    2898             :                 {
    2899           1 :                     uint16_t *panData = static_cast<uint16_t *>(pBuffer);
    2900         401 :                     for (size_t i = 0; i < nSamples; ++i)
    2901             :                     {
    2902         400 :                         panData[i] = static_cast<uint16_t>(
    2903         400 :                             (std::min(static_cast<int>(panData[i]), nMaxVal) *
    2904         400 :                                  65535 +
    2905         400 :                              nMavValHalf) /
    2906         400 :                             nMaxVal);
    2907             :                     }
    2908             :                 }
    2909             :             }
    2910         101 :         };
    2911             : 
    2912          63 :         Rescale(abyInputData.data(), nBaseChannels);
    2913             : 
    2914          63 :         if (JxlEncoderAddImageFrame(opts, &format, abyInputData.data(),
    2915          63 :                                     abyInputData.size()) != JXL_ENC_SUCCESS)
    2916             :         {
    2917           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2918             :                      "JxlEncoderAddImageFrame() failed");
    2919           0 :             return nullptr;
    2920             :         }
    2921             : 
    2922             : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
    2923          63 :         format.num_channels = 1;
    2924         101 :         for (int i = nBaseChannels; i < poSrcDS->GetRasterCount(); ++i)
    2925             :         {
    2926          76 :             if (poSrcDS->GetRasterBand(i + 1)->RasterIO(
    2927             :                     GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
    2928          38 :                     poSrcDS->GetRasterYSize(), abyInputData.data(),
    2929             :                     poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
    2930          38 :                     0, 0, nullptr) != CE_None)
    2931             :             {
    2932           0 :                 return nullptr;
    2933             :             }
    2934             : 
    2935          38 :             Rescale(abyInputData.data(), 1);
    2936             : 
    2937          38 :             if (JxlEncoderSetExtraChannelBuffer(
    2938          38 :                     opts, &format, abyInputData.data(),
    2939         114 :                     static_cast<size_t>(poSrcDS->GetRasterXSize()) *
    2940          38 :                         poSrcDS->GetRasterYSize() * nDataSize,
    2941          76 :                     i - nBaseChannels + (bHasInterleavedAlphaBand ? 1 : 0)) !=
    2942             :                 JXL_ENC_SUCCESS)
    2943             :             {
    2944           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2945             :                          "JxlEncoderSetExtraChannelBuffer() failed");
    2946           0 :                 return nullptr;
    2947             :             }
    2948             :         }
    2949             : #endif
    2950             :     }
    2951             : 
    2952          67 :     JxlEncoderCloseInput(encoder.get());
    2953             : 
    2954             :     // Flush to file
    2955         201 :     std::vector<GByte> abyOutputBuffer(4096 * 10);
    2956             :     while (true)
    2957             :     {
    2958          67 :         size_t len = abyOutputBuffer.size();
    2959          67 :         uint8_t *buf = abyOutputBuffer.data();
    2960             :         JxlEncoderStatus process_result =
    2961          67 :             JxlEncoderProcessOutput(encoder.get(), &buf, &len);
    2962          67 :         if (process_result == JXL_ENC_ERROR)
    2963             :         {
    2964           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2965             :                      "JxlEncoderProcessOutput() failed");
    2966          10 :             return nullptr;
    2967             :         }
    2968          67 :         size_t nToWrite = abyOutputBuffer.size() - len;
    2969          67 :         if (VSIFWriteL(abyOutputBuffer.data(), 1, nToWrite, fp.get()) !=
    2970             :             nToWrite)
    2971             :         {
    2972          10 :             CPLError(CE_Failure, CPLE_FileIO, "VSIFWriteL() failed");
    2973          10 :             return nullptr;
    2974             :         }
    2975          57 :         if (process_result != JXL_ENC_NEED_MORE_OUTPUT)
    2976          57 :             break;
    2977           0 :     }
    2978             : 
    2979          57 :     fp.reset();
    2980             : 
    2981          57 :     if (pfnProgress)
    2982          57 :         pfnProgress(1.0, "", pProgressData);
    2983             : 
    2984             :     // Re-open file and clone missing info to PAM
    2985          57 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    2986          57 :     auto poDS = OpenStaticPAM(&oOpenInfo);
    2987          57 :     if (poDS)
    2988             :     {
    2989             :         // Do not create a .aux.xml file just for AREA_OR_POINT=Area
    2990             :         const char *pszAreaOfPoint =
    2991          57 :             poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
    2992          57 :         if (pszAreaOfPoint && EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
    2993             :         {
    2994           9 :             poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_AREA);
    2995           9 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    2996             :         }
    2997             : #ifdef HAVE_JXL_BOX_API
    2998             :         // When copying from JPEG, expose the EXIF metadata in the main domain,
    2999             :         // so that PAM doesn't copy it.
    3000          57 :         if (bEXIFFromMainDomain)
    3001             :         {
    3002          51 :             for (CSLConstList papszIter = papszEXIF; papszIter && *papszIter;
    3003             :                  ++papszIter)
    3004             :             {
    3005          48 :                 if (STARTS_WITH(*papszIter, "EXIF_"))
    3006             :                 {
    3007          48 :                     char *pszKey = nullptr;
    3008             :                     const char *pszValue =
    3009          48 :                         CPLParseNameValue(*papszIter, &pszKey);
    3010          48 :                     if (pszKey && pszValue)
    3011             :                     {
    3012          48 :                         poDS->SetMetadataItem(pszKey, pszValue);
    3013             :                     }
    3014          48 :                     CPLFree(pszKey);
    3015             :                 }
    3016             :             }
    3017           3 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    3018             :         }
    3019             : #endif
    3020          57 :         poDS->CloneInfo(poSrcDS, nPamMask);
    3021             :     }
    3022             : 
    3023          57 :     return poDS;
    3024             : }
    3025             : 
    3026             : /************************************************************************/
    3027             : /*                        GDALRegister_JPEGXL()                         */
    3028             : /************************************************************************/
    3029             : 
    3030          12 : void GDALRegister_JPEGXL()
    3031             : 
    3032             : {
    3033          12 :     if (GDALGetDriverByName("JPEGXL") != nullptr)
    3034           0 :         return;
    3035             : 
    3036          12 :     GDALDriver *poDriver = new GDALDriver();
    3037             : 
    3038          12 :     JPEGXLDriverSetCommonMetadata(poDriver);
    3039          12 :     poDriver->pfnOpen = JPEGXLDataset::OpenStatic;
    3040          12 :     poDriver->pfnIdentify = JPEGXLDataset::Identify;
    3041          12 :     poDriver->pfnCreateCopy = JPEGXLDataset::CreateCopy;
    3042             : 
    3043          12 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    3044             : }

Generated by: LCOV version 1.14