• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15899162844

26 Jun 2025 10:14AM UTC coverage: 71.088% (+0.004%) from 71.084%
15899162844

Pull #12623

github

web-flow
Merge c704a8392 into f5cb024d4
Pull Request #12623: gdal raster overview add: add a --overview-src option

209 of 244 new or added lines in 5 files covered. (85.66%)

96 existing lines in 44 files now uncovered.

574014 of 807474 relevant lines covered (71.09%)

250815.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.21
/frmts/jpegxl/jpegxl.cpp
1
/******************************************************************************
2
 *
3
 * Project:  JPEG-XL Driver
4
 * Purpose:  Implement GDAL JPEG-XL Support based on libjxl
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "cpl_error.h"
14
#include "gdalexif.h"
15
#include "gdaljp2metadata.h"
16
#include "gdaljp2abstractdataset.h"
17
#include "gdalorienteddataset.h"
18

19
#include <algorithm>
20
#include <cassert>
21
#include <cstdlib>
22
#include <limits>
23

24
#include "jxl_headers.h"
25

26
#include "jpegxldrivercore.h"
27

28
namespace
29
{
30
struct VSILFileReleaser
31
{
32
    void operator()(VSILFILE *fp)
67✔
33
    {
34
        if (fp)
67✔
35
            VSIFCloseL(fp);
67✔
36
    }
67✔
37
};
38
}  // namespace
39

40
constexpr float MIN_DISTANCE = 0.01f;
41

42
/************************************************************************/
43
/*                        JPEGXLDataset                                 */
44
/************************************************************************/
45

46
class JPEGXLDataset final : public GDALJP2AbstractDataset
47
{
48
    friend class JPEGXLRasterBand;
49

50
    VSILFILE *m_fp = nullptr;
51
    JxlDecoderPtr m_decoder{};
52
#ifdef HAVE_JXL_THREADS
53
    JxlResizableParallelRunnerPtr m_parallelRunner{};
54
#endif
55
    bool m_bDecodingFailed = false;
56
    std::vector<GByte> m_abyImage{};
57
    std::vector<std::vector<GByte>> m_abyExtraChannels{};
58
    std::vector<GByte> m_abyInputData{};
59
    int m_nBits = 0;
60
    int m_nNonAlphaExtraChannels = 0;
61
#ifdef HAVE_JXL_BOX_API
62
    std::string m_osXMP{};
63
    char *m_apszXMP[2] = {nullptr, nullptr};
64
    std::vector<GByte> m_abyEXIFBox{};
65
    CPLStringList m_aosEXIFMetadata{};
66
    bool m_bHasJPEGReconstructionData = false;
67
    std::string m_osJPEGData{};
68
#endif
69

70
    bool Open(GDALOpenInfo *poOpenInfo);
71

72
    void GetDecodedImage(void *pabyOutputData, size_t nOutputDataSize);
73

74
  protected:
75
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
76
                     GDALDataType, int, BANDMAP_TYPE, GSpacing, GSpacing,
77
                     GSpacing, GDALRasterIOExtraArg *psExtraArg) override;
78

79
  public:
80
    ~JPEGXLDataset();
81

82
    char **GetMetadataDomainList() override;
83
    char **GetMetadata(const char *pszDomain) override;
84
    const char *GetMetadataItem(const char *pszName,
85
                                const char *pszDomain) override;
86

87
    CPLStringList GetCompressionFormats(int nXOff, int nYOff, int nXSize,
88
                                        int nYSize, int nBandCount,
89
                                        const int *panBandList) override;
90
    CPLErr ReadCompressedData(const char *pszFormat, int nXOff, int nYOff,
91
                              int nXSize, int nYSize, int nBandCount,
92
                              const int *panBandList, void **ppBuffer,
93
                              size_t *pnBufferSize,
94
                              char **ppszDetailedFormat) override;
95

96
    const std::vector<GByte> &GetDecodedImage();
97

98
    static int Identify(GDALOpenInfo *poOpenInfo);
99
    static GDALPamDataset *OpenStaticPAM(GDALOpenInfo *poOpenInfo);
100
    static GDALDataset *OpenStatic(GDALOpenInfo *poOpenInfo);
101
    static GDALDataset *CreateCopy(const char *pszFilename,
102
                                   GDALDataset *poSrcDS, int bStrict,
103
                                   char **papszOptions,
104
                                   GDALProgressFunc pfnProgress,
105
                                   void *pProgressData);
106
};
107

108
/************************************************************************/
109
/*                      JPEGXLRasterBand                                */
110
/************************************************************************/
111

112
class JPEGXLRasterBand final : public GDALPamRasterBand
113
{
114
  protected:
115
    CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
116

117
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
118
                     GDALDataType, GSpacing, GSpacing,
119
                     GDALRasterIOExtraArg *psExtraArg) override;
120

121
  public:
122
    JPEGXLRasterBand(JPEGXLDataset *poDSIn, int nBandIn,
123
                     GDALDataType eDataTypeIn, int nBitsPerSample,
124
                     GDALColorInterp eInterp);
125
};
126

127
/************************************************************************/
128
/*                         ~JPEGXLDataset()                             */
129
/************************************************************************/
130

131
JPEGXLDataset::~JPEGXLDataset()
676✔
132
{
133
    if (m_fp)
338✔
134
        VSIFCloseL(m_fp);
338✔
135
}
676✔
136

137
/************************************************************************/
138
/*                         JPEGXLRasterBand()                           */
139
/************************************************************************/
140

141
JPEGXLRasterBand::JPEGXLRasterBand(JPEGXLDataset *poDSIn, int nBandIn,
830✔
142
                                   GDALDataType eDataTypeIn, int nBitsPerSample,
143
                                   GDALColorInterp eInterp)
830✔
144
{
145
    poDS = poDSIn;
830✔
146
    nBand = nBandIn;
830✔
147
    eDataType = eDataTypeIn;
830✔
148
    nRasterXSize = poDS->GetRasterXSize();
830✔
149
    nRasterYSize = poDS->GetRasterYSize();
830✔
150
    nBlockXSize = poDS->GetRasterXSize();
830✔
151
    nBlockYSize = 1;
830✔
152
    SetColorInterpretation(eInterp);
830✔
153
    if ((eDataType == GDT_Byte && nBitsPerSample < 8) ||
830✔
154
        (eDataType == GDT_UInt16 && nBitsPerSample < 16))
828✔
155
    {
156
        SetMetadataItem("NBITS", CPLSPrintf("%d", nBitsPerSample),
4✔
157
                        "IMAGE_STRUCTURE");
158
    }
159
}
830✔
160

161
/************************************************************************/
162
/*                             IReadBlock()                             */
163
/************************************************************************/
164

165
CPLErr JPEGXLRasterBand::IReadBlock(int /*nBlockXOff*/, int nBlockYOff,
35,191✔
166
                                    void *pData)
167
{
168
    auto poGDS = cpl::down_cast<JPEGXLDataset *>(poDS);
35,191✔
169

170
    const auto &abyDecodedImage = poGDS->GetDecodedImage();
35,191✔
171
    if (abyDecodedImage.empty())
35,191✔
172
    {
173
        return CE_Failure;
×
174
    }
175

176
    const auto nDataSize = GDALGetDataTypeSizeBytes(eDataType);
35,191✔
177
    const int nNonExtraBands = poGDS->nBands - poGDS->m_nNonAlphaExtraChannels;
35,191✔
178
    if (nBand <= nNonExtraBands)
35,191✔
179
    {
180
        GDALCopyWords(abyDecodedImage.data() +
24,545✔
181
                          ((nBand - 1) + static_cast<size_t>(nBlockYOff) *
24,545✔
182
                                             nRasterXSize * nNonExtraBands) *
24,545✔
183
                              nDataSize,
24,545✔
184
                      eDataType, nDataSize * nNonExtraBands, pData, eDataType,
185
                      nDataSize, nRasterXSize);
186
    }
187
    else
188
    {
189
        const uint32_t nIndex = nBand - 1 - nNonExtraBands;
10,646✔
190
        memcpy(pData,
21,292✔
191
               poGDS->m_abyExtraChannels[nIndex].data() +
10,646✔
192
                   static_cast<size_t>(nBlockYOff) * nRasterXSize * nDataSize,
10,646✔
193
               nRasterXSize * nDataSize);
10,646✔
194
    }
195

196
    return CE_None;
35,191✔
197
}
198

199
/************************************************************************/
200
/*                         Identify()                                   */
201
/************************************************************************/
202

203
int JPEGXLDataset::Identify(GDALOpenInfo *poOpenInfo)
44,157✔
204
{
205
    if (poOpenInfo->fpL == nullptr)
44,157✔
206
        return false;
41,634✔
207

208
    if (poOpenInfo->IsExtensionEqualToCI("jxl"))
2,523✔
209
        return true;
179✔
210

211
    // See
212
    // https://github.com/libjxl/libjxl/blob/c98f133f3f5e456caaa2ba00bc920e923b713abc/lib/jxl/decode.cc#L107-L138
213

214
    // JPEG XL codestream
215
    if (poOpenInfo->nHeaderBytes >= 2 && poOpenInfo->pabyHeader[0] == 0xff &&
2,345✔
216
        poOpenInfo->pabyHeader[1] == 0x0a)
581✔
217
    {
218
        // Two bytes is not enough to reliably identify, so let's try to decode
219
        // basic info
220
        auto decoder = JxlDecoderMake(nullptr);
750✔
221
        if (!decoder)
375✔
222
            return false;
×
223
        JxlDecoderStatus status =
224
            JxlDecoderSubscribeEvents(decoder.get(), JXL_DEC_BASIC_INFO);
375✔
225
        if (status != JXL_DEC_SUCCESS)
375✔
226
        {
227
            return false;
×
228
        }
229

230
        status = JxlDecoderSetInput(decoder.get(), poOpenInfo->pabyHeader,
375✔
231
                                    poOpenInfo->nHeaderBytes);
375✔
232
        if (status != JXL_DEC_SUCCESS)
375✔
233
        {
234
            return false;
×
235
        }
236

237
        status = JxlDecoderProcessInput(decoder.get());
375✔
238
        if (status != JXL_DEC_BASIC_INFO)
375✔
239
        {
240
            return false;
×
241
        }
242

243
        return true;
375✔
244
    }
245

246
    return IsJPEGXLContainer(poOpenInfo);
1,970✔
247
}
248

249
/************************************************************************/
250
/*                             Open()                                   */
251
/************************************************************************/
252

253
bool JPEGXLDataset::Open(GDALOpenInfo *poOpenInfo)
338✔
254
{
255
    m_decoder = JxlDecoderMake(nullptr);
338✔
256
    if (!m_decoder)
338✔
257
    {
258
        CPLError(CE_Failure, CPLE_AppDefined, "JxlDecoderMake() failed");
×
259
        return false;
×
260
    }
261

262
#ifdef HAVE_JXL_THREADS
263
    m_parallelRunner = JxlResizableParallelRunnerMake(nullptr);
338✔
264
    if (!m_parallelRunner)
338✔
265
    {
266
        CPLError(CE_Failure, CPLE_AppDefined,
×
267
                 "JxlResizableParallelRunnerMake() failed");
268
        return false;
×
269
    }
270

271
    if (JxlDecoderSetParallelRunner(m_decoder.get(), JxlResizableParallelRunner,
338✔
272
                                    m_parallelRunner.get()) != JXL_DEC_SUCCESS)
338✔
273
    {
274
        CPLError(CE_Failure, CPLE_AppDefined,
×
275
                 "JxlDecoderSetParallelRunner() failed");
276
        return false;
×
277
    }
278
#endif
279

280
    JxlDecoderStatus status =
281
        JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO |
338✔
282
#ifdef HAVE_JXL_BOX_API
283
                                                       JXL_DEC_BOX |
284
#endif
285
                                                       JXL_DEC_COLOR_ENCODING);
286
    if (status != JXL_DEC_SUCCESS)
338✔
287
    {
288
        CPLError(CE_Failure, CPLE_AppDefined,
×
289
                 "JxlDecoderSubscribeEvents() failed");
290
        return false;
×
291
    }
292

293
    JxlBasicInfo info;
294
    memset(&info, 0, sizeof(info));
338✔
295
    bool bGotInfo = false;
338✔
296

297
    // Steal file handle
298
    m_fp = poOpenInfo->fpL;
338✔
299
    poOpenInfo->fpL = nullptr;
338✔
300
    VSIFSeekL(m_fp, 0, SEEK_SET);
338✔
301

302
    m_abyInputData.resize(1024 * 1024);
338✔
303

304
#ifdef HAVE_JXL_BOX_API
305
    JxlDecoderSetDecompressBoxes(m_decoder.get(), TRUE);
338✔
306
    std::vector<GByte> abyBoxBuffer(1024 * 1024);
676✔
307
    std::string osCurrentBox;
676✔
308
    std::vector<GByte> abyJumbBoxBuffer;
676✔
309
    const auto ProcessCurrentBox = [&]()
62✔
310
    {
311
        const size_t nRemainingBytesInBuffer =
312
            JxlDecoderReleaseBoxBuffer(m_decoder.get());
62✔
313
        CPLAssert(nRemainingBytesInBuffer < abyBoxBuffer.size());
62✔
314
        if (osCurrentBox == "xml " && m_osXMP.empty())
62✔
315
        {
316
            std::string osXML(reinterpret_cast<char *>(abyBoxBuffer.data()),
4✔
317
                              abyBoxBuffer.size() - nRemainingBytesInBuffer);
8✔
318
            if (osXML.compare(0, strlen("<?xpacket"), "<?xpacket") == 0)
4✔
319
            {
320
                m_osXMP = std::move(osXML);
4✔
321
            }
322
        }
323
        else if (osCurrentBox == "Exif" && m_aosEXIFMetadata.empty())
58✔
324
        {
325
            const size_t nSize = abyBoxBuffer.size() - nRemainingBytesInBuffer;
18✔
326
            // The first 4 bytes are at 0, before the TIFF EXIF file content
327
            if (nSize > 12 && abyBoxBuffer[0] == 0 && abyBoxBuffer[1] == 0 &&
18✔
328
                abyBoxBuffer[2] == 0 && abyBoxBuffer[3] == 0 &&
54✔
329
                (abyBoxBuffer[4] == 0x4d  // TIFF_BIGENDIAN
18✔
330
                 || abyBoxBuffer[4] == 0x49 /* TIFF_LITTLEENDIAN */))
18✔
331
            {
332
                m_abyEXIFBox.insert(m_abyEXIFBox.end(), abyBoxBuffer.data() + 4,
×
333
                                    abyBoxBuffer.data() + nSize);
18✔
334
#ifdef CPL_LSB
335
                const bool bSwab = abyBoxBuffer[4] == 0x4d;
18✔
336
#else
337
                const bool bSwab = abyBoxBuffer[4] == 0x49;
338
#endif
339
                constexpr int nTIFFHEADER = 0;
18✔
340
                uint32_t nTiffDirStart;
341
                memcpy(&nTiffDirStart, abyBoxBuffer.data() + 8,
18✔
342
                       sizeof(uint32_t));
343
                if (bSwab)
18✔
344
                {
345
                    CPL_LSBPTR32(&nTiffDirStart);
×
346
                }
347
                VSILFILE *fpEXIF =
348
                    VSIFileFromMemBuffer(nullptr, abyBoxBuffer.data() + 4,
18✔
349
                                         abyBoxBuffer.size() - 4, false);
18✔
350
                int nExifOffset = 0;
18✔
351
                int nInterOffset = 0;
18✔
352
                int nGPSOffset = 0;
18✔
353
                char **papszEXIFMetadata = nullptr;
18✔
354
                EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nTiffDirStart,
18✔
355
                                    bSwab, nTIFFHEADER, nExifOffset,
356
                                    nInterOffset, nGPSOffset);
357

358
                if (nExifOffset > 0)
18✔
359
                {
360
                    EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nExifOffset,
9✔
361
                                        bSwab, nTIFFHEADER, nExifOffset,
362
                                        nInterOffset, nGPSOffset);
363
                }
364
                if (nInterOffset > 0)
18✔
365
                {
366
                    EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nInterOffset,
×
367
                                        bSwab, nTIFFHEADER, nExifOffset,
368
                                        nInterOffset, nGPSOffset);
369
                }
370
                if (nGPSOffset > 0)
18✔
371
                {
372
                    EXIFExtractMetadata(papszEXIFMetadata, fpEXIF, nGPSOffset,
9✔
373
                                        bSwab, nTIFFHEADER, nExifOffset,
374
                                        nInterOffset, nGPSOffset);
375
                }
376
                VSIFCloseL(fpEXIF);
18✔
377
                m_aosEXIFMetadata.Assign(papszEXIFMetadata,
18✔
378
                                         /*takeOwnership=*/true);
18✔
379
            }
380
        }
381
        else if (osCurrentBox == "jumb")
40✔
382
        {
383
            abyJumbBoxBuffer = abyBoxBuffer;
40✔
384
        }
385
        osCurrentBox.clear();
62✔
386
    };
62✔
387

388
    // Process input to get boxes and basic info
389
    const uint64_t nMaxBoxBufferSize = std::strtoull(
338✔
390
        CPLGetConfigOption("GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE", "100000000"),
391
        nullptr, 10);
392
#endif
393

394
    int l_nBands = 0;
338✔
395
    GDALDataType eDT = GDT_Unknown;
338✔
396

397
    while (true)
398
    {
399
        status = JxlDecoderProcessInput(m_decoder.get());
1,867✔
400

401
#ifdef HAVE_JXL_BOX_API
402
        if ((status == JXL_DEC_SUCCESS || status == JXL_DEC_BOX) &&
2,713✔
403
            !osCurrentBox.empty())
846✔
404
        {
405
            try
406
            {
407
                ProcessCurrentBox();
62✔
408
            }
409
            catch (const std::exception &)
×
410
            {
411
                CPLError(CE_Warning, CPLE_OutOfMemory,
×
412
                         "Not enough memory to read box '%s'",
413
                         osCurrentBox.c_str());
414
            }
415
        }
416
#endif
417

418
        if (status == JXL_DEC_SUCCESS)
1,867✔
419
        {
420
            break;
338✔
421
        }
422
        else if (status == JXL_DEC_NEED_MORE_INPUT)
1,529✔
423
        {
424
            JxlDecoderReleaseInput(m_decoder.get());
338✔
425

426
            const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
338✔
427
                                           m_abyInputData.size(), m_fp);
428
            if (nRead == 0)
338✔
429
            {
430
#ifdef HAVE_JXL_BOX_API
431
                JxlDecoderCloseInput(m_decoder.get());
×
432
#endif
433
                break;
×
434
            }
435
            if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
338✔
436
                                   nRead) != JXL_DEC_SUCCESS)
338✔
437
            {
438
                CPLError(CE_Failure, CPLE_AppDefined,
×
439
                         "JxlDecoderSetInput() failed");
440
                return false;
×
441
            }
442
#ifdef HAVE_JXL_BOX_API
443
            if (nRead < m_abyInputData.size())
338✔
444
            {
445
                JxlDecoderCloseInput(m_decoder.get());
338✔
446
            }
447
#endif
448
        }
449
        else if (status == JXL_DEC_BASIC_INFO)
1,191✔
450
        {
451
            bGotInfo = true;
338✔
452
            status = JxlDecoderGetBasicInfo(m_decoder.get(), &info);
338✔
453
            if (status != JXL_DEC_SUCCESS)
338✔
454
            {
455
                CPLError(CE_Failure, CPLE_AppDefined,
×
456
                         "JxlDecoderGetBasicInfo() failed");
457
                return false;
×
458
            }
459

460
            if (info.xsize > static_cast<uint32_t>(INT_MAX) ||
338✔
461
                info.ysize > static_cast<uint32_t>(INT_MAX))
338✔
462
            {
UNCOV
463
                CPLError(CE_Failure, CPLE_AppDefined, "Too big raster");
×
464
                return false;
×
465
            }
466

467
            nRasterXSize = static_cast<int>(info.xsize);
338✔
468
            nRasterYSize = static_cast<int>(info.ysize);
338✔
469

470
            m_nBits = info.bits_per_sample;
338✔
471
            if (info.exponent_bits_per_sample == 0)
338✔
472
            {
473
                if (info.bits_per_sample <= 8)
323✔
474
                    eDT = GDT_Byte;
282✔
475
                else if (info.bits_per_sample <= 16)
41✔
476
                    eDT = GDT_UInt16;
41✔
477
            }
478
            else if (info.exponent_bits_per_sample == 5)
15✔
479
            {
480
                // Float16
481
                CPLDebug("JXL", "16-bit floating point data");
1✔
482
                eDT = GDT_Float32;
1✔
483
            }
484
            else if (info.exponent_bits_per_sample == 8)
14✔
485
            {
486
                eDT = GDT_Float32;
14✔
487
            }
488
            if (eDT == GDT_Unknown)
338✔
489
            {
490
                CPLError(CE_Failure, CPLE_AppDefined, "Unhandled data type");
×
491
                return false;
×
492
            }
493

494
            l_nBands = static_cast<int>(info.num_color_channels) +
338✔
495
                       static_cast<int>(info.num_extra_channels);
338✔
496
            if (info.num_extra_channels == 1 &&
338✔
497
                (info.num_color_channels == 1 ||
61✔
498
                 info.num_color_channels == 3) &&
36✔
499
                info.alpha_bits != 0)
61✔
500
            {
501
                m_nNonAlphaExtraChannels = 0;
53✔
502
            }
503
            else
504
            {
505
                m_nNonAlphaExtraChannels =
285✔
506
                    static_cast<int>(info.num_extra_channels);
285✔
507
            }
508
        }
509
#ifdef HAVE_JXL_BOX_API
510
        else if (status == JXL_DEC_BOX)
853✔
511
        {
512
            osCurrentBox.clear();
508✔
513
            JxlBoxType type = {0};
508✔
514
            if (JxlDecoderGetBoxType(m_decoder.get(), type,
508✔
515
                                     /* decompressed = */ TRUE) !=
508✔
516
                JXL_DEC_SUCCESS)
517
            {
518
                CPLError(CE_Warning, CPLE_AppDefined,
×
519
                         "JxlDecoderGetBoxType() failed");
520
                continue;
×
521
            }
522
            char szType[5] = {0};
508✔
523
            memcpy(szType, type, sizeof(type));
508✔
524
            // CPLDebug("JPEGXL", "box: %s", szType);
525
            if (strcmp(szType, "xml ") == 0 || strcmp(szType, "Exif") == 0 ||
508✔
526
                strcmp(szType, "jumb") == 0)
485✔
527
            {
528
                uint64_t nRawSize = 0;
63✔
529
                JxlDecoderGetBoxSizeRaw(m_decoder.get(), &nRawSize);
63✔
530
                if (nRawSize > nMaxBoxBufferSize)
63✔
531
                {
532
                    CPLError(
×
533
                        CE_Warning, CPLE_OutOfMemory,
534
                        "Reading a '%s' box involves at least " CPL_FRMT_GUIB
535
                        " bytes, "
536
                        "but the current limitation of the "
537
                        "GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE "
538
                        "configuration option is " CPL_FRMT_GUIB " bytes",
539
                        szType, static_cast<GUIntBig>(nRawSize),
540
                        static_cast<GUIntBig>(nMaxBoxBufferSize));
541
                    continue;
×
542
                }
543
                if (nRawSize > abyBoxBuffer.size())
63✔
544
                {
545
                    if (nRawSize > std::numeric_limits<size_t>::max() / 2)
×
546
                    {
547
                        CPLError(CE_Warning, CPLE_OutOfMemory,
×
548
                                 "Not enough memory to read box '%s'", szType);
549
                        continue;
×
550
                    }
551
                    try
552
                    {
553
                        abyBoxBuffer.clear();
×
554
                        abyBoxBuffer.resize(static_cast<size_t>(nRawSize));
×
555
                    }
556
                    catch (const std::exception &)
×
557
                    {
558
                        abyBoxBuffer.resize(1024 * 1024);
×
559
                        CPLError(CE_Warning, CPLE_OutOfMemory,
×
560
                                 "Not enough memory to read box '%s'", szType);
561
                        continue;
×
562
                    }
563
                }
564

565
                if (JxlDecoderSetBoxBuffer(m_decoder.get(), abyBoxBuffer.data(),
63✔
566
                                           abyBoxBuffer.size()) !=
63✔
567
                    JXL_DEC_SUCCESS)
568
                {
569
                    CPLError(CE_Warning, CPLE_AppDefined,
×
570
                             "JxlDecoderSetBoxBuffer() failed");
571
                    continue;
×
572
                }
573
                osCurrentBox = szType;
63✔
574
            }
575
            else if (strcmp(szType, "jbrd") == 0)
445✔
576
            {
577
                m_bHasJPEGReconstructionData = true;
18✔
578
            }
579
        }
580
#endif
581
        else if (status == JXL_DEC_COLOR_ENCODING)
345✔
582
        {
583
#ifdef HAVE_JxlDecoderDefaultPixelFormat
584
            JxlPixelFormat format = {
585
                static_cast<uint32_t>(nBands),
586
                eDT == GDT_Byte     ? JXL_TYPE_UINT8
587
                : eDT == GDT_UInt16 ? JXL_TYPE_UINT16
588
                                    : JXL_TYPE_FLOAT,
589
                JXL_NATIVE_ENDIAN, 0 /* alignment */
590
            };
591
#endif
592

593
            bool bIsDefaultColorEncoding = false;
338✔
594
            JxlColorEncoding color_encoding;
595

596
            // Check if the color profile is the default one we set on creation.
597
            // If so, do not expose it as ICC color profile
598
            if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile(
338✔
599
                                       m_decoder.get(),
338✔
600
#ifdef HAVE_JxlDecoderDefaultPixelFormat
601
                                       &format,
602
#endif
603
                                       JXL_COLOR_PROFILE_TARGET_DATA,
604
                                       &color_encoding))
605
            {
606
                JxlColorEncoding default_color_encoding;
607
                JxlColorEncodingSetToSRGB(&default_color_encoding,
336✔
608
                                          info.num_color_channels ==
336✔
609
                                              1 /*is_gray*/);
610

611
                bIsDefaultColorEncoding =
336✔
612
                    color_encoding.color_space ==
336✔
613
                        default_color_encoding.color_space &&
672✔
614
                    color_encoding.white_point ==
336✔
615
                        default_color_encoding.white_point &&
336✔
616
                    color_encoding.white_point_xy[0] ==
336✔
617
                        default_color_encoding.white_point_xy[0] &&
336✔
618
                    color_encoding.white_point_xy[1] ==
336✔
619
                        default_color_encoding.white_point_xy[1] &&
336✔
620
                    (color_encoding.color_space == JXL_COLOR_SPACE_GRAY ||
336✔
621
                     color_encoding.color_space == JXL_COLOR_SPACE_XYB ||
156✔
622
                     (color_encoding.primaries ==
156✔
623
                          default_color_encoding.primaries &&
156✔
624
                      color_encoding.primaries_red_xy[0] ==
156✔
625
                          default_color_encoding.primaries_red_xy[0] &&
156✔
626
                      color_encoding.primaries_red_xy[1] ==
156✔
627
                          default_color_encoding.primaries_red_xy[1] &&
156✔
628
                      color_encoding.primaries_green_xy[0] ==
156✔
629
                          default_color_encoding.primaries_green_xy[0] &&
156✔
630
                      color_encoding.primaries_green_xy[1] ==
156✔
631
                          default_color_encoding.primaries_green_xy[1] &&
156✔
632
                      color_encoding.primaries_blue_xy[0] ==
156✔
633
                          default_color_encoding.primaries_blue_xy[0] &&
156✔
634
                      color_encoding.primaries_blue_xy[1] ==
156✔
635
                          default_color_encoding.primaries_blue_xy[1])) &&
156✔
636
                    color_encoding.transfer_function ==
336✔
637
                        default_color_encoding.transfer_function &&
336✔
638
                    color_encoding.gamma == default_color_encoding.gamma &&
1,007✔
639
                    color_encoding.rendering_intent ==
335✔
640
                        default_color_encoding.rendering_intent;
335✔
641
            }
642

643
            if (!bIsDefaultColorEncoding)
338✔
644
            {
645
                size_t icc_size = 0;
3✔
646
                if (JXL_DEC_SUCCESS ==
3✔
647
                    JxlDecoderGetICCProfileSize(m_decoder.get(),
3✔
648
#ifdef HAVE_JxlDecoderDefaultPixelFormat
649
                                                &format,
650
#endif
651
                                                JXL_COLOR_PROFILE_TARGET_DATA,
652
                                                &icc_size))
653
                {
654
                    std::vector<GByte> icc(icc_size);
6✔
655
                    if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsICCProfile(
6✔
656
                                               m_decoder.get(),
3✔
657
#ifdef HAVE_JxlDecoderDefaultPixelFormat
658
                                               &format,
659
#endif
660
                                               JXL_COLOR_PROFILE_TARGET_DATA,
661
                                               icc.data(), icc_size))
662
                    {
663
                        // Escape the profile.
664
                        char *pszBase64Profile = CPLBase64Encode(
3✔
665
                            static_cast<int>(icc.size()), icc.data());
3✔
666

667
                        // Set ICC profile metadata.
668
                        SetMetadataItem("SOURCE_ICC_PROFILE", pszBase64Profile,
3✔
669
                                        "COLOR_PROFILE");
3✔
670

671
                        CPLFree(pszBase64Profile);
3✔
672
                    }
673
                }
674
            }
675
        }
676
#ifdef HAVE_JXL_BOX_API
677
        else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT)
7✔
678
        {
679
            // Grow abyBoxBuffer if it is too small
680
            const size_t nRemainingBytesInBuffer =
681
                JxlDecoderReleaseBoxBuffer(m_decoder.get());
7✔
682
            const size_t nBytesUsed =
683
                abyBoxBuffer.size() - nRemainingBytesInBuffer;
7✔
684
            if (abyBoxBuffer.size() > std::numeric_limits<size_t>::max() / 2)
7✔
685
            {
686
                CPLError(CE_Warning, CPLE_OutOfMemory,
×
687
                         "Not enough memory to read box '%s'",
688
                         osCurrentBox.c_str());
689
                osCurrentBox.clear();
×
690
                continue;
×
691
            }
692
            const size_t nNewBoxBufferSize = abyBoxBuffer.size() * 2;
7✔
693
            if (nNewBoxBufferSize > nMaxBoxBufferSize)
7✔
694
            {
695
                CPLError(CE_Warning, CPLE_OutOfMemory,
1✔
696
                         "Reading a '%s' box involves at least " CPL_FRMT_GUIB
697
                         " bytes, "
698
                         "but the current limitation of the "
699
                         "GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE "
700
                         "configuration option is " CPL_FRMT_GUIB " bytes",
701
                         osCurrentBox.c_str(),
702
                         static_cast<GUIntBig>(nNewBoxBufferSize),
703
                         static_cast<GUIntBig>(nMaxBoxBufferSize));
704
                osCurrentBox.clear();
1✔
705
                continue;
1✔
706
            }
707
            try
708
            {
709
                abyBoxBuffer.resize(nNewBoxBufferSize);
6✔
710
            }
711
            catch (const std::exception &)
×
712
            {
713
                CPLError(CE_Warning, CPLE_OutOfMemory,
×
714
                         "Not enough memory to read box '%s'",
715
                         osCurrentBox.c_str());
716
                osCurrentBox.clear();
×
717
                continue;
×
718
            }
719
            if (JxlDecoderSetBoxBuffer(
6✔
720
                    m_decoder.get(), abyBoxBuffer.data() + nBytesUsed,
6✔
721
                    abyBoxBuffer.size() - nBytesUsed) != JXL_DEC_SUCCESS)
12✔
722
            {
723
                CPLError(CE_Warning, CPLE_AppDefined,
×
724
                         "JxlDecoderSetBoxBuffer() failed");
725
                osCurrentBox.clear();
×
726
                continue;
×
727
            }
728
        }
729
#endif
730
        else
731
        {
732
            CPLError(CE_Warning, CPLE_AppDefined, "Unexpected event: %d",
×
733
                     status);
734
            break;
×
735
        }
736
    }
1,529✔
737

738
    JxlDecoderReleaseInput(m_decoder.get());
338✔
739

740
#ifdef HAVE_JXL_BOX_API
741
    // Load georeferencing from jumb box or from worldfile sidecar.
742
    if (!abyJumbBoxBuffer.empty())
338✔
743
    {
744
        VSILFILE *fpJUMB = VSIFileFromMemBuffer(
40✔
745
            nullptr, abyJumbBoxBuffer.data(), abyJumbBoxBuffer.size(), false);
40✔
746
        LoadJP2Metadata(poOpenInfo, nullptr, fpJUMB);
40✔
747
        VSIFCloseL(fpJUMB);
40✔
748
    }
749
#else
750
    if (IsJPEGXLContainer(poOpenInfo))
751
    {
752
        // A JPEGXL container can be explored with the JPEG2000 box reading
753
        // logic
754
        VSIFSeekL(m_fp, 12, SEEK_SET);
755
        poOpenInfo->fpL = m_fp;
756
        LoadJP2Metadata(poOpenInfo);
757
        poOpenInfo->fpL = nullptr;
758
    }
759
#endif
760
    else
761
    {
762
        // Only try to read worldfile
763
        VSILFILE *fpDummy = VSIFileFromMemBuffer(nullptr, nullptr, 0, false);
298✔
764
        LoadJP2Metadata(poOpenInfo, nullptr, fpDummy);
298✔
765
        VSIFCloseL(fpDummy);
298✔
766
    }
767

768
    if (!bGotInfo)
338✔
769
    {
770
        CPLError(CE_Failure, CPLE_AppDefined, "Did not get basic info");
×
771
        return false;
×
772
    }
773

774
    GDALDataset::SetMetadataItem("COMPRESSION_REVERSIBILITY",
338✔
775
                                 info.uses_original_profile
338✔
776
#ifdef HAVE_JXL_BOX_API
777
                                         && !m_bHasJPEGReconstructionData
311✔
778
#endif
779
                                     ? "LOSSLESS (possibly)"
780
                                     : "LOSSY",
781
                                 "IMAGE_STRUCTURE");
782
#ifdef HAVE_JXL_BOX_API
783
    if (m_bHasJPEGReconstructionData)
338✔
784
    {
785
        GDALDataset::SetMetadataItem("ORIGINAL_COMPRESSION", "JPEG",
18✔
786
                                     "IMAGE_STRUCTURE");
787
    }
788
#endif
789

790
#ifdef HAVE_JXL_THREADS
791
    const char *pszNumThreads =
792
        CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
338✔
793
    uint32_t nMaxThreads = static_cast<uint32_t>(
676✔
794
        EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
338✔
795
                                         : atoi(pszNumThreads));
×
796
    if (nMaxThreads > 1024)
338✔
797
        nMaxThreads = 1024;  // to please Coverity
×
798

799
    const uint32_t nThreads = std::min(
800
        nMaxThreads,
801
        JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
338✔
802
    CPLDebug("JPEGXL", "Using %u threads", nThreads);
338✔
803
    JxlResizableParallelRunnerSetThreads(m_parallelRunner.get(), nThreads);
338✔
804
#endif
805

806
    // Instantiate bands
807
    const int nNonExtraBands = l_nBands - m_nNonAlphaExtraChannels;
338✔
808
    for (int i = 1; i <= l_nBands; i++)
1,168✔
809
    {
810
        GDALColorInterp eInterp = GCI_Undefined;
830✔
811
        if (info.num_color_channels == 1)
830✔
812
        {
813
            if (i == 1 && l_nBands <= 2)
278✔
814
                eInterp = GCI_GrayIndex;
160✔
815
            else if (i == 2 && info.num_extra_channels == 1 &&
118✔
816
                     info.alpha_bits != 0)
25✔
817
                eInterp = GCI_AlphaBand;
17✔
818
        }
819
        else if (info.num_color_channels == 3)
552✔
820
        {
821
            if (i <= 3)
552✔
822
                eInterp = static_cast<GDALColorInterp>(GCI_RedBand + (i - 1));
474✔
823
            else if (i == 4 && info.num_extra_channels == 1 &&
78✔
824
                     info.alpha_bits != 0)
36✔
825
                eInterp = GCI_AlphaBand;
36✔
826
        }
827
        std::string osBandName;
1,660✔
828

829
        if (i - 1 >= nNonExtraBands)
830✔
830
        {
831
            JxlExtraChannelInfo sExtraInfo;
832
            memset(&sExtraInfo, 0, sizeof(sExtraInfo));
123✔
833
            const size_t nIndex = static_cast<size_t>(i - 1 - nNonExtraBands);
123✔
834
            if (JxlDecoderGetExtraChannelInfo(m_decoder.get(), nIndex,
123✔
835
                                              &sExtraInfo) == JXL_DEC_SUCCESS)
123✔
836
            {
837
                switch (sExtraInfo.type)
123✔
838
                {
839
                    case JXL_CHANNEL_ALPHA:
21✔
840
                        eInterp = GCI_AlphaBand;
21✔
841
                        break;
21✔
842
                    case JXL_CHANNEL_DEPTH:
×
843
                        osBandName = "Depth channel";
×
844
                        break;
×
845
                    case JXL_CHANNEL_SPOT_COLOR:
×
846
                        osBandName = "Spot color channel";
×
847
                        break;
×
848
                    case JXL_CHANNEL_SELECTION_MASK:
×
849
                        osBandName = "Selection mask channel";
×
850
                        break;
×
851
                    case JXL_CHANNEL_BLACK:
×
852
                        osBandName = "Black channel";
×
853
                        break;
×
854
                    case JXL_CHANNEL_CFA:
×
855
                        osBandName = "CFA channel";
×
856
                        break;
×
857
                    case JXL_CHANNEL_THERMAL:
×
858
                        osBandName = "Thermal channel";
×
859
                        break;
×
860
                    case JXL_CHANNEL_RESERVED0:
102✔
861
                    case JXL_CHANNEL_RESERVED1:
862
                    case JXL_CHANNEL_RESERVED2:
863
                    case JXL_CHANNEL_RESERVED3:
864
                    case JXL_CHANNEL_RESERVED4:
865
                    case JXL_CHANNEL_RESERVED5:
866
                    case JXL_CHANNEL_RESERVED6:
867
                    case JXL_CHANNEL_RESERVED7:
868
                    case JXL_CHANNEL_UNKNOWN:
869
                    case JXL_CHANNEL_OPTIONAL:
870
                        break;
102✔
871
                }
872

873
                if (sExtraInfo.name_length > 0)
123✔
874
                {
875
                    std::string osName;
172✔
876
                    osName.resize(sExtraInfo.name_length);
86✔
877
                    if (JxlDecoderGetExtraChannelName(
86✔
878
                            m_decoder.get(), nIndex, &osName[0],
86✔
879
                            osName.size() + 1) == JXL_DEC_SUCCESS &&
258✔
880
                        osName != CPLSPrintf("Band %d", i))
86✔
881
                    {
882
                        osBandName = std::move(osName);
2✔
883
                    }
884
                }
885
            }
886
        }
887

888
        auto poBand = new JPEGXLRasterBand(
889
            this, i, eDT, static_cast<int>(info.bits_per_sample), eInterp);
830✔
890
        SetBand(i, poBand);
830✔
891
        if (!osBandName.empty())
830✔
892
            poBand->SetDescription(osBandName.c_str());
2✔
893
    }
894

895
    if (l_nBands > 1)
338✔
896
    {
897
        SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
203✔
898
    }
899

900
    // Initialize any PAM information.
901
    SetDescription(poOpenInfo->pszFilename);
338✔
902
    TryLoadXML(poOpenInfo->GetSiblingFiles());
338✔
903
    oOvManager.Initialize(this, poOpenInfo->pszFilename,
676✔
904
                          poOpenInfo->GetSiblingFiles());
338✔
905

906
    nPamFlags &= ~GPF_DIRTY;
338✔
907

908
    return true;
338✔
909
}
910

911
/************************************************************************/
912
/*                        GetDecodedImage()                             */
913
/************************************************************************/
914

915
const std::vector<GByte> &JPEGXLDataset::GetDecodedImage()
35,207✔
916
{
917
    if (m_bDecodingFailed || !m_abyImage.empty())
35,207✔
918
        return m_abyImage;
35,122✔
919

920
    const auto eDT = GetRasterBand(1)->GetRasterDataType();
85✔
921
    const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
85✔
922
    assert(nDataSize > 0);
85✔
923
    const int nNonExtraBands = nBands - m_nNonAlphaExtraChannels;
85✔
924
    if (static_cast<size_t>(nRasterXSize) > std::numeric_limits<size_t>::max() /
85✔
925
                                                nRasterYSize / nDataSize /
85✔
926
                                                nNonExtraBands)
85✔
927
    {
928
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
929
                 "Image too big for architecture");
930
        m_bDecodingFailed = true;
×
931
        return m_abyImage;
×
932
    }
933

934
    try
935
    {
936
        m_abyImage.resize(static_cast<size_t>(nRasterXSize) * nRasterYSize *
85✔
937
                          nNonExtraBands * nDataSize);
85✔
938
    }
939
    catch (const std::exception &e)
×
940
    {
941
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
942
                 "Cannot allocate image buffer: %s", e.what());
×
943
        m_bDecodingFailed = true;
×
944
        return m_abyImage;
×
945
    }
946

947
    m_abyExtraChannels.resize(m_nNonAlphaExtraChannels);
85✔
948
    for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
161✔
949
    {
950
        try
951
        {
952
            m_abyExtraChannels[i].resize(static_cast<size_t>(nRasterXSize) *
76✔
953
                                         nRasterYSize * nDataSize);
76✔
954
        }
955
        catch (const std::exception &e)
×
956
        {
957
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
958
                     "Cannot allocate image buffer: %s", e.what());
×
959
            m_bDecodingFailed = true;
×
960
            return m_abyImage;
×
961
        }
962
    }
963

964
    GetDecodedImage(m_abyImage.data(), m_abyImage.size());
85✔
965

966
    if (m_bDecodingFailed)
85✔
967
        m_abyImage.clear();
×
968

969
    return m_abyImage;
85✔
970
}
971

972
/************************************************************************/
973
/*                      GetMetadataDomainList()                         */
974
/************************************************************************/
975

976
char **JPEGXLDataset::GetMetadataDomainList()
3✔
977
{
978
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3✔
979
                                   TRUE, "xml:XMP", "EXIF", nullptr);
3✔
980
}
981

982
/************************************************************************/
983
/*                            GetMetadata()                             */
984
/************************************************************************/
985

986
char **JPEGXLDataset::GetMetadata(const char *pszDomain)
203✔
987
{
988
#ifdef HAVE_JXL_BOX_API
989
    if (pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP") && !m_osXMP.empty())
203✔
990
    {
991
        m_apszXMP[0] = &m_osXMP[0];
3✔
992
        return m_apszXMP;
3✔
993
    }
994

995
    if (pszDomain != nullptr && EQUAL(pszDomain, "EXIF") &&
214✔
996
        !m_aosEXIFMetadata.empty())
14✔
997
    {
998
        return m_aosEXIFMetadata.List();
10✔
999
    }
1000
#endif
1001
    return GDALPamDataset::GetMetadata(pszDomain);
190✔
1002
}
1003

1004
/************************************************************************/
1005
/*                       GetCompressionFormats()                        */
1006
/************************************************************************/
1007

1008
CPLStringList JPEGXLDataset::GetCompressionFormats(int nXOff, int nYOff,
2✔
1009
                                                   int nXSize, int nYSize,
1010
                                                   int nBandCount,
1011
                                                   const int *panBandList)
1012
{
1013
    CPLStringList aosRet;
2✔
1014
    if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
2✔
1015
        nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
4✔
1016
    {
1017
        aosRet.AddString("JXL");
2✔
1018
#ifdef HAVE_JXL_BOX_API
1019
        if (m_bHasJPEGReconstructionData)
2✔
1020
            aosRet.AddString("JPEG");
1✔
1021
#endif
1022
    }
1023
    return aosRet;
2✔
1024
}
1025

1026
/************************************************************************/
1027
/*                       ReadCompressedData()                           */
1028
/************************************************************************/
1029

1030
CPLErr JPEGXLDataset::ReadCompressedData(const char *pszFormat, int nXOff,
17✔
1031
                                         int nYOff, int nXSize, int nYSize,
1032
                                         int nBandCount, const int *panBandList,
1033
                                         void **ppBuffer, size_t *pnBufferSize,
1034
                                         char **ppszDetailedFormat)
1035
{
1036
    if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
17✔
1037
        nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
34✔
1038
    {
1039
        const CPLStringList aosTokens(CSLTokenizeString2(pszFormat, ";", 0));
17✔
1040
        if (aosTokens.size() != 1)
17✔
1041
            return CE_Failure;
×
1042

1043
        if (EQUAL(aosTokens[0], "JXL"))
17✔
1044
        {
1045
            if (ppszDetailedFormat)
7✔
1046
                *ppszDetailedFormat = VSIStrdup("JXL");
1✔
1047
            VSIFSeekL(m_fp, 0, SEEK_END);
7✔
1048
            const auto nFileSize = VSIFTellL(m_fp);
7✔
1049
            if (nFileSize > std::numeric_limits<size_t>::max() / 2)
7✔
1050
                return CE_Failure;
×
1051
            auto nSize = static_cast<size_t>(nFileSize);
7✔
1052
            if (ppBuffer)
7✔
1053
            {
1054
                if (pnBufferSize == nullptr)
6✔
1055
                    return CE_Failure;
1✔
1056
                bool bFreeOnError = false;
5✔
1057
                if (*ppBuffer)
5✔
1058
                {
1059
                    if (*pnBufferSize < nSize)
3✔
1060
                        return CE_Failure;
1✔
1061
                }
1062
                else
1063
                {
1064
                    *ppBuffer = VSI_MALLOC_VERBOSE(nSize);
2✔
1065
                    if (*ppBuffer == nullptr)
2✔
1066
                        return CE_Failure;
×
1067
                    bFreeOnError = true;
2✔
1068
                }
1069
                VSIFSeekL(m_fp, 0, SEEK_SET);
4✔
1070
                if (VSIFReadL(*ppBuffer, nSize, 1, m_fp) != 1)
4✔
1071
                {
1072
                    if (bFreeOnError)
×
1073
                    {
1074
                        VSIFree(*ppBuffer);
×
1075
                        *ppBuffer = nullptr;
×
1076
                    }
1077
                    return CE_Failure;
×
1078
                }
1079

1080
                size_t nPos = 0;
4✔
1081
                GByte *pabyData = static_cast<GByte *>(*ppBuffer);
4✔
1082
                while (nSize - nPos >= 8)
25✔
1083
                {
1084
                    uint32_t nBoxSize;
1085
                    memcpy(&nBoxSize, pabyData + nPos, 4);
21✔
1086
                    CPL_MSBPTR32(&nBoxSize);
21✔
1087
                    if (nBoxSize < 8 || nBoxSize > nSize - nPos)
21✔
1088
                        break;
1089
                    char szBoxName[5] = {0, 0, 0, 0, 0};
21✔
1090
                    memcpy(szBoxName, pabyData + nPos + 4, 4);
21✔
1091
                    if (memcmp(szBoxName, "Exif", 4) == 0 ||
21✔
1092
                        memcmp(szBoxName, "xml ", 4) == 0 ||
21✔
1093
                        memcmp(szBoxName, "jumb", 4) == 0)
21✔
1094
                    {
1095
                        CPLDebug("JPEGXL",
4✔
1096
                                 "Remove existing %s box from "
1097
                                 "source compressed data",
1098
                                 szBoxName);
1099
                        memmove(pabyData + nPos, pabyData + nPos + nBoxSize,
4✔
1100
                                nSize - (nPos + nBoxSize));
4✔
1101
                        nSize -= nBoxSize;
4✔
1102
                    }
1103
                    else
1104
                    {
1105
                        nPos += nBoxSize;
17✔
1106
                    }
1107
                }
1108
            }
1109
            if (pnBufferSize)
5✔
1110
                *pnBufferSize = nSize;
5✔
1111
            return CE_None;
5✔
1112
        }
1113

1114
#ifdef HAVE_JXL_BOX_API
1115
        if (m_bHasJPEGReconstructionData && EQUAL(aosTokens[0], "JPEG"))
10✔
1116
        {
1117
            auto decoder = JxlDecoderMake(nullptr);
9✔
1118
            if (!decoder)
9✔
1119
            {
1120
                CPLError(CE_Failure, CPLE_AppDefined,
×
1121
                         "JxlDecoderMake() failed");
1122
                return CE_Failure;
×
1123
            }
1124
            auto status = JxlDecoderSubscribeEvents(
9✔
1125
                decoder.get(), JXL_DEC_BASIC_INFO |
1126
                                   JXL_DEC_JPEG_RECONSTRUCTION |
1127
                                   JXL_DEC_FULL_IMAGE);
1128
            if (status != JXL_DEC_SUCCESS)
9✔
1129
            {
1130
                CPLError(CE_Failure, CPLE_AppDefined,
×
1131
                         "JxlDecoderSubscribeEvents() failed");
1132
                return CE_Failure;
×
1133
            }
1134

1135
            VSIFSeekL(m_fp, 0, SEEK_SET);
9✔
1136
            try
1137
            {
1138
                std::vector<GByte> jpeg_bytes;
9✔
1139
                std::vector<GByte> jpeg_data_chunk(16 * 1024);
9✔
1140

1141
                bool bJPEGReconstruction = false;
9✔
1142
                while (true)
1143
                {
1144
                    status = JxlDecoderProcessInput(decoder.get());
45✔
1145
                    if (status == JXL_DEC_SUCCESS)
45✔
1146
                    {
1147
                        break;
9✔
1148
                    }
1149
                    else if (status == JXL_DEC_NEED_MORE_INPUT)
36✔
1150
                    {
1151
                        JxlDecoderReleaseInput(decoder.get());
9✔
1152

1153
                        const size_t nRead =
1154
                            VSIFReadL(m_abyInputData.data(), 1,
9✔
1155
                                      m_abyInputData.size(), m_fp);
1156
                        if (nRead == 0)
9✔
1157
                        {
1158
                            break;
×
1159
                        }
1160
                        if (JxlDecoderSetInput(decoder.get(),
9✔
1161
                                               m_abyInputData.data(),
9✔
1162
                                               nRead) != JXL_DEC_SUCCESS)
9✔
1163
                        {
1164
                            CPLError(CE_Failure, CPLE_AppDefined,
×
1165
                                     "JxlDecoderSetInput() failed");
1166
                            return CE_Failure;
×
1167
                        }
1168
                    }
1169
                    else if (status == JXL_DEC_JPEG_RECONSTRUCTION)
27✔
1170
                    {
1171
                        bJPEGReconstruction = true;
9✔
1172
                        // Decoding to JPEG.
1173
                        if (JXL_DEC_SUCCESS !=
9✔
1174
                            JxlDecoderSetJPEGBuffer(decoder.get(),
9✔
1175
                                                    jpeg_data_chunk.data(),
1176
                                                    jpeg_data_chunk.size()))
1177
                        {
1178
                            CPLError(CE_Warning, CPLE_AppDefined,
×
1179
                                     "Decoder failed to set JPEG Buffer\n");
1180
                            return CE_Failure;
×
1181
                        }
1182
                    }
1183
                    else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT)
18✔
1184
                    {
1185
                        // Decoded a chunk to JPEG.
1186
                        size_t used_jpeg_output =
1187
                            jpeg_data_chunk.size() -
×
1188
                            JxlDecoderReleaseJPEGBuffer(decoder.get());
×
1189
                        jpeg_bytes.insert(
1190
                            jpeg_bytes.end(), jpeg_data_chunk.data(),
×
1191
                            jpeg_data_chunk.data() + used_jpeg_output);
×
1192
                        if (used_jpeg_output == 0)
×
1193
                        {
1194
                            // Chunk is too small.
1195
                            jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
×
1196
                        }
1197
                        if (JXL_DEC_SUCCESS !=
×
1198
                            JxlDecoderSetJPEGBuffer(decoder.get(),
×
1199
                                                    jpeg_data_chunk.data(),
1200
                                                    jpeg_data_chunk.size()))
1201
                        {
1202
                            CPLError(CE_Warning, CPLE_AppDefined,
×
1203
                                     "Decoder failed to set JPEG Buffer\n");
1204
                            return CE_Failure;
×
1205
                        }
1206
                    }
1207
                    else if (status == JXL_DEC_BASIC_INFO ||
18✔
1208
                             status == JXL_DEC_FULL_IMAGE)
1209
                    {
1210
                        // do nothing
1211
                    }
1212
                    else
1213
                    {
1214
                        CPLError(CE_Warning, CPLE_AppDefined,
×
1215
                                 "Unexpected event: %d", status);
1216
                        break;
×
1217
                    }
1218
                }
36✔
1219
                if (bJPEGReconstruction)
9✔
1220
                {
1221
                    size_t used_jpeg_output =
1222
                        jpeg_data_chunk.size() -
9✔
1223
                        JxlDecoderReleaseJPEGBuffer(decoder.get());
9✔
1224
                    jpeg_bytes.insert(jpeg_bytes.end(), jpeg_data_chunk.data(),
9✔
1225
                                      jpeg_data_chunk.data() +
9✔
1226
                                          used_jpeg_output);
18✔
1227
                }
1228

1229
                JxlDecoderReleaseInput(decoder.get());
9✔
1230

1231
                if (!jpeg_bytes.empty() &&
18✔
1232
                    jpeg_bytes.size() < static_cast<size_t>(INT_MAX))
9✔
1233
                {
1234
                    constexpr GByte EXIF_SIGNATURE[] = {'E', 'x',  'i',
9✔
1235
                                                        'f', '\0', '\0'};
1236
                    constexpr char APP1_XMP_SIGNATURE[] =
9✔
1237
                        "http://ns.adobe.com/xap/1.0/";
1238
                    size_t nChunkLoc = 2;
9✔
1239
                    while (nChunkLoc + 4 <= jpeg_bytes.size())
60✔
1240
                    {
1241
                        if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
120✔
1242
                            jpeg_bytes[nChunkLoc + 1] == 0xDA)
60✔
1243
                        {
1244
                            break;
9✔
1245
                        }
1246
                        if (jpeg_bytes[nChunkLoc + 0] != 0xFF)
51✔
1247
                            break;
×
1248
                        const int nChunkLength =
1249
                            jpeg_bytes[nChunkLoc + 2] * 256 +
51✔
1250
                            jpeg_bytes[nChunkLoc + 3];
51✔
1251
                        if (nChunkLength < 2 ||
102✔
1252
                            static_cast<size_t>(nChunkLength) >
51✔
1253
                                jpeg_bytes.size() - (nChunkLoc + 2))
51✔
1254
                            break;
×
1255
                        if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
51✔
1256
                            jpeg_bytes[nChunkLoc + 1] == 0xE1 &&
51✔
1257
                            nChunkLoc + 4 + sizeof(EXIF_SIGNATURE) <=
×
1258
                                jpeg_bytes.size() &&
102✔
1259
                            memcmp(jpeg_bytes.data() + nChunkLoc + 4,
×
1260
                                   EXIF_SIGNATURE, sizeof(EXIF_SIGNATURE)) == 0)
1261
                        {
1262
                            CPLDebug("JPEGXL", "Remove existing EXIF from "
×
1263
                                               "source compressed data");
1264
                            jpeg_bytes.erase(jpeg_bytes.begin() + nChunkLoc,
×
1265
                                             jpeg_bytes.begin() + nChunkLoc +
×
1266
                                                 2 + nChunkLength);
×
1267
                            continue;
×
1268
                        }
1269
                        else if (jpeg_bytes[nChunkLoc + 0] == 0xFF &&
51✔
1270
                                 jpeg_bytes[nChunkLoc + 1] == 0xE1 &&
51✔
1271
                                 nChunkLoc + 4 + sizeof(APP1_XMP_SIGNATURE) <=
×
1272
                                     jpeg_bytes.size() &&
102✔
1273
                                 memcmp(jpeg_bytes.data() + nChunkLoc + 4,
×
1274
                                        APP1_XMP_SIGNATURE,
1275
                                        sizeof(APP1_XMP_SIGNATURE)) == 0)
1276
                        {
1277
                            CPLDebug("JPEGXL", "Remove existing XMP from "
×
1278
                                               "source compressed data");
1279
                            jpeg_bytes.erase(jpeg_bytes.begin() + nChunkLoc,
×
1280
                                             jpeg_bytes.begin() + nChunkLoc +
×
1281
                                                 2 + nChunkLength);
×
1282
                            continue;
×
1283
                        }
1284
                        nChunkLoc += 2 + nChunkLength;
51✔
1285
                    }
1286

1287
                    if (ppszDetailedFormat)
9✔
1288
                    {
1289
                        *ppszDetailedFormat =
1✔
1290
                            VSIStrdup(GDALGetCompressionFormatForJPEG(
2✔
1291
                                          jpeg_bytes.data(), jpeg_bytes.size())
1✔
1292
                                          .c_str());
1293
                    }
1294

1295
                    const auto nSize = jpeg_bytes.size();
9✔
1296
                    if (ppBuffer)
9✔
1297
                    {
1298
                        if (*ppBuffer)
8✔
1299
                        {
1300
                            if (pnBufferSize == nullptr)
4✔
1301
                                return CE_Failure;
1✔
1302
                            if (*pnBufferSize < nSize)
3✔
1303
                                return CE_Failure;
1✔
1304
                        }
1305
                        else
1306
                        {
1307
                            *ppBuffer = VSI_MALLOC_VERBOSE(nSize);
4✔
1308
                            if (*ppBuffer == nullptr)
4✔
1309
                                return CE_Failure;
×
1310
                        }
1311
                        memcpy(*ppBuffer, jpeg_bytes.data(), nSize);
6✔
1312
                    }
1313
                    if (pnBufferSize)
7✔
1314
                        *pnBufferSize = nSize;
7✔
1315

1316
                    return CE_None;
7✔
1317
                }
1318
            }
1319
            catch (const std::exception &)
×
1320
            {
1321
            }
1322
        }
1323
#endif
1324
    }
1325
    return CE_Failure;
1✔
1326
}
1327

1328
/************************************************************************/
1329
/*                          GetMetadataItem()                           */
1330
/************************************************************************/
1331

1332
const char *JPEGXLDataset::GetMetadataItem(const char *pszName,
200✔
1333
                                           const char *pszDomain)
1334
{
1335
#ifdef HAVE_JXL_BOX_API
1336
    if (pszDomain != nullptr && EQUAL(pszDomain, "EXIF") &&
208✔
1337
        !m_aosEXIFMetadata.empty())
8✔
1338
    {
1339
        return m_aosEXIFMetadata.FetchNameValue(pszName);
8✔
1340
    }
1341
#endif
1342

1343
    return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
192✔
1344
}
1345

1346
/************************************************************************/
1347
/*                        GetDecodedImage()                             */
1348
/************************************************************************/
1349

1350
void JPEGXLDataset::GetDecodedImage(void *pabyOutputData,
255✔
1351
                                    size_t nOutputDataSize)
1352
{
1353
    JxlDecoderRewind(m_decoder.get());
255✔
1354
    VSIFSeekL(m_fp, 0, SEEK_SET);
255✔
1355

1356
    if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_FULL_IMAGE) !=
255✔
1357
        JXL_DEC_SUCCESS)
1358
    {
1359
        CPLError(CE_Failure, CPLE_AppDefined,
×
1360
                 "JxlDecoderSubscribeEvents() failed");
1361
        return;
×
1362
    }
1363

1364
    const auto eDT = GetRasterBand(1)->GetRasterDataType();
255✔
1365
    while (true)
1366
    {
1367
        const JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get());
1,020✔
1368
        if (status == JXL_DEC_ERROR)
1,020✔
1369
        {
1370
            CPLError(CE_Failure, CPLE_AppDefined, "Decoding error");
×
1371
            m_bDecodingFailed = true;
×
1372
            break;
×
1373
        }
1374
        else if (status == JXL_DEC_NEED_MORE_INPUT)
1,020✔
1375
        {
1376
            JxlDecoderReleaseInput(m_decoder.get());
255✔
1377

1378
            const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
255✔
1379
                                           m_abyInputData.size(), m_fp);
1380
            if (nRead == 0)
255✔
1381
            {
1382
                CPLError(CE_Failure, CPLE_AppDefined,
×
1383
                         "Decoder expected more input, but no more available");
1384
                m_bDecodingFailed = true;
×
1385
                break;
×
1386
            }
1387
            if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
255✔
1388
                                   nRead) != JXL_DEC_SUCCESS)
255✔
1389
            {
1390
                CPLError(CE_Failure, CPLE_AppDefined,
×
1391
                         "JxlDecoderSetInput() failed");
1392
                m_bDecodingFailed = true;
×
1393
                break;
×
1394
            }
1395
        }
1396
        else if (status == JXL_DEC_SUCCESS)
765✔
1397
        {
1398
            break;
255✔
1399
        }
1400
        else if (status == JXL_DEC_FULL_IMAGE)
510✔
1401
        {
1402
            // ok
1403
        }
1404
        else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
255✔
1405
        {
1406
            JxlPixelFormat format = {
255✔
1407
                static_cast<uint32_t>(nBands - m_nNonAlphaExtraChannels),
255✔
1408
                eDT == GDT_Byte     ? JXL_TYPE_UINT8
255✔
1409
                : eDT == GDT_UInt16 ? JXL_TYPE_UINT16
39✔
1410
                                    : JXL_TYPE_FLOAT,
1411
                JXL_NATIVE_ENDIAN, 0 /* alignment */
1412
            };
255✔
1413

1414
            size_t buffer_size;
1415
            if (JxlDecoderImageOutBufferSize(m_decoder.get(), &format,
255✔
1416
                                             &buffer_size) != JXL_DEC_SUCCESS)
255✔
1417
            {
1418
                CPLError(CE_Failure, CPLE_AppDefined,
×
1419
                         "JxlDecoderImageOutBufferSize failed()");
1420
                m_bDecodingFailed = true;
×
1421
                break;
×
1422
            }
1423
            if (buffer_size != nOutputDataSize)
255✔
1424
            {
1425
                CPLError(CE_Failure, CPLE_AppDefined,
×
1426
                         "JxlDecoderImageOutBufferSize returned an unexpected "
1427
                         "buffer_size");
1428
                m_bDecodingFailed = true;
×
1429
                break;
×
1430
            }
1431

1432
            // It could be interesting to use JxlDecoderSetImageOutCallback()
1433
            // to do progressive decoding, but at the time of writing, libjxl
1434
            // seems to just call the callback when all the image is
1435
            // decompressed
1436
            if (JxlDecoderSetImageOutBuffer(m_decoder.get(), &format,
255✔
1437
                                            pabyOutputData,
1438
                                            nOutputDataSize) != JXL_DEC_SUCCESS)
255✔
1439
            {
1440
                CPLError(CE_Failure, CPLE_AppDefined,
×
1441
                         "JxlDecoderSetImageOutBuffer failed()");
1442
                m_bDecodingFailed = true;
×
1443
                break;
×
1444
            }
1445

1446
            format.num_channels = 1;
255✔
1447
            for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
331✔
1448
            {
1449
                if (JxlDecoderExtraChannelBufferSize(m_decoder.get(), &format,
76✔
1450
                                                     &buffer_size,
1451
                                                     i) != JXL_DEC_SUCCESS)
76✔
1452
                {
1453
                    CPLError(CE_Failure, CPLE_AppDefined,
×
1454
                             "JxlDecoderExtraChannelBufferSize failed()");
1455
                    m_bDecodingFailed = true;
×
1456
                    break;
×
1457
                }
1458
                if (buffer_size != m_abyExtraChannels[i].size())
76✔
1459
                {
1460
                    CPLError(CE_Failure, CPLE_AppDefined,
×
1461
                             "JxlDecoderExtraChannelBufferSize returned an "
1462
                             "unexpected buffer_size");
1463
                    m_bDecodingFailed = true;
×
1464
                    break;
×
1465
                }
1466
                if (JxlDecoderSetExtraChannelBuffer(
152✔
1467
                        m_decoder.get(), &format, m_abyExtraChannels[i].data(),
76✔
1468
                        m_abyExtraChannels[i].size(), i) != JXL_DEC_SUCCESS)
152✔
1469
                {
1470
                    CPLError(CE_Failure, CPLE_AppDefined,
×
1471
                             "JxlDecoderSetExtraChannelBuffer failed()");
1472
                    m_bDecodingFailed = true;
×
1473
                    break;
×
1474
                }
1475
            }
1476
            if (m_bDecodingFailed)
255✔
1477
            {
1478
                break;
×
1479
            }
1480
        }
1481
        else
1482
        {
1483
            CPLError(CE_Warning, CPLE_AppDefined,
×
1484
                     "Unexpected decoder state: %d", status);
1485
        }
1486
    }
765✔
1487

1488
    // Rescale from 8-bits/16-bits
1489
    if (m_nBits < GDALGetDataTypeSizeBits(eDT))
255✔
1490
    {
1491
        const auto Rescale = [this, eDT](void *pBuffer, int nChannels)
5✔
1492
        {
1493
            const size_t nSamples =
3✔
1494
                static_cast<size_t>(nRasterXSize) * nRasterYSize * nChannels;
3✔
1495
            const int nMaxVal = (1 << m_nBits) - 1;
3✔
1496
            if (eDT == GDT_Byte)
3✔
1497
            {
1498
                const int nHalfMaxWidth = 127;
1✔
1499
                GByte *panData = static_cast<GByte *>(pBuffer);
1✔
1500
                for (size_t i = 0; i < nSamples; ++i)
401✔
1501
                {
1502
                    panData[i] = static_cast<GByte>(
400✔
1503
                        (panData[i] * nMaxVal + nHalfMaxWidth) / 255);
400✔
1504
                }
1505
            }
1506
            else if (eDT == GDT_UInt16)
2✔
1507
            {
1508
                const int nHalfMaxWidth = 32767;
1✔
1509
                uint16_t *panData = static_cast<uint16_t *>(pBuffer);
1✔
1510
                for (size_t i = 0; i < nSamples; ++i)
401✔
1511
                {
1512
                    panData[i] = static_cast<uint16_t>(
400✔
1513
                        (panData[i] * nMaxVal + nHalfMaxWidth) / 65535);
400✔
1514
                }
1515
            }
1516
        };
6✔
1517

1518
        Rescale(pabyOutputData, nBands - m_nNonAlphaExtraChannels);
3✔
1519
        for (int i = 0; i < m_nNonAlphaExtraChannels; ++i)
3✔
1520
        {
1521
            Rescale(m_abyExtraChannels[i].data(), 1);
×
1522
        }
1523
    }
1524
}
1525

1526
/************************************************************************/
1527
/*                             IRasterIO()                              */
1528
/************************************************************************/
1529

1530
CPLErr JPEGXLDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
187✔
1531
                                int nXSize, int nYSize, void *pData,
1532
                                int nBufXSize, int nBufYSize,
1533
                                GDALDataType eBufType, int nBandCount,
1534
                                BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1535
                                GSpacing nLineSpace, GSpacing nBandSpace,
1536
                                GDALRasterIOExtraArg *psExtraArg)
1537

1538
{
1539
    const auto AreSequentialBands = [](const int *panItems, int nItems)
186✔
1540
    {
1541
        for (int i = 0; i < nItems; i++)
545✔
1542
        {
1543
            if (panItems[i] != i + 1)
364✔
1544
                return false;
5✔
1545
        }
1546
        return true;
181✔
1547
    };
1548

1549
    if (eRWFlag == GF_Read && nXOff == 0 && nYOff == 0 &&
187✔
1550
        nXSize == nRasterXSize && nYSize == nRasterYSize &&
187✔
1551
        nBufXSize == nXSize && nBufYSize == nYSize)
186✔
1552
    {
1553
        // Get the full image in a pixel-interleaved way
1554
        if (m_bDecodingFailed)
186✔
1555
            return CE_Failure;
×
1556

1557
        CPLDebug("JPEGXL", "Using optimized IRasterIO() code path");
186✔
1558

1559
        const auto nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
186✔
1560
        const bool bIsPixelInterleaveBuffer =
186✔
1561
            ((nBandSpace == 0 && nBandCount == 1) ||
13✔
1562
             nBandSpace == nBufTypeSize) &&
173✔
1563
            nPixelSpace == static_cast<GSpacing>(nBufTypeSize) * nBandCount &&
550✔
1564
            nLineSpace == nPixelSpace * nRasterXSize;
178✔
1565

1566
        const auto eNativeDT = GetRasterBand(1)->GetRasterDataType();
186✔
1567
        const auto nNativeDataSize = GDALGetDataTypeSizeBytes(eNativeDT);
186✔
1568
        const bool bIsBandSequential =
1569
            AreSequentialBands(panBandMap, nBandCount);
186✔
1570
        if (eBufType == eNativeDT && bIsBandSequential &&
186✔
1571
            nBandCount == nBands && m_nNonAlphaExtraChannels == 0 &&
177✔
1572
            bIsPixelInterleaveBuffer)
1573
        {
1574
            // We can directly use the user output buffer
1575
            GetDecodedImage(pData, static_cast<size_t>(nRasterXSize) *
170✔
1576
                                       nRasterYSize * nBands * nNativeDataSize);
170✔
1577
            return m_bDecodingFailed ? CE_Failure : CE_None;
170✔
1578
        }
1579

1580
        const auto &abyDecodedImage = GetDecodedImage();
16✔
1581
        if (abyDecodedImage.empty())
16✔
1582
        {
1583
            return CE_Failure;
×
1584
        }
1585
        const int nNonExtraBands = nBands - m_nNonAlphaExtraChannels;
16✔
1586
        if (bIsPixelInterleaveBuffer && bIsBandSequential &&
16✔
1587
            nBandCount == nNonExtraBands)
1588
        {
1589
            GDALCopyWords64(abyDecodedImage.data(), eNativeDT, nNativeDataSize,
3✔
1590
                            pData, eBufType, nBufTypeSize,
1591
                            static_cast<GPtrDiff_t>(nRasterXSize) *
3✔
1592
                                nRasterYSize * nBandCount);
3✔
1593
        }
1594
        else
1595
        {
1596
            for (int iBand = 0; iBand < nBandCount; iBand++)
49✔
1597
            {
1598
                const int iSrcBand = panBandMap[iBand] - 1;
36✔
1599
                if (iSrcBand < nNonExtraBands)
36✔
1600
                {
1601
                    for (int iY = 0; iY < nRasterYSize; iY++)
1,009✔
1602
                    {
1603
                        const GByte *pSrc = abyDecodedImage.data() +
994✔
1604
                                            (static_cast<size_t>(iY) *
994✔
1605
                                                 nRasterXSize * nNonExtraBands +
994✔
1606
                                             iSrcBand) *
994✔
1607
                                                nNativeDataSize;
994✔
1608
                        GByte *pDst = static_cast<GByte *>(pData) +
994✔
1609
                                      iY * nLineSpace + iBand * nBandSpace;
994✔
1610
                        GDALCopyWords(pSrc, eNativeDT,
994✔
1611
                                      nNativeDataSize * nNonExtraBands, pDst,
1612
                                      eBufType, static_cast<int>(nPixelSpace),
1613
                                      nRasterXSize);
1614
                    }
1615
                }
1616
                else
1617
                {
1618
                    for (int iY = 0; iY < nRasterYSize; iY++)
891✔
1619
                    {
1620
                        const GByte *pSrc =
1621
                            m_abyExtraChannels[iSrcBand - nNonExtraBands]
870✔
1622
                                .data() +
870✔
1623
                            static_cast<size_t>(iY) * nRasterXSize *
870✔
1624
                                nNativeDataSize;
870✔
1625
                        GByte *pDst = static_cast<GByte *>(pData) +
870✔
1626
                                      iY * nLineSpace + iBand * nBandSpace;
870✔
1627
                        GDALCopyWords(pSrc, eNativeDT, nNativeDataSize, pDst,
870✔
1628
                                      eBufType, static_cast<int>(nPixelSpace),
1629
                                      nRasterXSize);
1630
                    }
1631
                }
1632
            }
1633
        }
1634
        return CE_None;
16✔
1635
    }
1636

1637
    return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1✔
1638
                                     pData, nBufXSize, nBufYSize, eBufType,
1639
                                     nBandCount, panBandMap, nPixelSpace,
1640
                                     nLineSpace, nBandSpace, psExtraArg);
1✔
1641
}
1642

1643
/************************************************************************/
1644
/*                             IRasterIO()                              */
1645
/************************************************************************/
1646

1647
CPLErr JPEGXLRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
35,607✔
1648
                                   int nXSize, int nYSize, void *pData,
1649
                                   int nBufXSize, int nBufYSize,
1650
                                   GDALDataType eBufType, GSpacing nPixelSpace,
1651
                                   GSpacing nLineSpace,
1652
                                   GDALRasterIOExtraArg *psExtraArg)
1653

1654
{
1655
    if (eRWFlag == GF_Read && nXOff == 0 && nYOff == 0 &&
35,607✔
1656
        nXSize == nRasterXSize && nYSize == nRasterYSize &&
229✔
1657
        nBufXSize == nXSize && nBufYSize == nYSize)
4✔
1658
    {
1659
        return cpl::down_cast<JPEGXLDataset *>(poDS)->IRasterIO(
4✔
1660
            GF_Read, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1661
            eBufType, 1, &nBand, nPixelSpace, nLineSpace, 0, psExtraArg);
4✔
1662
    }
1663

1664
    return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
35,603✔
1665
                                        pData, nBufXSize, nBufYSize, eBufType,
1666
                                        nPixelSpace, nLineSpace, psExtraArg);
35,603✔
1667
}
1668

1669
/************************************************************************/
1670
/*                          OpenStaticPAM()                             */
1671
/************************************************************************/
1672

1673
GDALPamDataset *JPEGXLDataset::OpenStaticPAM(GDALOpenInfo *poOpenInfo)
338✔
1674
{
1675
    if (!Identify(poOpenInfo))
338✔
1676
        return nullptr;
×
1677

1678
    auto poDS = std::make_unique<JPEGXLDataset>();
676✔
1679
    if (!poDS->Open(poOpenInfo))
338✔
1680
        return nullptr;
×
1681

1682
    return poDS.release();
338✔
1683
}
1684

1685
/************************************************************************/
1686
/*                          OpenStatic()                                */
1687
/************************************************************************/
1688

1689
GDALDataset *JPEGXLDataset::OpenStatic(GDALOpenInfo *poOpenInfo)
281✔
1690
{
1691
    GDALDataset *poDS = OpenStaticPAM(poOpenInfo);
281✔
1692

1693
#ifdef HAVE_JXL_BOX_API
1694
    if (poDS &&
562✔
1695
        CPLFetchBool(poOpenInfo->papszOpenOptions, "APPLY_ORIENTATION", false))
281✔
1696
    {
1697
        const char *pszOrientation =
1698
            poDS->GetMetadataItem("EXIF_Orientation", "EXIF");
8✔
1699
        if (pszOrientation && !EQUAL(pszOrientation, "1"))
8✔
1700
        {
1701
            int nOrientation = atoi(pszOrientation);
7✔
1702
            if (nOrientation >= 2 && nOrientation <= 8)
7✔
1703
            {
1704
                std::unique_ptr<GDALDataset> poOriDS(poDS);
14✔
1705
                auto poOrientedDS = std::make_unique<GDALOrientedDataset>(
1706
                    std::move(poOriDS),
7✔
1707
                    static_cast<GDALOrientedDataset::Origin>(nOrientation));
14✔
1708
                poDS = poOrientedDS.release();
7✔
1709
            }
1710
        }
1711
    }
1712
#endif
1713

1714
    return poDS;
281✔
1715
}
1716

1717
/************************************************************************/
1718
/*                              CreateCopy()                            */
1719
/************************************************************************/
1720

1721
GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename,
91✔
1722
                                       GDALDataset *poSrcDS, int /*bStrict*/,
1723
                                       char **papszOptions,
1724
                                       GDALProgressFunc pfnProgress,
1725
                                       void *pProgressData)
1726

1727
{
1728
    if (poSrcDS->GetRasterXSize() <= 0 || poSrcDS->GetRasterYSize() <= 0 ||
182✔
1729
        poSrcDS->GetRasterCount() == 0)
91✔
1730
    {
1731
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid source dataset");
2✔
1732
        return nullptr;
2✔
1733
    }
1734

1735
    // Look for EXIF metadata first in the EXIF metadata domain, and fallback
1736
    // to main domain.
1737
    const bool bWriteExifMetadata =
1738
        CPLFetchBool(papszOptions, "WRITE_EXIF_METADATA", true);
89✔
1739
    char **papszEXIF = poSrcDS->GetMetadata("EXIF");
89✔
1740
    bool bEXIFFromMainDomain = false;
89✔
1741
    if (papszEXIF == nullptr && bWriteExifMetadata)
89✔
1742
    {
1743
        char **papszMetadata = poSrcDS->GetMetadata();
88✔
1744
        for (CSLConstList papszIter = papszMetadata; papszIter && *papszIter;
127✔
1745
             ++papszIter)
1746
        {
1747
            if (STARTS_WITH(*papszIter, "EXIF_"))
44✔
1748
            {
1749
                papszEXIF = papszMetadata;
5✔
1750
                bEXIFFromMainDomain = true;
5✔
1751
                break;
5✔
1752
            }
1753
        }
1754
    }
1755

1756
    // Write "xml " box with xml:XMP metadata
1757
    const bool bWriteXMP = CPLFetchBool(papszOptions, "WRITE_XMP", true);
89✔
1758
    char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
89✔
1759

1760
    const bool bWriteGeoJP2 = CPLFetchBool(papszOptions, "WRITE_GEOJP2", true);
89✔
1761
    GDALGeoTransform gt;
89✔
1762
    const bool bHasGeoTransform = poSrcDS->GetGeoTransform(gt) == CE_None;
89✔
1763
    const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
89✔
1764
    const int nGCPCount = poSrcDS->GetGCPCount();
89✔
1765
    char **papszRPCMD = poSrcDS->GetMetadata("RPC");
89✔
1766
    std::unique_ptr<GDALJP2Box> poJUMBFBox;
89✔
1767
    if (bWriteGeoJP2 &&
89✔
1768
        (poSRS != nullptr || bHasGeoTransform || nGCPCount || papszRPCMD))
50✔
1769
    {
1770
        GDALJP2Metadata oJP2Metadata;
78✔
1771
        if (poSRS)
39✔
1772
            oJP2Metadata.SetSpatialRef(poSRS);
39✔
1773
        if (bHasGeoTransform)
39✔
1774
            oJP2Metadata.SetGeoTransform(gt);
39✔
1775
        if (nGCPCount)
39✔
1776
        {
1777
            const OGRSpatialReference *poSRSGCP = poSrcDS->GetGCPSpatialRef();
×
1778
            if (poSRSGCP)
×
1779
                oJP2Metadata.SetSpatialRef(poSRSGCP);
×
1780
            oJP2Metadata.SetGCPs(nGCPCount, poSrcDS->GetGCPs());
×
1781
        }
1782
        if (papszRPCMD)
39✔
1783
            oJP2Metadata.SetRPCMD(papszRPCMD);
×
1784

1785
        const char *pszAreaOfPoint =
1786
            poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
39✔
1787
        oJP2Metadata.bPixelIsPoint =
39✔
1788
            pszAreaOfPoint && EQUAL(pszAreaOfPoint, GDALMD_AOP_POINT);
39✔
1789

1790
        std::unique_ptr<GDALJP2Box> poJP2GeoTIFF;
39✔
1791
        poJP2GeoTIFF.reset(oJP2Metadata.CreateJP2GeoTIFF());
39✔
1792
        if (poJP2GeoTIFF)
39✔
1793
        {
1794
            // Per JUMBF spec: UUID Content Type. The JUMBF box contains exactly
1795
            // one UUID box
1796
            const GByte abyUUIDTypeUUID[16] = {
39✔
1797
                0x75, 0x75, 0x69, 0x64, 0x00, 0x11, 0x00, 0x10,
1798
                0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71};
1799
            std::unique_ptr<GDALJP2Box> poJUMBFDescrBox;
39✔
1800
            poJUMBFDescrBox.reset(GDALJP2Box::CreateJUMBFDescriptionBox(
39✔
1801
                abyUUIDTypeUUID, "GeoJP2 box"));
1802
            const GDALJP2Box *poJP2GeoTIFFConst = poJP2GeoTIFF.get();
39✔
1803
            poJUMBFBox.reset(GDALJP2Box::CreateJUMBFBox(poJUMBFDescrBox.get(),
39✔
1804
                                                        1, &poJP2GeoTIFFConst));
1805
        }
1806
    }
1807

1808
    constexpr int DEFAULT_EFFORT = 5;
89✔
1809
    const int nEffort = atoi(CSLFetchNameValueDef(
89✔
1810
        papszOptions, "EFFORT", CPLSPrintf("%d", DEFAULT_EFFORT)));
1811
    const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE");
89✔
1812
    const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
89✔
1813
    const char *pszAlphaDistance =
1814
        CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE");
89✔
1815
    const char *pszLossLessCopy =
1816
        CSLFetchNameValueDef(papszOptions, "LOSSLESS_COPY", "AUTO");
89✔
1817
    if ((EQUAL(pszLossLessCopy, "AUTO") && !pszDistance && !pszQuality &&
83✔
1818
         !pszAlphaDistance && nEffort == DEFAULT_EFFORT) ||
179✔
1819
        (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
19✔
1820
    {
1821
        void *pJPEGXLContent = nullptr;
74✔
1822
        size_t nJPEGXLContent = 0;
74✔
1823
        const bool bSrcIsJXL =
1824
            (poSrcDS->ReadCompressedData(
74✔
1825
                 "JXL", 0, 0, poSrcDS->GetRasterXSize(),
1826
                 poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr,
1827
                 &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None);
74✔
1828
        if (bSrcIsJXL && (pszDistance || pszQuality || pszAlphaDistance ||
74✔
1829
                          nEffort != DEFAULT_EFFORT))
1830
        {
1831
            CPLError(CE_Failure, CPLE_NotSupported,
×
1832
                     "LOSSLESS_COPY=YES not supported when EFFORT, QUALITY, "
1833
                     "DISTANCE or ALPHA_DISTANCE are specified");
1834
            return nullptr;
2✔
1835
        }
1836
        else if (bSrcIsJXL)
74✔
1837
        {
1838
            CPLDebug("JPEGXL", "Lossless copy from source dataset");
2✔
1839
            GByte abySizeAndBoxName[8];
1840
            std::vector<GByte> abyData;
2✔
1841
            bool bFallbackToGeneral = false;
2✔
1842
            try
1843
            {
1844
                abyData.assign(static_cast<const GByte *>(pJPEGXLContent),
2✔
1845
                               static_cast<const GByte *>(pJPEGXLContent) +
2✔
1846
                                   nJPEGXLContent);
2✔
1847

1848
                size_t nInsertPos = 0;
2✔
1849
                if (abyData.size() >= 2 && abyData[0] == 0xff &&
3✔
1850
                    abyData[1] == 0x0a)
1✔
1851
                {
1852
                    // If we get a "naked" codestream, insert it into a
1853
                    // ISOBMFF-based container
1854
                    constexpr const GByte abyJXLContainerSignatureAndFtypBox[] =
1✔
1855
                        {0x00, 0x00, 0x00, 0x0C, 'J',  'X',  'L',  ' ',
1856
                         0x0D, 0x0A, 0x87, 0x0A, 0x00, 0x00, 0x00, 0x14,
1857
                         'f',  't',  'y',  'p',  'j',  'x',  'l',  ' ',
1858
                         0,    0,    0,    0,    'j',  'x',  'l',  ' '};
1859
                    uint32_t nBoxSize =
1860
                        static_cast<uint32_t>(8 + abyData.size());
1✔
1861
                    abyData.insert(
1862
                        abyData.begin(), abyJXLContainerSignatureAndFtypBox,
×
1863
                        abyJXLContainerSignatureAndFtypBox +
1864
                            sizeof(abyJXLContainerSignatureAndFtypBox));
1✔
1865
                    CPL_MSBPTR32(&nBoxSize);
1✔
1866
                    memcpy(abySizeAndBoxName, &nBoxSize, 4);
1✔
1867
                    memcpy(abySizeAndBoxName + 4, "jxlc", 4);
1✔
1868
                    nInsertPos = sizeof(abyJXLContainerSignatureAndFtypBox);
1✔
1869
                    abyData.insert(
1870
                        abyData.begin() + nInsertPos, abySizeAndBoxName,
1✔
1871
                        abySizeAndBoxName + sizeof(abySizeAndBoxName));
2✔
1872
                }
1873
                else
1874
                {
1875
                    size_t nPos = 0;
1✔
1876
                    const GByte *pabyData = abyData.data();
1✔
1877
                    while (nPos + 8 <= abyData.size())
4✔
1878
                    {
1879
                        uint32_t nBoxSize;
1880
                        memcpy(&nBoxSize, pabyData + nPos, 4);
4✔
1881
                        CPL_MSBPTR32(&nBoxSize);
4✔
1882
                        if (nBoxSize < 8 || nBoxSize > abyData.size() - nPos)
4✔
1883
                            break;
1✔
1884
                        char szBoxName[5] = {0, 0, 0, 0, 0};
4✔
1885
                        memcpy(szBoxName, pabyData + nPos + 4, 4);
4✔
1886
                        if (memcmp(szBoxName, "jxlp", 4) == 0 ||
4✔
1887
                            memcmp(szBoxName, "jxlc", 4) == 0)
3✔
1888
                        {
1889
                            nInsertPos = nPos;
1✔
1890
                            break;
1✔
1891
                        }
1892
                        else
1893
                        {
1894
                            nPos += nBoxSize;
3✔
1895
                        }
1896
                    }
1897
                }
1898

1899
                // Write "Exif" box with EXIF metadata
1900
                if (papszEXIF && bWriteExifMetadata)
2✔
1901
                {
1902
                    if (nInsertPos)
×
1903
                    {
1904
                        GUInt32 nMarkerSize = 0;
×
1905
                        GByte *pabyEXIF =
1906
                            EXIFCreate(papszEXIF, nullptr, 0, 0, 0,  // overview
×
1907
                                       &nMarkerSize);
1908
                        CPLAssert(nMarkerSize > 6 &&
×
1909
                                  memcmp(pabyEXIF, "Exif\0\0", 6) == 0);
1910
                        // Add 4 leading bytes at 0
1911
                        std::vector<GByte> abyEXIF(4 + nMarkerSize - 6);
×
1912
                        memcpy(&abyEXIF[4], pabyEXIF + 6, nMarkerSize - 6);
×
1913
                        CPLFree(pabyEXIF);
×
1914

1915
                        uint32_t nBoxSize =
1916
                            static_cast<uint32_t>(8 + abyEXIF.size());
×
1917
                        CPL_MSBPTR32(&nBoxSize);
×
1918
                        memcpy(abySizeAndBoxName, &nBoxSize, 4);
×
1919
                        memcpy(abySizeAndBoxName + 4, "Exif", 4);
×
1920
                        abyData.insert(
1921
                            abyData.begin() + nInsertPos, abySizeAndBoxName,
×
1922
                            abySizeAndBoxName + sizeof(abySizeAndBoxName));
×
1923
                        abyData.insert(abyData.begin() + nInsertPos + 8,
×
1924
                                       abyEXIF.data(),
1925
                                       abyEXIF.data() + abyEXIF.size());
×
1926
                        nInsertPos += 8 + abyEXIF.size();
×
1927
                    }
1928
                    else
1929
                    {
1930
                        // shouldn't happen
1931
                        CPLDebug("JPEGX", "Cannot add Exif box to codestream");
×
1932
                        bFallbackToGeneral = true;
×
1933
                    }
1934
                }
1935

1936
                if (papszXMP && papszXMP[0] && bWriteXMP)
2✔
1937
                {
1938
                    if (nInsertPos)
×
1939
                    {
1940
                        const size_t nXMPLen = strlen(papszXMP[0]);
×
1941
                        uint32_t nBoxSize = static_cast<uint32_t>(8 + nXMPLen);
×
1942
                        CPL_MSBPTR32(&nBoxSize);
×
1943
                        memcpy(abySizeAndBoxName, &nBoxSize, 4);
×
1944
                        memcpy(abySizeAndBoxName + 4, "xml ", 4);
×
1945
                        abyData.insert(
1946
                            abyData.begin() + nInsertPos, abySizeAndBoxName,
×
1947
                            abySizeAndBoxName + sizeof(abySizeAndBoxName));
×
1948
                        abyData.insert(abyData.begin() + nInsertPos + 8,
×
1949
                                       reinterpret_cast<GByte *>(papszXMP[0]),
1950
                                       reinterpret_cast<GByte *>(papszXMP[0]) +
×
1951
                                           nXMPLen);
×
1952
                        nInsertPos += 8 + nXMPLen;
×
1953
                    }
1954
                    else
1955
                    {
1956
                        // shouldn't happen
1957
                        CPLDebug("JPEGX", "Cannot add XMP box to codestream");
×
1958
                        bFallbackToGeneral = true;
×
1959
                    }
1960
                }
1961

1962
                // Write GeoJP2 box in a JUMBF box from georeferencing information
1963
                if (poJUMBFBox)
2✔
1964
                {
1965
                    if (nInsertPos)
2✔
1966
                    {
1967
                        const size_t nDataLen =
1968
                            static_cast<size_t>(poJUMBFBox->GetBoxLength());
2✔
1969
                        uint32_t nBoxSize = static_cast<uint32_t>(8 + nDataLen);
2✔
1970
                        CPL_MSBPTR32(&nBoxSize);
2✔
1971
                        memcpy(abySizeAndBoxName, &nBoxSize, 4);
2✔
1972
                        memcpy(abySizeAndBoxName + 4, "jumb", 4);
2✔
1973
                        abyData.insert(
1974
                            abyData.begin() + nInsertPos, abySizeAndBoxName,
2✔
1975
                            abySizeAndBoxName + sizeof(abySizeAndBoxName));
4✔
1976
                        GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
2✔
1977
                        abyData.insert(abyData.begin() + nInsertPos + 8,
2✔
1978
                                       pabyBoxData, pabyBoxData + nDataLen);
4✔
1979
                        VSIFree(pabyBoxData);
2✔
1980
                        nInsertPos += 8 + nDataLen;
2✔
1981
                    }
1982
                    else
1983
                    {
1984
                        // shouldn't happen
1985
                        CPLDebug("JPEGX",
×
1986
                                 "Cannot add JUMBF GeoJP2 box to codestream");
1987
                        bFallbackToGeneral = true;
×
1988
                    }
1989
                }
1990

1991
                CPL_IGNORE_RET_VAL(nInsertPos);
2✔
1992
            }
1993
            catch (const std::exception &)
×
1994
            {
1995
                abyData.clear();
×
1996
            }
1997
            VSIFree(pJPEGXLContent);
2✔
1998

1999
            if (!bFallbackToGeneral && !abyData.empty())
2✔
2000
            {
2001
                VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
2✔
2002
                if (fpImage == nullptr)
2✔
2003
                {
2004
                    CPLError(CE_Failure, CPLE_OpenFailed,
×
2005
                             "Unable to create jpeg file %s.", pszFilename);
2006

2007
                    return nullptr;
×
2008
                }
2009
                if (VSIFWriteL(abyData.data(), 1, abyData.size(), fpImage) !=
4✔
2010
                    abyData.size())
2✔
2011
                {
2012
                    CPLError(CE_Failure, CPLE_FileIO,
×
2013
                             "Failure writing data: %s", VSIStrerror(errno));
×
2014
                    VSIFCloseL(fpImage);
×
2015
                    return nullptr;
×
2016
                }
2017
                if (VSIFCloseL(fpImage) != 0)
2✔
2018
                {
2019
                    CPLError(CE_Failure, CPLE_FileIO,
×
2020
                             "Failure writing data: %s", VSIStrerror(errno));
×
2021
                    return nullptr;
×
2022
                }
2023

2024
                pfnProgress(1.0, nullptr, pProgressData);
2✔
2025

2026
                // Re-open file and clone missing info to PAM
2027
                GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
2✔
2028
                auto poDS = OpenStaticPAM(&oOpenInfo);
2✔
2029
                if (poDS)
2✔
2030
                {
2031
                    // Do not create a .aux.xml file just for AREA_OR_POINT=Area
2032
                    const char *pszAreaOfPoint =
2033
                        poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
2✔
2034
                    if (pszAreaOfPoint &&
2✔
2035
                        EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
1✔
2036
                    {
2037
                        poDS->SetMetadataItem(GDALMD_AREA_OR_POINT,
1✔
2038
                                              GDALMD_AOP_AREA);
1✔
2039
                        poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
1✔
2040
                    }
2041

2042
                    // When copying from JPEG, expose the EXIF metadata in the main domain,
2043
                    // so that PAM doesn't copy it.
2044
                    if (bEXIFFromMainDomain)
2✔
2045
                    {
2046
                        for (CSLConstList papszIter = papszEXIF;
×
2047
                             papszIter && *papszIter; ++papszIter)
×
2048
                        {
2049
                            if (STARTS_WITH(*papszIter, "EXIF_"))
×
2050
                            {
2051
                                char *pszKey = nullptr;
×
2052
                                const char *pszValue =
2053
                                    CPLParseNameValue(*papszIter, &pszKey);
×
2054
                                if (pszKey && pszValue)
×
2055
                                {
2056
                                    poDS->SetMetadataItem(pszKey, pszValue);
×
2057
                                }
2058
                                CPLFree(pszKey);
×
2059
                            }
2060
                        }
2061
                        poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
×
2062
                    }
2063

2064
                    poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
2✔
2065
                }
2066

2067
                return poDS;
2✔
2068
            }
2069
        }
2070
    }
2071

2072
    JxlPixelFormat format = {0, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
87✔
2073
    const auto eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
87✔
2074
    switch (eDT)
87✔
2075
    {
2076
        case GDT_Byte:
63✔
2077
            format.data_type = JXL_TYPE_UINT8;
63✔
2078
            break;
63✔
2079
        case GDT_UInt16:
13✔
2080
            format.data_type = JXL_TYPE_UINT16;
13✔
2081
            break;
13✔
2082
        case GDT_Float32:
2✔
2083
            format.data_type = JXL_TYPE_FLOAT;
2✔
2084
            break;
2✔
2085
        default:
9✔
2086
            CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type");
9✔
2087
            return nullptr;
9✔
2088
    }
2089

2090
    const char *pszLossLess = CSLFetchNameValue(papszOptions, "LOSSLESS");
78✔
2091

2092
    const bool bLossless = (pszLossLess == nullptr && pszDistance == nullptr &&
71✔
2093
                            pszQuality == nullptr) ||
156✔
2094
                           (pszLossLess != nullptr && CPLTestBool(pszLossLess));
7✔
2095
    if (pszLossLess == nullptr &&
78✔
2096
        (pszDistance != nullptr || pszQuality != nullptr))
65✔
2097
    {
2098
        CPLDebug("JPEGXL", "Using lossy mode");
8✔
2099
    }
2100
    if ((pszLossLess != nullptr && bLossless) && pszDistance != nullptr)
78✔
2101
    {
2102
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
2103
                 "DISTANCE and LOSSLESS=YES are mutually exclusive");
2104
        return nullptr;
1✔
2105
    }
2106
    if ((pszLossLess != nullptr && bLossless) && pszAlphaDistance != nullptr)
77✔
2107
    {
2108
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
2109
                 "ALPHA_DISTANCE and LOSSLESS=YES are mutually exclusive");
2110
        return nullptr;
1✔
2111
    }
2112
    if ((pszLossLess != nullptr && bLossless) && pszQuality != nullptr)
76✔
2113
    {
2114
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
2115
                 "QUALITY and LOSSLESS=YES are mutually exclusive");
2116
        return nullptr;
1✔
2117
    }
2118
    if (pszDistance != nullptr && pszQuality != nullptr)
75✔
2119
    {
2120
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
2121
                 "QUALITY and DISTANCE are mutually exclusive");
2122
        return nullptr;
1✔
2123
    }
2124

2125
    float fDistance = 0.0f;
74✔
2126
    float fAlphaDistance = -1.0;
74✔
2127
    if (!bLossless)
74✔
2128
    {
2129
        fDistance =
10✔
2130
            pszDistance ? static_cast<float>(CPLAtof(pszDistance)) : 1.0f;
10✔
2131
        if (pszQuality != nullptr)
10✔
2132
        {
2133
            const double quality = CPLAtof(pszQuality);
2✔
2134
            // Quality settings roughly match libjpeg qualities.
2135
            // Formulas taken from cjxl.cc
2136
            if (quality >= 100)
2✔
2137
            {
2138
                fDistance = 0;
1✔
2139
            }
2140
            else if (quality >= 30)
1✔
2141
            {
2142
                fDistance = static_cast<float>(0.1 + (100 - quality) * 0.09);
×
2143
            }
2144
            else
2145
            {
2146
                fDistance =
1✔
2147
                    static_cast<float>(53.0 / 3000.0 * quality * quality -
1✔
2148
                                       23.0 / 20.0 * quality + 25.0);
1✔
2149
            }
2150
        }
2151
        if (fDistance >= 0.0f && fDistance < MIN_DISTANCE)
10✔
2152
            fDistance = MIN_DISTANCE;
2✔
2153

2154
        if (pszAlphaDistance)
10✔
2155
        {
2156
            fAlphaDistance = static_cast<float>(CPLAtof(pszAlphaDistance));
1✔
2157
            if (fAlphaDistance > 0.0f && fAlphaDistance < MIN_DISTANCE)
1✔
2158
                fAlphaDistance = MIN_DISTANCE;
×
2159
        }
2160
    }
2161

2162
    const bool bAlphaDistanceSameAsMainChannel =
74✔
2163
        (fAlphaDistance < 0.0f) ||
75✔
2164
        ((bLossless && fAlphaDistance == 0.0f) ||
×
2165
         (!bLossless && fAlphaDistance == fDistance));
1✔
2166
#ifndef HAVE_JxlEncoderSetExtraChannelDistance
2167
    if (!bAlphaDistanceSameAsMainChannel)
2168
    {
2169
        CPLError(CE_Warning, CPLE_NotSupported,
2170
                 "ALPHA_DISTANCE ignored due to "
2171
                 "JxlEncoderSetExtraChannelDistance() not being "
2172
                 "available. Please upgrade libjxl to > 0.8.1");
2173
    }
2174
#endif
2175

2176
    auto encoder = JxlEncoderMake(nullptr);
148✔
2177
    if (!encoder)
74✔
2178
    {
2179
        CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderMake() failed");
×
2180
        return nullptr;
×
2181
    }
2182

2183
    const char *pszNBits = CSLFetchNameValue(papszOptions, "NBITS");
74✔
2184
    if (pszNBits == nullptr)
74✔
2185
        pszNBits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
144✔
2186
            "NBITS", "IMAGE_STRUCTURE");
72✔
2187
    const int nBits =
2188
        ((eDT == GDT_Byte || eDT == GDT_UInt16) && pszNBits != nullptr)
74✔
2189
            ? atoi(pszNBits)
76✔
2190
            : GDALGetDataTypeSizeBits(eDT);
72✔
2191

2192
    JxlBasicInfo basic_info;
2193
    JxlEncoderInitBasicInfo(&basic_info);
74✔
2194
    basic_info.xsize = poSrcDS->GetRasterXSize();
74✔
2195
    basic_info.ysize = poSrcDS->GetRasterYSize();
74✔
2196
    basic_info.bits_per_sample = nBits;
74✔
2197
    basic_info.orientation = JXL_ORIENT_IDENTITY;
74✔
2198
    if (format.data_type == JXL_TYPE_FLOAT)
74✔
2199
    {
2200
        basic_info.exponent_bits_per_sample = 8;
2✔
2201
    }
2202

2203
    const int nSrcBands = poSrcDS->GetRasterCount();
74✔
2204

2205
    bool bHasInterleavedAlphaBand = false;
74✔
2206
    if (nSrcBands == 1)
74✔
2207
    {
2208
        basic_info.num_color_channels = 1;
28✔
2209
    }
2210
    else if (nSrcBands == 2)
46✔
2211
    {
2212
        basic_info.num_color_channels = 1;
8✔
2213
        basic_info.num_extra_channels = 1;
8✔
2214
        if (poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
8✔
2215
                GCI_AlphaBand &&
8✔
2216
            bAlphaDistanceSameAsMainChannel)
2217
        {
2218
            bHasInterleavedAlphaBand = true;
5✔
2219
            basic_info.alpha_bits = basic_info.bits_per_sample;
5✔
2220
            basic_info.alpha_exponent_bits =
5✔
2221
                basic_info.exponent_bits_per_sample;
5✔
2222
        }
2223
    }
2224
    else /* if( nSrcBands >= 3 ) */
2225
    {
2226
        if (poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
38✔
2227
                GCI_RedBand &&
33✔
2228
            poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
33✔
2229
                GCI_GreenBand &&
71✔
2230
            poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand)
33✔
2231
        {
2232
            basic_info.num_color_channels = 3;
31✔
2233
            basic_info.num_extra_channels = nSrcBands - 3;
31✔
2234
            if (nSrcBands >= 4 &&
50✔
2235
                poSrcDS->GetRasterBand(4)->GetColorInterpretation() ==
19✔
2236
                    GCI_AlphaBand &&
50✔
2237
                bAlphaDistanceSameAsMainChannel)
2238
            {
2239
                bHasInterleavedAlphaBand = true;
14✔
2240
                basic_info.alpha_bits = basic_info.bits_per_sample;
14✔
2241
                basic_info.alpha_exponent_bits =
14✔
2242
                    basic_info.exponent_bits_per_sample;
14✔
2243
            }
2244
        }
2245
        else
2246
        {
2247
            basic_info.num_color_channels = 1;
7✔
2248
            basic_info.num_extra_channels = nSrcBands - 1;
7✔
2249
        }
2250
    }
2251

2252
    const int nBaseChannels = static_cast<int>(
74✔
2253
        basic_info.num_color_channels + (bHasInterleavedAlphaBand ? 1 : 0));
74✔
2254
    format.num_channels = nBaseChannels;
74✔
2255

2256
#ifndef HAVE_JxlEncoderInitExtraChannelInfo
2257
    if (basic_info.num_extra_channels != (bHasInterleavedAlphaBand ? 1 : 0))
2258
    {
2259
        CPLError(CE_Failure, CPLE_AppDefined,
2260
                 "This version of libjxl does not support "
2261
                 "creating non-alpha extra channels.");
2262
        return nullptr;
2263
    }
2264
#endif
2265

2266
#ifdef HAVE_JXL_THREADS
2267
    auto parallelRunner = JxlResizableParallelRunnerMake(nullptr);
148✔
2268
    if (!parallelRunner)
74✔
2269
    {
2270
        CPLError(CE_Failure, CPLE_AppDefined,
×
2271
                 "JxlResizableParallelRunnerMake() failed");
2272
        return nullptr;
×
2273
    }
2274

2275
    const char *pszNumThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
74✔
2276
    if (pszNumThreads == nullptr)
74✔
2277
        pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
74✔
2278
    uint32_t nMaxThreads = static_cast<uint32_t>(
148✔
2279
        EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
74✔
2280
                                         : atoi(pszNumThreads));
×
2281
    if (nMaxThreads > 1024)
74✔
2282
        nMaxThreads = 1024;  // to please Coverity
×
2283

2284
    const uint32_t nThreads =
2285
        std::min(nMaxThreads, JxlResizableParallelRunnerSuggestThreads(
222✔
2286
                                  basic_info.xsize, basic_info.ysize));
74✔
2287
    CPLDebug("JPEGXL", "Using %u threads", nThreads);
74✔
2288
    JxlResizableParallelRunnerSetThreads(parallelRunner.get(), nThreads);
74✔
2289

2290
    if (JxlEncoderSetParallelRunner(encoder.get(), JxlResizableParallelRunner,
74✔
2291
                                    parallelRunner.get()) != JXL_ENC_SUCCESS)
74✔
2292
    {
2293
        CPLError(CE_Failure, CPLE_AppDefined,
×
2294
                 "JxlEncoderSetParallelRunner() failed");
2295
        return nullptr;
×
2296
    }
2297
#endif
2298

2299
#ifdef HAVE_JxlEncoderFrameSettingsCreate
2300
    JxlEncoderFrameSettings *opts =
2301
        JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
74✔
2302
#else
2303
    JxlEncoderOptions *opts = JxlEncoderOptionsCreate(encoder.get(), nullptr);
2304
#endif
2305
    if (opts == nullptr)
74✔
2306
    {
2307
        CPLError(CE_Failure, CPLE_AppDefined,
×
2308
                 "JxlEncoderFrameSettingsCreate() failed");
2309
        return nullptr;
×
2310
    }
2311

2312
#ifdef HAVE_JxlEncoderSetCodestreamLevel
2313
    if (poSrcDS->GetRasterXSize() > 262144 ||
74✔
2314
        poSrcDS->GetRasterYSize() > 262144 ||
148✔
2315
        poSrcDS->GetRasterXSize() > 268435456 / poSrcDS->GetRasterYSize())
74✔
2316
    {
2317
        JxlEncoderSetCodestreamLevel(encoder.get(), 10);
×
2318
    }
2319
#endif
2320

2321
    if (bLossless)
74✔
2322
    {
2323
#ifdef HAVE_JxlEncoderSetCodestreamLevel
2324
        if (nBits > 12)
64✔
2325
        {
2326
            JxlEncoderSetCodestreamLevel(encoder.get(), 10);
14✔
2327
        }
2328
#endif
2329

2330
#ifdef HAVE_JxlEncoderSetFrameLossless
2331
        JxlEncoderSetFrameLossless(opts, TRUE);
64✔
2332
#else
2333
        JxlEncoderOptionsSetLossless(opts, TRUE);
2334
#endif
2335
        basic_info.uses_original_profile = JXL_TRUE;
64✔
2336
    }
2337
    else
2338
    {
2339
#ifdef HAVE_JxlEncoderSetFrameDistance
2340
        if (JxlEncoderSetFrameDistance(opts, fDistance) != JXL_ENC_SUCCESS)
10✔
2341
#else
2342
        if (JxlEncoderOptionsSetDistance(opts, fDistance) != JXL_ENC_SUCCESS)
2343
#endif
2344
        {
2345
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
2346
                     "JxlEncoderSetFrameDistance() failed");
2347
            return nullptr;
1✔
2348
        }
2349
    }
2350

2351
#ifdef HAVE_JxlEncoderFrameSettingsSetOption
2352
    if (JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT,
73✔
2353
                                         nEffort) != JXL_ENC_SUCCESS)
73✔
2354
#else
2355
    if (JxlEncoderOptionsSetEffort(opts, nEffort) != JXL_ENC_SUCCESS)
2356
#endif
2357
    {
2358
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
2359
                 "JxlEncoderFrameSettingsSetOption() failed");
2360
        return nullptr;
1✔
2361
    }
2362

2363
    std::vector<GByte> abyJPEG;
144✔
2364
    void *pJPEGContent = nullptr;
72✔
2365
    size_t nJPEGContent = 0;
72✔
2366
    char *pszDetailedFormat = nullptr;
72✔
2367
    // If the source dataset is a JPEG file or compatible of it, try to
2368
    // losslessly add it
2369
    if ((EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy)) &&
142✔
2370
        poSrcDS->ReadCompressedData(
70✔
2371
            "JPEG", 0, 0, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(),
2372
            poSrcDS->GetRasterCount(), nullptr, &pJPEGContent, &nJPEGContent,
2373
            &pszDetailedFormat) == CE_None)
70✔
2374
    {
2375
        CPLAssert(pszDetailedFormat != nullptr);
5✔
2376
        const CPLStringList aosTokens(
2377
            CSLTokenizeString2(pszDetailedFormat, ";", 0));
10✔
2378
        VSIFree(pszDetailedFormat);
5✔
2379
        const char *pszBitDepth = aosTokens.FetchNameValueDef("bit_depth", "");
5✔
2380
        if (pJPEGContent && !EQUAL(pszBitDepth, "8"))
5✔
2381
        {
2382
            CPLDebug(
×
2383
                "JPEGXL",
2384
                "Unsupported bit_depth=%s for lossless transcoding from JPEG",
2385
                pszBitDepth);
2386
            VSIFree(pJPEGContent);
×
2387
            pJPEGContent = nullptr;
×
2388
        }
2389
        const char *pszColorspace =
2390
            aosTokens.FetchNameValueDef("colorspace", "");
5✔
2391
        if (pJPEGContent && !EQUAL(pszColorspace, "unknown") &&
5✔
2392
            !EQUAL(pszColorspace, "RGB") && !EQUAL(pszColorspace, "YCbCr"))
4✔
2393
        {
2394
            CPLDebug(
×
2395
                "JPEGXL",
2396
                "Unsupported colorspace=%s for lossless transcoding from JPEG",
2397
                pszColorspace);
2398
            VSIFree(pJPEGContent);
×
2399
            pJPEGContent = nullptr;
×
2400
        }
2401
        const char *pszSOF = aosTokens.FetchNameValueDef("frame_type", "");
5✔
2402
        if (pJPEGContent && !EQUAL(pszSOF, "SOF0_baseline") &&
5✔
2403
            !EQUAL(pszSOF, "SOF1_extended_sequential") &&
×
2404
            !EQUAL(pszSOF, "SOF2_progressive_huffman"))
×
2405
        {
2406
            CPLDebug(
×
2407
                "JPEGXL",
2408
                "Unsupported frame_type=%s for lossless transcoding from JPEG",
2409
                pszSOF);
2410
            VSIFree(pJPEGContent);
×
2411
            pJPEGContent = nullptr;
×
2412
        }
2413
    }
2414
    if (pJPEGContent)
72✔
2415
    {
2416
        try
2417
        {
2418
            abyJPEG.reserve(nJPEGContent);
5✔
2419
            abyJPEG.insert(abyJPEG.end(), static_cast<GByte *>(pJPEGContent),
×
2420
                           static_cast<GByte *>(pJPEGContent) + nJPEGContent);
5✔
2421
            VSIFree(pJPEGContent);
5✔
2422

2423
            std::vector<GByte> abyJPEGMod;
10✔
2424
            abyJPEGMod.reserve(abyJPEG.size());
5✔
2425

2426
            // Append Start Of Image marker (0xff 0xd8)
2427
            abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin(),
5✔
2428
                              abyJPEG.begin() + 2);
10✔
2429

2430
            // Rework JPEG data to remove APP (except APP0) and COM
2431
            // markers as it confuses libjxl, when trying to
2432
            // reconstruct a JPEG file
2433
            size_t i = 2;
5✔
2434
            while (i + 1 < abyJPEG.size())
10✔
2435
            {
2436
                if (abyJPEG[i] != 0xFF)
10✔
2437
                {
2438
                    // Not a valid tag (shouldn't happen)
2439
                    abyJPEGMod.clear();
×
2440
                    break;
×
2441
                }
2442

2443
                // Stop when encountering a marker that is not a APP
2444
                // or COM marker
2445
                const bool bIsCOM = abyJPEG[i + 1] == 0xFE;
10✔
2446
                if ((abyJPEG[i + 1] & 0xF0) != 0xE0 && !bIsCOM)
10✔
2447
                {
2448
                    // Append all markers until end
2449
                    abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i,
5✔
2450
                                      abyJPEG.end());
10✔
2451
                    break;
5✔
2452
                }
2453
                const bool bIsAPP0 = abyJPEG[i + 1] == 0xE0;
5✔
2454

2455
                // Skip marker ID
2456
                i += 2;
5✔
2457
                // Check we can read chunk length
2458
                if (i + 1 >= abyJPEG.size())
5✔
2459
                {
2460
                    // Truncated JPEG file
2461
                    abyJPEGMod.clear();
×
2462
                    break;
×
2463
                }
2464
                const int nChunkLength = abyJPEG[i] * 256 + abyJPEG[i + 1];
5✔
2465
                if ((bIsCOM || bIsAPP0) && i + nChunkLength <= abyJPEG.size())
5✔
2466
                {
2467
                    // Append COM or APP0 marker
2468
                    abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i - 2,
10✔
2469
                                      abyJPEG.begin() + i + nChunkLength);
15✔
2470
                }
2471
                i += nChunkLength;
5✔
2472
            }
2473
            abyJPEG = std::move(abyJPEGMod);
5✔
2474
        }
2475
        catch (const std::exception &)
×
2476
        {
2477
        }
2478
    }
2479
    if (abyJPEG.empty() && !bLossless &&
81✔
2480
        (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
9✔
2481
    {
2482
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
2483
                 "LOSSLESS_COPY=YES requested but not possible");
2484
        return nullptr;
1✔
2485
    }
2486

2487
    const char *pszICCProfile =
2488
        CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
71✔
2489
    if (pszICCProfile == nullptr)
71✔
2490
    {
2491
        pszICCProfile =
2492
            poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
71✔
2493
    }
2494
    if (pszICCProfile && pszICCProfile[0] != '\0')
71✔
2495
    {
2496
        basic_info.uses_original_profile = JXL_TRUE;
1✔
2497
    }
2498

2499
    if (abyJPEG.empty())
71✔
2500
    {
2501
        if (JXL_ENC_SUCCESS !=
66✔
2502
            JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
66✔
2503
        {
2504
            CPLError(CE_Failure, CPLE_AppDefined,
×
2505
                     "JxlEncoderSetBasicInfo() failed");
2506
            return nullptr;
×
2507
        }
2508

2509
        if (pszICCProfile && pszICCProfile[0] != '\0')
66✔
2510
        {
2511
            char *pEmbedBuffer = CPLStrdup(pszICCProfile);
1✔
2512
            GInt32 nEmbedLen =
2513
                CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pEmbedBuffer));
1✔
2514
            if (JXL_ENC_SUCCESS !=
1✔
2515
                JxlEncoderSetICCProfile(encoder.get(),
1✔
2516
                                        reinterpret_cast<GByte *>(pEmbedBuffer),
2517
                                        nEmbedLen))
2518
            {
2519
                CPLError(CE_Failure, CPLE_AppDefined,
×
2520
                         "JxlEncoderSetICCProfile() failed");
2521
                CPLFree(pEmbedBuffer);
×
2522
                return nullptr;
×
2523
            }
2524
            CPLFree(pEmbedBuffer);
1✔
2525
        }
2526
        else
2527
        {
2528
            JxlColorEncoding color_encoding;
2529
            JxlColorEncodingSetToSRGB(&color_encoding,
65✔
2530
                                      basic_info.num_color_channels ==
65✔
2531
                                          1 /*is_gray*/);
2532
            if (JXL_ENC_SUCCESS !=
65✔
2533
                JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
65✔
2534
            {
2535
                CPLError(CE_Failure, CPLE_AppDefined,
×
2536
                         "JxlEncoderSetColorEncoding() failed");
2537
                return nullptr;
×
2538
            }
2539
        }
2540
    }
2541

2542
#ifdef HAVE_JxlEncoderInitExtraChannelInfo
2543
    if (abyJPEG.empty() && basic_info.num_extra_channels > 0)
71✔
2544
    {
2545
        if (basic_info.num_extra_channels >= 5)
33✔
2546
            JxlEncoderSetCodestreamLevel(encoder.get(), 10);
×
2547

2548
        for (int i = (bHasInterleavedAlphaBand ? 1 : 0);
71✔
2549
             i < static_cast<int>(basic_info.num_extra_channels); ++i)
71✔
2550
        {
2551
            const int nBand =
38✔
2552
                static_cast<int>(1 + basic_info.num_color_channels + i);
38✔
2553
            const auto poBand = poSrcDS->GetRasterBand(nBand);
38✔
2554
            JxlExtraChannelInfo extra_channel_info;
2555
            const JxlExtraChannelType channelType =
2556
                poBand->GetColorInterpretation() == GCI_AlphaBand
38✔
2557
                    ? JXL_CHANNEL_ALPHA
38✔
2558
                    : JXL_CHANNEL_OPTIONAL;
38✔
2559
            JxlEncoderInitExtraChannelInfo(channelType, &extra_channel_info);
38✔
2560
            extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
38✔
2561
            extra_channel_info.exponent_bits_per_sample =
38✔
2562
                basic_info.exponent_bits_per_sample;
38✔
2563

2564
            const uint32_t nIndex = static_cast<uint32_t>(i);
38✔
2565
            if (JXL_ENC_SUCCESS !=
38✔
2566
                JxlEncoderSetExtraChannelInfo(encoder.get(), nIndex,
38✔
2567
                                              &extra_channel_info))
2568
            {
2569
                CPLError(CE_Failure, CPLE_AppDefined,
×
2570
                         "JxlEncoderSetExtraChannelInfo() failed");
2571
                return nullptr;
×
2572
            }
2573
            std::string osChannelName(CPLSPrintf("Band %d", nBand));
38✔
2574
            const char *pszDescription = poBand->GetDescription();
38✔
2575
            if (pszDescription && pszDescription[0] != '\0')
38✔
2576
                osChannelName = pszDescription;
1✔
2577
            if (JXL_ENC_SUCCESS !=
38✔
2578
                JxlEncoderSetExtraChannelName(encoder.get(), nIndex,
76✔
2579
                                              osChannelName.data(),
38✔
2580
                                              osChannelName.size()))
2581
            {
2582
                CPLError(CE_Failure, CPLE_AppDefined,
×
2583
                         "JxlEncoderSetExtraChannelName() failed");
2584
                return nullptr;
×
2585
            }
2586
#if HAVE_JxlEncoderSetExtraChannelDistance
2587
            if (channelType == JXL_CHANNEL_ALPHA && fAlphaDistance >= 0.0f)
38✔
2588
            {
2589
                if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(
1✔
2590
                                           opts, nIndex, fAlphaDistance))
2591
                {
2592
                    CPLError(CE_Failure, CPLE_AppDefined,
×
2593
                             "JxlEncoderSetExtraChannelDistance failed");
2594
                    return nullptr;
×
2595
                }
2596
            }
2597
            else if (!bLossless)
37✔
2598
            {
2599
                // By default libjxl applies lossless encoding for extra channels
2600
                if (JXL_ENC_SUCCESS !=
4✔
2601
                    JxlEncoderSetExtraChannelDistance(opts, nIndex, fDistance))
4✔
2602
                {
2603
                    CPLError(CE_Failure, CPLE_AppDefined,
×
2604
                             "JxlEncoderSetExtraChannelDistance failed");
2605
                    return nullptr;
×
2606
                }
2607
            }
2608
#endif
2609
        }
2610
    }
2611
#endif
2612

2613
#ifdef HAVE_JXL_BOX_API
2614
    const bool bCompressBox =
2615
        CPLFetchBool(papszOptions, "COMPRESS_BOXES", false);
71✔
2616

2617
    if (papszXMP && papszXMP[0] && bWriteXMP)
71✔
2618
    {
2619
        JxlEncoderUseBoxes(encoder.get());
2✔
2620

2621
        const char *pszXMP = papszXMP[0];
2✔
2622
        if (JxlEncoderAddBox(encoder.get(), "xml ",
2✔
2623
                             reinterpret_cast<const uint8_t *>(pszXMP),
2624
                             strlen(pszXMP), bCompressBox) != JXL_ENC_SUCCESS)
2✔
2625
        {
2626
            CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
×
2627
            return nullptr;
×
2628
        }
2629
    }
2630

2631
    // Write "Exif" box with EXIF metadata
2632
    if (papszEXIF && bWriteExifMetadata)
71✔
2633
    {
2634
        GUInt32 nMarkerSize = 0;
6✔
2635
        GByte *pabyEXIF = EXIFCreate(papszEXIF, nullptr, 0, 0, 0,  // overview
6✔
2636
                                     &nMarkerSize);
2637
        CPLAssert(nMarkerSize > 6 && memcmp(pabyEXIF, "Exif\0\0", 6) == 0);
6✔
2638
        // Add 4 leading bytes at 0
2639
        std::vector<GByte> abyEXIF(4 + nMarkerSize - 6);
6✔
2640
        memcpy(&abyEXIF[4], pabyEXIF + 6, nMarkerSize - 6);
6✔
2641
        CPLFree(pabyEXIF);
6✔
2642

2643
        JxlEncoderUseBoxes(encoder.get());
6✔
2644
        if (JxlEncoderAddBox(encoder.get(), "Exif", abyEXIF.data(),
6✔
2645
                             abyEXIF.size(), bCompressBox) != JXL_ENC_SUCCESS)
6✔
2646
        {
2647
            CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
×
2648
            return nullptr;
×
2649
        }
2650
    }
2651

2652
    // Write GeoJP2 box in a JUMBF box from georeferencing information
2653
    if (poJUMBFBox)
71✔
2654
    {
2655
        GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
29✔
2656
        JxlEncoderUseBoxes(encoder.get());
29✔
2657
        if (JxlEncoderAddBox(encoder.get(), "jumb", pabyBoxData,
29✔
2658
                             static_cast<size_t>(poJUMBFBox->GetBoxLength()),
29✔
2659
                             bCompressBox) != JXL_ENC_SUCCESS)
29✔
2660
        {
2661
            VSIFree(pabyBoxData);
×
2662
            CPLError(CE_Failure, CPLE_AppDefined,
×
2663
                     "JxlEncoderAddBox() failed for jumb");
2664
            return nullptr;
×
2665
        }
2666
        VSIFree(pabyBoxData);
29✔
2667
    }
2668
#endif
2669

2670
    auto fp = std::unique_ptr<VSILFILE, VSILFileReleaser>(
2671
        VSIFOpenL(pszFilename, "wb"));
142✔
2672
    if (!fp)
71✔
2673
    {
2674
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s: %s", pszFilename,
4✔
2675
                 VSIStrerror(errno));
4✔
2676
        return nullptr;
4✔
2677
    }
2678

2679
    int nPamMask = GCIF_PAM_DEFAULT;
67✔
2680

2681
    if (!abyJPEG.empty())
67✔
2682
    {
2683
#ifdef HAVE_JxlEncoderInitExtraChannelInfo
2684
        const bool bHasMaskBand =
2685
            basic_info.num_extra_channels == 0 &&
10✔
2686
            poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET;
5✔
2687
        if (bHasMaskBand)
5✔
2688
        {
2689
            nPamMask &= ~GCIF_MASK;
1✔
2690

2691
            basic_info.alpha_bits = basic_info.bits_per_sample;
1✔
2692
            basic_info.num_extra_channels = 1;
1✔
2693
            if (JXL_ENC_SUCCESS !=
1✔
2694
                JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
1✔
2695
            {
2696
                CPLError(CE_Failure, CPLE_AppDefined,
×
2697
                         "JxlEncoderSetBasicInfo() failed");
2698
                return nullptr;
×
2699
            }
2700

2701
            JxlExtraChannelInfo extra_channel_info;
2702
            JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA,
1✔
2703
                                           &extra_channel_info);
2704
            extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
1✔
2705
            extra_channel_info.exponent_bits_per_sample =
1✔
2706
                basic_info.exponent_bits_per_sample;
1✔
2707

2708
            if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(
1✔
2709
                                       encoder.get(), 0, &extra_channel_info))
2710
            {
2711
                CPLError(CE_Failure, CPLE_AppDefined,
×
2712
                         "JxlEncoderSetExtraChannelInfo() failed");
2713
                return nullptr;
×
2714
            }
2715
        }
2716
#endif
2717

2718
        CPLDebug("JPEGXL", "Adding JPEG frame");
5✔
2719
        JxlEncoderStoreJPEGMetadata(encoder.get(), true);
5✔
2720
        if (JxlEncoderAddJPEGFrame(opts, abyJPEG.data(), abyJPEG.size()) !=
5✔
2721
            JXL_ENC_SUCCESS)
2722
        {
2723
            if (EQUAL(pszLossLessCopy, "AUTO"))
1✔
2724
            {
2725
                // could happen with a file with arithmetic encoding for example
2726
                CPLDebug("JPEGXL",
1✔
2727
                         "JxlEncoderAddJPEGFrame() framed. "
2728
                         "Perhaps unsupported JPEG formulation for libjxl. "
2729
                         "Retrying with normal code path");
2730
                CPLStringList aosOptions(papszOptions);
2✔
2731
                aosOptions.SetNameValue("LOSSLESS_COPY", "NO");
1✔
2732
                CPLConfigOptionSetter oSetter("GDAL_ERROR_ON_LIBJPEG_WARNING",
2733
                                              "YES", true);
2✔
2734
                return CreateCopy(pszFilename, poSrcDS, FALSE,
1✔
2735
                                  aosOptions.List(), pfnProgress,
2736
                                  pProgressData);
1✔
2737
            }
2738
            else
2739
            {
2740
                CPLError(CE_Failure, CPLE_AppDefined,
×
2741
                         "JxlEncoderAddJPEGFrame() failed");
2742
                return nullptr;
×
2743
            }
2744
        }
2745

2746
#ifdef HAVE_JxlEncoderInitExtraChannelInfo
2747
        if (bHasMaskBand)
4✔
2748
        {
2749
            JxlColorEncoding color_encoding;
2750
            JxlColorEncodingSetToSRGB(&color_encoding,
1✔
2751
                                      basic_info.num_color_channels ==
1✔
2752
                                          1 /*is_gray*/);
2753
            // libjxl until commit
2754
            // https://github.com/libjxl/libjxl/commits/c70c9d0bdc03f77d6bd8d9c3c56d4dac1b9b1652
2755
            // needs JxlEncoderSetColorEncoding()
2756
            // But post it (308b5f1eed81becac506569080e4490cc486660c,
2757
            // "Use chunked frame adapter instead of image bundle in
2758
            // EncodeFrame. (#2983)"), this errors out.
2759
            CPL_IGNORE_RET_VAL(
1✔
2760
                JxlEncoderSetColorEncoding(encoder.get(), &color_encoding));
1✔
2761

2762
            const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
1✔
2763
            if (nDataSize <= 0 ||
2✔
2764
                static_cast<size_t>(poSrcDS->GetRasterXSize()) >
1✔
2765
                    std::numeric_limits<size_t>::max() /
1✔
2766
                        poSrcDS->GetRasterYSize() / nDataSize)
1✔
2767
            {
2768
                CPLError(CE_Failure, CPLE_OutOfMemory,
×
2769
                         "Image too big for architecture");
2770
                return nullptr;
×
2771
            }
2772
            const size_t nInputDataSize =
2773
                static_cast<size_t>(poSrcDS->GetRasterXSize()) *
1✔
2774
                poSrcDS->GetRasterYSize() * nDataSize;
1✔
2775

2776
            std::vector<GByte> abyInputData;
1✔
2777
            try
2778
            {
2779
                abyInputData.resize(nInputDataSize);
1✔
2780
            }
2781
            catch (const std::exception &e)
×
2782
            {
2783
                CPLError(CE_Failure, CPLE_OutOfMemory,
×
2784
                         "Cannot allocate image buffer: %s", e.what());
×
2785
                return nullptr;
×
2786
            }
2787

2788
            format.num_channels = 1;
1✔
2789
            if (poSrcDS->GetRasterBand(1)->GetMaskBand()->RasterIO(
2✔
2790
                    GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2791
                    poSrcDS->GetRasterYSize(), abyInputData.data(),
1✔
2792
                    poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2793
                    0, 0, nullptr) != CE_None)
1✔
2794
            {
2795
                return nullptr;
×
2796
            }
2797
            if (JxlEncoderSetExtraChannelBuffer(
1✔
2798
                    opts, &format, abyInputData.data(),
1✔
2799
                    static_cast<size_t>(poSrcDS->GetRasterXSize()) *
1✔
2800
                        poSrcDS->GetRasterYSize() * nDataSize,
1✔
2801
                    0) != JXL_ENC_SUCCESS)
1✔
2802
            {
2803
                CPLError(CE_Failure, CPLE_AppDefined,
×
2804
                         "JxlEncoderSetExtraChannelBuffer() failed");
2805
                return nullptr;
×
2806
            }
2807
        }
2808
#endif
2809
    }
2810
    else
2811
    {
2812
        const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
62✔
2813

2814
        if (nDataSize <= 0 || static_cast<size_t>(poSrcDS->GetRasterXSize()) >
124✔
2815
                                  std::numeric_limits<size_t>::max() /
62✔
2816
                                      poSrcDS->GetRasterYSize() /
62✔
2817
                                      nBaseChannels / nDataSize)
62✔
2818
        {
2819
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
2820
                     "Image too big for architecture");
2821
            return nullptr;
1✔
2822
        }
2823
        const size_t nInputDataSize =
2824
            static_cast<size_t>(poSrcDS->GetRasterXSize()) *
62✔
2825
            poSrcDS->GetRasterYSize() * nBaseChannels * nDataSize;
62✔
2826

2827
        std::vector<GByte> abyInputData;
62✔
2828
        try
2829
        {
2830
            abyInputData.resize(nInputDataSize);
62✔
2831
        }
2832
        catch (const std::exception &e)
×
2833
        {
2834
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
2835
                     "Cannot allocate image buffer: %s", e.what());
×
2836
            return nullptr;
×
2837
        }
2838

2839
        if (poSrcDS->RasterIO(
124✔
2840
                GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2841
                poSrcDS->GetRasterYSize(), abyInputData.data(),
62✔
2842
                poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2843
                nBaseChannels, nullptr, nDataSize * nBaseChannels,
62✔
2844
                nDataSize * nBaseChannels * poSrcDS->GetRasterXSize(),
62✔
2845
                nDataSize, nullptr) != CE_None)
62✔
2846
        {
2847
            return nullptr;
1✔
2848
        }
2849

2850
        const auto Rescale = [eDT, nBits, poSrcDS](void *pBuffer, int nChannels)
301✔
2851
        {
2852
            // Rescale to 8-bits/16-bits
2853
            if ((eDT == GDT_Byte && nBits < 8) ||
99✔
2854
                (eDT == GDT_UInt16 && nBits < 16))
23✔
2855
            {
2856
                const size_t nSamples =
2857
                    static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2✔
2858
                    poSrcDS->GetRasterYSize() * nChannels;
2✔
2859
                const int nMaxVal = (1 << nBits) - 1;
2✔
2860
                const int nMavValHalf = nMaxVal / 2;
2✔
2861
                if (eDT == GDT_Byte)
2✔
2862
                {
2863
                    uint8_t *panData = static_cast<uint8_t *>(pBuffer);
1✔
2864
                    for (size_t i = 0; i < nSamples; ++i)
401✔
2865
                    {
2866
                        panData[i] = static_cast<GByte>(
400✔
2867
                            (std::min(static_cast<int>(panData[i]), nMaxVal) *
400✔
2868
                                 255 +
400✔
2869
                             nMavValHalf) /
400✔
2870
                            nMaxVal);
400✔
2871
                    }
2872
                }
2873
                else if (eDT == GDT_UInt16)
1✔
2874
                {
2875
                    uint16_t *panData = static_cast<uint16_t *>(pBuffer);
1✔
2876
                    for (size_t i = 0; i < nSamples; ++i)
401✔
2877
                    {
2878
                        panData[i] = static_cast<uint16_t>(
400✔
2879
                            (std::min(static_cast<int>(panData[i]), nMaxVal) *
400✔
2880
                                 65535 +
400✔
2881
                             nMavValHalf) /
400✔
2882
                            nMaxVal);
400✔
2883
                    }
2884
                }
2885
            }
2886
        };
99✔
2887

2888
        Rescale(abyInputData.data(), nBaseChannels);
61✔
2889

2890
        if (JxlEncoderAddImageFrame(opts, &format, abyInputData.data(),
61✔
2891
                                    abyInputData.size()) != JXL_ENC_SUCCESS)
61✔
2892
        {
2893
            CPLError(CE_Failure, CPLE_AppDefined,
×
2894
                     "JxlEncoderAddImageFrame() failed");
2895
            return nullptr;
×
2896
        }
2897

2898
#ifdef HAVE_JxlEncoderInitExtraChannelInfo
2899
        format.num_channels = 1;
61✔
2900
        for (int i = nBaseChannels; i < poSrcDS->GetRasterCount(); ++i)
99✔
2901
        {
2902
            if (poSrcDS->GetRasterBand(i + 1)->RasterIO(
76✔
2903
                    GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2904
                    poSrcDS->GetRasterYSize(), abyInputData.data(),
38✔
2905
                    poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2906
                    0, 0, nullptr) != CE_None)
38✔
2907
            {
2908
                return nullptr;
×
2909
            }
2910

2911
            Rescale(abyInputData.data(), 1);
38✔
2912

2913
            if (JxlEncoderSetExtraChannelBuffer(
38✔
2914
                    opts, &format, abyInputData.data(),
38✔
2915
                    static_cast<size_t>(poSrcDS->GetRasterXSize()) *
114✔
2916
                        poSrcDS->GetRasterYSize() * nDataSize,
38✔
2917
                    i - nBaseChannels + (bHasInterleavedAlphaBand ? 1 : 0)) !=
76✔
2918
                JXL_ENC_SUCCESS)
2919
            {
2920
                CPLError(CE_Failure, CPLE_AppDefined,
×
2921
                         "JxlEncoderSetExtraChannelBuffer() failed");
2922
                return nullptr;
×
2923
            }
2924
        }
2925
#endif
2926
    }
2927

2928
    JxlEncoderCloseInput(encoder.get());
65✔
2929

2930
    // Flush to file
2931
    std::vector<GByte> abyOutputBuffer(4096 * 10);
195✔
2932
    while (true)
2933
    {
2934
        size_t len = abyOutputBuffer.size();
65✔
2935
        uint8_t *buf = abyOutputBuffer.data();
65✔
2936
        JxlEncoderStatus process_result =
2937
            JxlEncoderProcessOutput(encoder.get(), &buf, &len);
65✔
2938
        if (process_result == JXL_ENC_ERROR)
65✔
2939
        {
2940
            CPLError(CE_Failure, CPLE_AppDefined,
×
2941
                     "JxlEncoderProcessOutput() failed");
2942
            return nullptr;
10✔
2943
        }
2944
        size_t nToWrite = abyOutputBuffer.size() - len;
65✔
2945
        if (VSIFWriteL(abyOutputBuffer.data(), 1, nToWrite, fp.get()) !=
65✔
2946
            nToWrite)
2947
        {
2948
            CPLError(CE_Failure, CPLE_FileIO, "VSIFWriteL() failed");
10✔
2949
            return nullptr;
10✔
2950
        }
2951
        if (process_result != JXL_ENC_NEED_MORE_OUTPUT)
55✔
2952
            break;
55✔
2953
    }
×
2954

2955
    fp.reset();
55✔
2956

2957
    if (pfnProgress)
55✔
2958
        pfnProgress(1.0, "", pProgressData);
55✔
2959

2960
    // Re-open file and clone missing info to PAM
2961
    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
55✔
2962
    auto poDS = OpenStaticPAM(&oOpenInfo);
55✔
2963
    if (poDS)
55✔
2964
    {
2965
        // Do not create a .aux.xml file just for AREA_OR_POINT=Area
2966
        const char *pszAreaOfPoint =
2967
            poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
55✔
2968
        if (pszAreaOfPoint && EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
55✔
2969
        {
2970
            poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_AREA);
8✔
2971
            poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
8✔
2972
        }
2973
#ifdef HAVE_JXL_BOX_API
2974
        // When copying from JPEG, expose the EXIF metadata in the main domain,
2975
        // so that PAM doesn't copy it.
2976
        if (bEXIFFromMainDomain)
55✔
2977
        {
2978
            for (CSLConstList papszIter = papszEXIF; papszIter && *papszIter;
51✔
2979
                 ++papszIter)
2980
            {
2981
                if (STARTS_WITH(*papszIter, "EXIF_"))
48✔
2982
                {
2983
                    char *pszKey = nullptr;
48✔
2984
                    const char *pszValue =
2985
                        CPLParseNameValue(*papszIter, &pszKey);
48✔
2986
                    if (pszKey && pszValue)
48✔
2987
                    {
2988
                        poDS->SetMetadataItem(pszKey, pszValue);
48✔
2989
                    }
2990
                    CPLFree(pszKey);
48✔
2991
                }
2992
            }
2993
            poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
3✔
2994
        }
2995
#endif
2996
        poDS->CloneInfo(poSrcDS, nPamMask);
55✔
2997
    }
2998

2999
    return poDS;
55✔
3000
}
3001

3002
/************************************************************************/
3003
/*                        GDALRegister_JPEGXL()                         */
3004
/************************************************************************/
3005

3006
void GDALRegister_JPEGXL()
12✔
3007

3008
{
3009
    if (GDALGetDriverByName("JPEGXL") != nullptr)
12✔
3010
        return;
×
3011

3012
    GDALDriver *poDriver = new GDALDriver();
12✔
3013

3014
    JPEGXLDriverSetCommonMetadata(poDriver);
12✔
3015
    poDriver->pfnOpen = JPEGXLDataset::OpenStatic;
12✔
3016
    poDriver->pfnIdentify = JPEGXLDataset::Identify;
12✔
3017
    poDriver->pfnCreateCopy = JPEGXLDataset::CreateCopy;
12✔
3018

3019
    GetGDALDriverManager()->RegisterDriver(poDriver);
12✔
3020
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc