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