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

OSGeo / gdal / 13836648005

13 Mar 2025 02:09PM UTC coverage: 70.436% (-0.01%) from 70.446%
13836648005

push

github

web-flow
New Transform type: Homography (#11949)

Add new transform type, Homography.
Add functions to compute homography from a list of GCPs.
Add functions to serialize and deserialize a homography
Automatically select homography transfrom when there are 4 or 5 GCPs present.

Fixes #11940

231 of 274 new or added lines in 2 files covered. (84.31%)

16257 existing lines in 42 files now uncovered.

553736 of 786159 relevant lines covered (70.44%)

221595.72 hits per line

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

78.19
/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
}
674✔
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)
35,169✔
204
{
205
    if (poOpenInfo->fpL == nullptr)
35,169✔
206
        return false;
32,931✔
207

208
    if (poOpenInfo->IsExtensionEqualToCI("jxl"))
2,238✔
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,059✔
216
        poOpenInfo->pabyHeader[1] == 0x0a)
574✔
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,684✔
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
        {
UNCOV
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 < GDALGetDataTypeSize(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
    double adfGeoTransform[6];
1762
    const bool bHasGeoTransform =
1763
        poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None;
89✔
1764
    const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
89✔
1765
    const int nGCPCount = poSrcDS->GetGCPCount();
89✔
1766
    char **papszRPCMD = poSrcDS->GetMetadata("RPC");
89✔
1767
    std::unique_ptr<GDALJP2Box> poJUMBFBox;
89✔
1768
    if (bWriteGeoJP2 &&
89✔
1769
        (poSRS != nullptr || bHasGeoTransform || nGCPCount || papszRPCMD))
50✔
1770
    {
1771
        GDALJP2Metadata oJP2Metadata;
78✔
1772
        if (poSRS)
39✔
1773
            oJP2Metadata.SetSpatialRef(poSRS);
39✔
1774
        if (bHasGeoTransform)
39✔
1775
            oJP2Metadata.SetGeoTransform(adfGeoTransform);
39✔
1776
        if (nGCPCount)
39✔
1777
        {
1778
            const OGRSpatialReference *poSRSGCP = poSrcDS->GetGCPSpatialRef();
×
1779
            if (poSRSGCP)
×
1780
                oJP2Metadata.SetSpatialRef(poSRSGCP);
×
1781
            oJP2Metadata.SetGCPs(nGCPCount, poSrcDS->GetGCPs());
×
1782
        }
1783
        if (papszRPCMD)
39✔
1784
            oJP2Metadata.SetRPCMD(papszRPCMD);
×
1785

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2680
    int nPamMask = GCIF_PAM_DEFAULT;
67✔
2681

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2956
    fp.reset();
55✔
2957

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

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

3000
    return poDS;
55✔
3001
}
3002

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

3007
void GDALRegister_JPEGXL()
11✔
3008

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

3013
    GDALDriver *poDriver = new GDALDriver();
11✔
3014

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

3020
    GetGDALDriverManager()->RegisterDriver(poDriver);
11✔
3021
}
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