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