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