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

Generated by: LCOV version 1.14