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