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() override;
85 :
86 : char **GetMetadataDomainList() override;
87 : char **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 681 : JPEGXLDataset::~JPEGXLDataset()
136 : {
137 340 : JPEGXLDataset::Close();
138 680 : }
139 :
140 : /************************************************************************/
141 : /* Close() */
142 : /************************************************************************/
143 :
144 501 : CPLErr JPEGXLDataset::Close()
145 : {
146 501 : CPLErr eErr = CE_None;
147 :
148 501 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
149 : {
150 340 : eErr = JPEGXLDataset::FlushCache(true);
151 :
152 340 : if (m_fp != nullptr && VSIFCloseL(m_fp) != 0)
153 0 : eErr = CE_Failure;
154 340 : m_fp = nullptr;
155 :
156 340 : eErr = GDAL::Combine(eErr, GDALPamDataset::Close());
157 : }
158 502 : return eErr;
159 : }
160 :
161 : /************************************************************************/
162 : /* JPEGXLRasterBand() */
163 : /************************************************************************/
164 :
165 835 : JPEGXLRasterBand::JPEGXLRasterBand(JPEGXLDataset *poDSIn, int nBandIn,
166 : GDALDataType eDataTypeIn, int nBitsPerSample,
167 835 : GDALColorInterp eInterp)
168 : {
169 835 : poDS = poDSIn;
170 835 : nBand = nBandIn;
171 835 : eDataType = eDataTypeIn;
172 835 : nRasterXSize = poDS->GetRasterXSize();
173 836 : nRasterYSize = poDS->GetRasterYSize();
174 836 : nBlockXSize = poDS->GetRasterXSize();
175 836 : nBlockYSize = 1;
176 836 : SetColorInterpretation(eInterp);
177 839 : if ((eDataType == GDT_Byte && nBitsPerSample < 8) ||
178 837 : (eDataType == GDT_UInt16 && nBitsPerSample < 16))
179 : {
180 4 : SetMetadataItem("NBITS", CPLSPrintf("%d", nBitsPerSample),
181 : "IMAGE_STRUCTURE");
182 : }
183 839 : }
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 45041 : int JPEGXLDataset::Identify(GDALOpenInfo *poOpenInfo)
228 : {
229 45041 : if (poOpenInfo->fpL == nullptr)
230 42456 : return false;
231 :
232 2585 : 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 2403 : if (poOpenInfo->nHeaderBytes >= 2 && poOpenInfo->pabyHeader[0] == 0xff &&
240 578 : poOpenInfo->pabyHeader[1] == 0x0a)
241 : {
242 : // Two bytes is not enough to reliably identify, so let's try to decode
243 : // basic info
244 745 : auto decoder = JxlDecoderMake(nullptr);
245 371 : if (!decoder)
246 0 : return false;
247 : JxlDecoderStatus status =
248 371 : JxlDecoderSubscribeEvents(decoder.get(), JXL_DEC_BASIC_INFO);
249 371 : if (status != JXL_DEC_SUCCESS)
250 : {
251 0 : return false;
252 : }
253 :
254 373 : status = JxlDecoderSetInput(decoder.get(), poOpenInfo->pabyHeader,
255 371 : poOpenInfo->nHeaderBytes);
256 372 : if (status != JXL_DEC_SUCCESS)
257 : {
258 0 : return false;
259 : }
260 :
261 372 : status = JxlDecoderProcessInput(decoder.get());
262 372 : if (status != JXL_DEC_BASIC_INFO)
263 : {
264 0 : return false;
265 : }
266 :
267 372 : return true;
268 : }
269 :
270 2030 : return IsJPEGXLContainer(poOpenInfo);
271 : }
272 :
273 : /************************************************************************/
274 : /* Open() */
275 : /************************************************************************/
276 :
277 338 : bool JPEGXLDataset::Open(GDALOpenInfo *poOpenInfo)
278 : {
279 338 : m_decoder = JxlDecoderMake(nullptr);
280 341 : 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 341 : 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 341 : if (JxlDecoderSetParallelRunner(m_decoder.get(), JxlResizableParallelRunner,
296 340 : 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 340 : 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 340 : 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 340 : memset(&info, 0, sizeof(info));
319 340 : bool bGotInfo = false;
320 :
321 : // Steal file handle
322 340 : m_fp = poOpenInfo->fpL;
323 340 : poOpenInfo->fpL = nullptr;
324 340 : 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 676 : std::vector<GByte> abyBoxBuffer(1024 * 1024);
331 678 : std::string osCurrentBox;
332 680 : 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 int 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 : int nExifOffset = 0;
375 18 : int nInterOffset = 0;
376 18 : int 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 341 : const uint64_t nMaxBoxBufferSize = std::strtoull(
414 : CPLGetConfigOption("GDAL_JPEGXL_MAX_BOX_BUFFER_SIZE", "100000000"),
415 : nullptr, 10);
416 : #endif
417 :
418 341 : int l_nBands = 0;
419 341 : GDALDataType eDT = GDT_Unknown;
420 :
421 : while (true)
422 : {
423 1884 : status = JxlDecoderProcessInput(m_decoder.get());
424 :
425 : #ifdef HAVE_JXL_BOX_API
426 2741 : 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 1886 : if (status == JXL_DEC_SUCCESS)
443 : {
444 339 : break;
445 : }
446 1547 : else if (status == JXL_DEC_NEED_MORE_INPUT)
447 : {
448 341 : JxlDecoderReleaseInput(m_decoder.get());
449 :
450 341 : const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
451 : m_abyInputData.size(), m_fp);
452 340 : if (nRead == 0)
453 : {
454 : #ifdef HAVE_JXL_BOX_API
455 0 : JxlDecoderCloseInput(m_decoder.get());
456 : #endif
457 0 : break;
458 : }
459 340 : if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
460 340 : 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 340 : if (nRead < m_abyInputData.size())
468 : {
469 341 : JxlDecoderCloseInput(m_decoder.get());
470 : }
471 : #endif
472 : }
473 1206 : else if (status == JXL_DEC_BASIC_INFO)
474 : {
475 339 : bGotInfo = true;
476 339 : status = JxlDecoderGetBasicInfo(m_decoder.get(), &info);
477 338 : if (status != JXL_DEC_SUCCESS)
478 : {
479 0 : CPLError(CE_Failure, CPLE_AppDefined,
480 : "JxlDecoderGetBasicInfo() failed");
481 0 : return false;
482 : }
483 :
484 338 : if (info.xsize > static_cast<uint32_t>(INT_MAX) ||
485 341 : 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 322 : if (info.bits_per_sample <= 8)
498 282 : eDT = GDT_Byte;
499 40 : else if (info.bits_per_sample <= 16)
500 41 : eDT = GDT_UInt16;
501 : }
502 17 : 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 15 : 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 867 : 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 350 : else if (status == JXL_DEC_COLOR_ENCODING)
606 : {
607 : #ifdef HAVE_JxlDecoderDefaultPixelFormat
608 : JxlPixelFormat format = {
609 : static_cast<uint32_t>(nBands),
610 : eDT == GDT_Byte ? 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 341 : 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 341 : if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile(
623 341 : 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 339 : JxlColorEncodingSetToSRGB(&default_color_encoding,
632 339 : info.num_color_channels ==
633 : 1 /*is_gray*/);
634 :
635 339 : bIsDefaultColorEncoding =
636 339 : color_encoding.color_space ==
637 678 : default_color_encoding.color_space &&
638 339 : color_encoding.white_point ==
639 339 : default_color_encoding.white_point &&
640 339 : color_encoding.white_point_xy[0] ==
641 339 : default_color_encoding.white_point_xy[0] &&
642 339 : color_encoding.white_point_xy[1] ==
643 339 : default_color_encoding.white_point_xy[1] &&
644 339 : (color_encoding.color_space == JXL_COLOR_SPACE_GRAY ||
645 159 : color_encoding.color_space == JXL_COLOR_SPACE_XYB ||
646 159 : (color_encoding.primaries ==
647 159 : default_color_encoding.primaries &&
648 159 : color_encoding.primaries_red_xy[0] ==
649 159 : default_color_encoding.primaries_red_xy[0] &&
650 159 : color_encoding.primaries_red_xy[1] ==
651 159 : default_color_encoding.primaries_red_xy[1] &&
652 159 : color_encoding.primaries_green_xy[0] ==
653 159 : default_color_encoding.primaries_green_xy[0] &&
654 159 : color_encoding.primaries_green_xy[1] ==
655 159 : default_color_encoding.primaries_green_xy[1] &&
656 159 : color_encoding.primaries_blue_xy[0] ==
657 159 : default_color_encoding.primaries_blue_xy[0] &&
658 159 : color_encoding.primaries_blue_xy[1] ==
659 159 : default_color_encoding.primaries_blue_xy[1])) &&
660 339 : color_encoding.transfer_function ==
661 339 : default_color_encoding.transfer_function &&
662 1015 : color_encoding.gamma == default_color_encoding.gamma &&
663 337 : color_encoding.rendering_intent ==
664 337 : default_color_encoding.rendering_intent;
665 : }
666 :
667 341 : 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 9 : 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 2 : CPLError(CE_Warning, CPLE_AppDefined, "Unexpected event: %d",
757 : status);
758 0 : break;
759 : }
760 1543 : }
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 340 : 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 296 : LoadJP2Metadata(poOpenInfo, nullptr, fpDummy);
789 299 : 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 314 : && !m_bHasJPEGReconstructionData
802 : #endif
803 : ? "LOSSLESS (possibly)"
804 : : "LOSSY",
805 : "IMAGE_STRUCTURE");
806 : #ifdef HAVE_JXL_BOX_API
807 338 : 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 338 : CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
817 682 : uint32_t nMaxThreads = static_cast<uint32_t>(
818 341 : EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
819 0 : : atoi(pszNumThreads));
820 341 : if (nMaxThreads > 1024)
821 0 : nMaxThreads = 1024; // to please Coverity
822 :
823 : const uint32_t nThreads = std::min(
824 : nMaxThreads,
825 341 : JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
826 339 : CPLDebug("JPEGXL", "Using %u threads", nThreads);
827 341 : JxlResizableParallelRunnerSetThreads(m_parallelRunner.get(), nThreads);
828 : #endif
829 :
830 : // Instantiate bands
831 338 : const int nNonExtraBands = l_nBands - m_nNonAlphaExtraChannels;
832 1172 : for (int i = 1; i <= l_nBands; i++)
833 : {
834 831 : GDALColorInterp eInterp = GCI_Undefined;
835 831 : 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 553 : else if (info.num_color_channels == 3)
844 : {
845 553 : if (i <= 3)
846 475 : 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 1664 : std::string osBandName;
852 :
853 831 : 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 831 : this, i, eDT, static_cast<int>(info.bits_per_sample), eInterp);
914 839 : SetBand(i, poBand);
915 833 : if (!osBandName.empty())
916 2 : poBand->SetDescription(osBandName.c_str());
917 : }
918 :
919 341 : if (l_nBands > 1)
920 : {
921 205 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
922 : }
923 :
924 : // Initialize any PAM information.
925 339 : SetDescription(poOpenInfo->pszFilename);
926 338 : TryLoadXML(poOpenInfo->GetSiblingFiles());
927 676 : oOvManager.Initialize(this, poOpenInfo->pszFilename,
928 336 : 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 : char **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 255 : void JPEGXLDataset::GetDecodedImage(void *pabyOutputData,
1375 : size_t nOutputDataSize)
1376 : {
1377 255 : JxlDecoderRewind(m_decoder.get());
1378 255 : VSIFSeekL(m_fp, 0, SEEK_SET);
1379 :
1380 255 : 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 255 : const auto eDT = GetRasterBand(1)->GetRasterDataType();
1389 : while (true)
1390 : {
1391 1019 : const JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get());
1392 1018 : 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 1018 : else if (status == JXL_DEC_NEED_MORE_INPUT)
1399 : {
1400 255 : JxlDecoderReleaseInput(m_decoder.get());
1401 :
1402 255 : const size_t nRead = VSIFReadL(m_abyInputData.data(), 1,
1403 : m_abyInputData.size(), m_fp);
1404 255 : 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 255 : if (JxlDecoderSetInput(m_decoder.get(), m_abyInputData.data(),
1412 255 : 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 763 : else if (status == JXL_DEC_SUCCESS)
1421 : {
1422 254 : break;
1423 : }
1424 509 : else if (status == JXL_DEC_FULL_IMAGE)
1425 : {
1426 : // ok
1427 : }
1428 255 : else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER)
1429 : {
1430 255 : JxlPixelFormat format = {
1431 255 : static_cast<uint32_t>(nBands - m_nNonAlphaExtraChannels),
1432 255 : eDT == GDT_Byte ? JXL_TYPE_UINT8
1433 40 : : eDT == GDT_UInt16 ? JXL_TYPE_UINT16
1434 : : JXL_TYPE_FLOAT,
1435 : JXL_NATIVE_ENDIAN, 0 /* alignment */
1436 255 : };
1437 :
1438 : size_t buffer_size;
1439 255 : if (JxlDecoderImageOutBufferSize(m_decoder.get(), &format,
1440 255 : &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 255 : 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 255 : if (JxlDecoderSetImageOutBuffer(m_decoder.get(), &format,
1461 : pabyOutputData,
1462 255 : 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 255 : format.num_channels = 1;
1471 331 : 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 255 : 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 764 : }
1511 :
1512 : // Rescale from 8-bits/16-bits
1513 254 : 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_Byte)
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 187 : 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 186 : const auto AreSequentialBands = [](const int *panItems, int nItems)
1564 : {
1565 545 : for (int i = 0; i < nItems; i++)
1566 : {
1567 364 : if (panItems[i] != i + 1)
1568 5 : return false;
1569 : }
1570 181 : return true;
1571 : };
1572 :
1573 187 : if (eRWFlag == GF_Read && nXOff == 0 && nYOff == 0 &&
1574 187 : nXSize == nRasterXSize && nYSize == nRasterYSize &&
1575 186 : nBufXSize == nXSize && nBufYSize == nYSize)
1576 : {
1577 : // Get the full image in a pixel-interleaved way
1578 186 : if (m_bDecodingFailed)
1579 0 : return CE_Failure;
1580 :
1581 186 : CPLDebug("JPEGXL", "Using optimized IRasterIO() code path");
1582 :
1583 186 : const auto nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
1584 186 : const bool bIsPixelInterleaveBuffer =
1585 13 : ((nBandSpace == 0 && nBandCount == 1) ||
1586 173 : nBandSpace == nBufTypeSize) &&
1587 549 : nPixelSpace == static_cast<GSpacing>(nBufTypeSize) * nBandCount &&
1588 177 : nLineSpace == nPixelSpace * nRasterXSize;
1589 :
1590 186 : const auto eNativeDT = GetRasterBand(1)->GetRasterDataType();
1591 186 : const auto nNativeDataSize = GDALGetDataTypeSizeBytes(eNativeDT);
1592 : const bool bIsBandSequential =
1593 186 : AreSequentialBands(panBandMap, nBandCount);
1594 186 : if (eBufType == eNativeDT && bIsBandSequential &&
1595 176 : nBandCount == nBands && m_nNonAlphaExtraChannels == 0 &&
1596 : bIsPixelInterleaveBuffer)
1597 : {
1598 : // We can directly use the user output buffer
1599 169 : GetDecodedImage(pData, static_cast<size_t>(nRasterXSize) *
1600 169 : nRasterYSize * nBands * nNativeDataSize);
1601 168 : 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 341 : GDALPamDataset *JPEGXLDataset::OpenStaticPAM(GDALOpenInfo *poOpenInfo)
1698 : {
1699 341 : if (!Identify(poOpenInfo))
1700 0 : return nullptr;
1701 :
1702 677 : auto poDS = std::make_unique<JPEGXLDataset>();
1703 338 : if (!poDS->Open(poOpenInfo))
1704 0 : return nullptr;
1705 :
1706 336 : return poDS.release();
1707 : }
1708 :
1709 : /************************************************************************/
1710 : /* OpenStatic() */
1711 : /************************************************************************/
1712 :
1713 282 : GDALDataset *JPEGXLDataset::OpenStatic(GDALOpenInfo *poOpenInfo)
1714 : {
1715 282 : GDALDataset *poDS = OpenStaticPAM(poOpenInfo);
1716 :
1717 : #ifdef HAVE_JXL_BOX_API
1718 554 : if (poDS &&
1719 275 : 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 277 : 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 : char **papszEXIF = poSrcDS->GetMetadata("EXIF");
1764 91 : bool bEXIFFromMainDomain = false;
1765 91 : if (papszEXIF == nullptr && bWriteExifMetadata)
1766 : {
1767 90 : char **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 : char **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 : char **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 0 : abyData.insert(abyData.begin() + nInsertPos + 8,
1973 : reinterpret_cast<GByte *>(papszXMP[0]),
1974 0 : reinterpret_cast<GByte *>(papszXMP[0]) +
1975 0 : nXMPLen);
1976 0 : nInsertPos += 8 + nXMPLen;
1977 : }
1978 : else
1979 : {
1980 : // shouldn't happen
1981 0 : CPLDebug("JPEGX", "Cannot add XMP box to codestream");
1982 0 : bFallbackToGeneral = true;
1983 : }
1984 : }
1985 :
1986 : // Write GeoJP2 box in a JUMBF box from georeferencing information
1987 2 : if (poJUMBFBox)
1988 : {
1989 2 : if (nInsertPos)
1990 : {
1991 : const size_t nDataLen =
1992 2 : static_cast<size_t>(poJUMBFBox->GetBoxLength());
1993 2 : uint32_t nBoxSize = static_cast<uint32_t>(8 + nDataLen);
1994 2 : CPL_MSBPTR32(&nBoxSize);
1995 2 : memcpy(abySizeAndBoxName, &nBoxSize, 4);
1996 2 : memcpy(abySizeAndBoxName + 4, "jumb", 4);
1997 : abyData.insert(
1998 2 : abyData.begin() + nInsertPos, abySizeAndBoxName,
1999 4 : abySizeAndBoxName + sizeof(abySizeAndBoxName));
2000 2 : GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
2001 2 : abyData.insert(abyData.begin() + nInsertPos + 8,
2002 4 : pabyBoxData, pabyBoxData + nDataLen);
2003 2 : VSIFree(pabyBoxData);
2004 2 : nInsertPos += 8 + nDataLen;
2005 : }
2006 : else
2007 : {
2008 : // shouldn't happen
2009 0 : CPLDebug("JPEGX",
2010 : "Cannot add JUMBF GeoJP2 box to codestream");
2011 0 : bFallbackToGeneral = true;
2012 : }
2013 : }
2014 :
2015 2 : CPL_IGNORE_RET_VAL(nInsertPos);
2016 : }
2017 0 : catch (const std::exception &)
2018 : {
2019 0 : abyData.clear();
2020 : }
2021 2 : VSIFree(pJPEGXLContent);
2022 :
2023 2 : if (!bFallbackToGeneral && !abyData.empty())
2024 : {
2025 2 : VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
2026 2 : if (fpImage == nullptr)
2027 : {
2028 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2029 : "Unable to create jpeg file %s.", pszFilename);
2030 :
2031 0 : return nullptr;
2032 : }
2033 4 : if (VSIFWriteL(abyData.data(), 1, abyData.size(), fpImage) !=
2034 2 : abyData.size())
2035 : {
2036 0 : CPLError(CE_Failure, CPLE_FileIO,
2037 0 : "Failure writing data: %s", VSIStrerror(errno));
2038 0 : VSIFCloseL(fpImage);
2039 0 : return nullptr;
2040 : }
2041 2 : if (VSIFCloseL(fpImage) != 0)
2042 : {
2043 0 : CPLError(CE_Failure, CPLE_FileIO,
2044 0 : "Failure writing data: %s", VSIStrerror(errno));
2045 0 : return nullptr;
2046 : }
2047 :
2048 2 : pfnProgress(1.0, nullptr, pProgressData);
2049 :
2050 : // Re-open file and clone missing info to PAM
2051 2 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
2052 2 : auto poDS = OpenStaticPAM(&oOpenInfo);
2053 2 : if (poDS)
2054 : {
2055 : // Do not create a .aux.xml file just for AREA_OR_POINT=Area
2056 : const char *pszAreaOfPoint =
2057 2 : poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
2058 2 : if (pszAreaOfPoint &&
2059 1 : EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
2060 : {
2061 1 : poDS->SetMetadataItem(GDALMD_AREA_OR_POINT,
2062 1 : GDALMD_AOP_AREA);
2063 1 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
2064 : }
2065 :
2066 : // When copying from JPEG, expose the EXIF metadata in the main domain,
2067 : // so that PAM doesn't copy it.
2068 2 : if (bEXIFFromMainDomain)
2069 : {
2070 0 : for (CSLConstList papszIter = papszEXIF;
2071 0 : papszIter && *papszIter; ++papszIter)
2072 : {
2073 0 : if (STARTS_WITH(*papszIter, "EXIF_"))
2074 : {
2075 0 : char *pszKey = nullptr;
2076 : const char *pszValue =
2077 0 : CPLParseNameValue(*papszIter, &pszKey);
2078 0 : if (pszKey && pszValue)
2079 : {
2080 0 : poDS->SetMetadataItem(pszKey, pszValue);
2081 : }
2082 0 : CPLFree(pszKey);
2083 : }
2084 : }
2085 0 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
2086 : }
2087 :
2088 2 : poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
2089 : }
2090 :
2091 2 : return poDS;
2092 : }
2093 : }
2094 : }
2095 :
2096 89 : JxlPixelFormat format = {0, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
2097 89 : const auto eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
2098 89 : switch (eDT)
2099 : {
2100 65 : case GDT_Byte:
2101 65 : format.data_type = JXL_TYPE_UINT8;
2102 65 : break;
2103 13 : case GDT_UInt16:
2104 13 : format.data_type = JXL_TYPE_UINT16;
2105 13 : break;
2106 2 : case GDT_Float32:
2107 2 : format.data_type = JXL_TYPE_FLOAT;
2108 2 : break;
2109 9 : default:
2110 9 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type");
2111 9 : return nullptr;
2112 : }
2113 :
2114 80 : const char *pszLossLess = CSLFetchNameValue(papszOptions, "LOSSLESS");
2115 :
2116 73 : const bool bLossless = (pszLossLess == nullptr && pszDistance == nullptr &&
2117 160 : pszQuality == nullptr) ||
2118 7 : (pszLossLess != nullptr && CPLTestBool(pszLossLess));
2119 80 : if (pszLossLess == nullptr &&
2120 67 : (pszDistance != nullptr || pszQuality != nullptr))
2121 : {
2122 8 : CPLDebug("JPEGXL", "Using lossy mode");
2123 : }
2124 80 : if ((pszLossLess != nullptr && bLossless) && pszDistance != nullptr)
2125 : {
2126 1 : CPLError(CE_Failure, CPLE_NotSupported,
2127 : "DISTANCE and LOSSLESS=YES are mutually exclusive");
2128 1 : return nullptr;
2129 : }
2130 79 : if ((pszLossLess != nullptr && bLossless) && pszAlphaDistance != nullptr)
2131 : {
2132 1 : CPLError(CE_Failure, CPLE_NotSupported,
2133 : "ALPHA_DISTANCE and LOSSLESS=YES are mutually exclusive");
2134 1 : return nullptr;
2135 : }
2136 78 : if ((pszLossLess != nullptr && bLossless) && pszQuality != nullptr)
2137 : {
2138 1 : CPLError(CE_Failure, CPLE_NotSupported,
2139 : "QUALITY and LOSSLESS=YES are mutually exclusive");
2140 1 : return nullptr;
2141 : }
2142 77 : if (pszDistance != nullptr && pszQuality != nullptr)
2143 : {
2144 1 : CPLError(CE_Failure, CPLE_NotSupported,
2145 : "QUALITY and DISTANCE are mutually exclusive");
2146 1 : return nullptr;
2147 : }
2148 :
2149 76 : float fDistance = 0.0f;
2150 76 : float fAlphaDistance = -1.0;
2151 76 : if (!bLossless)
2152 : {
2153 10 : fDistance =
2154 10 : pszDistance ? static_cast<float>(CPLAtof(pszDistance)) : 1.0f;
2155 10 : if (pszQuality != nullptr)
2156 : {
2157 2 : const double quality = CPLAtof(pszQuality);
2158 : // Quality settings roughly match libjpeg qualities.
2159 : // Formulas taken from cjxl.cc
2160 2 : if (quality >= 100)
2161 : {
2162 1 : fDistance = 0;
2163 : }
2164 1 : else if (quality >= 30)
2165 : {
2166 0 : fDistance = static_cast<float>(0.1 + (100 - quality) * 0.09);
2167 : }
2168 : else
2169 : {
2170 1 : fDistance =
2171 1 : static_cast<float>(53.0 / 3000.0 * quality * quality -
2172 1 : 23.0 / 20.0 * quality + 25.0);
2173 : }
2174 : }
2175 10 : if (fDistance >= 0.0f && fDistance < MIN_DISTANCE)
2176 2 : fDistance = MIN_DISTANCE;
2177 :
2178 10 : if (pszAlphaDistance)
2179 : {
2180 1 : fAlphaDistance = static_cast<float>(CPLAtof(pszAlphaDistance));
2181 1 : if (fAlphaDistance > 0.0f && fAlphaDistance < MIN_DISTANCE)
2182 0 : fAlphaDistance = MIN_DISTANCE;
2183 : }
2184 : }
2185 :
2186 76 : const bool bAlphaDistanceSameAsMainChannel =
2187 77 : (fAlphaDistance < 0.0f) ||
2188 0 : ((bLossless && fAlphaDistance == 0.0f) ||
2189 1 : (!bLossless && fAlphaDistance == fDistance));
2190 : #ifndef HAVE_JxlEncoderSetExtraChannelDistance
2191 : if (!bAlphaDistanceSameAsMainChannel)
2192 : {
2193 : CPLError(CE_Warning, CPLE_NotSupported,
2194 : "ALPHA_DISTANCE ignored due to "
2195 : "JxlEncoderSetExtraChannelDistance() not being "
2196 : "available. Please upgrade libjxl to > 0.8.1");
2197 : }
2198 : #endif
2199 :
2200 152 : auto encoder = JxlEncoderMake(nullptr);
2201 76 : if (!encoder)
2202 : {
2203 0 : CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderMake() failed");
2204 0 : return nullptr;
2205 : }
2206 :
2207 76 : const char *pszNBits = CSLFetchNameValue(papszOptions, "NBITS");
2208 76 : if (pszNBits == nullptr)
2209 148 : pszNBits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
2210 74 : "NBITS", "IMAGE_STRUCTURE");
2211 : const int nBits =
2212 76 : ((eDT == GDT_Byte || eDT == GDT_UInt16) && pszNBits != nullptr)
2213 78 : ? atoi(pszNBits)
2214 74 : : GDALGetDataTypeSizeBits(eDT);
2215 :
2216 : JxlBasicInfo basic_info;
2217 76 : JxlEncoderInitBasicInfo(&basic_info);
2218 76 : basic_info.xsize = poSrcDS->GetRasterXSize();
2219 76 : basic_info.ysize = poSrcDS->GetRasterYSize();
2220 76 : basic_info.bits_per_sample = nBits;
2221 76 : basic_info.orientation = JXL_ORIENT_IDENTITY;
2222 76 : if (format.data_type == JXL_TYPE_FLOAT)
2223 : {
2224 2 : basic_info.exponent_bits_per_sample = 8;
2225 : }
2226 :
2227 76 : const int nSrcBands = poSrcDS->GetRasterCount();
2228 :
2229 76 : bool bHasInterleavedAlphaBand = false;
2230 76 : if (nSrcBands == 1)
2231 : {
2232 28 : basic_info.num_color_channels = 1;
2233 : }
2234 48 : else if (nSrcBands == 2)
2235 : {
2236 8 : basic_info.num_color_channels = 1;
2237 8 : basic_info.num_extra_channels = 1;
2238 8 : if (poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
2239 8 : GCI_AlphaBand &&
2240 : bAlphaDistanceSameAsMainChannel)
2241 : {
2242 5 : bHasInterleavedAlphaBand = true;
2243 5 : basic_info.alpha_bits = basic_info.bits_per_sample;
2244 5 : basic_info.alpha_exponent_bits =
2245 5 : basic_info.exponent_bits_per_sample;
2246 : }
2247 : }
2248 : else /* if( nSrcBands >= 3 ) */
2249 : {
2250 40 : if (poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
2251 35 : GCI_RedBand &&
2252 35 : poSrcDS->GetRasterBand(2)->GetColorInterpretation() ==
2253 75 : GCI_GreenBand &&
2254 35 : poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand)
2255 : {
2256 33 : basic_info.num_color_channels = 3;
2257 33 : basic_info.num_extra_channels = nSrcBands - 3;
2258 52 : if (nSrcBands >= 4 &&
2259 19 : poSrcDS->GetRasterBand(4)->GetColorInterpretation() ==
2260 52 : GCI_AlphaBand &&
2261 : bAlphaDistanceSameAsMainChannel)
2262 : {
2263 14 : bHasInterleavedAlphaBand = true;
2264 14 : basic_info.alpha_bits = basic_info.bits_per_sample;
2265 14 : basic_info.alpha_exponent_bits =
2266 14 : basic_info.exponent_bits_per_sample;
2267 : }
2268 : }
2269 : else
2270 : {
2271 7 : basic_info.num_color_channels = 1;
2272 7 : basic_info.num_extra_channels = nSrcBands - 1;
2273 : }
2274 : }
2275 :
2276 76 : const int nBaseChannels = static_cast<int>(
2277 76 : basic_info.num_color_channels + (bHasInterleavedAlphaBand ? 1 : 0));
2278 76 : format.num_channels = nBaseChannels;
2279 :
2280 : #ifndef HAVE_JxlEncoderInitExtraChannelInfo
2281 : if (basic_info.num_extra_channels != (bHasInterleavedAlphaBand ? 1 : 0))
2282 : {
2283 : CPLError(CE_Failure, CPLE_AppDefined,
2284 : "This version of libjxl does not support "
2285 : "creating non-alpha extra channels.");
2286 : return nullptr;
2287 : }
2288 : #endif
2289 :
2290 : #ifdef HAVE_JXL_THREADS
2291 152 : auto parallelRunner = JxlResizableParallelRunnerMake(nullptr);
2292 76 : if (!parallelRunner)
2293 : {
2294 0 : CPLError(CE_Failure, CPLE_AppDefined,
2295 : "JxlResizableParallelRunnerMake() failed");
2296 0 : return nullptr;
2297 : }
2298 :
2299 76 : const char *pszNumThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
2300 76 : if (pszNumThreads == nullptr)
2301 76 : pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
2302 152 : uint32_t nMaxThreads = static_cast<uint32_t>(
2303 76 : EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
2304 0 : : atoi(pszNumThreads));
2305 76 : if (nMaxThreads > 1024)
2306 0 : nMaxThreads = 1024; // to please Coverity
2307 :
2308 : const uint32_t nThreads =
2309 228 : std::min(nMaxThreads, JxlResizableParallelRunnerSuggestThreads(
2310 76 : basic_info.xsize, basic_info.ysize));
2311 76 : CPLDebug("JPEGXL", "Using %u threads", nThreads);
2312 76 : JxlResizableParallelRunnerSetThreads(parallelRunner.get(), nThreads);
2313 :
2314 76 : if (JxlEncoderSetParallelRunner(encoder.get(), JxlResizableParallelRunner,
2315 76 : parallelRunner.get()) != JXL_ENC_SUCCESS)
2316 : {
2317 0 : CPLError(CE_Failure, CPLE_AppDefined,
2318 : "JxlEncoderSetParallelRunner() failed");
2319 0 : return nullptr;
2320 : }
2321 : #endif
2322 :
2323 : #ifdef HAVE_JxlEncoderFrameSettingsCreate
2324 : JxlEncoderFrameSettings *opts =
2325 76 : JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
2326 : #else
2327 : JxlEncoderOptions *opts = JxlEncoderOptionsCreate(encoder.get(), nullptr);
2328 : #endif
2329 76 : if (opts == nullptr)
2330 : {
2331 0 : CPLError(CE_Failure, CPLE_AppDefined,
2332 : "JxlEncoderFrameSettingsCreate() failed");
2333 0 : return nullptr;
2334 : }
2335 :
2336 : #ifdef HAVE_JxlEncoderSetCodestreamLevel
2337 76 : if (poSrcDS->GetRasterXSize() > 262144 ||
2338 152 : poSrcDS->GetRasterYSize() > 262144 ||
2339 76 : poSrcDS->GetRasterXSize() > 268435456 / poSrcDS->GetRasterYSize())
2340 : {
2341 0 : JxlEncoderSetCodestreamLevel(encoder.get(), 10);
2342 : }
2343 : #endif
2344 :
2345 76 : if (bLossless)
2346 : {
2347 : #ifdef HAVE_JxlEncoderSetCodestreamLevel
2348 66 : if (nBits > 12)
2349 : {
2350 14 : JxlEncoderSetCodestreamLevel(encoder.get(), 10);
2351 : }
2352 : #endif
2353 :
2354 : #ifdef HAVE_JxlEncoderSetFrameLossless
2355 66 : JxlEncoderSetFrameLossless(opts, TRUE);
2356 : #else
2357 : JxlEncoderOptionsSetLossless(opts, TRUE);
2358 : #endif
2359 66 : basic_info.uses_original_profile = JXL_TRUE;
2360 : }
2361 : else
2362 : {
2363 : #ifdef HAVE_JxlEncoderSetFrameDistance
2364 10 : if (JxlEncoderSetFrameDistance(opts, fDistance) != JXL_ENC_SUCCESS)
2365 : #else
2366 : if (JxlEncoderOptionsSetDistance(opts, fDistance) != JXL_ENC_SUCCESS)
2367 : #endif
2368 : {
2369 1 : CPLError(CE_Failure, CPLE_AppDefined,
2370 : "JxlEncoderSetFrameDistance() failed");
2371 1 : return nullptr;
2372 : }
2373 : }
2374 :
2375 : #ifdef HAVE_JxlEncoderFrameSettingsSetOption
2376 75 : if (JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT,
2377 75 : nEffort) != JXL_ENC_SUCCESS)
2378 : #else
2379 : if (JxlEncoderOptionsSetEffort(opts, nEffort) != JXL_ENC_SUCCESS)
2380 : #endif
2381 : {
2382 1 : CPLError(CE_Failure, CPLE_AppDefined,
2383 : "JxlEncoderFrameSettingsSetOption() failed");
2384 1 : return nullptr;
2385 : }
2386 :
2387 148 : std::vector<GByte> abyJPEG;
2388 74 : void *pJPEGContent = nullptr;
2389 74 : size_t nJPEGContent = 0;
2390 74 : char *pszDetailedFormat = nullptr;
2391 : // If the source dataset is a JPEG file or compatible of it, try to
2392 : // losslessly add it
2393 146 : if ((EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy)) &&
2394 72 : poSrcDS->ReadCompressedData(
2395 : "JPEG", 0, 0, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(),
2396 : poSrcDS->GetRasterCount(), nullptr, &pJPEGContent, &nJPEGContent,
2397 72 : &pszDetailedFormat) == CE_None)
2398 : {
2399 5 : CPLAssert(pszDetailedFormat != nullptr);
2400 : const CPLStringList aosTokens(
2401 10 : CSLTokenizeString2(pszDetailedFormat, ";", 0));
2402 5 : VSIFree(pszDetailedFormat);
2403 5 : const char *pszBitDepth = aosTokens.FetchNameValueDef("bit_depth", "");
2404 5 : if (pJPEGContent && !EQUAL(pszBitDepth, "8"))
2405 : {
2406 0 : CPLDebug(
2407 : "JPEGXL",
2408 : "Unsupported bit_depth=%s for lossless transcoding from JPEG",
2409 : pszBitDepth);
2410 0 : VSIFree(pJPEGContent);
2411 0 : pJPEGContent = nullptr;
2412 : }
2413 : const char *pszColorspace =
2414 5 : aosTokens.FetchNameValueDef("colorspace", "");
2415 5 : if (pJPEGContent && !EQUAL(pszColorspace, "unknown") &&
2416 4 : !EQUAL(pszColorspace, "RGB") && !EQUAL(pszColorspace, "YCbCr"))
2417 : {
2418 0 : CPLDebug(
2419 : "JPEGXL",
2420 : "Unsupported colorspace=%s for lossless transcoding from JPEG",
2421 : pszColorspace);
2422 0 : VSIFree(pJPEGContent);
2423 0 : pJPEGContent = nullptr;
2424 : }
2425 5 : const char *pszSOF = aosTokens.FetchNameValueDef("frame_type", "");
2426 5 : if (pJPEGContent && !EQUAL(pszSOF, "SOF0_baseline") &&
2427 0 : !EQUAL(pszSOF, "SOF1_extended_sequential") &&
2428 0 : !EQUAL(pszSOF, "SOF2_progressive_huffman"))
2429 : {
2430 0 : CPLDebug(
2431 : "JPEGXL",
2432 : "Unsupported frame_type=%s for lossless transcoding from JPEG",
2433 : pszSOF);
2434 0 : VSIFree(pJPEGContent);
2435 0 : pJPEGContent = nullptr;
2436 : }
2437 : }
2438 74 : if (pJPEGContent)
2439 : {
2440 : try
2441 : {
2442 5 : abyJPEG.reserve(nJPEGContent);
2443 0 : abyJPEG.insert(abyJPEG.end(), static_cast<GByte *>(pJPEGContent),
2444 5 : static_cast<GByte *>(pJPEGContent) + nJPEGContent);
2445 5 : VSIFree(pJPEGContent);
2446 :
2447 10 : std::vector<GByte> abyJPEGMod;
2448 5 : abyJPEGMod.reserve(abyJPEG.size());
2449 :
2450 : // Append Start Of Image marker (0xff 0xd8)
2451 5 : abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin(),
2452 10 : abyJPEG.begin() + 2);
2453 :
2454 : // Rework JPEG data to remove APP (except APP0) and COM
2455 : // markers as it confuses libjxl, when trying to
2456 : // reconstruct a JPEG file
2457 5 : size_t i = 2;
2458 10 : while (i + 1 < abyJPEG.size())
2459 : {
2460 10 : if (abyJPEG[i] != 0xFF)
2461 : {
2462 : // Not a valid tag (shouldn't happen)
2463 0 : abyJPEGMod.clear();
2464 0 : break;
2465 : }
2466 :
2467 : // Stop when encountering a marker that is not a APP
2468 : // or COM marker
2469 10 : const bool bIsCOM = abyJPEG[i + 1] == 0xFE;
2470 10 : if ((abyJPEG[i + 1] & 0xF0) != 0xE0 && !bIsCOM)
2471 : {
2472 : // Append all markers until end
2473 5 : abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i,
2474 10 : abyJPEG.end());
2475 5 : break;
2476 : }
2477 5 : const bool bIsAPP0 = abyJPEG[i + 1] == 0xE0;
2478 :
2479 : // Skip marker ID
2480 5 : i += 2;
2481 : // Check we can read chunk length
2482 5 : if (i + 1 >= abyJPEG.size())
2483 : {
2484 : // Truncated JPEG file
2485 0 : abyJPEGMod.clear();
2486 0 : break;
2487 : }
2488 5 : const int nChunkLength = abyJPEG[i] * 256 + abyJPEG[i + 1];
2489 5 : if ((bIsCOM || bIsAPP0) && i + nChunkLength <= abyJPEG.size())
2490 : {
2491 : // Append COM or APP0 marker
2492 10 : abyJPEGMod.insert(abyJPEGMod.end(), abyJPEG.begin() + i - 2,
2493 15 : abyJPEG.begin() + i + nChunkLength);
2494 : }
2495 5 : i += nChunkLength;
2496 : }
2497 5 : abyJPEG = std::move(abyJPEGMod);
2498 : }
2499 0 : catch (const std::exception &)
2500 : {
2501 : }
2502 : }
2503 83 : if (abyJPEG.empty() && !bLossless &&
2504 9 : (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
2505 : {
2506 1 : CPLError(CE_Failure, CPLE_AppDefined,
2507 : "LOSSLESS_COPY=YES requested but not possible");
2508 1 : return nullptr;
2509 : }
2510 :
2511 : const char *pszICCProfile =
2512 73 : CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
2513 73 : if (pszICCProfile == nullptr)
2514 : {
2515 : pszICCProfile =
2516 73 : poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
2517 : }
2518 73 : if (pszICCProfile && pszICCProfile[0] != '\0')
2519 : {
2520 1 : basic_info.uses_original_profile = JXL_TRUE;
2521 : }
2522 :
2523 73 : if (abyJPEG.empty())
2524 : {
2525 68 : if (JXL_ENC_SUCCESS !=
2526 68 : JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
2527 : {
2528 0 : CPLError(CE_Failure, CPLE_AppDefined,
2529 : "JxlEncoderSetBasicInfo() failed");
2530 0 : return nullptr;
2531 : }
2532 :
2533 68 : if (pszICCProfile && pszICCProfile[0] != '\0')
2534 : {
2535 1 : char *pEmbedBuffer = CPLStrdup(pszICCProfile);
2536 : GInt32 nEmbedLen =
2537 1 : CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pEmbedBuffer));
2538 1 : if (JXL_ENC_SUCCESS !=
2539 1 : JxlEncoderSetICCProfile(encoder.get(),
2540 : reinterpret_cast<GByte *>(pEmbedBuffer),
2541 : nEmbedLen))
2542 : {
2543 0 : CPLError(CE_Failure, CPLE_AppDefined,
2544 : "JxlEncoderSetICCProfile() failed");
2545 0 : CPLFree(pEmbedBuffer);
2546 0 : return nullptr;
2547 : }
2548 1 : CPLFree(pEmbedBuffer);
2549 : }
2550 : else
2551 : {
2552 : JxlColorEncoding color_encoding;
2553 67 : JxlColorEncodingSetToSRGB(&color_encoding,
2554 67 : basic_info.num_color_channels ==
2555 : 1 /*is_gray*/);
2556 67 : if (JXL_ENC_SUCCESS !=
2557 67 : JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
2558 : {
2559 0 : CPLError(CE_Failure, CPLE_AppDefined,
2560 : "JxlEncoderSetColorEncoding() failed");
2561 0 : return nullptr;
2562 : }
2563 : }
2564 : }
2565 :
2566 : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
2567 73 : if (abyJPEG.empty() && basic_info.num_extra_channels > 0)
2568 : {
2569 33 : if (basic_info.num_extra_channels >= 5)
2570 0 : JxlEncoderSetCodestreamLevel(encoder.get(), 10);
2571 :
2572 71 : for (int i = (bHasInterleavedAlphaBand ? 1 : 0);
2573 71 : i < static_cast<int>(basic_info.num_extra_channels); ++i)
2574 : {
2575 38 : const int nBand =
2576 38 : static_cast<int>(1 + basic_info.num_color_channels + i);
2577 38 : const auto poBand = poSrcDS->GetRasterBand(nBand);
2578 : JxlExtraChannelInfo extra_channel_info;
2579 : const JxlExtraChannelType channelType =
2580 38 : poBand->GetColorInterpretation() == GCI_AlphaBand
2581 38 : ? JXL_CHANNEL_ALPHA
2582 38 : : JXL_CHANNEL_OPTIONAL;
2583 38 : JxlEncoderInitExtraChannelInfo(channelType, &extra_channel_info);
2584 38 : extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
2585 38 : extra_channel_info.exponent_bits_per_sample =
2586 38 : basic_info.exponent_bits_per_sample;
2587 :
2588 38 : const uint32_t nIndex = static_cast<uint32_t>(i);
2589 38 : if (JXL_ENC_SUCCESS !=
2590 38 : JxlEncoderSetExtraChannelInfo(encoder.get(), nIndex,
2591 : &extra_channel_info))
2592 : {
2593 0 : CPLError(CE_Failure, CPLE_AppDefined,
2594 : "JxlEncoderSetExtraChannelInfo() failed");
2595 0 : return nullptr;
2596 : }
2597 38 : std::string osChannelName(CPLSPrintf("Band %d", nBand));
2598 38 : const char *pszDescription = poBand->GetDescription();
2599 38 : if (pszDescription && pszDescription[0] != '\0')
2600 1 : osChannelName = pszDescription;
2601 38 : if (JXL_ENC_SUCCESS !=
2602 76 : JxlEncoderSetExtraChannelName(encoder.get(), nIndex,
2603 38 : osChannelName.data(),
2604 : osChannelName.size()))
2605 : {
2606 0 : CPLError(CE_Failure, CPLE_AppDefined,
2607 : "JxlEncoderSetExtraChannelName() failed");
2608 0 : return nullptr;
2609 : }
2610 : #if HAVE_JxlEncoderSetExtraChannelDistance
2611 38 : if (channelType == JXL_CHANNEL_ALPHA && fAlphaDistance >= 0.0f)
2612 : {
2613 1 : if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(
2614 : opts, nIndex, fAlphaDistance))
2615 : {
2616 0 : CPLError(CE_Failure, CPLE_AppDefined,
2617 : "JxlEncoderSetExtraChannelDistance failed");
2618 0 : return nullptr;
2619 : }
2620 : }
2621 37 : else if (!bLossless)
2622 : {
2623 : // By default libjxl applies lossless encoding for extra channels
2624 4 : if (JXL_ENC_SUCCESS !=
2625 4 : JxlEncoderSetExtraChannelDistance(opts, nIndex, fDistance))
2626 : {
2627 0 : CPLError(CE_Failure, CPLE_AppDefined,
2628 : "JxlEncoderSetExtraChannelDistance failed");
2629 0 : return nullptr;
2630 : }
2631 : }
2632 : #endif
2633 : }
2634 : }
2635 : #endif
2636 :
2637 : #ifdef HAVE_JXL_BOX_API
2638 : const bool bCompressBox =
2639 73 : CPLFetchBool(papszOptions, "COMPRESS_BOXES", false);
2640 :
2641 73 : if (papszXMP && papszXMP[0] && bWriteXMP)
2642 : {
2643 2 : JxlEncoderUseBoxes(encoder.get());
2644 :
2645 2 : const char *pszXMP = papszXMP[0];
2646 2 : if (JxlEncoderAddBox(encoder.get(), "xml ",
2647 : reinterpret_cast<const uint8_t *>(pszXMP),
2648 2 : strlen(pszXMP), bCompressBox) != JXL_ENC_SUCCESS)
2649 : {
2650 0 : CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
2651 0 : return nullptr;
2652 : }
2653 : }
2654 :
2655 : // Write "Exif" box with EXIF metadata
2656 73 : if (papszEXIF && bWriteExifMetadata)
2657 : {
2658 6 : GUInt32 nMarkerSize = 0;
2659 6 : GByte *pabyEXIF = EXIFCreate(papszEXIF, nullptr, 0, 0, 0, // overview
2660 : &nMarkerSize);
2661 6 : CPLAssert(nMarkerSize > 6 && memcmp(pabyEXIF, "Exif\0\0", 6) == 0);
2662 : // Add 4 leading bytes at 0
2663 6 : std::vector<GByte> abyEXIF(4 + nMarkerSize - 6);
2664 6 : memcpy(&abyEXIF[4], pabyEXIF + 6, nMarkerSize - 6);
2665 6 : CPLFree(pabyEXIF);
2666 :
2667 6 : JxlEncoderUseBoxes(encoder.get());
2668 6 : if (JxlEncoderAddBox(encoder.get(), "Exif", abyEXIF.data(),
2669 6 : abyEXIF.size(), bCompressBox) != JXL_ENC_SUCCESS)
2670 : {
2671 0 : CPLError(CE_Failure, CPLE_AppDefined, "JxlEncoderAddBox() failed");
2672 0 : return nullptr;
2673 : }
2674 : }
2675 :
2676 : // Write GeoJP2 box in a JUMBF box from georeferencing information
2677 73 : if (poJUMBFBox)
2678 : {
2679 30 : GByte *pabyBoxData = poJUMBFBox->GetWritableBoxData();
2680 30 : JxlEncoderUseBoxes(encoder.get());
2681 30 : if (JxlEncoderAddBox(encoder.get(), "jumb", pabyBoxData,
2682 30 : static_cast<size_t>(poJUMBFBox->GetBoxLength()),
2683 30 : bCompressBox) != JXL_ENC_SUCCESS)
2684 : {
2685 0 : VSIFree(pabyBoxData);
2686 0 : CPLError(CE_Failure, CPLE_AppDefined,
2687 : "JxlEncoderAddBox() failed for jumb");
2688 0 : return nullptr;
2689 : }
2690 30 : VSIFree(pabyBoxData);
2691 : }
2692 : #endif
2693 :
2694 : auto fp = std::unique_ptr<VSILFILE, VSILFileReleaser>(
2695 146 : VSIFOpenL(pszFilename, "wb"));
2696 73 : if (!fp)
2697 : {
2698 4 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s: %s", pszFilename,
2699 4 : VSIStrerror(errno));
2700 4 : return nullptr;
2701 : }
2702 :
2703 69 : int nPamMask = GCIF_PAM_DEFAULT;
2704 :
2705 69 : if (!abyJPEG.empty())
2706 : {
2707 : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
2708 : const bool bHasMaskBand =
2709 10 : basic_info.num_extra_channels == 0 &&
2710 5 : poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET;
2711 5 : if (bHasMaskBand)
2712 : {
2713 1 : nPamMask &= ~GCIF_MASK;
2714 :
2715 1 : basic_info.alpha_bits = basic_info.bits_per_sample;
2716 1 : basic_info.num_extra_channels = 1;
2717 1 : if (JXL_ENC_SUCCESS !=
2718 1 : JxlEncoderSetBasicInfo(encoder.get(), &basic_info))
2719 : {
2720 0 : CPLError(CE_Failure, CPLE_AppDefined,
2721 : "JxlEncoderSetBasicInfo() failed");
2722 0 : return nullptr;
2723 : }
2724 :
2725 : JxlExtraChannelInfo extra_channel_info;
2726 1 : JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA,
2727 : &extra_channel_info);
2728 1 : extra_channel_info.bits_per_sample = basic_info.bits_per_sample;
2729 1 : extra_channel_info.exponent_bits_per_sample =
2730 1 : basic_info.exponent_bits_per_sample;
2731 :
2732 1 : if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(
2733 : encoder.get(), 0, &extra_channel_info))
2734 : {
2735 0 : CPLError(CE_Failure, CPLE_AppDefined,
2736 : "JxlEncoderSetExtraChannelInfo() failed");
2737 0 : return nullptr;
2738 : }
2739 : }
2740 : #endif
2741 :
2742 5 : CPLDebug("JPEGXL", "Adding JPEG frame");
2743 5 : JxlEncoderStoreJPEGMetadata(encoder.get(), true);
2744 5 : if (JxlEncoderAddJPEGFrame(opts, abyJPEG.data(), abyJPEG.size()) !=
2745 : JXL_ENC_SUCCESS)
2746 : {
2747 1 : if (EQUAL(pszLossLessCopy, "AUTO"))
2748 : {
2749 : // could happen with a file with arithmetic encoding for example
2750 1 : CPLDebug("JPEGXL",
2751 : "JxlEncoderAddJPEGFrame() framed. "
2752 : "Perhaps unsupported JPEG formulation for libjxl. "
2753 : "Retrying with normal code path");
2754 2 : CPLStringList aosOptions(papszOptions);
2755 1 : aosOptions.SetNameValue("LOSSLESS_COPY", "NO");
2756 : CPLConfigOptionSetter oSetter("GDAL_ERROR_ON_LIBJPEG_WARNING",
2757 2 : "YES", true);
2758 1 : return CreateCopy(pszFilename, poSrcDS, FALSE,
2759 : aosOptions.List(), pfnProgress,
2760 1 : pProgressData);
2761 : }
2762 : else
2763 : {
2764 0 : CPLError(CE_Failure, CPLE_AppDefined,
2765 : "JxlEncoderAddJPEGFrame() failed");
2766 0 : return nullptr;
2767 : }
2768 : }
2769 :
2770 : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
2771 4 : if (bHasMaskBand)
2772 : {
2773 : JxlColorEncoding color_encoding;
2774 1 : JxlColorEncodingSetToSRGB(&color_encoding,
2775 1 : basic_info.num_color_channels ==
2776 : 1 /*is_gray*/);
2777 : // libjxl until commit
2778 : // https://github.com/libjxl/libjxl/commits/c70c9d0bdc03f77d6bd8d9c3c56d4dac1b9b1652
2779 : // needs JxlEncoderSetColorEncoding()
2780 : // But post it (308b5f1eed81becac506569080e4490cc486660c,
2781 : // "Use chunked frame adapter instead of image bundle in
2782 : // EncodeFrame. (#2983)"), this errors out.
2783 1 : CPL_IGNORE_RET_VAL(
2784 1 : JxlEncoderSetColorEncoding(encoder.get(), &color_encoding));
2785 :
2786 1 : const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
2787 2 : if (nDataSize <= 0 ||
2788 1 : static_cast<size_t>(poSrcDS->GetRasterXSize()) >
2789 1 : std::numeric_limits<size_t>::max() /
2790 1 : poSrcDS->GetRasterYSize() / nDataSize)
2791 : {
2792 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2793 : "Image too big for architecture");
2794 0 : return nullptr;
2795 : }
2796 : const size_t nInputDataSize =
2797 1 : static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2798 1 : poSrcDS->GetRasterYSize() * nDataSize;
2799 :
2800 1 : std::vector<GByte> abyInputData;
2801 : try
2802 : {
2803 1 : abyInputData.resize(nInputDataSize);
2804 : }
2805 0 : catch (const std::exception &e)
2806 : {
2807 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2808 0 : "Cannot allocate image buffer: %s", e.what());
2809 0 : return nullptr;
2810 : }
2811 :
2812 1 : format.num_channels = 1;
2813 2 : if (poSrcDS->GetRasterBand(1)->GetMaskBand()->RasterIO(
2814 : GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2815 1 : poSrcDS->GetRasterYSize(), abyInputData.data(),
2816 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2817 1 : 0, 0, nullptr) != CE_None)
2818 : {
2819 0 : return nullptr;
2820 : }
2821 1 : if (JxlEncoderSetExtraChannelBuffer(
2822 1 : opts, &format, abyInputData.data(),
2823 1 : static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2824 1 : poSrcDS->GetRasterYSize() * nDataSize,
2825 1 : 0) != JXL_ENC_SUCCESS)
2826 : {
2827 0 : CPLError(CE_Failure, CPLE_AppDefined,
2828 : "JxlEncoderSetExtraChannelBuffer() failed");
2829 0 : return nullptr;
2830 : }
2831 : }
2832 : #endif
2833 : }
2834 : else
2835 : {
2836 64 : const auto nDataSize = GDALGetDataTypeSizeBytes(eDT);
2837 :
2838 128 : if (nDataSize <= 0 || static_cast<size_t>(poSrcDS->GetRasterXSize()) >
2839 64 : std::numeric_limits<size_t>::max() /
2840 64 : poSrcDS->GetRasterYSize() /
2841 64 : nBaseChannels / nDataSize)
2842 : {
2843 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2844 : "Image too big for architecture");
2845 1 : return nullptr;
2846 : }
2847 : const size_t nInputDataSize =
2848 64 : static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2849 64 : poSrcDS->GetRasterYSize() * nBaseChannels * nDataSize;
2850 :
2851 64 : std::vector<GByte> abyInputData;
2852 : try
2853 : {
2854 64 : abyInputData.resize(nInputDataSize);
2855 : }
2856 0 : catch (const std::exception &e)
2857 : {
2858 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2859 0 : "Cannot allocate image buffer: %s", e.what());
2860 0 : return nullptr;
2861 : }
2862 :
2863 128 : if (poSrcDS->RasterIO(
2864 : GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2865 64 : poSrcDS->GetRasterYSize(), abyInputData.data(),
2866 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2867 64 : nBaseChannels, nullptr, nDataSize * nBaseChannels,
2868 64 : nDataSize * nBaseChannels * poSrcDS->GetRasterXSize(),
2869 64 : nDataSize, nullptr) != CE_None)
2870 : {
2871 1 : return nullptr;
2872 : }
2873 :
2874 307 : const auto Rescale = [eDT, nBits, poSrcDS](void *pBuffer, int nChannels)
2875 : {
2876 : // Rescale to 8-bits/16-bits
2877 101 : if ((eDT == GDT_Byte && nBits < 8) ||
2878 23 : (eDT == GDT_UInt16 && nBits < 16))
2879 : {
2880 : const size_t nSamples =
2881 2 : static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2882 2 : poSrcDS->GetRasterYSize() * nChannels;
2883 2 : const int nMaxVal = (1 << nBits) - 1;
2884 2 : const int nMavValHalf = nMaxVal / 2;
2885 2 : if (eDT == GDT_Byte)
2886 : {
2887 1 : uint8_t *panData = static_cast<uint8_t *>(pBuffer);
2888 401 : for (size_t i = 0; i < nSamples; ++i)
2889 : {
2890 400 : panData[i] = static_cast<GByte>(
2891 400 : (std::min(static_cast<int>(panData[i]), nMaxVal) *
2892 400 : 255 +
2893 400 : nMavValHalf) /
2894 400 : nMaxVal);
2895 : }
2896 : }
2897 1 : else if (eDT == GDT_UInt16)
2898 : {
2899 1 : uint16_t *panData = static_cast<uint16_t *>(pBuffer);
2900 401 : for (size_t i = 0; i < nSamples; ++i)
2901 : {
2902 400 : panData[i] = static_cast<uint16_t>(
2903 400 : (std::min(static_cast<int>(panData[i]), nMaxVal) *
2904 400 : 65535 +
2905 400 : nMavValHalf) /
2906 400 : nMaxVal);
2907 : }
2908 : }
2909 : }
2910 101 : };
2911 :
2912 63 : Rescale(abyInputData.data(), nBaseChannels);
2913 :
2914 63 : if (JxlEncoderAddImageFrame(opts, &format, abyInputData.data(),
2915 63 : abyInputData.size()) != JXL_ENC_SUCCESS)
2916 : {
2917 0 : CPLError(CE_Failure, CPLE_AppDefined,
2918 : "JxlEncoderAddImageFrame() failed");
2919 0 : return nullptr;
2920 : }
2921 :
2922 : #ifdef HAVE_JxlEncoderInitExtraChannelInfo
2923 63 : format.num_channels = 1;
2924 101 : for (int i = nBaseChannels; i < poSrcDS->GetRasterCount(); ++i)
2925 : {
2926 76 : if (poSrcDS->GetRasterBand(i + 1)->RasterIO(
2927 : GF_Read, 0, 0, poSrcDS->GetRasterXSize(),
2928 38 : poSrcDS->GetRasterYSize(), abyInputData.data(),
2929 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), eDT,
2930 38 : 0, 0, nullptr) != CE_None)
2931 : {
2932 0 : return nullptr;
2933 : }
2934 :
2935 38 : Rescale(abyInputData.data(), 1);
2936 :
2937 38 : if (JxlEncoderSetExtraChannelBuffer(
2938 38 : opts, &format, abyInputData.data(),
2939 114 : static_cast<size_t>(poSrcDS->GetRasterXSize()) *
2940 38 : poSrcDS->GetRasterYSize() * nDataSize,
2941 76 : i - nBaseChannels + (bHasInterleavedAlphaBand ? 1 : 0)) !=
2942 : JXL_ENC_SUCCESS)
2943 : {
2944 0 : CPLError(CE_Failure, CPLE_AppDefined,
2945 : "JxlEncoderSetExtraChannelBuffer() failed");
2946 0 : return nullptr;
2947 : }
2948 : }
2949 : #endif
2950 : }
2951 :
2952 67 : JxlEncoderCloseInput(encoder.get());
2953 :
2954 : // Flush to file
2955 201 : std::vector<GByte> abyOutputBuffer(4096 * 10);
2956 : while (true)
2957 : {
2958 67 : size_t len = abyOutputBuffer.size();
2959 67 : uint8_t *buf = abyOutputBuffer.data();
2960 : JxlEncoderStatus process_result =
2961 67 : JxlEncoderProcessOutput(encoder.get(), &buf, &len);
2962 67 : if (process_result == JXL_ENC_ERROR)
2963 : {
2964 0 : CPLError(CE_Failure, CPLE_AppDefined,
2965 : "JxlEncoderProcessOutput() failed");
2966 10 : return nullptr;
2967 : }
2968 67 : size_t nToWrite = abyOutputBuffer.size() - len;
2969 67 : if (VSIFWriteL(abyOutputBuffer.data(), 1, nToWrite, fp.get()) !=
2970 : nToWrite)
2971 : {
2972 10 : CPLError(CE_Failure, CPLE_FileIO, "VSIFWriteL() failed");
2973 10 : return nullptr;
2974 : }
2975 57 : if (process_result != JXL_ENC_NEED_MORE_OUTPUT)
2976 57 : break;
2977 0 : }
2978 :
2979 57 : fp.reset();
2980 :
2981 57 : if (pfnProgress)
2982 57 : pfnProgress(1.0, "", pProgressData);
2983 :
2984 : // Re-open file and clone missing info to PAM
2985 57 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
2986 57 : auto poDS = OpenStaticPAM(&oOpenInfo);
2987 57 : if (poDS)
2988 : {
2989 : // Do not create a .aux.xml file just for AREA_OR_POINT=Area
2990 : const char *pszAreaOfPoint =
2991 57 : poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
2992 57 : if (pszAreaOfPoint && EQUAL(pszAreaOfPoint, GDALMD_AOP_AREA))
2993 : {
2994 9 : poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_AREA);
2995 9 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
2996 : }
2997 : #ifdef HAVE_JXL_BOX_API
2998 : // When copying from JPEG, expose the EXIF metadata in the main domain,
2999 : // so that PAM doesn't copy it.
3000 57 : if (bEXIFFromMainDomain)
3001 : {
3002 51 : for (CSLConstList papszIter = papszEXIF; papszIter && *papszIter;
3003 : ++papszIter)
3004 : {
3005 48 : if (STARTS_WITH(*papszIter, "EXIF_"))
3006 : {
3007 48 : char *pszKey = nullptr;
3008 : const char *pszValue =
3009 48 : CPLParseNameValue(*papszIter, &pszKey);
3010 48 : if (pszKey && pszValue)
3011 : {
3012 48 : poDS->SetMetadataItem(pszKey, pszValue);
3013 : }
3014 48 : CPLFree(pszKey);
3015 : }
3016 : }
3017 3 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
3018 : }
3019 : #endif
3020 57 : poDS->CloneInfo(poSrcDS, nPamMask);
3021 : }
3022 :
3023 57 : return poDS;
3024 : }
3025 :
3026 : /************************************************************************/
3027 : /* GDALRegister_JPEGXL() */
3028 : /************************************************************************/
3029 :
3030 12 : void GDALRegister_JPEGXL()
3031 :
3032 : {
3033 12 : if (GDALGetDriverByName("JPEGXL") != nullptr)
3034 0 : return;
3035 :
3036 12 : GDALDriver *poDriver = new GDALDriver();
3037 :
3038 12 : JPEGXLDriverSetCommonMetadata(poDriver);
3039 12 : poDriver->pfnOpen = JPEGXLDataset::OpenStatic;
3040 12 : poDriver->pfnIdentify = JPEGXLDataset::Identify;
3041 12 : poDriver->pfnCreateCopy = JPEGXLDataset::CreateCopy;
3042 :
3043 12 : GetGDALDriverManager()->RegisterDriver(poDriver);
3044 : }
|