Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: JPEG2000 driver based on OpenJPEG or Grok library
4 : * Purpose: JPEG2000 driver based on OpenJPEG or Grok library
5 : * Authors: Even Rouault, <even dot rouault at spatialys dot com>
6 : * Aaron Boxer, <boxerab at protonmail dot com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys dot com>
10 : * Copyright (c) 2015, European Union (European Environment Agency)
11 : * Copyright (c) 2023, Grok Image Compression Inc.
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include <cassert>
17 : #include <vector>
18 :
19 : #include "cpl_atomic_ops.h"
20 : #include "cpl_multiproc.h"
21 : #include "cpl_string.h"
22 : #include "cpl_worker_thread_pool.h"
23 : #include "gdal_frmts.h"
24 : #include "gdaljp2abstractdataset.h"
25 : #include "gdaljp2metadata.h"
26 : #include "vrt/vrtdataset.h"
27 :
28 : #include <algorithm>
29 :
30 : #include "jp2opjlikedataset.h"
31 :
32 : /************************************************************************/
33 : /* JP2OPJLikeRasterBand() */
34 : /************************************************************************/
35 :
36 : template <typename CODEC, typename BASE>
37 1146 : JP2OPJLikeRasterBand<CODEC, BASE>::JP2OPJLikeRasterBand(
38 : JP2OPJLikeDataset<CODEC, BASE> *poDSIn, int nBandIn,
39 : GDALDataType eDataTypeIn, int nBits, int bPromoteTo8BitIn,
40 1146 : int nBlockXSizeIn, int nBlockYSizeIn)
41 :
42 : {
43 1146 : this->eDataType = eDataTypeIn;
44 1146 : this->nBlockXSize = nBlockXSizeIn;
45 1146 : this->nBlockYSize = nBlockYSizeIn;
46 1146 : this->bPromoteTo8Bit = bPromoteTo8BitIn;
47 1146 : poCT = nullptr;
48 :
49 1146 : if ((nBits % 8) != 0)
50 41 : GDALRasterBand::SetMetadataItem(
51 82 : "NBITS", CPLString().Printf("%d", nBits), "IMAGE_STRUCTURE");
52 1146 : GDALRasterBand::SetMetadataItem("COMPRESSION", "JPEG2000",
53 : "IMAGE_STRUCTURE");
54 1146 : this->poDS = poDSIn;
55 1146 : this->nBand = nBandIn;
56 1146 : }
57 :
58 : template <typename CODEC, typename BASE>
59 50 : GDALColorTable *JP2OPJLikeRasterBand<CODEC, BASE>::GetColorTable()
60 : {
61 50 : return poCT;
62 : }
63 :
64 : template <typename CODEC, typename BASE>
65 0 : int JP2OPJLikeRasterBand<CODEC, BASE>::HasArbitraryOverviews()
66 : {
67 0 : return poCT == nullptr;
68 : }
69 :
70 : /************************************************************************/
71 : /* ~JP2OPJLikeRasterBand() */
72 : /************************************************************************/
73 :
74 : template <typename CODEC, typename BASE>
75 2292 : JP2OPJLikeRasterBand<CODEC, BASE>::~JP2OPJLikeRasterBand()
76 : {
77 24 : delete poCT;
78 2316 : }
79 :
80 : /************************************************************************/
81 : /* CLAMP_0_255() */
82 : /************************************************************************/
83 :
84 134000 : static CPL_INLINE GByte CLAMP_0_255(int val)
85 : {
86 134000 : if (val < 0)
87 4039 : return 0;
88 129961 : else if (val > 255)
89 128 : return 255;
90 : else
91 129833 : return (GByte)val;
92 : }
93 :
94 : /************************************************************************/
95 : /* IReadBlock() */
96 : /************************************************************************/
97 :
98 : template <typename CODEC, typename BASE>
99 254 : CPLErr JP2OPJLikeRasterBand<CODEC, BASE>::IReadBlock(int nBlockXOff,
100 : int nBlockYOff,
101 : void *pImage)
102 : {
103 254 : auto poGDS = (JP2OPJLikeDataset<CODEC, BASE> *)poDS;
104 :
105 : #ifdef DEBUG_VERBOSE
106 : int nXOff = nBlockXOff * nBlockXSize;
107 : int nYOff = nBlockYOff * nBlockYSize;
108 : int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
109 : int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
110 : if (poGDS->iLevel == 0)
111 : {
112 : CPLDebug(CODEC::debugId(),
113 : "ds.GetRasterBand(%d).ReadRaster(%d,%d,%d,%d)", nBand, nXOff,
114 : nYOff, nXSize, nYSize);
115 : }
116 : else
117 : {
118 : CPLDebug(CODEC::debugId(),
119 : "ds.GetRasterBand(%d).GetOverview(%d).ReadRaster(%d,%d,%d,%d)",
120 : nBand, poGDS->iLevel - 1, nXOff, nYOff, nXSize, nYSize);
121 : }
122 : #endif
123 :
124 254 : if (poGDS->bEnoughMemoryToLoadOtherBands)
125 : return poGDS->ReadBlock(nBand, poGDS->fp_, nBlockXOff, nBlockYOff,
126 254 : pImage, poGDS->nBands, nullptr);
127 : else
128 : return poGDS->ReadBlock(nBand, poGDS->fp_, nBlockXOff, nBlockYOff,
129 0 : pImage, 1, &nBand);
130 : }
131 :
132 : /************************************************************************/
133 : /* IRasterIO() */
134 : /************************************************************************/
135 :
136 : template <typename CODEC, typename BASE>
137 227 : CPLErr JP2OPJLikeRasterBand<CODEC, BASE>::IRasterIO(
138 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
139 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
140 : GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
141 : {
142 227 : auto poGDS = (JP2OPJLikeDataset<CODEC, BASE> *)poDS;
143 :
144 227 : if (eRWFlag != GF_Read)
145 0 : return CE_Failure;
146 :
147 : /* ==================================================================== */
148 : /* Do we have overviews that would be appropriate to satisfy */
149 : /* this request? */
150 : /* ==================================================================== */
151 227 : if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
152 : {
153 : int bTried;
154 1 : CPLErr eErr = TryOverviewRasterIO(
155 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
156 : eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
157 1 : if (bTried)
158 1 : return eErr;
159 : }
160 :
161 226 : int nRet =
162 : poGDS->PreloadBlocks(this, nXOff, nYOff, nXSize, nYSize, 0, nullptr);
163 226 : if (nRet < 0)
164 0 : return CE_Failure;
165 226 : poGDS->bEnoughMemoryToLoadOtherBands = nRet;
166 :
167 226 : CPLErr eErr = GDALPamRasterBand::IRasterIO(
168 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
169 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
170 :
171 : // cppcheck-suppress redundantAssignment
172 226 : poGDS->bEnoughMemoryToLoadOtherBands = TRUE;
173 226 : return eErr;
174 : }
175 :
176 : template <typename CODEC, typename BASE> class JP2JobStruct
177 : {
178 : public:
179 : JP2OPJLikeDataset<CODEC, BASE> *poGDS_;
180 : int nBand;
181 : std::vector<std::pair<int, int>> oPairs;
182 : volatile int nCurPair;
183 : int nBandCount;
184 : const int *panBandMap;
185 : volatile bool bSuccess;
186 : };
187 :
188 : /************************************************************************/
189 : /* GetFileHandle() */
190 : /************************************************************************/
191 :
192 : template <typename CODEC, typename BASE>
193 8 : VSILFILE *JP2OPJLikeDataset<CODEC, BASE>::GetFileHandle()
194 : {
195 8 : return this->fp_;
196 : }
197 :
198 : /************************************************************************/
199 : /* ReadBlockInThread() */
200 : /************************************************************************/
201 :
202 : template <typename CODEC, typename BASE>
203 17 : void JP2OPJLikeDataset<CODEC, BASE>::ReadBlockInThread(void *userdata)
204 : {
205 : int nPair;
206 17 : auto poJob = (JP2JobStruct<CODEC, BASE> *)userdata;
207 :
208 17 : JP2OPJLikeDataset *poGDS = poJob->poGDS_;
209 17 : int nBand = poJob->nBand;
210 17 : int nPairs = (int)poJob->oPairs.size();
211 17 : int nBandCount = poJob->nBandCount;
212 17 : const int *panBandMap = poJob->panBandMap;
213 17 : VSILFILE *fp = VSIFOpenL(poGDS->m_osFilename.c_str(), "rb");
214 17 : if (fp == nullptr)
215 : {
216 0 : CPLDebug(CODEC::debugId(), "Cannot open %s",
217 : poGDS->m_osFilename.c_str());
218 0 : poJob->bSuccess = false;
219 : // VSIFree(pDummy);
220 0 : return;
221 : }
222 :
223 291 : while ((nPair = CPLAtomicInc(&(poJob->nCurPair))) < nPairs &&
224 137 : poJob->bSuccess)
225 : {
226 137 : int nBlockXOff = poJob->oPairs[nPair].first;
227 137 : int nBlockYOff = poJob->oPairs[nPair].second;
228 137 : poGDS->AcquireMutex();
229 : GDALRasterBlock *poBlock =
230 137 : poGDS->GetRasterBand(nBand)->GetLockedBlockRef(nBlockXOff,
231 : nBlockYOff, TRUE);
232 137 : poGDS->ReleaseMutex();
233 137 : if (poBlock == nullptr)
234 : {
235 0 : poJob->bSuccess = false;
236 0 : break;
237 : }
238 :
239 137 : void *pDstBuffer = poBlock->GetDataRef();
240 137 : if (poGDS->ReadBlock(nBand, fp, nBlockXOff, nBlockYOff, pDstBuffer,
241 137 : nBandCount, panBandMap) != CE_None)
242 : {
243 0 : poJob->bSuccess = false;
244 : }
245 :
246 137 : poBlock->DropLock();
247 : }
248 :
249 16 : VSIFCloseL(fp);
250 : // VSIFree(pDummy);
251 : }
252 :
253 : /************************************************************************/
254 : /* PreloadBlocks() */
255 : /************************************************************************/
256 :
257 : template <typename CODEC, typename BASE>
258 240 : int JP2OPJLikeDataset<CODEC, BASE>::PreloadBlocks(
259 : JP2OPJLikeRasterBand<CODEC, BASE> *poBand, int nXOff, int nYOff, int nXSize,
260 : int nYSize, int nBandCount, const int *panBandMap)
261 : {
262 240 : int bRet = TRUE;
263 240 : int nXStart = nXOff / poBand->nBlockXSize;
264 240 : int nXEnd = (nXOff + nXSize - 1) / poBand->nBlockXSize;
265 240 : int nYStart = nYOff / poBand->nBlockYSize;
266 240 : int nYEnd = (nYOff + nYSize - 1) / poBand->nBlockYSize;
267 480 : GIntBig nReqMem = (GIntBig)(nXEnd - nXStart + 1) * (nYEnd - nYStart + 1) *
268 240 : poBand->nBlockXSize * poBand->nBlockYSize *
269 240 : (GDALGetDataTypeSize(poBand->eDataType) / 8);
270 :
271 240 : int nMaxThreads = this->GetNumThreads();
272 240 : if (!this->bUseSetDecodeArea && nMaxThreads > 1)
273 : {
274 96 : if (nReqMem > GDALGetCacheMax64() / (nBandCount == 0 ? 1 : nBandCount))
275 0 : return FALSE;
276 :
277 96 : JP2JobStruct<CODEC, BASE> oJob;
278 96 : this->m_nBlocksToLoad = 0;
279 : try
280 : {
281 226 : for (int nBlockXOff = nXStart; nBlockXOff <= nXEnd; ++nBlockXOff)
282 : {
283 484 : for (int nBlockYOff = nYStart; nBlockYOff <= nYEnd;
284 : ++nBlockYOff)
285 : {
286 354 : GDALRasterBlock *poBlock =
287 : poBand->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
288 354 : if (poBlock != nullptr)
289 : {
290 156 : poBlock->DropLock();
291 156 : continue;
292 : }
293 198 : oJob.oPairs.push_back(
294 : std::pair<int, int>(nBlockXOff, nBlockYOff));
295 198 : this->m_nBlocksToLoad++;
296 : }
297 : }
298 : }
299 0 : catch (const std::bad_alloc &)
300 : {
301 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory error");
302 0 : this->m_nBlocksToLoad = 0;
303 0 : return -1;
304 : }
305 :
306 96 : if (this->m_nBlocksToLoad > 1)
307 : {
308 5 : const int l_nThreads = std::min(this->m_nBlocksToLoad, nMaxThreads);
309 : CPLJoinableThread **pahThreads =
310 5 : (CPLJoinableThread **)VSI_CALLOC_VERBOSE(
311 : sizeof(CPLJoinableThread *), l_nThreads);
312 5 : if (pahThreads == nullptr)
313 : {
314 0 : this->m_nBlocksToLoad = 0;
315 0 : return -1;
316 : }
317 : int i;
318 :
319 5 : CPLDebug(CODEC::debugId(), "%d blocks to load (%d threads)",
320 : this->m_nBlocksToLoad, l_nThreads);
321 :
322 5 : oJob.poGDS_ = this;
323 5 : oJob.nBand = poBand->GetBand();
324 5 : oJob.nCurPair = -1;
325 5 : if (nBandCount > 0)
326 : {
327 2 : oJob.nBandCount = nBandCount;
328 2 : oJob.panBandMap = panBandMap;
329 : }
330 : else
331 : {
332 3 : if (nReqMem <= GDALGetCacheMax64() / nBands)
333 : {
334 3 : oJob.nBandCount = nBands;
335 3 : oJob.panBandMap = nullptr;
336 : }
337 : else
338 : {
339 0 : bRet = FALSE;
340 0 : oJob.nBandCount = 1;
341 0 : oJob.panBandMap = &oJob.nBand;
342 : }
343 : }
344 5 : oJob.bSuccess = true;
345 :
346 : /* Flushes all dirty blocks from cache to disk to avoid them */
347 : /* to be flushed randomly, and simultaneously, from our worker
348 : * threads, */
349 : /* which might cause races in the output driver. */
350 : /* This is a workaround to a design defect of the block cache */
351 5 : GDALRasterBlock::FlushDirtyBlocks();
352 :
353 22 : for (i = 0; i < l_nThreads; i++)
354 : {
355 34 : pahThreads[i] =
356 17 : CPLCreateJoinableThread(ReadBlockInThread, &oJob);
357 17 : if (pahThreads[i] == nullptr)
358 0 : oJob.bSuccess = false;
359 : }
360 5 : TemporarilyDropReadWriteLock();
361 22 : for (i = 0; i < l_nThreads; i++)
362 17 : CPLJoinThread(pahThreads[i]);
363 5 : ReacquireReadWriteLock();
364 5 : CPLFree(pahThreads);
365 5 : if (!oJob.bSuccess)
366 : {
367 0 : this->m_nBlocksToLoad = 0;
368 0 : return -1;
369 : }
370 5 : this->m_nBlocksToLoad = 0;
371 : }
372 : }
373 :
374 240 : return bRet;
375 : }
376 :
377 : /************************************************************************/
378 : /* GetEstimatedRAMUsage() */
379 : /************************************************************************/
380 :
381 : template <typename CODEC, typename BASE>
382 100 : GIntBig JP2OPJLikeDataset<CODEC, BASE>::GetEstimatedRAMUsage()
383 : {
384 : // libopenjp2 holds the code block values in a uint32_t array.
385 100 : GIntBig nVal = static_cast<GIntBig>(this->m_nTileWidth) *
386 100 : this->m_nTileHeight * this->nBands * sizeof(uint32_t);
387 100 : if (this->bSingleTiled)
388 : {
389 : // libopenjp2 ingests the codestream for a whole tile. So for a
390 : // single-tiled image, this is roughly the size of the file.
391 100 : const auto nCurPos = VSIFTellL(this->fp_);
392 100 : VSIFSeekL(this->fp_, 0, SEEK_END);
393 100 : nVal += VSIFTellL(this->fp_);
394 100 : VSIFSeekL(this->fp_, nCurPos, SEEK_SET);
395 : }
396 100 : CPLDebug(CODEC::debugId(), "Estimated RAM usage for %s: %.2f GB",
397 100 : GetDescription(), static_cast<double>(nVal * 1e-9));
398 100 : return nVal;
399 : }
400 :
401 : /************************************************************************/
402 : /* IRasterIO() */
403 : /************************************************************************/
404 :
405 : template <typename CODEC, typename BASE>
406 15 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::IRasterIO(
407 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
408 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
409 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
410 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
411 : {
412 15 : if (eRWFlag != GF_Read)
413 0 : return CE_Failure;
414 :
415 15 : if (nBandCount < 1)
416 0 : return CE_Failure;
417 :
418 : auto poBand =
419 15 : (JP2OPJLikeRasterBand<CODEC, BASE> *)GetRasterBand(panBandMap[0]);
420 :
421 : /* ==================================================================== */
422 : /* Do we have overviews that would be appropriate to satisfy */
423 : /* this request? */
424 : /* ==================================================================== */
425 :
426 16 : if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
427 1 : poBand->GetOverviewCount() > 0)
428 : {
429 : int bTried;
430 1 : CPLErr eErr = TryOverviewRasterIO(
431 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
432 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
433 : nBandSpace, psExtraArg, &bTried);
434 1 : if (bTried)
435 1 : return eErr;
436 : }
437 :
438 14 : this->bEnoughMemoryToLoadOtherBands = PreloadBlocks(
439 : poBand, nXOff, nYOff, nXSize, nYSize, nBandCount, panBandMap);
440 :
441 14 : CPLErr eErr = GDALPamDataset::IRasterIO(
442 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
443 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
444 : psExtraArg);
445 :
446 14 : this->bEnoughMemoryToLoadOtherBands = TRUE;
447 14 : return eErr;
448 : }
449 :
450 : /************************************************************************/
451 : /* IBuildOverviews() */
452 : /************************************************************************/
453 :
454 : template <typename CODEC, typename BASE>
455 3 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::IBuildOverviews(
456 : const char *pszResampling, int nOverviews, const int *panOverviewList,
457 : int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
458 : void *pProgressData, CSLConstList papszOptions)
459 :
460 : {
461 : // In order for building external overviews to work properly, we
462 : // discard any concept of internal overviews when the user
463 : // first requests to build external overviews.
464 5 : for (int i = 0; i < this->nOverviewCount; i++)
465 : {
466 2 : delete papoOverviewDS[i];
467 : }
468 3 : CPLFree(papoOverviewDS);
469 3 : papoOverviewDS = nullptr;
470 3 : this->nOverviewCount = 0;
471 :
472 3 : return GDALPamDataset::IBuildOverviews(
473 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
474 3 : pfnProgress, pProgressData, papszOptions);
475 : }
476 :
477 : /************************************************************************/
478 : /* ReadBlock() */
479 : /************************************************************************/
480 :
481 : template <typename CODEC, typename BASE>
482 391 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::ReadBlock(int nBand, VSILFILE *fpIn,
483 : int nBlockXOff, int nBlockYOff,
484 : void *pImage, int nBandCount,
485 : const int *panBandMap)
486 : {
487 391 : CPLErr eErr = CE_None;
488 391 : CODEC localctx;
489 :
490 391 : auto poBand = (JP2OPJLikeRasterBand<CODEC, BASE> *)GetRasterBand(nBand);
491 391 : int nBlockXSize = poBand->nBlockXSize;
492 391 : int nBlockYSize = poBand->nBlockYSize;
493 391 : GDALDataType eDataType = poBand->eDataType;
494 :
495 391 : int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
496 :
497 391 : int nTileNumber = nBlockXOff + nBlockYOff * poBand->nBlocksPerRow;
498 391 : const int nWidthToRead =
499 391 : std::min(nBlockXSize, nRasterXSize - nBlockXOff * nBlockXSize);
500 391 : const int nHeightToRead =
501 391 : std::min(nBlockYSize, nRasterYSize - nBlockYOff * nBlockYSize);
502 :
503 391 : eErr = this->readBlockInit(fpIn, &localctx, nBlockXOff, nBlockYOff,
504 : this->nRasterXSize, this->nRasterYSize,
505 : nBlockXSize, nBlockYSize, nTileNumber);
506 391 : if (eErr != CE_None)
507 0 : goto end;
508 :
509 841 : for (unsigned int iBand = 0; iBand < localctx.psImage->numcomps; iBand++)
510 : {
511 450 : if (localctx.psImage->comps[iBand].data == nullptr)
512 : {
513 0 : CPLError(CE_Failure, CPLE_AppDefined,
514 : "localctx.psImage->comps[%d].data == nullptr", iBand);
515 0 : eErr = CE_Failure;
516 0 : goto end;
517 : }
518 : }
519 :
520 841 : for (int xBand = 0; xBand < nBandCount; xBand++)
521 : {
522 450 : GDALRasterBlock *poBlock = nullptr;
523 450 : int iBand = (panBandMap) ? panBandMap[xBand] : xBand + 1;
524 450 : int bPromoteTo8Bit =
525 450 : ((JP2OPJLikeRasterBand<CODEC, BASE> *)GetRasterBand(iBand))
526 : ->bPromoteTo8Bit;
527 :
528 450 : void *pDstBuffer = nullptr;
529 450 : if (iBand == nBand)
530 391 : pDstBuffer = pImage;
531 : else
532 : {
533 59 : AcquireMutex();
534 118 : poBlock =
535 59 : ((JP2OPJLikeRasterBand<CODEC, BASE> *)GetRasterBand(iBand))
536 : ->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
537 59 : if (poBlock != nullptr)
538 : {
539 0 : ReleaseMutex();
540 0 : poBlock->DropLock();
541 0 : continue;
542 : }
543 :
544 59 : poBlock = GetRasterBand(iBand)->GetLockedBlockRef(nBlockXOff,
545 : nBlockYOff, TRUE);
546 59 : ReleaseMutex();
547 59 : if (poBlock == nullptr)
548 : {
549 0 : continue;
550 : }
551 :
552 59 : pDstBuffer = poBlock->GetDataRef();
553 : }
554 :
555 450 : if (this->bIs420)
556 : {
557 7 : if ((int)localctx.psImage->comps[0].w < nWidthToRead ||
558 7 : (int)localctx.psImage->comps[0].h < nHeightToRead ||
559 7 : localctx.psImage->comps[1].w !=
560 7 : (localctx.psImage->comps[0].w + 1) / 2 ||
561 7 : localctx.psImage->comps[1].h !=
562 7 : (localctx.psImage->comps[0].h + 1) / 2 ||
563 7 : localctx.psImage->comps[2].w !=
564 7 : (localctx.psImage->comps[0].w + 1) / 2 ||
565 7 : localctx.psImage->comps[2].h !=
566 7 : (localctx.psImage->comps[0].h + 1) / 2 ||
567 7 : (nBands == 4 &&
568 4 : ((int)localctx.psImage->comps[3].w < nWidthToRead ||
569 4 : (int)localctx.psImage->comps[3].h < nHeightToRead)))
570 : {
571 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
572 : "Assertion at line %d of %s failed", __LINE__,
573 : __FILE__);
574 0 : if (poBlock != nullptr)
575 0 : poBlock->DropLock();
576 0 : eErr = CE_Failure;
577 0 : goto end;
578 : }
579 :
580 7 : GByte *pDst = (GByte *)pDstBuffer;
581 7 : if (iBand == 4)
582 : {
583 1 : const auto pSrcA = localctx.psImage->comps[3].data;
584 151 : for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
585 : {
586 300 : memcpy(pDst + j * nBlockXSize,
587 150 : pSrcA + j * localctx.stride(localctx.psImage->comps),
588 : nWidthToRead);
589 : }
590 : }
591 : else
592 : {
593 6 : const auto pSrcY = localctx.psImage->comps[0].data;
594 6 : const auto pSrcCb = localctx.psImage->comps[1].data;
595 6 : const auto pSrcCr = localctx.psImage->comps[2].data;
596 606 : for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
597 : {
598 81000 : for (int i = 0; i < nWidthToRead; i++)
599 : {
600 80400 : int Y =
601 80400 : pSrcY[j * localctx.stride(localctx.psImage->comps) +
602 80400 : i];
603 80400 : int Cb =
604 160800 : pSrcCb[(j / 2) * localctx.stride(
605 80400 : localctx.psImage->comps + 1) +
606 80400 : (i / 2)];
607 80400 : int Cr =
608 160800 : pSrcCr[(j / 2) * localctx.stride(
609 80400 : localctx.psImage->comps + 2) +
610 80400 : (i / 2)];
611 80400 : if (iBand == 1)
612 26800 : pDst[j * nBlockXSize + i] =
613 26800 : CLAMP_0_255((int)(Y + 1.402 * (Cr - 128)));
614 53600 : else if (iBand == 2)
615 26800 : pDst[j * nBlockXSize + i] =
616 26800 : CLAMP_0_255((int)(Y - 0.34414 * (Cb - 128) -
617 26800 : 0.71414 * (Cr - 128)));
618 26800 : else if (iBand == 3)
619 26800 : pDst[j * nBlockXSize + i] =
620 26800 : CLAMP_0_255((int)(Y + 1.772 * (Cb - 128)));
621 : }
622 : }
623 : }
624 :
625 7 : if (bPromoteTo8Bit)
626 : {
627 0 : for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
628 : {
629 0 : for (int i = 0; i < nWidthToRead; i++)
630 : {
631 0 : pDst[j * nBlockXSize + i] *= 255;
632 : }
633 : }
634 : }
635 : }
636 : else
637 : {
638 443 : if ((int)localctx.psImage->comps[iBand - 1].w < nWidthToRead ||
639 443 : (int)localctx.psImage->comps[iBand - 1].h < nHeightToRead)
640 : {
641 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
642 : "Assertion at line %d of %s failed", __LINE__,
643 : __FILE__);
644 0 : if (poBlock != nullptr)
645 0 : poBlock->DropLock();
646 0 : eErr = CE_Failure;
647 0 : goto end;
648 : }
649 :
650 443 : if (bPromoteTo8Bit)
651 : {
652 529 : for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
653 : {
654 79500 : for (int i = 0; i < nWidthToRead; i++)
655 : {
656 157950 : localctx.psImage->comps[iBand - 1]
657 157950 : .data[j * localctx.stride(localctx.psImage->comps +
658 78975 : iBand - 1) +
659 78975 : i] *= 255;
660 : }
661 : }
662 : }
663 :
664 443 : if ((int)localctx.stride(localctx.psImage->comps + iBand - 1) ==
665 857 : nBlockXSize &&
666 414 : (int)localctx.psImage->comps[iBand - 1].h == nBlockYSize)
667 : {
668 393 : GDALCopyWords64(
669 393 : localctx.psImage->comps[iBand - 1].data, GDT_Int32, 4,
670 : pDstBuffer, eDataType, nDataTypeSize,
671 393 : static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize);
672 : }
673 : else
674 : {
675 12249 : for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
676 : {
677 24398 : GDALCopyWords(
678 24398 : localctx.psImage->comps[iBand - 1].data +
679 12199 : j * localctx.stride(localctx.psImage->comps +
680 12199 : iBand - 1),
681 : GDT_Int32, 4,
682 12199 : (GByte *)pDstBuffer + j * nBlockXSize * nDataTypeSize,
683 : eDataType, nDataTypeSize, nWidthToRead);
684 : }
685 : }
686 : }
687 :
688 450 : if (poBlock != nullptr)
689 59 : poBlock->DropLock();
690 : }
691 :
692 391 : end:
693 391 : this->cache(&localctx);
694 :
695 782 : return eErr;
696 : }
697 :
698 : /************************************************************************/
699 : /* GetOverviewCount() */
700 : /************************************************************************/
701 :
702 : template <typename CODEC, typename BASE>
703 119 : int JP2OPJLikeRasterBand<CODEC, BASE>::GetOverviewCount()
704 : {
705 119 : auto poGDS = cpl::down_cast<JP2OPJLikeDataset<CODEC, BASE> *>(poDS);
706 119 : if (!poGDS->AreOverviewsEnabled())
707 0 : return 0;
708 :
709 119 : if (GDALPamRasterBand::GetOverviewCount() > 0)
710 4 : return GDALPamRasterBand::GetOverviewCount();
711 :
712 115 : return poGDS->nOverviewCount;
713 : }
714 :
715 : /************************************************************************/
716 : /* GetOverview() */
717 : /************************************************************************/
718 :
719 : template <typename CODEC, typename BASE>
720 22 : GDALRasterBand *JP2OPJLikeRasterBand<CODEC, BASE>::GetOverview(int iOvrLevel)
721 : {
722 22 : if (GDALPamRasterBand::GetOverviewCount() > 0)
723 6 : return GDALPamRasterBand::GetOverview(iOvrLevel);
724 :
725 16 : auto poGDS = (JP2OPJLikeDataset<CODEC, BASE> *)poDS;
726 16 : if (iOvrLevel < 0 || iOvrLevel >= poGDS->nOverviewCount)
727 0 : return nullptr;
728 :
729 16 : return poGDS->papoOverviewDS[iOvrLevel]->GetRasterBand(nBand);
730 : }
731 :
732 : /************************************************************************/
733 : /* GetColorInterpretation() */
734 : /************************************************************************/
735 :
736 : template <typename CODEC, typename BASE>
737 570 : GDALColorInterp JP2OPJLikeRasterBand<CODEC, BASE>::GetColorInterpretation()
738 : {
739 570 : auto poGDS = (JP2OPJLikeDataset<CODEC, BASE> *)poDS;
740 :
741 570 : if (poCT)
742 6 : return GCI_PaletteIndex;
743 :
744 564 : if (nBand == poGDS->nAlphaIndex + 1)
745 22 : return GCI_AlphaBand;
746 :
747 949 : if (poGDS->nBands <= 2 &&
748 407 : poGDS->eColorSpace == CODEC::cvtenum(JP2_CLRSPC_GRAY))
749 386 : return GCI_GrayIndex;
750 247 : else if (poGDS->eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SRGB) ||
751 91 : poGDS->eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SYCC))
752 : {
753 77 : if (nBand == poGDS->nRedIndex + 1)
754 26 : return GCI_RedBand;
755 51 : if (nBand == poGDS->nGreenIndex + 1)
756 25 : return GCI_GreenBand;
757 26 : if (nBand == poGDS->nBlueIndex + 1)
758 25 : return GCI_BlueBand;
759 : }
760 :
761 80 : return GCI_Undefined;
762 : }
763 :
764 : /************************************************************************/
765 : /* ==================================================================== */
766 : /* JP2OPJLikeDataset */
767 : /* ==================================================================== */
768 : /************************************************************************/
769 :
770 : /************************************************************************/
771 : /* JP2OPJLikeDataset() */
772 : /************************************************************************/
773 :
774 : template <typename CODEC, typename BASE>
775 1902 : JP2OPJLikeDataset<CODEC, BASE>::JP2OPJLikeDataset()
776 : {
777 1902 : this->init();
778 1902 : }
779 :
780 : /************************************************************************/
781 : /* ~JP2OPJLikeDataset() */
782 : /************************************************************************/
783 :
784 : template <typename CODEC, typename BASE>
785 2778 : JP2OPJLikeDataset<CODEC, BASE>::~JP2OPJLikeDataset()
786 :
787 : {
788 1902 : JP2OPJLikeDataset::Close();
789 1902 : this->deinit();
790 4680 : }
791 :
792 : /************************************************************************/
793 : /* Close() */
794 : /************************************************************************/
795 :
796 : template <typename CODEC, typename BASE>
797 2594 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::Close()
798 : {
799 2594 : CPLErr eErr = CE_None;
800 2594 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
801 : {
802 1902 : if (JP2OPJLikeDataset::FlushCache(true) != CE_None)
803 0 : eErr = CE_Failure;
804 :
805 1902 : this->closeJP2();
806 1902 : if (this->iLevel == 0 && this->fp_ != nullptr)
807 : {
808 759 : if (this->bRewrite)
809 : {
810 18 : GDALJP2Box oBox(this->fp_);
811 9 : vsi_l_offset nOffsetJP2C = 0;
812 9 : vsi_l_offset nLengthJP2C = 0;
813 9 : vsi_l_offset nOffsetXML = 0;
814 9 : vsi_l_offset nOffsetASOC = 0;
815 9 : vsi_l_offset nOffsetUUID = 0;
816 9 : vsi_l_offset nOffsetIHDR = 0;
817 9 : vsi_l_offset nLengthIHDR = 0;
818 9 : int bMSIBox = FALSE;
819 9 : int bGMLData = FALSE;
820 9 : int bUnsupportedConfiguration = FALSE;
821 9 : if (oBox.ReadFirst())
822 : {
823 47 : while (strlen(oBox.GetType()) > 0)
824 : {
825 47 : if (EQUAL(oBox.GetType(), "jp2c"))
826 : {
827 9 : if (nOffsetJP2C == 0)
828 : {
829 9 : nOffsetJP2C = VSIFTellL(this->fp_);
830 9 : nLengthJP2C = oBox.GetDataLength();
831 : }
832 : else
833 0 : bUnsupportedConfiguration = TRUE;
834 : }
835 38 : else if (EQUAL(oBox.GetType(), "jp2h"))
836 : {
837 18 : GDALJP2Box oSubBox(this->fp_);
838 18 : if (oSubBox.ReadFirstChild(&oBox) &&
839 9 : EQUAL(oSubBox.GetType(), "ihdr"))
840 : {
841 9 : nOffsetIHDR = VSIFTellL(this->fp_);
842 9 : nLengthIHDR = oSubBox.GetDataLength();
843 : }
844 : }
845 29 : else if (EQUAL(oBox.GetType(), "xml "))
846 : {
847 2 : if (nOffsetXML == 0)
848 2 : nOffsetXML = VSIFTellL(this->fp_);
849 : }
850 27 : else if (EQUAL(oBox.GetType(), "asoc"))
851 : {
852 3 : if (nOffsetASOC == 0)
853 3 : nOffsetASOC = VSIFTellL(this->fp_);
854 :
855 6 : GDALJP2Box oSubBox(this->fp_);
856 6 : if (oSubBox.ReadFirstChild(&oBox) &&
857 3 : EQUAL(oSubBox.GetType(), "lbl "))
858 : {
859 3 : char *pszLabel = (char *)oSubBox.ReadBoxData();
860 3 : if (pszLabel != nullptr &&
861 3 : EQUAL(pszLabel, "gml.data"))
862 : {
863 3 : bGMLData = TRUE;
864 : }
865 : else
866 0 : bUnsupportedConfiguration = TRUE;
867 3 : CPLFree(pszLabel);
868 : }
869 : else
870 0 : bUnsupportedConfiguration = TRUE;
871 : }
872 24 : else if (EQUAL(oBox.GetType(), "uuid"))
873 : {
874 4 : if (nOffsetUUID == 0)
875 4 : nOffsetUUID = VSIFTellL(this->fp_);
876 4 : if (GDALJP2Metadata::IsUUID_MSI(oBox.GetUUID()))
877 4 : bMSIBox = TRUE;
878 0 : else if (!GDALJP2Metadata::IsUUID_XMP(
879 : oBox.GetUUID()))
880 0 : bUnsupportedConfiguration = TRUE;
881 : }
882 20 : else if (!EQUAL(oBox.GetType(), "jP ") &&
883 11 : !EQUAL(oBox.GetType(), "ftyp") &&
884 2 : !EQUAL(oBox.GetType(), "rreq") &&
885 31 : !EQUAL(oBox.GetType(), "jp2h") &&
886 0 : !EQUAL(oBox.GetType(), "jp2i"))
887 : {
888 0 : bUnsupportedConfiguration = TRUE;
889 : }
890 :
891 47 : if (bUnsupportedConfiguration || !oBox.ReadNext())
892 9 : break;
893 : }
894 : }
895 :
896 : const char *pszGMLJP2;
897 9 : int bGeoreferencingCompatOfGMLJP2 =
898 9 : (!m_oSRS.IsEmpty() && bGeoTransformValid && nGCPCount == 0);
899 9 : if (bGeoreferencingCompatOfGMLJP2 &&
900 3 : ((this->bHasGeoreferencingAtOpening && bGMLData) ||
901 2 : (!this->bHasGeoreferencingAtOpening)))
902 2 : pszGMLJP2 = "GMLJP2=YES";
903 : else
904 7 : pszGMLJP2 = "GMLJP2=NO";
905 :
906 : const char *pszGeoJP2;
907 9 : int bGeoreferencingCompatOfGeoJP2 =
908 9 : (!m_oSRS.IsEmpty() || nGCPCount != 0 || bGeoTransformValid);
909 9 : if (bGeoreferencingCompatOfGeoJP2 &&
910 6 : ((this->bHasGeoreferencingAtOpening && bMSIBox) ||
911 4 : (!this->bHasGeoreferencingAtOpening) ||
912 2 : this->nGCPCount > 0))
913 5 : pszGeoJP2 = "GeoJP2=YES";
914 : else
915 4 : pszGeoJP2 = "GeoJP2=NO";
916 :
917 : /* Test that the length of the JP2C box is not 0 */
918 9 : int bJP2CBoxOKForRewriteInPlace = TRUE;
919 9 : if (nOffsetJP2C > 16 && !bUnsupportedConfiguration)
920 : {
921 9 : VSIFSeekL(this->fp_, nOffsetJP2C - 8, SEEK_SET);
922 : GByte abyBuffer[8];
923 9 : VSIFReadL(abyBuffer, 1, 8, this->fp_);
924 9 : if (STARTS_WITH_CI((const char *)abyBuffer + 4, "jp2c") &&
925 9 : abyBuffer[0] == 0 && abyBuffer[1] == 0 &&
926 9 : abyBuffer[2] == 0 && abyBuffer[3] == 0)
927 : {
928 1 : if ((vsi_l_offset)(GUInt32)(nLengthJP2C + 8) ==
929 1 : (nLengthJP2C + 8))
930 : {
931 1 : CPLDebug(
932 : CODEC::debugId(),
933 : "Patching length of JP2C box with real length");
934 1 : VSIFSeekL(this->fp_, nOffsetJP2C - 8, SEEK_SET);
935 1 : GUInt32 nLength = (GUInt32)nLengthJP2C + 8;
936 1 : CPL_MSBPTR32(&nLength);
937 1 : if (VSIFWriteL(&nLength, 1, 4, this->fp_) != 1)
938 1 : eErr = CE_Failure;
939 : }
940 : else
941 0 : bJP2CBoxOKForRewriteInPlace = FALSE;
942 : }
943 : }
944 :
945 9 : if (nOffsetJP2C == 0 || bUnsupportedConfiguration)
946 : {
947 0 : eErr = CE_Failure;
948 0 : CPLError(CE_Failure, CPLE_AppDefined,
949 : "Cannot rewrite file due to unsupported JP2 box "
950 : "configuration");
951 0 : VSIFCloseL(this->fp_);
952 : }
953 9 : else if (bJP2CBoxOKForRewriteInPlace &&
954 9 : (nOffsetXML == 0 || nOffsetXML > nOffsetJP2C) &&
955 9 : (nOffsetASOC == 0 || nOffsetASOC > nOffsetJP2C) &&
956 4 : (nOffsetUUID == 0 || nOffsetUUID > nOffsetJP2C))
957 : {
958 5 : CPLDebug(CODEC::debugId(),
959 : "Rewriting boxes after codestream");
960 :
961 : /* Update IPR flag */
962 5 : if (nLengthIHDR == 14)
963 : {
964 5 : VSIFSeekL(this->fp_, nOffsetIHDR + nLengthIHDR - 1,
965 : SEEK_SET);
966 5 : GByte bIPR = GetMetadata("xml:IPR") != nullptr;
967 5 : if (VSIFWriteL(&bIPR, 1, 1, this->fp_) != 1)
968 0 : eErr = CE_Failure;
969 : }
970 :
971 5 : VSIFSeekL(this->fp_, nOffsetJP2C + nLengthJP2C, SEEK_SET);
972 :
973 10 : GDALJP2Metadata oJP2MD;
974 5 : if (GetGCPCount() > 0)
975 : {
976 1 : oJP2MD.SetGCPs(GetGCPCount(), GetGCPs());
977 1 : oJP2MD.SetSpatialRef(GetGCPSpatialRef());
978 : }
979 : else
980 : {
981 4 : const OGRSpatialReference *poSRS = GetSpatialRef();
982 4 : if (poSRS != nullptr)
983 : {
984 1 : oJP2MD.SetSpatialRef(poSRS);
985 : }
986 4 : if (bGeoTransformValid)
987 : {
988 1 : oJP2MD.SetGeoTransform(adfGeoTransform);
989 : }
990 : }
991 :
992 : const char *pszAreaOrPoint =
993 5 : GetMetadataItem(GDALMD_AREA_OR_POINT);
994 5 : oJP2MD.bPixelIsPoint =
995 5 : pszAreaOrPoint != nullptr &&
996 0 : EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);
997 :
998 5 : if (!WriteIPRBox(this->fp_, this))
999 0 : eErr = CE_Failure;
1000 :
1001 5 : if (bGeoreferencingCompatOfGMLJP2 &&
1002 1 : EQUAL(pszGMLJP2, "GMLJP2=YES"))
1003 : {
1004 : GDALJP2Box *poBox =
1005 1 : oJP2MD.CreateGMLJP2(nRasterXSize, nRasterYSize);
1006 1 : if (!WriteBox(this->fp_, poBox))
1007 0 : eErr = CE_Failure;
1008 1 : delete poBox;
1009 : }
1010 :
1011 10 : if (!WriteXMLBoxes(this->fp_, this) ||
1012 5 : !WriteGDALMetadataBox(this->fp_, this, nullptr))
1013 0 : eErr = CE_Failure;
1014 :
1015 5 : if (bGeoreferencingCompatOfGeoJP2 &&
1016 2 : EQUAL(pszGeoJP2, "GeoJP2=YES"))
1017 : {
1018 2 : GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
1019 2 : if (!WriteBox(this->fp_, poBox))
1020 0 : eErr = CE_Failure;
1021 2 : delete poBox;
1022 : }
1023 :
1024 5 : if (!WriteXMPBox(this->fp_, this))
1025 0 : eErr = CE_Failure;
1026 :
1027 5 : if (VSIFTruncateL(this->fp_, VSIFTellL(this->fp_)) != 0)
1028 0 : eErr = CE_Failure;
1029 :
1030 5 : if (VSIFCloseL(this->fp_) != 0)
1031 5 : eErr = CE_Failure;
1032 : }
1033 : else
1034 : {
1035 4 : VSIFCloseL(this->fp_);
1036 :
1037 4 : CPLDebug(CODEC::debugId(), "Rewriting whole file");
1038 :
1039 4 : const char *const apszOptions[] = {"USE_SRC_CODESTREAM=YES",
1040 : "CODEC=JP2",
1041 : "WRITE_METADATA=YES",
1042 : pszGMLJP2,
1043 : pszGeoJP2,
1044 : nullptr};
1045 8 : CPLString osTmpFilename(
1046 4 : CPLSPrintf("%s.tmp", GetDescription()));
1047 4 : GDALDataset *poOutDS = CreateCopy(
1048 : osTmpFilename, this, FALSE, (char **)apszOptions,
1049 : GDALDummyProgress, nullptr);
1050 4 : if (poOutDS)
1051 : {
1052 4 : if (GDALClose(poOutDS) != CE_None)
1053 0 : eErr = CE_Failure;
1054 4 : if (VSIRename(osTmpFilename, GetDescription()) != 0)
1055 0 : eErr = CE_Failure;
1056 : }
1057 : else
1058 : {
1059 0 : eErr = CE_Failure;
1060 0 : VSIUnlink(osTmpFilename);
1061 : }
1062 4 : VSIUnlink(CPLSPrintf("%s.tmp.aux.xml", GetDescription()));
1063 : }
1064 : }
1065 : else
1066 750 : VSIFCloseL(this->fp_);
1067 : }
1068 :
1069 1902 : JP2OPJLikeDataset::CloseDependentDatasets();
1070 :
1071 1902 : if (GDALPamDataset::Close() != CE_None)
1072 0 : eErr = CE_Failure;
1073 : }
1074 2594 : return eErr;
1075 : }
1076 :
1077 : /************************************************************************/
1078 : /* CloseDependentDatasets() */
1079 : /************************************************************************/
1080 :
1081 : template <typename CODEC, typename BASE>
1082 1918 : int JP2OPJLikeDataset<CODEC, BASE>::CloseDependentDatasets()
1083 : {
1084 1918 : int bRet = GDALJP2AbstractDataset::CloseDependentDatasets();
1085 1918 : if (papoOverviewDS)
1086 : {
1087 178 : for (int i = 0; i < this->nOverviewCount; i++)
1088 115 : delete papoOverviewDS[i];
1089 63 : CPLFree(papoOverviewDS);
1090 63 : papoOverviewDS = nullptr;
1091 63 : bRet = TRUE;
1092 : }
1093 1918 : return bRet;
1094 : }
1095 :
1096 : /************************************************************************/
1097 : /* SetSpatialRef() */
1098 : /************************************************************************/
1099 :
1100 : template <typename CODEC, typename BASE>
1101 : CPLErr
1102 18 : JP2OPJLikeDataset<CODEC, BASE>::SetSpatialRef(const OGRSpatialReference *poSRS)
1103 : {
1104 18 : if (eAccess == GA_Update)
1105 : {
1106 2 : this->bRewrite = TRUE;
1107 2 : m_oSRS.Clear();
1108 2 : if (poSRS)
1109 1 : m_oSRS = *poSRS;
1110 2 : return CE_None;
1111 : }
1112 : else
1113 16 : return GDALJP2AbstractDataset::SetSpatialRef(poSRS);
1114 : }
1115 :
1116 : /************************************************************************/
1117 : /* SetGeoTransform() */
1118 : /************************************************************************/
1119 :
1120 : template <typename CODEC, typename BASE>
1121 24 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::SetGeoTransform(double *padfGeoTransform)
1122 : {
1123 24 : if (eAccess == GA_Update)
1124 : {
1125 4 : this->bRewrite = TRUE;
1126 4 : memcpy(adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
1127 4 : bGeoTransformValid =
1128 6 : !(adfGeoTransform[0] == 0.0 && adfGeoTransform[1] == 1.0 &&
1129 2 : adfGeoTransform[2] == 0.0 && adfGeoTransform[3] == 0.0 &&
1130 1 : adfGeoTransform[4] == 0.0 && adfGeoTransform[5] == 1.0);
1131 4 : return CE_None;
1132 : }
1133 : else
1134 20 : return GDALJP2AbstractDataset::SetGeoTransform(padfGeoTransform);
1135 : }
1136 :
1137 : /************************************************************************/
1138 : /* SetGCPs() */
1139 : /************************************************************************/
1140 :
1141 : template <typename CODEC, typename BASE>
1142 4 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::SetGCPs(int nGCPCountIn,
1143 : const GDAL_GCP *pasGCPListIn,
1144 : const OGRSpatialReference *poSRS)
1145 : {
1146 4 : if (eAccess == GA_Update)
1147 : {
1148 3 : this->bRewrite = TRUE;
1149 3 : if (nGCPCount > 0)
1150 : {
1151 1 : GDALDeinitGCPs(nGCPCount, pasGCPList);
1152 1 : CPLFree(pasGCPList);
1153 : }
1154 :
1155 3 : m_oSRS.Clear();
1156 3 : if (poSRS)
1157 2 : m_oSRS = *poSRS;
1158 :
1159 3 : nGCPCount = nGCPCountIn;
1160 3 : pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);
1161 :
1162 3 : return CE_None;
1163 : }
1164 : else
1165 1 : return GDALJP2AbstractDataset::SetGCPs(nGCPCountIn, pasGCPListIn,
1166 1 : poSRS);
1167 : }
1168 :
1169 : /************************************************************************/
1170 : /* SetMetadata() */
1171 : /************************************************************************/
1172 :
1173 : template <typename CODEC, typename BASE>
1174 11 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::SetMetadata(char **papszMetadata,
1175 : const char *pszDomain)
1176 : {
1177 11 : if (eAccess == GA_Update)
1178 : {
1179 2 : this->bRewrite = TRUE;
1180 2 : if (pszDomain == nullptr || EQUAL(pszDomain, ""))
1181 : {
1182 1 : CSLDestroy(m_papszMainMD);
1183 1 : m_papszMainMD = CSLDuplicate(papszMetadata);
1184 : }
1185 2 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1186 : }
1187 9 : return GDALJP2AbstractDataset::SetMetadata(papszMetadata, pszDomain);
1188 : }
1189 :
1190 : /************************************************************************/
1191 : /* SetMetadata() */
1192 : /************************************************************************/
1193 :
1194 : template <typename CODEC, typename BASE>
1195 3 : CPLErr JP2OPJLikeDataset<CODEC, BASE>::SetMetadataItem(const char *pszName,
1196 : const char *pszValue,
1197 : const char *pszDomain)
1198 : {
1199 3 : if (eAccess == GA_Update)
1200 : {
1201 1 : this->bRewrite = TRUE;
1202 1 : if (pszDomain == nullptr || EQUAL(pszDomain, ""))
1203 : {
1204 1 : m_papszMainMD = CSLSetNameValue(GetMetadata(), pszName, pszValue);
1205 : }
1206 1 : return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
1207 : }
1208 2 : return GDALJP2AbstractDataset::SetMetadataItem(pszName, pszValue,
1209 2 : pszDomain);
1210 : }
1211 :
1212 : /************************************************************************/
1213 : /* Identify() */
1214 : /************************************************************************/
1215 :
1216 : #ifndef jpc_header_defined
1217 : #define jpc_header_defined
1218 : static const unsigned char jpc_header[] = {0xff, 0x4f, 0xff,
1219 : 0x51}; // SOC + RSIZ markers
1220 : static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; /* 'jP ' */
1221 : #endif
1222 :
1223 : template <typename CODEC, typename BASE>
1224 766 : int JP2OPJLikeDataset<CODEC, BASE>::Identify(GDALOpenInfo *poOpenInfo)
1225 :
1226 : {
1227 766 : if (poOpenInfo->nHeaderBytes >= 16 &&
1228 766 : (memcmp(poOpenInfo->pabyHeader, jpc_header, sizeof(jpc_header)) == 0 ||
1229 683 : memcmp(poOpenInfo->pabyHeader + 4, jp2_box_jp, sizeof(jp2_box_jp)) ==
1230 : 0))
1231 766 : return TRUE;
1232 :
1233 : else
1234 0 : return FALSE;
1235 : }
1236 :
1237 : /************************************************************************/
1238 : /* JP2FindCodeStream() */
1239 : /************************************************************************/
1240 :
1241 773 : static vsi_l_offset JP2FindCodeStream(VSILFILE *fp, vsi_l_offset *pnLength)
1242 : {
1243 773 : vsi_l_offset nCodeStreamStart = 0;
1244 773 : vsi_l_offset nCodeStreamLength = 0;
1245 :
1246 773 : VSIFSeekL(fp, 0, SEEK_SET);
1247 : GByte abyHeader[16];
1248 773 : VSIFReadL(abyHeader, 1, 16, fp);
1249 :
1250 773 : if (memcmp(abyHeader, jpc_header, sizeof(jpc_header)) == 0)
1251 : {
1252 83 : VSIFSeekL(fp, 0, SEEK_END);
1253 83 : nCodeStreamLength = VSIFTellL(fp);
1254 : }
1255 690 : else if (memcmp(abyHeader + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0)
1256 : {
1257 : /* Find offset of first jp2c box */
1258 1378 : GDALJP2Box oBox(fp);
1259 689 : if (oBox.ReadFirst())
1260 : {
1261 3585 : while (strlen(oBox.GetType()) > 0)
1262 : {
1263 3585 : if (EQUAL(oBox.GetType(), "jp2c"))
1264 : {
1265 688 : nCodeStreamStart = VSIFTellL(fp);
1266 688 : nCodeStreamLength = oBox.GetDataLength();
1267 688 : break;
1268 : }
1269 :
1270 2897 : if (!oBox.ReadNext())
1271 1 : break;
1272 : }
1273 : }
1274 : }
1275 773 : *pnLength = nCodeStreamLength;
1276 773 : return nCodeStreamStart;
1277 : }
1278 :
1279 : /************************************************************************/
1280 : /* Open() */
1281 : /************************************************************************/
1282 :
1283 : template <typename CODEC, typename BASE>
1284 766 : GDALDataset *JP2OPJLikeDataset<CODEC, BASE>::Open(GDALOpenInfo *poOpenInfo)
1285 :
1286 : {
1287 766 : if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
1288 0 : return nullptr;
1289 :
1290 : /* Detect which codec to use : J2K or JP2 ? */
1291 766 : vsi_l_offset nCodeStreamLength = 0;
1292 : vsi_l_offset nCodeStreamStart =
1293 766 : JP2FindCodeStream(poOpenInfo->fpL, &nCodeStreamLength);
1294 :
1295 766 : if (nCodeStreamStart == 0 && nCodeStreamLength == 0)
1296 : {
1297 1 : CPLError(CE_Failure, CPLE_AppDefined, "No code-stream in JP2 file");
1298 1 : return nullptr;
1299 : }
1300 1530 : JP2OPJLikeDataset oTmpDS;
1301 765 : int numThreads = oTmpDS.GetNumThreads();
1302 765 : auto eCodecFormat = (nCodeStreamStart == 0) ? CODEC::cvtenum(JP2_CODEC_J2K)
1303 682 : : CODEC::cvtenum(JP2_CODEC_JP2);
1304 :
1305 765 : uint32_t nTileW = 0, nTileH = 0;
1306 765 : int numResolutions = 0;
1307 1530 : CODEC localctx;
1308 765 : localctx.open(poOpenInfo->fpL, nCodeStreamStart);
1309 765 : if (!localctx.setUpDecompress(numThreads, nCodeStreamLength, &nTileW,
1310 : &nTileH, &numResolutions))
1311 6 : return nullptr;
1312 :
1313 759 : GDALDataType eDataType = GDT_Byte;
1314 759 : if (localctx.psImage->comps[0].prec > 16)
1315 : {
1316 0 : if (localctx.psImage->comps[0].sgnd)
1317 0 : eDataType = GDT_Int32;
1318 : else
1319 0 : eDataType = GDT_UInt32;
1320 : }
1321 759 : else if (localctx.psImage->comps[0].prec > 8)
1322 : {
1323 26 : if (localctx.psImage->comps[0].sgnd)
1324 7 : eDataType = GDT_Int16;
1325 : else
1326 19 : eDataType = GDT_UInt16;
1327 : }
1328 :
1329 759 : int bIs420 =
1330 1518 : (localctx.psImage->color_space != CODEC::cvtenum(JP2_CLRSPC_SRGB) &&
1331 733 : eDataType == GDT_Byte &&
1332 733 : (localctx.psImage->numcomps == 3 || localctx.psImage->numcomps == 4) &&
1333 58 : localctx.psImage->comps[1].w == localctx.psImage->comps[0].w / 2 &&
1334 3 : localctx.psImage->comps[1].h == localctx.psImage->comps[0].h / 2 &&
1335 3 : localctx.psImage->comps[2].w == localctx.psImage->comps[0].w / 2 &&
1336 1521 : localctx.psImage->comps[2].h == localctx.psImage->comps[0].h / 2) &&
1337 3 : (localctx.psImage->numcomps == 3 ||
1338 2 : (localctx.psImage->numcomps == 4 &&
1339 2 : localctx.psImage->comps[3].w == localctx.psImage->comps[0].w &&
1340 2 : localctx.psImage->comps[3].h == localctx.psImage->comps[0].h));
1341 :
1342 759 : if (bIs420)
1343 : {
1344 3 : CPLDebug(CODEC::debugId(), "420 format");
1345 : }
1346 : else
1347 : {
1348 : int iBand;
1349 917 : for (iBand = 2; iBand <= (int)localctx.psImage->numcomps; iBand++)
1350 : {
1351 161 : if (localctx.psImage->comps[iBand - 1].w !=
1352 161 : localctx.psImage->comps[0].w ||
1353 161 : localctx.psImage->comps[iBand - 1].h !=
1354 161 : localctx.psImage->comps[0].h)
1355 : {
1356 0 : CPLDebug(CODEC::debugId(), "Unable to handle that image (2)");
1357 0 : localctx.free();
1358 0 : return nullptr;
1359 : }
1360 : }
1361 : }
1362 :
1363 : /* -------------------------------------------------------------------- */
1364 : /* Create a corresponding GDALDataset. */
1365 : /* -------------------------------------------------------------------- */
1366 : JP2OPJLikeDataset *poDS;
1367 : int iBand;
1368 :
1369 759 : poDS = new JP2OPJLikeDataset();
1370 759 : poDS->m_osFilename = poOpenInfo->pszFilename;
1371 759 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2))
1372 678 : poDS->eAccess = poOpenInfo->eAccess;
1373 759 : poDS->eColorSpace = localctx.psImage->color_space;
1374 759 : poDS->nRasterXSize = localctx.psImage->x1 - localctx.psImage->x0;
1375 759 : poDS->nRasterYSize = localctx.psImage->y1 - localctx.psImage->y0;
1376 759 : poDS->nBands = localctx.psImage->numcomps;
1377 759 : poDS->fp_ = poOpenInfo->fpL;
1378 759 : poOpenInfo->fpL = nullptr;
1379 759 : poDS->nCodeStreamStart = nCodeStreamStart;
1380 759 : poDS->nCodeStreamLength = nCodeStreamLength;
1381 759 : poDS->bIs420 = bIs420;
1382 1468 : poDS->bSingleTiled = (poDS->nRasterXSize == (int)nTileW &&
1383 709 : poDS->nRasterYSize == (int)nTileH);
1384 759 : poDS->m_nX0 = localctx.psImage->x0;
1385 759 : poDS->m_nY0 = localctx.psImage->y0;
1386 759 : poDS->m_nTileWidth = nTileW;
1387 759 : poDS->m_nTileHeight = nTileH;
1388 :
1389 759 : int nBlockXSize = (int)nTileW;
1390 759 : int nBlockYSize = (int)nTileH;
1391 :
1392 759 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "USE_TILE_AS_BLOCK", false))
1393 : {
1394 0 : poDS->bUseSetDecodeArea = false;
1395 : }
1396 :
1397 759 : poDS->m_bStrict = CPLTestBool(
1398 759 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "STRICT", "YES"));
1399 759 : localctx.updateStrict(poDS->m_bStrict);
1400 :
1401 759 : if (localctx.preferPerBlockDeCompress())
1402 : {
1403 : /* Some Sentinel2 preview datasets are 343x343 large, but with 8x8 blocks */
1404 : /* Using the tile API for that is super slow, so expose a single block */
1405 759 : if (poDS->nRasterXSize <= 1024 && poDS->nRasterYSize <= 1024 &&
1406 743 : nTileW < 32 && nTileH < 32)
1407 : {
1408 574 : poDS->bUseSetDecodeArea = true;
1409 574 : nBlockXSize = poDS->nRasterXSize;
1410 574 : nBlockYSize = poDS->nRasterYSize;
1411 : }
1412 : else
1413 : {
1414 185 : poDS->bUseSetDecodeArea =
1415 322 : poDS->bSingleTiled &&
1416 137 : (poDS->nRasterXSize > 1024 || poDS->nRasterYSize > 1024);
1417 :
1418 : /* Other Sentinel2 preview datasets are 343x343 and 60m are 1830x1830,
1419 : * but they */
1420 : /* are tiled with tile dimensions 2048x2048. It would be a waste of */
1421 : /* memory to allocate such big blocks */
1422 185 : if (poDS->nRasterXSize < (int)nTileW &&
1423 18 : poDS->nRasterYSize < (int)nTileH)
1424 : {
1425 18 : poDS->bUseSetDecodeArea = TRUE;
1426 18 : nBlockXSize = poDS->nRasterXSize;
1427 18 : nBlockYSize = poDS->nRasterYSize;
1428 18 : if (nBlockXSize > 2048)
1429 0 : nBlockXSize = 2048;
1430 18 : if (nBlockYSize > 2048)
1431 0 : nBlockYSize = 2048;
1432 : }
1433 167 : else if (poDS->bUseSetDecodeArea)
1434 : {
1435 : // Arbitrary threshold... ~4 million at least needed for the GRIB2
1436 : // images mentioned below.
1437 7 : if (nTileH == 1 && nTileW < 20 * 1024 * 1024)
1438 : {
1439 : // Some GRIB2 JPEG2000 compressed images are a 2D image
1440 : // organized as a single line image...
1441 : }
1442 : else
1443 : {
1444 7 : if (nBlockXSize > 1024)
1445 7 : nBlockXSize = 1024;
1446 7 : if (nBlockYSize > 1024)
1447 7 : nBlockYSize = 1024;
1448 : }
1449 : }
1450 : }
1451 : }
1452 :
1453 759 : GDALColorTable *poCT = nullptr;
1454 :
1455 : /* -------------------------------------------------------------------- */
1456 : /* Look for color table or cdef box */
1457 : /* -------------------------------------------------------------------- */
1458 759 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2))
1459 : {
1460 678 : vsi_l_offset nCurOffset = VSIFTellL(poDS->fp_);
1461 :
1462 1356 : GDALJP2Box oBox(poDS->fp_);
1463 678 : if (oBox.ReadFirst())
1464 : {
1465 3625 : while (strlen(oBox.GetType()) > 0)
1466 : {
1467 3625 : if (EQUAL(oBox.GetType(), "jp2h"))
1468 : {
1469 1356 : GDALJP2Box oSubBox(poDS->fp_);
1470 :
1471 2146 : for (oSubBox.ReadFirstChild(&oBox);
1472 2146 : strlen(oSubBox.GetType()) > 0;
1473 1468 : oSubBox.ReadNextChild(&oBox))
1474 : {
1475 1468 : GIntBig nDataLength = oSubBox.GetDataLength();
1476 2911 : if (poCT == nullptr &&
1477 1443 : EQUAL(oSubBox.GetType(), "pclr") &&
1478 2911 : nDataLength >= 3 &&
1479 : nDataLength <= 2 + 1 + 4 + 4 * 256)
1480 : {
1481 24 : GByte *pabyCT = oSubBox.ReadBoxData();
1482 24 : if (pabyCT != nullptr)
1483 : {
1484 24 : int nEntries = (pabyCT[0] << 8) | pabyCT[1];
1485 24 : int nComponents = pabyCT[2];
1486 : /* CPLDebug(CODEC::debugId(), "Color table found"); */
1487 24 : if (nEntries <= 256 && nComponents == 3)
1488 : {
1489 : /*CPLDebug(CODEC::debugId(), "resol[0] = %d",
1490 : pabyCT[3]); CPLDebug(CODEC::debugId(), "resol[1] =
1491 : %d", pabyCT[4]); CPLDebug(CODEC::debugId(),
1492 : "resol[2] = %d", pabyCT[5]);*/
1493 19 : if (pabyCT[3] == 7 && pabyCT[4] == 7 &&
1494 19 : pabyCT[5] == 7 &&
1495 19 : nDataLength == 2 + 1 + 3 + 3 * nEntries)
1496 : {
1497 19 : poCT = new GDALColorTable();
1498 1103 : for (int i = 0; i < nEntries; i++)
1499 : {
1500 : GDALColorEntry sEntry;
1501 1084 : sEntry.c1 = pabyCT[6 + 3 * i];
1502 1084 : sEntry.c2 = pabyCT[6 + 3 * i + 1];
1503 1084 : sEntry.c3 = pabyCT[6 + 3 * i + 2];
1504 1084 : sEntry.c4 = 255;
1505 1084 : poCT->SetColorEntry(i, &sEntry);
1506 : }
1507 19 : }
1508 : }
1509 5 : else if (nEntries <= 256 && nComponents == 4)
1510 : {
1511 5 : if (pabyCT[3] == 7 && pabyCT[4] == 7 &&
1512 5 : pabyCT[5] == 7 && pabyCT[6] == 7 &&
1513 5 : nDataLength == 2 + 1 + 4 + 4 * nEntries)
1514 : {
1515 5 : poCT = new GDALColorTable();
1516 17 : for (int i = 0; i < nEntries; i++)
1517 : {
1518 : GDALColorEntry sEntry;
1519 12 : sEntry.c1 = pabyCT[7 + 4 * i];
1520 12 : sEntry.c2 = pabyCT[7 + 4 * i + 1];
1521 12 : sEntry.c3 = pabyCT[7 + 4 * i + 2];
1522 12 : sEntry.c4 = pabyCT[7 + 4 * i + 3];
1523 12 : poCT->SetColorEntry(i, &sEntry);
1524 : }
1525 : }
1526 : }
1527 24 : CPLFree(pabyCT);
1528 : }
1529 : }
1530 : /* There's a bug/misfeature in openjpeg: the color_space
1531 : only gets set at read tile time */
1532 1444 : else if (EQUAL(oSubBox.GetType(), "colr") &&
1533 : nDataLength == 7)
1534 : {
1535 678 : GByte *pabyContent = oSubBox.ReadBoxData();
1536 678 : if (pabyContent != nullptr)
1537 : {
1538 678 : if (pabyContent[0] ==
1539 : 1 /* enumerated colourspace */)
1540 : {
1541 678 : GUInt32 enumcs = (pabyContent[3] << 24) |
1542 678 : (pabyContent[4] << 16) |
1543 678 : (pabyContent[5] << 8) |
1544 : (pabyContent[6]);
1545 678 : if (enumcs == 16)
1546 : {
1547 53 : poDS->eColorSpace =
1548 53 : CODEC::cvtenum(JP2_CLRSPC_SRGB);
1549 53 : CPLDebug(CODEC::debugId(),
1550 : "SRGB color space");
1551 : }
1552 625 : else if (enumcs == 17)
1553 : {
1554 618 : poDS->eColorSpace =
1555 618 : CODEC::cvtenum(JP2_CLRSPC_GRAY);
1556 618 : CPLDebug(CODEC::debugId(),
1557 : "Grayscale color space");
1558 : }
1559 7 : else if (enumcs == 18)
1560 : {
1561 3 : poDS->eColorSpace =
1562 3 : CODEC::cvtenum(JP2_CLRSPC_SYCC);
1563 3 : CPLDebug(CODEC::debugId(),
1564 : "SYCC color space");
1565 : }
1566 4 : else if (enumcs == 20)
1567 : {
1568 : /* Used by
1569 : * J2KP4files/testfiles_jp2/file7.jp2 */
1570 0 : poDS->eColorSpace =
1571 0 : CODEC::cvtenum(JP2_CLRSPC_SRGB);
1572 0 : CPLDebug(CODEC::debugId(),
1573 : "e-sRGB color space");
1574 : }
1575 4 : else if (enumcs == 21)
1576 : {
1577 : /* Used by
1578 : * J2KP4files/testfiles_jp2/file5.jp2 */
1579 0 : poDS->eColorSpace =
1580 0 : CODEC::cvtenum(JP2_CLRSPC_SRGB);
1581 0 : CPLDebug(CODEC::debugId(),
1582 : "ROMM-RGB color space");
1583 : }
1584 : else
1585 : {
1586 4 : poDS->eColorSpace =
1587 4 : CODEC::cvtenum(JP2_CLRSPC_UNKNOWN);
1588 4 : CPLDebug(CODEC::debugId(),
1589 : "Unknown color space");
1590 : }
1591 : }
1592 678 : CPLFree(pabyContent);
1593 : }
1594 : }
1595 : /* Check if there's an alpha channel or odd channel
1596 : * attribution */
1597 796 : else if (EQUAL(oSubBox.GetType(), "cdef") &&
1598 30 : nDataLength == 2 + poDS->nBands * 6)
1599 : {
1600 29 : GByte *pabyContent = oSubBox.ReadBoxData();
1601 29 : if (pabyContent != nullptr)
1602 : {
1603 29 : int nEntries =
1604 29 : (pabyContent[0] << 8) | pabyContent[1];
1605 29 : if (nEntries == poDS->nBands)
1606 : {
1607 29 : poDS->nRedIndex = -1;
1608 29 : poDS->nGreenIndex = -1;
1609 29 : poDS->nBlueIndex = -1;
1610 130 : for (int i = 0; i < poDS->nBands; i++)
1611 : {
1612 101 : int CNi =
1613 101 : (pabyContent[2 + 6 * i] << 8) |
1614 101 : pabyContent[2 + 6 * i + 1];
1615 101 : int Typi =
1616 101 : (pabyContent[2 + 6 * i + 2] << 8) |
1617 101 : pabyContent[2 + 6 * i + 3];
1618 101 : int Asoci =
1619 101 : (pabyContent[2 + 6 * i + 4] << 8) |
1620 101 : pabyContent[2 + 6 * i + 5];
1621 101 : if (CNi < 0 || CNi >= poDS->nBands)
1622 : {
1623 0 : CPLError(CE_Failure,
1624 : CPLE_AppDefined,
1625 : "Wrong value of CN%d=%d",
1626 : i, CNi);
1627 0 : break;
1628 : }
1629 101 : if (Typi == 0)
1630 : {
1631 75 : if (Asoci == 1)
1632 24 : poDS->nRedIndex = CNi;
1633 51 : else if (Asoci == 2)
1634 18 : poDS->nGreenIndex = CNi;
1635 33 : else if (Asoci == 3)
1636 18 : poDS->nBlueIndex = CNi;
1637 15 : else if (Asoci < 0 ||
1638 15 : (Asoci > poDS->nBands &&
1639 : Asoci != 65535))
1640 : {
1641 0 : CPLError(
1642 : CE_Failure, CPLE_AppDefined,
1643 : "Wrong value of Asoc%d=%d",
1644 : i, Asoci);
1645 0 : break;
1646 : }
1647 : }
1648 26 : else if (Typi == 1)
1649 : {
1650 26 : poDS->nAlphaIndex = CNi;
1651 : }
1652 : }
1653 : }
1654 : else
1655 : {
1656 0 : CPLDebug(CODEC::debugId(),
1657 : "Unsupported cdef content");
1658 : }
1659 29 : CPLFree(pabyContent);
1660 : }
1661 : }
1662 : }
1663 : }
1664 :
1665 3625 : if (!oBox.ReadNext())
1666 678 : break;
1667 : }
1668 : }
1669 :
1670 678 : VSIFSeekL(poDS->fp_, nCurOffset, SEEK_SET);
1671 :
1672 678 : if (poDS->eColorSpace == CODEC::cvtenum(JP2_CLRSPC_GRAY) &&
1673 618 : poDS->nBands == 4 && poDS->nRedIndex == 0 &&
1674 1301 : poDS->nGreenIndex == 1 && poDS->nBlueIndex == 2 &&
1675 5 : poDS->m_osFilename.find("dop10rgbi") != std::string::npos)
1676 : {
1677 0 : CPLDebug(CODEC::debugId(),
1678 : "Autofix wrong colorspace from Greyscale to sRGB");
1679 : // Workaround https://github.com/uclouvain/openjpeg/issues/1464
1680 : // dop10rgbi products from https://www.opengeodata.nrw.de/produkte/geobasis/lusat/dop/dop_jp2_f10/
1681 : // have a wrong color space.
1682 0 : poDS->eColorSpace = CODEC::cvtenum(JP2_CLRSPC_SRGB);
1683 : }
1684 : }
1685 :
1686 : /* -------------------------------------------------------------------- */
1687 : /* Create band information objects. */
1688 : /* -------------------------------------------------------------------- */
1689 1687 : for (iBand = 1; iBand <= poDS->nBands; iBand++)
1690 : {
1691 928 : const bool bPromoteTo8Bit =
1692 954 : iBand == poDS->nAlphaIndex + 1 &&
1693 26 : localctx.psImage
1694 26 : ->comps[(poDS->nAlphaIndex == 0 && poDS->nBands > 1) ? 1
1695 : : 0]
1696 26 : .prec == 8 &&
1697 968 : localctx.psImage->comps[poDS->nAlphaIndex].prec == 1 &&
1698 14 : CPLFetchBool(poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
1699 14 : CPLTestBool(CPLGetConfigOption(
1700 : "JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES")));
1701 928 : if (bPromoteTo8Bit)
1702 10 : CPLDebug(CODEC::debugId(),
1703 : "Alpha band is promoted from 1 bit to 8 bit");
1704 :
1705 1846 : auto poBand = new JP2OPJLikeRasterBand<CODEC, BASE>(
1706 : poDS, iBand, eDataType,
1707 918 : bPromoteTo8Bit ? 8 : localctx.psImage->comps[iBand - 1].prec,
1708 : bPromoteTo8Bit, nBlockXSize, nBlockYSize);
1709 928 : if (iBand == 1 && poCT != nullptr)
1710 24 : poBand->poCT = poCT;
1711 928 : poDS->SetBand(iBand, poBand);
1712 : }
1713 :
1714 : /* -------------------------------------------------------------------- */
1715 : /* Create overview datasets. */
1716 : /* -------------------------------------------------------------------- */
1717 759 : int nW = poDS->nRasterXSize;
1718 759 : int nH = poDS->nRasterYSize;
1719 759 : poDS->nParentXSize = poDS->nRasterXSize;
1720 759 : poDS->nParentYSize = poDS->nRasterYSize;
1721 :
1722 : /* Lower resolutions are not compatible with a color-table */
1723 759 : if (poCT != nullptr)
1724 24 : numResolutions = 0;
1725 :
1726 759 : if (poDS->bSingleTiled && poDS->bUseSetDecodeArea)
1727 : {
1728 579 : poDS->cacheNew(&localctx);
1729 : }
1730 759 : poDS->m_pnLastLevel = new int(-1);
1731 :
1732 117 : while (
1733 876 : poDS->nOverviewCount + 1 < numResolutions && (nW > 128 || nH > 128) &&
1734 120 : (poDS->bUseSetDecodeArea || ((nTileW % 2) == 0 && (nTileH % 2) == 0)))
1735 : {
1736 : // This must be this exact formula per the JPEG2000 standard
1737 117 : nW = (nW + 1) / 2;
1738 117 : nH = (nH + 1) / 2;
1739 :
1740 234 : poDS->papoOverviewDS = (JP2OPJLikeDataset<CODEC, BASE> **)CPLRealloc(
1741 117 : poDS->papoOverviewDS, (poDS->nOverviewCount + 1) *
1742 : sizeof(JP2OPJLikeDataset<CODEC, BASE> *));
1743 117 : JP2OPJLikeDataset *poODS = new JP2OPJLikeDataset();
1744 117 : poODS->m_osFilename = poDS->m_osFilename;
1745 117 : poODS->nParentXSize = poDS->nRasterXSize;
1746 117 : poODS->nParentYSize = poDS->nRasterYSize;
1747 117 : poODS->SetDescription(poOpenInfo->pszFilename);
1748 117 : poODS->iLevel = poDS->nOverviewCount + 1;
1749 117 : poODS->bSingleTiled = poDS->bSingleTiled;
1750 117 : poODS->bUseSetDecodeArea = poDS->bUseSetDecodeArea;
1751 117 : poODS->nRedIndex = poDS->nRedIndex;
1752 117 : poODS->nGreenIndex = poDS->nGreenIndex;
1753 117 : poODS->nBlueIndex = poDS->nBlueIndex;
1754 117 : poODS->nAlphaIndex = poDS->nAlphaIndex;
1755 117 : if (!poDS->bUseSetDecodeArea)
1756 : {
1757 83 : nTileW /= 2;
1758 83 : nTileH /= 2;
1759 83 : nBlockXSize = (int)nTileW;
1760 83 : nBlockYSize = (int)nTileH;
1761 : }
1762 : else
1763 : {
1764 34 : nBlockXSize = std::min(nW, (int)nTileW);
1765 34 : nBlockYSize = std::min(nH, (int)nTileH);
1766 : }
1767 :
1768 117 : poODS->eColorSpace = poDS->eColorSpace;
1769 117 : poODS->nRasterXSize = nW;
1770 117 : poODS->nRasterYSize = nH;
1771 117 : poODS->nBands = poDS->nBands;
1772 117 : poODS->fp_ = poDS->fp_;
1773 117 : poODS->nCodeStreamStart = nCodeStreamStart;
1774 117 : poODS->nCodeStreamLength = nCodeStreamLength;
1775 117 : poODS->bIs420 = bIs420;
1776 :
1777 117 : if (poODS->bSingleTiled && poODS->bUseSetDecodeArea)
1778 : {
1779 32 : poODS->cache(poDS);
1780 : }
1781 117 : poODS->m_pnLastLevel = poDS->m_pnLastLevel;
1782 117 : poODS->m_bStrict = poDS->m_bStrict;
1783 :
1784 117 : poODS->m_nX0 = poDS->m_nX0;
1785 117 : poODS->m_nY0 = poDS->m_nY0;
1786 :
1787 335 : for (iBand = 1; iBand <= poDS->nBands; iBand++)
1788 : {
1789 218 : const bool bPromoteTo8Bit =
1790 237 : iBand == poDS->nAlphaIndex + 1 &&
1791 19 : localctx.psImage
1792 19 : ->comps[(poDS->nAlphaIndex == 0 && poDS->nBands > 1)
1793 : ? 1
1794 : : 0]
1795 19 : .prec == 8 &&
1796 246 : localctx.psImage->comps[poDS->nAlphaIndex].prec == 1 &&
1797 9 : CPLFetchBool(
1798 9 : poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
1799 9 : CPLTestBool(CPLGetConfigOption(
1800 : "JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES")));
1801 :
1802 218 : poODS->SetBand(iBand,
1803 430 : new JP2OPJLikeRasterBand<CODEC, BASE>(
1804 : poODS, iBand, eDataType,
1805 : bPromoteTo8Bit
1806 : ? 8
1807 212 : : localctx.psImage->comps[iBand - 1].prec,
1808 : bPromoteTo8Bit, nBlockXSize, nBlockYSize));
1809 : }
1810 :
1811 117 : poDS->papoOverviewDS[poDS->nOverviewCount++] = poODS;
1812 : }
1813 :
1814 759 : poDS->openCompleteJP2(&localctx);
1815 :
1816 : /* -------------------------------------------------------------------- */
1817 : /* More metadata. */
1818 : /* -------------------------------------------------------------------- */
1819 759 : if (poDS->nBands > 1)
1820 : {
1821 72 : poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
1822 : "IMAGE_STRUCTURE");
1823 : }
1824 :
1825 759 : poOpenInfo->fpL = poDS->fp_;
1826 759 : vsi_l_offset nCurOffset = VSIFTellL(poDS->fp_);
1827 759 : poDS->LoadJP2Metadata(poOpenInfo);
1828 759 : VSIFSeekL(poDS->fp_, nCurOffset, SEEK_SET);
1829 759 : poOpenInfo->fpL = nullptr;
1830 :
1831 759 : poDS->bHasGeoreferencingAtOpening =
1832 994 : (!poDS->m_oSRS.IsEmpty() || poDS->nGCPCount != 0 ||
1833 235 : poDS->bGeoTransformValid);
1834 :
1835 : /* -------------------------------------------------------------------- */
1836 : /* Vector layers */
1837 : /* -------------------------------------------------------------------- */
1838 759 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1839 : {
1840 66 : poDS->LoadVectorLayers(CPLFetchBool(poOpenInfo->papszOpenOptions,
1841 : "OPEN_REMOTE_GML", false));
1842 :
1843 : // If file opened in vector-only mode and there's no vector,
1844 : // return
1845 70 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
1846 4 : poDS->GetLayerCount() == 0)
1847 : {
1848 2 : delete poDS;
1849 2 : return nullptr;
1850 : }
1851 : }
1852 :
1853 : /* -------------------------------------------------------------------- */
1854 : /* Initialize any PAM information. */
1855 : /* -------------------------------------------------------------------- */
1856 757 : poDS->SetDescription(poOpenInfo->pszFilename);
1857 757 : poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
1858 :
1859 : /* -------------------------------------------------------------------- */
1860 : /* Check for overviews. */
1861 : /* -------------------------------------------------------------------- */
1862 757 : poDS->oOvManager.Initialize(poDS, poOpenInfo);
1863 :
1864 757 : return poDS;
1865 : }
1866 :
1867 : /************************************************************************/
1868 : /* WriteBox() */
1869 : /************************************************************************/
1870 :
1871 : template <typename CODEC, typename BASE>
1872 939 : bool JP2OPJLikeDataset<CODEC, BASE>::WriteBox(VSILFILE *fp, GDALJP2Box *poBox)
1873 : {
1874 : GUInt32 nLBox;
1875 : GUInt32 nTBox;
1876 :
1877 939 : if (poBox == nullptr)
1878 0 : return true;
1879 :
1880 939 : nLBox = (int)poBox->GetDataLength() + 8;
1881 939 : nLBox = CPL_MSBWORD32(nLBox);
1882 :
1883 939 : memcpy(&nTBox, poBox->GetType(), 4);
1884 :
1885 939 : return VSIFWriteL(&nLBox, 4, 1, fp) == 1 &&
1886 1878 : VSIFWriteL(&nTBox, 4, 1, fp) == 1 &&
1887 939 : VSIFWriteL(poBox->GetWritableData(), (int)poBox->GetDataLength(), 1,
1888 939 : fp) == 1;
1889 : }
1890 :
1891 : /************************************************************************/
1892 : /* WriteGDALMetadataBox() */
1893 : /************************************************************************/
1894 :
1895 : template <typename CODEC, typename BASE>
1896 19 : bool JP2OPJLikeDataset<CODEC, BASE>::WriteGDALMetadataBox(VSILFILE *fp,
1897 : GDALDataset *poSrcDS,
1898 : char **papszOptions)
1899 : {
1900 19 : bool bRet = true;
1901 38 : GDALJP2Box *poBox = GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(
1902 19 : poSrcDS, CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false));
1903 19 : if (poBox)
1904 6 : bRet = WriteBox(fp, poBox);
1905 19 : delete poBox;
1906 19 : return bRet;
1907 : }
1908 :
1909 : /************************************************************************/
1910 : /* WriteXMLBoxes() */
1911 : /************************************************************************/
1912 :
1913 : template <typename CODEC, typename BASE>
1914 19 : bool JP2OPJLikeDataset<CODEC, BASE>::WriteXMLBoxes(VSILFILE *fp,
1915 : GDALDataset *poSrcDS)
1916 : {
1917 19 : bool bRet = true;
1918 19 : int nBoxes = 0;
1919 19 : GDALJP2Box **papoBoxes = GDALJP2Metadata::CreateXMLBoxes(poSrcDS, &nBoxes);
1920 21 : for (int i = 0; i < nBoxes; i++)
1921 : {
1922 2 : if (!WriteBox(fp, papoBoxes[i]))
1923 0 : bRet = false;
1924 2 : delete papoBoxes[i];
1925 : }
1926 19 : CPLFree(papoBoxes);
1927 19 : return bRet;
1928 : }
1929 :
1930 : /************************************************************************/
1931 : /* WriteXMPBox() */
1932 : /************************************************************************/
1933 :
1934 : template <typename CODEC, typename BASE>
1935 19 : bool JP2OPJLikeDataset<CODEC, BASE>::WriteXMPBox(VSILFILE *fp,
1936 : GDALDataset *poSrcDS)
1937 : {
1938 19 : bool bRet = true;
1939 19 : GDALJP2Box *poBox = GDALJP2Metadata::CreateXMPBox(poSrcDS);
1940 19 : if (poBox)
1941 2 : bRet = WriteBox(fp, poBox);
1942 19 : delete poBox;
1943 19 : return bRet;
1944 : }
1945 :
1946 : /************************************************************************/
1947 : /* WriteIPRBox() */
1948 : /************************************************************************/
1949 :
1950 : template <typename CODEC, typename BASE>
1951 19 : bool JP2OPJLikeDataset<CODEC, BASE>::WriteIPRBox(VSILFILE *fp,
1952 : GDALDataset *poSrcDS)
1953 : {
1954 19 : bool bRet = true;
1955 19 : GDALJP2Box *poBox = GDALJP2Metadata::CreateIPRBox(poSrcDS);
1956 19 : if (poBox)
1957 2 : bRet = WriteBox(fp, poBox);
1958 19 : delete poBox;
1959 19 : return bRet;
1960 : }
1961 :
1962 : /************************************************************************/
1963 : /* FloorPowerOfTwo() */
1964 : /************************************************************************/
1965 :
1966 542 : static int FloorPowerOfTwo(int nVal)
1967 : {
1968 542 : int nBits = 0;
1969 3791 : while (nVal > 1)
1970 : {
1971 3249 : nBits++;
1972 3249 : nVal >>= 1;
1973 : }
1974 542 : return 1 << nBits;
1975 : }
1976 :
1977 : /************************************************************************/
1978 : /* CreateCopy() */
1979 : /************************************************************************/
1980 :
1981 : template <typename CODEC, typename BASE>
1982 281 : GDALDataset *JP2OPJLikeDataset<CODEC, BASE>::CreateCopy(
1983 : const char *pszFilename, GDALDataset *poSrcDS, CPL_UNUSED int bStrict,
1984 : char **papszOptions, GDALProgressFunc pfnProgress, void *pProgressData)
1985 :
1986 : {
1987 281 : int nBands = poSrcDS->GetRasterCount();
1988 281 : int nXSize = poSrcDS->GetRasterXSize();
1989 281 : int nYSize = poSrcDS->GetRasterYSize();
1990 :
1991 281 : if (nBands == 0 || nBands > 16384)
1992 : {
1993 2 : CPLError(
1994 : CE_Failure, CPLE_NotSupported,
1995 : "Unable to export files with %d bands. Must be >= 1 and <= 16384",
1996 : nBands);
1997 2 : return nullptr;
1998 : }
1999 :
2000 279 : GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
2001 279 : if (poCT != nullptr && nBands != 1)
2002 : {
2003 1 : CPLError(CE_Failure, CPLE_NotSupported,
2004 : "JP2 driver only supports a color table for a "
2005 : "single-band dataset");
2006 1 : return nullptr;
2007 : }
2008 :
2009 278 : GDALDataType eDataType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
2010 278 : int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
2011 278 : if (eDataType != GDT_Byte && eDataType != GDT_Int16 &&
2012 8 : eDataType != GDT_UInt16 && eDataType != GDT_Int32 &&
2013 : eDataType != GDT_UInt32)
2014 : {
2015 6 : CPLError(CE_Failure, CPLE_NotSupported,
2016 : "JP2 driver only supports creating Byte, GDT_Int16, "
2017 : "GDT_UInt16, GDT_Int32, GDT_UInt32");
2018 6 : return nullptr;
2019 : }
2020 :
2021 272 : const bool bInspireTG = CPLFetchBool(papszOptions, "INSPIRE_TG", false);
2022 :
2023 : /* -------------------------------------------------------------------- */
2024 : /* Analyze creation options. */
2025 : /* -------------------------------------------------------------------- */
2026 272 : auto eCodecFormat = CODEC::cvtenum(JP2_CODEC_J2K);
2027 272 : const char *pszCodec = CSLFetchNameValueDef(papszOptions, "CODEC", nullptr);
2028 272 : if (pszCodec)
2029 : {
2030 14 : if (EQUAL(pszCodec, "JP2"))
2031 5 : eCodecFormat = CODEC::cvtenum(JP2_CODEC_JP2);
2032 9 : else if (EQUAL(pszCodec, "J2K"))
2033 9 : eCodecFormat = CODEC::cvtenum(JP2_CODEC_J2K);
2034 : else
2035 : {
2036 0 : CPLError(CE_Warning, CPLE_NotSupported,
2037 : "Unsupported value for CODEC : %s. Defaulting to J2K",
2038 : pszCodec);
2039 : }
2040 : }
2041 : else
2042 : {
2043 258 : if (strlen(pszFilename) > 4 &&
2044 258 : EQUAL(pszFilename + strlen(pszFilename) - 4, ".JP2"))
2045 : {
2046 223 : eCodecFormat = CODEC::cvtenum(JP2_CODEC_JP2);
2047 : }
2048 : }
2049 272 : if (eCodecFormat != CODEC::cvtenum(JP2_CODEC_JP2) && bInspireTG)
2050 : {
2051 1 : CPLError(CE_Warning, CPLE_NotSupported,
2052 : "INSPIRE_TG=YES mandates CODEC=JP2 (TG requirement 21)");
2053 1 : return nullptr;
2054 : }
2055 :
2056 : // NOTE: if changing the default block size, the logic in nitfdataset.cpp
2057 : // CreateCopy() will have to be changed as well.
2058 271 : int nBlockXSize =
2059 271 : atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "1024"));
2060 271 : int nBlockYSize =
2061 271 : atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "1024"));
2062 271 : if (nBlockXSize <= 0 || nBlockYSize <= 0)
2063 : {
2064 0 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
2065 0 : return nullptr;
2066 : }
2067 :
2068 : // By default do not generate tile sizes larger than the dataset
2069 : // dimensions
2070 542 : if (!CPLFetchBool(papszOptions, "BLOCKSIZE_STRICT", false) &&
2071 271 : !CPLFetchBool(papszOptions, "@BLOCKSIZE_STRICT", false))
2072 : {
2073 267 : if (nBlockXSize < 32 || nBlockYSize < 32)
2074 : {
2075 0 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
2076 0 : return nullptr;
2077 : }
2078 :
2079 267 : if (nXSize < nBlockXSize)
2080 : {
2081 242 : CPLDebug(CODEC::debugId(), "Adjusting block width from %d to %d",
2082 : nBlockXSize, nXSize);
2083 242 : nBlockXSize = nXSize;
2084 : }
2085 267 : if (nYSize < nBlockYSize)
2086 : {
2087 243 : CPLDebug(CODEC::debugId(), "Adjusting block width from %d to %d",
2088 : nBlockYSize, nYSize);
2089 243 : nBlockYSize = nYSize;
2090 : }
2091 : }
2092 :
2093 271 : JP2_PROG_ORDER eProgOrder = JP2_LRCP;
2094 : const char *pszPROGORDER =
2095 271 : CSLFetchNameValueDef(papszOptions, "PROGRESSION", "LRCP");
2096 271 : if (EQUAL(pszPROGORDER, "LRCP"))
2097 271 : eProgOrder = JP2_LRCP;
2098 0 : else if (EQUAL(pszPROGORDER, "RLCP"))
2099 0 : eProgOrder = JP2_RLCP;
2100 0 : else if (EQUAL(pszPROGORDER, "RPCL"))
2101 0 : eProgOrder = JP2_RPCL;
2102 0 : else if (EQUAL(pszPROGORDER, "PCRL"))
2103 0 : eProgOrder = JP2_PCRL;
2104 0 : else if (EQUAL(pszPROGORDER, "CPRL"))
2105 0 : eProgOrder = JP2_CPRL;
2106 : else
2107 : {
2108 0 : CPLError(CE_Warning, CPLE_NotSupported,
2109 : "Unsupported value for PROGRESSION : %s. Defaulting to LRCP",
2110 : pszPROGORDER);
2111 : }
2112 :
2113 271 : const bool bIsIrreversible =
2114 271 : !CPLFetchBool(papszOptions, "REVERSIBLE", poCT != nullptr);
2115 :
2116 542 : std::vector<double> adfRates;
2117 : const char *pszQuality =
2118 271 : CSLFetchNameValueDef(papszOptions, "QUALITY", nullptr);
2119 271 : double dfDefaultQuality = (poCT != nullptr) ? 100.0 : 25.0;
2120 271 : if (pszQuality)
2121 : {
2122 : char **papszTokens =
2123 41 : CSLTokenizeStringComplex(pszQuality, ",", FALSE, FALSE);
2124 158 : for (int i = 0; papszTokens[i] != nullptr; i++)
2125 : {
2126 117 : double dfQuality = CPLAtof(papszTokens[i]);
2127 117 : if (dfQuality > 0 && dfQuality <= 100)
2128 : {
2129 117 : double dfRate = 100 / dfQuality;
2130 117 : adfRates.push_back(dfRate);
2131 : }
2132 : else
2133 : {
2134 0 : CPLError(CE_Warning, CPLE_NotSupported,
2135 : "Unsupported value for QUALITY: %s. Defaulting to "
2136 : "single-layer, with quality=%.0f",
2137 0 : papszTokens[i], dfDefaultQuality);
2138 0 : adfRates.resize(0);
2139 0 : break;
2140 : }
2141 : }
2142 41 : if (papszTokens[0] == nullptr)
2143 : {
2144 0 : CPLError(CE_Warning, CPLE_NotSupported,
2145 : "Unsupported value for QUALITY: %s. Defaulting to "
2146 : "single-layer, with quality=%.0f",
2147 : pszQuality, dfDefaultQuality);
2148 : }
2149 41 : CSLDestroy(papszTokens);
2150 : }
2151 271 : if (adfRates.empty())
2152 : {
2153 230 : adfRates.push_back(100. / dfDefaultQuality);
2154 230 : assert(!adfRates.empty());
2155 : }
2156 :
2157 271 : if (poCT != nullptr && (bIsIrreversible || adfRates.back() != 1.0))
2158 : {
2159 2 : CPLError(CE_Warning, CPLE_AppDefined,
2160 : "Encoding a dataset with a color table with REVERSIBLE != YES "
2161 : "or QUALITY != 100 will likely lead to bad visual results");
2162 : }
2163 :
2164 271 : const int nMaxTileDim = std::max(nBlockXSize, nBlockYSize);
2165 271 : const int nMinTileDim = std::min(nBlockXSize, nBlockYSize);
2166 271 : int nNumResolutions = 1;
2167 : /* Pickup a reasonable value compatible with PROFILE_1 requirements */
2168 362 : while ((nMaxTileDim >> (nNumResolutions - 1)) > 128 &&
2169 92 : (nMinTileDim >> nNumResolutions) > 0)
2170 91 : nNumResolutions++;
2171 271 : int nMinProfile1Resolutions = nNumResolutions;
2172 : const char *pszResolutions =
2173 271 : CSLFetchNameValueDef(papszOptions, "RESOLUTIONS", nullptr);
2174 271 : if (pszResolutions)
2175 : {
2176 10 : nNumResolutions = atoi(pszResolutions);
2177 10 : if (nNumResolutions <= 0 || nNumResolutions >= 32 ||
2178 9 : (nMinTileDim >> nNumResolutions) == 0 ||
2179 9 : (nMaxTileDim >> nNumResolutions) == 0)
2180 : {
2181 1 : CPLError(CE_Warning, CPLE_NotSupported,
2182 : "Unsupported value for RESOLUTIONS : %s. Defaulting to %d",
2183 : pszResolutions, nMinProfile1Resolutions);
2184 1 : nNumResolutions = nMinProfile1Resolutions;
2185 : }
2186 : }
2187 271 : int nRedBandIndex = -1;
2188 271 : int nGreenBandIndex = -1;
2189 271 : int nBlueBandIndex = -1;
2190 271 : int nAlphaBandIndex = -1;
2191 606 : for (int i = 0; i < nBands; i++)
2192 : {
2193 : GDALColorInterp eInterp =
2194 335 : poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation();
2195 335 : if (eInterp == GCI_RedBand)
2196 15 : nRedBandIndex = i;
2197 320 : else if (eInterp == GCI_GreenBand)
2198 15 : nGreenBandIndex = i;
2199 305 : else if (eInterp == GCI_BlueBand)
2200 15 : nBlueBandIndex = i;
2201 290 : else if (eInterp == GCI_AlphaBand)
2202 7 : nAlphaBandIndex = i;
2203 : }
2204 271 : const char *pszAlpha = CSLFetchNameValue(papszOptions, "ALPHA");
2205 273 : if (nAlphaBandIndex < 0 && nBands > 1 && pszAlpha != nullptr &&
2206 2 : CPLTestBool(pszAlpha))
2207 : {
2208 2 : nAlphaBandIndex = nBands - 1;
2209 : }
2210 :
2211 271 : const char *pszYCBCR420 = CSLFetchNameValue(papszOptions, "YCBCR420");
2212 271 : int bYCBCR420 = FALSE;
2213 271 : if (pszYCBCR420 && CPLTestBool(pszYCBCR420))
2214 : {
2215 2 : if ((nBands == 3 || nBands == 4) && eDataType == GDT_Byte &&
2216 2 : nRedBandIndex == 0 && nGreenBandIndex == 1 && nBlueBandIndex == 2)
2217 : {
2218 2 : if (((nXSize % 2) == 0 && (nYSize % 2) == 0 &&
2219 2 : (nBlockXSize % 2) == 0 && (nBlockYSize % 2) == 0))
2220 : {
2221 2 : bYCBCR420 = TRUE;
2222 : }
2223 : else
2224 : {
2225 0 : CPLError(CE_Warning, CPLE_NotSupported,
2226 : "YCBCR420 unsupported when image size and/or tile "
2227 : "size are not multiple of 2");
2228 : }
2229 : }
2230 : else
2231 : {
2232 0 : CPLError(CE_Warning, CPLE_NotSupported,
2233 : "YCBCR420 unsupported with this image band count and/or "
2234 : "data byte");
2235 : }
2236 : }
2237 :
2238 271 : const char *pszYCC = CSLFetchNameValue(papszOptions, "YCC");
2239 292 : int bYCC = ((nBands == 3 || nBands == 4) &&
2240 21 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "YCC", "TRUE")));
2241 :
2242 271 : if (bYCBCR420 && bYCC)
2243 : {
2244 2 : if (pszYCC != nullptr)
2245 : {
2246 0 : CPLError(CE_Warning, CPLE_NotSupported,
2247 : "YCC unsupported when YCbCr requesting");
2248 : }
2249 2 : bYCC = FALSE;
2250 : }
2251 :
2252 : /* -------------------------------------------------------------------- */
2253 : /* Deal with codeblocks size */
2254 : /* -------------------------------------------------------------------- */
2255 :
2256 : int nCblockW =
2257 271 : atoi(CSLFetchNameValueDef(papszOptions, "CODEBLOCK_WIDTH", "64"));
2258 : int nCblockH =
2259 271 : atoi(CSLFetchNameValueDef(papszOptions, "CODEBLOCK_HEIGHT", "64"));
2260 271 : if (nCblockW < 4 || nCblockW > 1024 || nCblockH < 4 || nCblockH > 1024)
2261 : {
2262 4 : CPLError(CE_Warning, CPLE_NotSupported,
2263 : "Invalid values for codeblock size. Defaulting to 64x64");
2264 4 : nCblockW = 64;
2265 4 : nCblockH = 64;
2266 : }
2267 267 : else if (nCblockW * nCblockH > 4096)
2268 : {
2269 1 : CPLError(CE_Warning, CPLE_NotSupported,
2270 : "Invalid values for codeblock size. "
2271 : "CODEBLOCK_WIDTH * CODEBLOCK_HEIGHT should be <= 4096. "
2272 : "Defaulting to 64x64");
2273 1 : nCblockW = 64;
2274 1 : nCblockH = 64;
2275 : }
2276 271 : int nCblockW_po2 = FloorPowerOfTwo(nCblockW);
2277 271 : int nCblockH_po2 = FloorPowerOfTwo(nCblockH);
2278 271 : if (nCblockW_po2 != nCblockW || nCblockH_po2 != nCblockH)
2279 : {
2280 1 : CPLError(CE_Warning, CPLE_NotSupported,
2281 : "Non power of two values used for codeblock size. "
2282 : "Using to %dx%d",
2283 : nCblockW_po2, nCblockH_po2);
2284 : }
2285 271 : nCblockW = nCblockW_po2;
2286 271 : nCblockH = nCblockH_po2;
2287 :
2288 : /* -------------------------------------------------------------------- */
2289 : /* Deal with codestream PROFILE */
2290 : /* -------------------------------------------------------------------- */
2291 : const char *pszProfile =
2292 271 : CSLFetchNameValueDef(papszOptions, "PROFILE", "AUTO");
2293 271 : int bProfile1 = FALSE;
2294 271 : if (EQUAL(pszProfile, "UNRESTRICTED"))
2295 : {
2296 1 : bProfile1 = FALSE;
2297 1 : if (bInspireTG)
2298 : {
2299 1 : CPLError(CE_Failure, CPLE_NotSupported,
2300 : "INSPIRE_TG=YES mandates PROFILE=PROFILE_1 (TG "
2301 : "requirement 21)");
2302 1 : return nullptr;
2303 : }
2304 : }
2305 270 : else if (EQUAL(pszProfile, "UNRESTRICTED_FORCED"))
2306 : {
2307 0 : bProfile1 = FALSE;
2308 : }
2309 270 : else if (EQUAL(pszProfile,
2310 : "PROFILE_1_FORCED")) /* For debug only: can produce
2311 : inconsistent codestream */
2312 : {
2313 0 : bProfile1 = TRUE;
2314 : }
2315 : else
2316 : {
2317 270 : if (!(EQUAL(pszProfile, "PROFILE_1") || EQUAL(pszProfile, "AUTO")))
2318 : {
2319 0 : CPLError(CE_Warning, CPLE_NotSupported,
2320 : "Unsupported value for PROFILE : %s. Defaulting to AUTO",
2321 : pszProfile);
2322 0 : pszProfile = "AUTO";
2323 : }
2324 :
2325 270 : bProfile1 = TRUE;
2326 270 : const char *pszReq21OrEmpty = bInspireTG ? " (TG requirement 21)" : "";
2327 270 : if ((nBlockXSize != nXSize || nBlockYSize != nYSize) &&
2328 24 : (nBlockXSize != nBlockYSize || nBlockXSize > 1024 ||
2329 19 : nBlockYSize > 1024))
2330 : {
2331 5 : bProfile1 = FALSE;
2332 5 : if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
2333 : {
2334 2 : CPLError(
2335 : CE_Failure, CPLE_NotSupported,
2336 : "Tile dimensions incompatible with PROFILE_1%s. "
2337 : "Should be whole image or square with dimension <= 1024.",
2338 : pszReq21OrEmpty);
2339 2 : return nullptr;
2340 : }
2341 : }
2342 268 : if ((nMaxTileDim >> (nNumResolutions - 1)) > 128)
2343 : {
2344 4 : bProfile1 = FALSE;
2345 4 : if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
2346 : {
2347 1 : CPLError(CE_Failure, CPLE_NotSupported,
2348 : "Number of resolutions incompatible with PROFILE_1%s. "
2349 : "Should be at least %d.",
2350 : pszReq21OrEmpty, nMinProfile1Resolutions);
2351 1 : return nullptr;
2352 : }
2353 : }
2354 267 : if (nCblockW > 64 || nCblockH > 64)
2355 : {
2356 2 : bProfile1 = FALSE;
2357 2 : if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
2358 : {
2359 2 : CPLError(CE_Failure, CPLE_NotSupported,
2360 : "Codeblock width incompatible with PROFILE_1%s. "
2361 : "Codeblock width or height should be <= 64.",
2362 : pszReq21OrEmpty);
2363 2 : return nullptr;
2364 : }
2365 : }
2366 : }
2367 :
2368 : /* -------------------------------------------------------------------- */
2369 : /* Work out the precision. */
2370 : /* -------------------------------------------------------------------- */
2371 : int nBits;
2372 265 : if (CSLFetchNameValue(papszOptions, "NBITS") != nullptr)
2373 : {
2374 22 : nBits = atoi(CSLFetchNameValue(papszOptions, "NBITS"));
2375 22 : if (bInspireTG &&
2376 1 : !(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32))
2377 : {
2378 1 : CPLError(CE_Failure, CPLE_NotSupported,
2379 : "INSPIRE_TG=YES mandates NBITS=1,8,16 or 32 (TG "
2380 : "requirement 24)");
2381 1 : return nullptr;
2382 : }
2383 : }
2384 243 : else if (poSrcDS->GetRasterBand(1)->GetMetadataItem(
2385 243 : "NBITS", "IMAGE_STRUCTURE") != nullptr)
2386 : {
2387 3 : nBits = atoi(poSrcDS->GetRasterBand(1)->GetMetadataItem(
2388 : "NBITS", "IMAGE_STRUCTURE"));
2389 3 : if (bInspireTG &&
2390 1 : !(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32))
2391 : {
2392 : /* Implements "NOTE If the original data do not satisfy this "
2393 : "requirement, they will be converted in a representation using "
2394 : "the next higher power of 2" */
2395 1 : nBits = GDALGetDataTypeSize(eDataType);
2396 : }
2397 : }
2398 : else
2399 : {
2400 240 : nBits = GDALGetDataTypeSize(eDataType);
2401 : }
2402 :
2403 516 : if ((GDALGetDataTypeSize(eDataType) == 8 && nBits > 8) ||
2404 780 : (GDALGetDataTypeSize(eDataType) == 16 && (nBits <= 8 || nBits > 16)) ||
2405 264 : (GDALGetDataTypeSize(eDataType) == 32 && (nBits <= 16 || nBits > 32)))
2406 : {
2407 0 : CPLError(CE_Warning, CPLE_NotSupported,
2408 : "Inconsistent NBITS value with data type. Using %d",
2409 : GDALGetDataTypeSize(eDataType));
2410 : }
2411 :
2412 : /* -------------------------------------------------------------------- */
2413 : /* Georeferencing options */
2414 : /* -------------------------------------------------------------------- */
2415 :
2416 264 : bool bGMLJP2Option = CPLFetchBool(papszOptions, "GMLJP2", true);
2417 264 : int nGMLJP2Version = 1;
2418 : const char *pszGMLJP2V2Def =
2419 264 : CSLFetchNameValue(papszOptions, "GMLJP2V2_DEF");
2420 264 : if (pszGMLJP2V2Def != nullptr)
2421 : {
2422 28 : bGMLJP2Option = true;
2423 28 : nGMLJP2Version = 2;
2424 28 : if (bInspireTG)
2425 : {
2426 0 : CPLError(CE_Warning, CPLE_NotSupported,
2427 : "INSPIRE_TG=YES is only compatible with GMLJP2 v1");
2428 0 : return nullptr;
2429 : }
2430 : }
2431 264 : const bool bGeoJP2Option = CPLFetchBool(papszOptions, "GeoJP2", true);
2432 :
2433 528 : GDALJP2Metadata oJP2MD;
2434 :
2435 264 : int bGeoreferencingCompatOfGeoJP2 = FALSE;
2436 264 : int bGeoreferencingCompatOfGMLJP2 = FALSE;
2437 270 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2) &&
2438 6 : (bGMLJP2Option || bGeoJP2Option))
2439 : {
2440 219 : if (poSrcDS->GetGCPCount() > 0)
2441 : {
2442 3 : bGeoreferencingCompatOfGeoJP2 = TRUE;
2443 3 : oJP2MD.SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs());
2444 3 : oJP2MD.SetSpatialRef(poSrcDS->GetGCPSpatialRef());
2445 : }
2446 : else
2447 : {
2448 216 : const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
2449 216 : if (poSRS)
2450 : {
2451 57 : bGeoreferencingCompatOfGeoJP2 = TRUE;
2452 57 : oJP2MD.SetSpatialRef(poSRS);
2453 : }
2454 : double adfGeoTransform[6];
2455 216 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
2456 : {
2457 164 : bGeoreferencingCompatOfGeoJP2 = TRUE;
2458 164 : oJP2MD.SetGeoTransform(adfGeoTransform);
2459 164 : if (poSRS && !poSRS->IsEmpty())
2460 : {
2461 57 : bGeoreferencingCompatOfGMLJP2 =
2462 57 : GDALJP2Metadata::IsSRSCompatible(poSRS);
2463 57 : if (!bGeoreferencingCompatOfGMLJP2)
2464 : {
2465 1 : CPLDebug(
2466 : CODEC::debugId(),
2467 : "Cannot write GMLJP2 box due to unsupported SRS");
2468 : }
2469 : }
2470 : }
2471 : }
2472 219 : if (poSrcDS->GetMetadata("RPC") != nullptr)
2473 : {
2474 1 : oJP2MD.SetRPCMD(poSrcDS->GetMetadata("RPC"));
2475 1 : bGeoreferencingCompatOfGeoJP2 = TRUE;
2476 : }
2477 :
2478 : const char *pszAreaOrPoint =
2479 219 : poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
2480 260 : oJP2MD.bPixelIsPoint = pszAreaOrPoint != nullptr &&
2481 41 : EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);
2482 :
2483 434 : if (bGMLJP2Option &&
2484 215 : CPLGetConfigOption("GMLJP2OVERRIDE", nullptr) != nullptr)
2485 : {
2486 : // Force V1 since this is the branch in which the hack is
2487 : // implemented
2488 7 : nGMLJP2Version = 1;
2489 7 : bGeoreferencingCompatOfGMLJP2 = TRUE;
2490 : }
2491 : }
2492 :
2493 264 : if (CSLFetchNameValue(papszOptions, "GMLJP2") != nullptr && bGMLJP2Option &&
2494 : !bGeoreferencingCompatOfGMLJP2)
2495 : {
2496 0 : CPLError(CE_Warning, CPLE_AppDefined,
2497 : "GMLJP2 box was explicitly required but cannot be written due "
2498 : "to lack of georeferencing and/or unsupported georeferencing "
2499 : "for GMLJP2");
2500 : }
2501 :
2502 264 : if (CSLFetchNameValue(papszOptions, "GeoJP2") != nullptr && bGeoJP2Option &&
2503 : !bGeoreferencingCompatOfGeoJP2)
2504 : {
2505 0 : CPLError(CE_Warning, CPLE_AppDefined,
2506 : "GeoJP2 box was explicitly required but cannot be written due "
2507 : "to lack of georeferencing");
2508 : }
2509 : const bool bGeoBoxesAfter =
2510 264 : CPLFetchBool(papszOptions, "GEOBOXES_AFTER_JP2C", bInspireTG);
2511 264 : GDALJP2Box *poGMLJP2Box = nullptr;
2512 264 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2) && bGMLJP2Option &&
2513 : bGeoreferencingCompatOfGMLJP2)
2514 : {
2515 61 : if (nGMLJP2Version == 1)
2516 33 : poGMLJP2Box = oJP2MD.CreateGMLJP2(nXSize, nYSize);
2517 : else
2518 : poGMLJP2Box =
2519 28 : oJP2MD.CreateGMLJP2V2(nXSize, nYSize, pszGMLJP2V2Def, poSrcDS);
2520 61 : if (poGMLJP2Box == nullptr)
2521 3 : return nullptr;
2522 : }
2523 :
2524 : /* ---------------------------------------------------------------- */
2525 : /* If the input driver is identifed as "GEORASTER" the following */
2526 : /* section will try to dump a ORACLE GeoRaster JP2 BLOB into a file */
2527 : /* ---------------------------------------------------------------- */
2528 :
2529 261 : if (EQUAL(poSrcDS->GetDriverName(), "GEORASTER"))
2530 : {
2531 : const char *pszGEOR_compress =
2532 0 : poSrcDS->GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE");
2533 :
2534 0 : if (pszGEOR_compress == nullptr)
2535 : {
2536 0 : pszGEOR_compress = "NONE";
2537 : }
2538 :
2539 : /* Check if the JP2 BLOB needs re-shaping */
2540 :
2541 0 : bool bGEOR_reshape = false;
2542 :
2543 0 : const char *apszIgnoredOptions[] = {"BLOCKXSIZE",
2544 : "BLOCKYSIZE",
2545 : "QUALITY",
2546 : "REVERSIBLE",
2547 : "RESOLUTIONS",
2548 : "PROGRESSION",
2549 : "SOP",
2550 : "EPH",
2551 : "YCBCR420",
2552 : "YCC",
2553 : "NBITS",
2554 : "1BIT_ALPHA",
2555 : "PRECINCTS",
2556 : "TILEPARTS",
2557 : "CODEBLOCK_WIDTH",
2558 : "CODEBLOCK_HEIGHT",
2559 : "PLT",
2560 : "TLM",
2561 : nullptr};
2562 :
2563 0 : for (int i = 0; apszIgnoredOptions[i]; i++)
2564 : {
2565 0 : if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i]))
2566 : {
2567 0 : bGEOR_reshape = true;
2568 : }
2569 : }
2570 :
2571 0 : if (CSLFetchNameValue(papszOptions, "USE_SRC_CODESTREAM"))
2572 : {
2573 0 : bGEOR_reshape = false;
2574 : }
2575 :
2576 0 : char **papszGEOR_files = poSrcDS->GetFileList();
2577 :
2578 0 : if (EQUAL(pszGEOR_compress, "JP2-F") && CSLCount(papszGEOR_files) > 0 &&
2579 0 : bGEOR_reshape == false)
2580 : {
2581 :
2582 0 : const char *pszVsiOciLob = papszGEOR_files[0];
2583 :
2584 0 : VSILFILE *fpBlob = VSIFOpenL(pszVsiOciLob, "r");
2585 0 : if (fpBlob == nullptr)
2586 : {
2587 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
2588 : pszVsiOciLob);
2589 0 : delete poGMLJP2Box;
2590 0 : return nullptr;
2591 : }
2592 0 : VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
2593 0 : if (fp == nullptr)
2594 : {
2595 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
2596 : pszFilename);
2597 0 : delete poGMLJP2Box;
2598 0 : VSIFCloseL(fpBlob);
2599 0 : return nullptr;
2600 : }
2601 :
2602 0 : VSIFSeekL(fpBlob, 0, SEEK_END);
2603 :
2604 0 : size_t nBlobSize = static_cast<size_t>(VSIFTellL(fpBlob));
2605 0 : size_t nChunk = (size_t)(GDALGetCacheMax() * 0.25);
2606 0 : size_t nSize = 0;
2607 0 : size_t nCount = 0;
2608 :
2609 0 : void *pBuffer = (GByte *)VSI_MALLOC_VERBOSE(nChunk);
2610 0 : if (pBuffer == nullptr)
2611 : {
2612 0 : delete poGMLJP2Box;
2613 0 : VSIFCloseL(fpBlob);
2614 0 : VSIFCloseL(fp);
2615 0 : return nullptr;
2616 : }
2617 :
2618 0 : VSIFSeekL(fpBlob, 0, SEEK_SET);
2619 :
2620 0 : while ((nSize = VSIFReadL(pBuffer, 1, nChunk, fpBlob)) > 0)
2621 : {
2622 0 : VSIFWriteL(pBuffer, 1, nSize, fp);
2623 0 : nCount += nSize;
2624 0 : pfnProgress((float)nCount / (float)nBlobSize, nullptr,
2625 : pProgressData);
2626 : }
2627 :
2628 0 : CPLFree(pBuffer);
2629 0 : VSIFCloseL(fpBlob);
2630 :
2631 0 : VSIFCloseL(fp);
2632 :
2633 : /* Return the GDALDaset object */
2634 :
2635 0 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
2636 0 : GDALDataset *poDS = JP2OPJLikeDataset::Open(&oOpenInfo);
2637 :
2638 : /* Copy essential metadata */
2639 :
2640 : double adfGeoTransform[6];
2641 :
2642 0 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
2643 : {
2644 0 : poDS->SetGeoTransform(adfGeoTransform);
2645 : }
2646 :
2647 0 : const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
2648 0 : if (poSRS)
2649 : {
2650 0 : poDS->SetSpatialRef(poSRS);
2651 : }
2652 :
2653 0 : delete poGMLJP2Box;
2654 0 : return poDS;
2655 : }
2656 : }
2657 :
2658 : /* -------------------------------------------------------------------- */
2659 : /* Setup encoder */
2660 : /* -------------------------------------------------------------------- */
2661 :
2662 522 : JP2OPJLikeDataset oTmpDS;
2663 261 : int numThreads = oTmpDS.GetNumThreads();
2664 :
2665 522 : CODEC localctx;
2666 261 : localctx.allocComponentParams(nBands);
2667 : int iBand;
2668 261 : int bSamePrecision = TRUE;
2669 261 : int b1BitAlpha = FALSE;
2670 586 : for (iBand = 0; iBand < nBands; iBand++)
2671 : {
2672 325 : localctx.pasBandParams[iBand].x0 = 0;
2673 325 : localctx.pasBandParams[iBand].y0 = 0;
2674 325 : if (bYCBCR420 && (iBand == 1 || iBand == 2))
2675 : {
2676 4 : localctx.pasBandParams[iBand].dx = 2;
2677 4 : localctx.pasBandParams[iBand].dy = 2;
2678 4 : localctx.pasBandParams[iBand].w = nXSize / 2;
2679 4 : localctx.pasBandParams[iBand].h = nYSize / 2;
2680 : }
2681 : else
2682 : {
2683 321 : localctx.pasBandParams[iBand].dx = 1;
2684 321 : localctx.pasBandParams[iBand].dy = 1;
2685 321 : localctx.pasBandParams[iBand].w = nXSize;
2686 321 : localctx.pasBandParams[iBand].h = nYSize;
2687 : }
2688 :
2689 325 : localctx.pasBandParams[iBand].sgnd =
2690 325 : (eDataType == GDT_Int16 || eDataType == GDT_Int32);
2691 325 : localctx.pasBandParams[iBand].prec = nBits;
2692 :
2693 : const char *pszNBits =
2694 325 : poSrcDS->GetRasterBand(iBand + 1)->GetMetadataItem(
2695 : "NBITS", "IMAGE_STRUCTURE");
2696 : /* Recommendation 38 In the case of an opacity channel, the bit depth
2697 : * should be 1-bit. */
2698 334 : if (iBand == nAlphaBandIndex &&
2699 0 : ((pszNBits != nullptr && EQUAL(pszNBits, "1")) ||
2700 9 : CPLFetchBool(papszOptions, "1BIT_ALPHA", bInspireTG)))
2701 : {
2702 3 : if (iBand != nBands - 1 && nBits != 1)
2703 : {
2704 : /* Might be a bug in openjpeg, but it seems that if the alpha */
2705 : /* band is the first one, it would select 1-bit for all
2706 : * channels... */
2707 0 : CPLError(CE_Warning, CPLE_NotSupported,
2708 : "Cannot output 1-bit alpha channel if it is not the "
2709 : "last one");
2710 : }
2711 : else
2712 : {
2713 3 : CPLDebug(CODEC::debugId(), "Using 1-bit alpha channel");
2714 3 : localctx.pasBandParams[iBand].sgnd = 0;
2715 3 : localctx.pasBandParams[iBand].prec = 1;
2716 3 : bSamePrecision = FALSE;
2717 3 : b1BitAlpha = TRUE;
2718 : }
2719 : }
2720 : }
2721 :
2722 261 : if (bInspireTG && nAlphaBandIndex >= 0 && !b1BitAlpha)
2723 : {
2724 0 : CPLError(
2725 : CE_Warning, CPLE_NotSupported,
2726 : "INSPIRE_TG=YES recommends 1BIT_ALPHA=YES (Recommendation 38)");
2727 : }
2728 261 : auto eColorSpace = CODEC::cvtenum(JP2_CLRSPC_GRAY);
2729 :
2730 261 : if (bYCBCR420)
2731 : {
2732 2 : eColorSpace = CODEC::cvtenum(JP2_CLRSPC_SYCC);
2733 : }
2734 259 : else if ((nBands == 3 || nBands == 4) && nRedBandIndex >= 0 &&
2735 13 : nGreenBandIndex >= 0 && nBlueBandIndex >= 0)
2736 : {
2737 13 : eColorSpace = CODEC::cvtenum(JP2_CLRSPC_SRGB);
2738 : }
2739 246 : else if (poCT != nullptr)
2740 : {
2741 6 : eColorSpace = CODEC::cvtenum(JP2_CLRSPC_SRGB);
2742 : }
2743 :
2744 : /* -------------------------------------------------------------------- */
2745 : /* Create the dataset. */
2746 : /* -------------------------------------------------------------------- */
2747 :
2748 261 : const char *pszAccess =
2749 261 : STARTS_WITH_CI(pszFilename, "/vsisubfile/") ? "r+b" : "w+b";
2750 261 : VSILFILE *fp = VSIFOpenL(pszFilename, pszAccess);
2751 261 : if (fp == nullptr)
2752 : {
2753 3 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create file");
2754 3 : CPLFree(localctx.pasBandParams);
2755 3 : localctx.pasBandParams = nullptr;
2756 3 : delete poGMLJP2Box;
2757 3 : return nullptr;
2758 : }
2759 :
2760 : /* -------------------------------------------------------------------- */
2761 : /* Add JP2 boxes. */
2762 : /* -------------------------------------------------------------------- */
2763 258 : vsi_l_offset nStartJP2C = 0;
2764 258 : int bUseXLBoxes = FALSE;
2765 :
2766 258 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2))
2767 : {
2768 436 : GDALJP2Box jPBox(fp);
2769 218 : jPBox.SetType("jP ");
2770 218 : jPBox.AppendWritableData(4, "\x0D\x0A\x87\x0A");
2771 218 : WriteBox(fp, &jPBox);
2772 :
2773 436 : GDALJP2Box ftypBox(fp);
2774 218 : ftypBox.SetType("ftyp");
2775 : // http://docs.opengeospatial.org/is/08-085r5/08-085r5.html Req 19
2776 218 : const bool bJPXOption = CPLFetchBool(papszOptions, "JPX", true);
2777 218 : if (nGMLJP2Version == 2 && bJPXOption)
2778 25 : ftypBox.AppendWritableData(4, "jpx "); /* Branding */
2779 : else
2780 193 : ftypBox.AppendWritableData(4, "jp2 "); /* Branding */
2781 218 : ftypBox.AppendUInt32(0); /* minimum version */
2782 218 : ftypBox.AppendWritableData(
2783 : 4, "jp2 "); /* Compatibility list: first value */
2784 :
2785 218 : if (bInspireTG && poGMLJP2Box != nullptr && !bJPXOption)
2786 : {
2787 1 : CPLError(
2788 : CE_Warning, CPLE_AppDefined,
2789 : "INSPIRE_TG=YES implies following GMLJP2 specification which "
2790 : "recommends advertise reader requirement 67 feature, and thus "
2791 : "JPX capability");
2792 : }
2793 217 : else if (poGMLJP2Box != nullptr && bJPXOption)
2794 : {
2795 : /* GMLJP2 uses lbl and asoc boxes, which are JPEG2000 Part II spec
2796 : */
2797 : /* advertizing jpx is required per 8.1 of 05-047r3 GMLJP2 */
2798 57 : ftypBox.AppendWritableData(
2799 : 4, "jpx "); /* Compatibility list: second value */
2800 : }
2801 218 : WriteBox(fp, &ftypBox);
2802 :
2803 220 : const bool bIPR = poSrcDS->GetMetadata("xml:IPR") != nullptr &&
2804 2 : CPLFetchBool(papszOptions, "WRITE_METADATA", false);
2805 :
2806 : /* Reader requirement box */
2807 218 : if (poGMLJP2Box != nullptr && bJPXOption)
2808 : {
2809 114 : GDALJP2Box rreqBox(fp);
2810 57 : rreqBox.SetType("rreq");
2811 57 : rreqBox.AppendUInt8(1); /* ML = 1 byte for mask length */
2812 :
2813 57 : rreqBox.AppendUInt8(0x80 | 0x40 | (bIPR ? 0x20 : 0)); /* FUAM */
2814 57 : rreqBox.AppendUInt8(0x80); /* DCM */
2815 :
2816 57 : rreqBox.AppendUInt16(
2817 : 2 + (bIPR ? 1 : 0)); /* NSF: Number of standard features */
2818 :
2819 57 : rreqBox.AppendUInt16(
2820 : (bProfile1) ? 4 : 5); /* SF0 : PROFILE 1 or PROFILE 2 */
2821 57 : rreqBox.AppendUInt8(0x80); /* SM0 */
2822 :
2823 57 : rreqBox.AppendUInt16(67); /* SF1 : GMLJP2 box */
2824 57 : rreqBox.AppendUInt8(0x40); /* SM1 */
2825 :
2826 57 : if (bIPR)
2827 : {
2828 0 : rreqBox.AppendUInt16(35); /* SF2 : IPR metadata */
2829 0 : rreqBox.AppendUInt8(0x20); /* SM2 */
2830 : }
2831 57 : rreqBox.AppendUInt16(0); /* NVF */
2832 57 : WriteBox(fp, &rreqBox);
2833 : }
2834 :
2835 436 : GDALJP2Box ihdrBox(fp);
2836 218 : ihdrBox.SetType("ihdr");
2837 218 : ihdrBox.AppendUInt32(nYSize);
2838 218 : ihdrBox.AppendUInt32(nXSize);
2839 218 : ihdrBox.AppendUInt16(static_cast<GUInt16>(nBands));
2840 : GByte BPC;
2841 218 : if (bSamePrecision)
2842 215 : BPC = static_cast<GByte>((localctx.pasBandParams[0].prec - 1) |
2843 215 : (localctx.pasBandParams[0].sgnd << 7));
2844 : else
2845 3 : BPC = 255;
2846 218 : ihdrBox.AppendUInt8(BPC);
2847 218 : ihdrBox.AppendUInt8(7); /* C=Compression type: fixed value */
2848 218 : ihdrBox.AppendUInt8(0); /* UnkC: 0= colourspace of the image is known */
2849 : /*and correctly specified in the Colourspace Specification boxes within
2850 : * the file */
2851 218 : ihdrBox.AppendUInt8(
2852 : bIPR ? 1 : 0); /* IPR: 0=no intellectual property, 1=IPR box */
2853 :
2854 436 : GDALJP2Box bpccBox(fp);
2855 218 : if (!bSamePrecision)
2856 : {
2857 3 : bpccBox.SetType("bpcc");
2858 13 : for (int i = 0; i < nBands; i++)
2859 10 : bpccBox.AppendUInt8(
2860 10 : static_cast<GByte>((localctx.pasBandParams[i].prec - 1) |
2861 10 : (localctx.pasBandParams[i].sgnd << 7)));
2862 : }
2863 :
2864 436 : GDALJP2Box colrBox(fp);
2865 218 : colrBox.SetType("colr");
2866 218 : colrBox.AppendUInt8(1); /* METHOD: 1=Enumerated Colourspace */
2867 218 : colrBox.AppendUInt8(
2868 : 0); /* PREC: Precedence. 0=(field reserved for ISO use) */
2869 218 : colrBox.AppendUInt8(0); /* APPROX: Colourspace approximation. */
2870 218 : GUInt32 enumcs = 16;
2871 218 : if (eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SRGB))
2872 16 : enumcs = 16;
2873 202 : else if (eColorSpace == CODEC::cvtenum(JP2_CLRSPC_GRAY))
2874 200 : enumcs = 17;
2875 2 : else if (eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SYCC))
2876 2 : enumcs = 18;
2877 218 : colrBox.AppendUInt32(enumcs); /* EnumCS: Enumerated colourspace */
2878 :
2879 436 : GDALJP2Box pclrBox(fp);
2880 436 : GDALJP2Box cmapBox(fp);
2881 218 : int nCTComponentCount = 0;
2882 218 : if (poCT != nullptr)
2883 : {
2884 6 : pclrBox.SetType("pclr");
2885 6 : const int nEntries = std::min(256, poCT->GetColorEntryCount());
2886 : nCTComponentCount =
2887 6 : atoi(CSLFetchNameValueDef(papszOptions, "CT_COMPONENTS", "0"));
2888 6 : if (bInspireTG)
2889 : {
2890 0 : if (nCTComponentCount != 0 && nCTComponentCount != 3)
2891 0 : CPLError(
2892 : CE_Warning, CPLE_AppDefined,
2893 : "Inspire TG mandates 3 components for color table");
2894 : else
2895 0 : nCTComponentCount = 3;
2896 : }
2897 6 : else if (nCTComponentCount != 3 && nCTComponentCount != 4)
2898 : {
2899 5 : nCTComponentCount = 3;
2900 21 : for (int i = 0; i < nEntries; i++)
2901 : {
2902 17 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
2903 17 : if (psEntry->c4 != 255)
2904 : {
2905 1 : CPLDebug(
2906 : CODEC::debugId(),
2907 : "Color table has at least one non-opaque value. "
2908 : "This may cause compatibility problems with some "
2909 : "readers. "
2910 : "In which case use CT_COMPONENTS=3 creation "
2911 : "option");
2912 1 : nCTComponentCount = 4;
2913 1 : break;
2914 : }
2915 : }
2916 : }
2917 6 : nRedBandIndex = 0;
2918 6 : nGreenBandIndex = 1;
2919 6 : nBlueBandIndex = 2;
2920 6 : nAlphaBandIndex = (nCTComponentCount == 4) ? 3 : -1;
2921 :
2922 6 : pclrBox.AppendUInt16(static_cast<GUInt16>(nEntries));
2923 6 : pclrBox.AppendUInt8(static_cast<GByte>(
2924 : nCTComponentCount)); /* NPC: Number of components */
2925 25 : for (int i = 0; i < nCTComponentCount; i++)
2926 : {
2927 19 : pclrBox.AppendUInt8(7); /* Bi: unsigned 8 bits */
2928 : }
2929 30 : for (int i = 0; i < nEntries; i++)
2930 : {
2931 24 : const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
2932 24 : pclrBox.AppendUInt8((GByte)psEntry->c1);
2933 24 : pclrBox.AppendUInt8((GByte)psEntry->c2);
2934 24 : pclrBox.AppendUInt8((GByte)psEntry->c3);
2935 24 : if (nCTComponentCount == 4)
2936 4 : pclrBox.AppendUInt8((GByte)psEntry->c4);
2937 : }
2938 :
2939 6 : cmapBox.SetType("cmap");
2940 25 : for (int i = 0; i < nCTComponentCount; i++)
2941 : {
2942 19 : cmapBox.AppendUInt16(0); /* CMPi: code stream component index */
2943 19 : cmapBox.AppendUInt8(1); /* MYTPi: 1=palette mapping */
2944 19 : cmapBox.AppendUInt8(static_cast<GByte>(
2945 : i)); /* PCOLi: index component from the map */
2946 : }
2947 : }
2948 :
2949 436 : GDALJP2Box cdefBox(fp);
2950 228 : if (((nBands == 3 || nBands == 4) &&
2951 22 : (eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SRGB) ||
2952 18 : eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SYCC)) &&
2953 10 : (nRedBandIndex != 0 || nGreenBandIndex != 1 ||
2954 436 : nBlueBandIndex != 2)) ||
2955 : nAlphaBandIndex >= 0)
2956 : {
2957 11 : cdefBox.SetType("cdef");
2958 11 : int nComponents = (nCTComponentCount == 4) ? 4 : nBands;
2959 11 : cdefBox.AppendUInt16(static_cast<GUInt16>(nComponents));
2960 50 : for (int i = 0; i < nComponents; i++)
2961 : {
2962 39 : cdefBox.AppendUInt16(
2963 : static_cast<GUInt16>(i)); /* Component number */
2964 39 : if (i != nAlphaBandIndex)
2965 : {
2966 29 : cdefBox.AppendUInt16(
2967 : 0); /* Signification: This channel is the colour image
2968 : data for the associated colour */
2969 29 : if (eColorSpace == CODEC::cvtenum(JP2_CLRSPC_GRAY) &&
2970 : nComponents == 2)
2971 2 : cdefBox.AppendUInt16(
2972 : 1); /* Colour of the component: associated with a
2973 : particular colour */
2974 33 : else if ((eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SRGB) ||
2975 54 : eColorSpace == CODEC::cvtenum(JP2_CLRSPC_SYCC)) &&
2976 21 : (nComponents == 3 || nComponents == 4))
2977 : {
2978 24 : if (i == nRedBandIndex)
2979 8 : cdefBox.AppendUInt16(1);
2980 16 : else if (i == nGreenBandIndex)
2981 8 : cdefBox.AppendUInt16(2);
2982 8 : else if (i == nBlueBandIndex)
2983 8 : cdefBox.AppendUInt16(3);
2984 : else
2985 : {
2986 0 : CPLError(CE_Warning, CPLE_AppDefined,
2987 : "Could not associate band %d with a "
2988 : "red/green/blue channel",
2989 : i + 1);
2990 0 : cdefBox.AppendUInt16(65535);
2991 : }
2992 : }
2993 : else
2994 3 : cdefBox.AppendUInt16(
2995 : 65535); /* Colour of the component: not associated
2996 : with any particular colour */
2997 : }
2998 : else
2999 : {
3000 10 : cdefBox.AppendUInt16(
3001 : 1); /* Signification: Non pre-multiplied alpha */
3002 10 : cdefBox.AppendUInt16(
3003 : 0); /* Colour of the component: This channel is
3004 : associated as the image as a whole */
3005 : }
3006 : }
3007 : }
3008 :
3009 : // Add res box if needed
3010 218 : GDALJP2Box *poRes = nullptr;
3011 218 : if (poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION") != nullptr &&
3012 223 : poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION") != nullptr &&
3013 5 : poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT") != nullptr)
3014 : {
3015 : double dfXRes =
3016 5 : CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION"));
3017 : double dfYRes =
3018 5 : CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION"));
3019 : int nResUnit =
3020 5 : atoi(poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT"));
3021 : #define PIXELS_PER_INCH 2
3022 : #define PIXELS_PER_CM 3
3023 :
3024 5 : if (nResUnit == PIXELS_PER_INCH)
3025 : {
3026 : // convert pixels per inch to pixels per cm.
3027 2 : dfXRes = dfXRes * 39.37 / 100.0;
3028 2 : dfYRes = dfYRes * 39.37 / 100.0;
3029 2 : nResUnit = PIXELS_PER_CM;
3030 : }
3031 :
3032 5 : if (nResUnit == PIXELS_PER_CM && dfXRes > 0 && dfYRes > 0 &&
3033 5 : dfXRes < 65535 && dfYRes < 65535)
3034 : {
3035 : /* Format a resd box and embed it inside a res box */
3036 10 : GDALJP2Box oResd;
3037 5 : oResd.SetType("resd");
3038 :
3039 5 : int nYDenom = 1;
3040 58 : while (nYDenom < 32767 && dfYRes < 32767)
3041 : {
3042 53 : dfYRes *= 2;
3043 53 : nYDenom *= 2;
3044 : }
3045 5 : int nXDenom = 1;
3046 56 : while (nXDenom < 32767 && dfXRes < 32767)
3047 : {
3048 51 : dfXRes *= 2;
3049 51 : nXDenom *= 2;
3050 : }
3051 :
3052 5 : oResd.AppendUInt16((GUInt16)dfYRes);
3053 5 : oResd.AppendUInt16((GUInt16)nYDenom);
3054 5 : oResd.AppendUInt16((GUInt16)dfXRes);
3055 5 : oResd.AppendUInt16((GUInt16)nXDenom);
3056 5 : oResd.AppendUInt8(2); /* vertical exponent */
3057 5 : oResd.AppendUInt8(2); /* horizontal exponent */
3058 :
3059 5 : GDALJP2Box *poResd = &oResd;
3060 5 : poRes = GDALJP2Box::CreateAsocBox(1, &poResd);
3061 5 : poRes->SetType("res ");
3062 : }
3063 : }
3064 :
3065 : /* Build and write jp2h super box now */
3066 : GDALJP2Box *apoBoxes[7];
3067 218 : int nBoxes = 1;
3068 218 : apoBoxes[0] = &ihdrBox;
3069 218 : if (bpccBox.GetDataLength())
3070 3 : apoBoxes[nBoxes++] = &bpccBox;
3071 218 : apoBoxes[nBoxes++] = &colrBox;
3072 218 : if (pclrBox.GetDataLength())
3073 6 : apoBoxes[nBoxes++] = &pclrBox;
3074 218 : if (cmapBox.GetDataLength())
3075 6 : apoBoxes[nBoxes++] = &cmapBox;
3076 218 : if (cdefBox.GetDataLength())
3077 11 : apoBoxes[nBoxes++] = &cdefBox;
3078 218 : if (poRes)
3079 5 : apoBoxes[nBoxes++] = poRes;
3080 : GDALJP2Box *psJP2HBox =
3081 218 : GDALJP2Box::CreateSuperBox("jp2h", nBoxes, apoBoxes);
3082 218 : WriteBox(fp, psJP2HBox);
3083 218 : delete psJP2HBox;
3084 218 : delete poRes;
3085 :
3086 218 : if (!bGeoBoxesAfter)
3087 : {
3088 207 : if (bGeoJP2Option && bGeoreferencingCompatOfGeoJP2)
3089 : {
3090 150 : GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
3091 150 : WriteBox(fp, poBox);
3092 150 : delete poBox;
3093 : }
3094 :
3095 218 : if (CPLFetchBool(papszOptions, "WRITE_METADATA", false) &&
3096 11 : !CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
3097 : {
3098 11 : WriteXMPBox(fp, poSrcDS);
3099 : }
3100 :
3101 207 : if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
3102 : {
3103 11 : if (!CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
3104 11 : WriteXMLBoxes(fp, poSrcDS);
3105 11 : WriteGDALMetadataBox(fp, poSrcDS, papszOptions);
3106 : }
3107 :
3108 207 : if (poGMLJP2Box != nullptr)
3109 : {
3110 53 : WriteBox(fp, poGMLJP2Box);
3111 : }
3112 : }
3113 : }
3114 :
3115 : /* -------------------------------------------------------------------- */
3116 : /* Try lossless reuse of an existing JPEG2000 codestream */
3117 : /* -------------------------------------------------------------------- */
3118 258 : vsi_l_offset nCodeStreamLength = 0;
3119 258 : vsi_l_offset nCodeStreamStart = 0;
3120 258 : VSILFILE *fpSrc = nullptr;
3121 258 : if (CPLFetchBool(papszOptions, "USE_SRC_CODESTREAM", false))
3122 : {
3123 14 : CPLString osSrcFilename(poSrcDS->GetDescription());
3124 14 : if (poSrcDS->GetDriver() != nullptr &&
3125 7 : poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
3126 : {
3127 0 : VRTDataset *poVRTDS = (VRTDataset *)poSrcDS;
3128 0 : GDALDataset *poSimpleSourceDS = poVRTDS->GetSingleSimpleSource();
3129 0 : if (poSimpleSourceDS)
3130 0 : osSrcFilename = poSimpleSourceDS->GetDescription();
3131 : }
3132 :
3133 7 : fpSrc = VSIFOpenL(osSrcFilename, "rb");
3134 7 : if (fpSrc)
3135 : {
3136 7 : nCodeStreamStart = JP2FindCodeStream(fpSrc, &nCodeStreamLength);
3137 : }
3138 7 : if (nCodeStreamLength == 0)
3139 : {
3140 1 : CPLError(
3141 : CE_Warning, CPLE_AppDefined,
3142 : "USE_SRC_CODESTREAM=YES specified, but no codestream found");
3143 : }
3144 : }
3145 :
3146 258 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2))
3147 : {
3148 : // Start codestream box
3149 218 : nStartJP2C = VSIFTellL(fp);
3150 218 : if (nCodeStreamLength)
3151 6 : bUseXLBoxes =
3152 6 : ((vsi_l_offset)(GUInt32)nCodeStreamLength != nCodeStreamLength);
3153 : else
3154 424 : bUseXLBoxes = CPLFetchBool(papszOptions, "JP2C_XLBOX",
3155 423 : false) || /* For debugging */
3156 211 : (GIntBig)nXSize * nYSize * nBands * nDataTypeSize /
3157 211 : adfRates.back() >
3158 : 4e9;
3159 218 : GUInt32 nLBox = (bUseXLBoxes) ? 1 : 0;
3160 218 : CPL_MSBPTR32(&nLBox);
3161 218 : VSIFWriteL(&nLBox, 1, 4, fp);
3162 218 : VSIFWriteL("jp2c", 1, 4, fp);
3163 218 : if (bUseXLBoxes)
3164 : {
3165 1 : GUIntBig nXLBox = 0;
3166 1 : VSIFWriteL(&nXLBox, 1, 8, fp);
3167 : }
3168 : }
3169 :
3170 : /* -------------------------------------------------------------------- */
3171 : /* Do lossless reuse of an existing JPEG2000 codestream */
3172 : /* -------------------------------------------------------------------- */
3173 258 : if (fpSrc)
3174 : {
3175 7 : const char *apszIgnoredOptions[] = {"BLOCKXSIZE",
3176 : "BLOCKYSIZE",
3177 : "QUALITY",
3178 : "REVERSIBLE",
3179 : "RESOLUTIONS",
3180 : "PROGRESSION",
3181 : "SOP",
3182 : "EPH",
3183 : "YCBCR420",
3184 : "YCC",
3185 : "NBITS",
3186 : "1BIT_ALPHA",
3187 : "PRECINCTS",
3188 : "TILEPARTS",
3189 : "CODEBLOCK_WIDTH",
3190 : "CODEBLOCK_HEIGHT",
3191 : "PLT",
3192 : nullptr};
3193 126 : for (int i = 0; apszIgnoredOptions[i]; i++)
3194 : {
3195 119 : if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i]))
3196 : {
3197 1 : CPLError(CE_Warning, CPLE_NotSupported,
3198 : "Option %s ignored when USE_SRC_CODESTREAM=YES",
3199 : apszIgnoredOptions[i]);
3200 : }
3201 : }
3202 : GByte abyBuffer[4096];
3203 7 : VSIFSeekL(fpSrc, nCodeStreamStart, SEEK_SET);
3204 7 : vsi_l_offset nRead = 0;
3205 : /* coverity[tainted_data] */
3206 17 : while (nRead < nCodeStreamLength)
3207 : {
3208 10 : int nToRead = (nCodeStreamLength - nRead > 4096)
3209 : ? 4096
3210 6 : : (int)(nCodeStreamLength - nRead);
3211 10 : if ((int)VSIFReadL(abyBuffer, 1, nToRead, fpSrc) != nToRead)
3212 : {
3213 0 : VSIFCloseL(fp);
3214 0 : VSIFCloseL(fpSrc);
3215 0 : delete poGMLJP2Box;
3216 0 : return nullptr;
3217 : }
3218 10 : if (nRead == 0 && (pszProfile || bInspireTG) &&
3219 6 : abyBuffer[2] == 0xFF && abyBuffer[3] == 0x51)
3220 : {
3221 6 : if (EQUAL(pszProfile, "UNRESTRICTED"))
3222 : {
3223 0 : abyBuffer[6] = 0;
3224 0 : abyBuffer[7] = 0;
3225 : }
3226 6 : else if (EQUAL(pszProfile, "PROFILE_1") || bInspireTG)
3227 : {
3228 : // TODO: ultimately we should check that we can really set
3229 : // Profile 1
3230 1 : abyBuffer[6] = 0;
3231 1 : abyBuffer[7] = 2;
3232 : }
3233 : }
3234 20 : if ((int)VSIFWriteL(abyBuffer, 1, nToRead, fp) != nToRead ||
3235 10 : !pfnProgress((nRead + nToRead) * 1.0 / nCodeStreamLength,
3236 : nullptr, pProgressData))
3237 : {
3238 0 : VSIFCloseL(fp);
3239 0 : VSIFCloseL(fpSrc);
3240 0 : delete poGMLJP2Box;
3241 0 : return nullptr;
3242 : }
3243 10 : nRead += nToRead;
3244 : }
3245 :
3246 7 : VSIFCloseL(fpSrc);
3247 : }
3248 : else
3249 : {
3250 251 : localctx.open(fp);
3251 251 : if (!localctx.initCompress(papszOptions, adfRates, nBlockXSize,
3252 : nBlockYSize, bIsIrreversible,
3253 : nNumResolutions, eProgOrder, bYCC, nCblockW,
3254 : nCblockH, bYCBCR420, bProfile1, nBands,
3255 : nXSize, nYSize, eColorSpace, numThreads))
3256 : {
3257 0 : CPLError(CE_Failure, CPLE_AppDefined, "init compress failed");
3258 0 : localctx.free();
3259 0 : VSIFCloseL(fp);
3260 0 : delete poGMLJP2Box;
3261 11 : return nullptr;
3262 : }
3263 251 : const int nTilesX = DIV_ROUND_UP(nXSize, nBlockXSize);
3264 251 : const int nTilesY = DIV_ROUND_UP(nYSize, nBlockYSize);
3265 :
3266 251 : const GUIntBig nTileSize =
3267 251 : (GUIntBig)nBlockXSize * nBlockYSize * nBands * nDataTypeSize;
3268 251 : GByte *pTempBuffer = nullptr;
3269 :
3270 251 : const bool bUseIOThread =
3271 502 : CODEC::preferPerTileCompress() && (nTilesX > 1 || nTilesY > 1) &&
3272 11 : nTileSize < 10 * 1024 * 1024 &&
3273 513 : strcmp(CPLGetThreadingModel(), "stub") != 0 &&
3274 11 : CPLTestBool(
3275 : CPLGetConfigOption("JP2OPENJPEG_USE_THREADED_IO", "YES"));
3276 :
3277 251 : if (nTileSize > UINT_MAX)
3278 : {
3279 1 : CPLError(CE_Failure, CPLE_NotSupported, "Tile size exceeds 4GB");
3280 1 : pTempBuffer = nullptr;
3281 : }
3282 : else
3283 : {
3284 : // Double memory buffer when using threaded I/O
3285 250 : const size_t nBufferSize =
3286 : static_cast<size_t>(bUseIOThread ? nTileSize * 2 : nTileSize);
3287 250 : pTempBuffer = (GByte *)VSIMalloc(nBufferSize);
3288 : }
3289 251 : if (pTempBuffer == nullptr)
3290 : {
3291 1 : localctx.free();
3292 1 : VSIFCloseL(fp);
3293 1 : delete poGMLJP2Box;
3294 1 : return nullptr;
3295 : }
3296 :
3297 250 : GByte *pYUV420Buffer = nullptr;
3298 250 : if (bYCBCR420)
3299 : {
3300 4 : pYUV420Buffer = (GByte *)VSIMalloc(
3301 3 : 3 * nBlockXSize * nBlockYSize / 2 +
3302 1 : ((nBands == 4) ? nBlockXSize * nBlockYSize : 0));
3303 2 : if (pYUV420Buffer == nullptr)
3304 : {
3305 0 : localctx.free();
3306 0 : CPLFree(pTempBuffer);
3307 0 : VSIFCloseL(fp);
3308 0 : delete poGMLJP2Box;
3309 0 : return nullptr;
3310 : }
3311 : }
3312 :
3313 : /* --------------------------------------------------------------------
3314 : */
3315 : /* Iterate over the tiles */
3316 : /* --------------------------------------------------------------------
3317 : */
3318 250 : pfnProgress(0.0, nullptr, pProgressData);
3319 :
3320 : struct ReadRasterJob
3321 : {
3322 : GDALDataset *poSrcDS;
3323 : int nXOff;
3324 : int nYOff;
3325 : int nWidthToRead;
3326 : int nHeightToRead;
3327 : GDALDataType eDataType;
3328 : GByte *pBuffer;
3329 : int nBands;
3330 : CPLErr eErr;
3331 : };
3332 :
3333 812 : const auto ReadRasterFunction = [](void *threadData)
3334 : {
3335 406 : ReadRasterJob *job = static_cast<ReadRasterJob *>(threadData);
3336 812 : job->eErr = job->poSrcDS->RasterIO(
3337 : GF_Read, job->nXOff, job->nYOff, job->nWidthToRead,
3338 406 : job->nHeightToRead, job->pBuffer, job->nWidthToRead,
3339 : job->nHeightToRead, job->eDataType, job->nBands, nullptr, 0, 0,
3340 : 0, nullptr);
3341 : };
3342 :
3343 250 : CPLWorkerThreadPool oPool;
3344 250 : if (bUseIOThread)
3345 : {
3346 10 : oPool.Setup(1, nullptr, nullptr);
3347 : }
3348 :
3349 250 : GByte *pabyActiveBuffer = pTempBuffer;
3350 250 : GByte *pabyBackgroundBuffer =
3351 250 : pTempBuffer + static_cast<size_t>(nTileSize);
3352 :
3353 250 : CPLErr eErr = CE_None;
3354 250 : int iTile = 0;
3355 :
3356 : ReadRasterJob job;
3357 250 : job.eDataType = eDataType;
3358 250 : job.pBuffer = pabyActiveBuffer;
3359 250 : job.nBands = nBands;
3360 250 : job.eErr = CE_Failure;
3361 250 : job.poSrcDS = poSrcDS;
3362 :
3363 250 : if (bUseIOThread)
3364 : {
3365 10 : job.nXOff = 0;
3366 10 : job.nYOff = 0;
3367 10 : job.nWidthToRead = std::min(nBlockXSize, nXSize);
3368 10 : job.nHeightToRead = std::min(nBlockYSize, nYSize);
3369 10 : job.pBuffer = pabyBackgroundBuffer;
3370 10 : ReadRasterFunction(&job);
3371 10 : eErr = job.eErr;
3372 : }
3373 :
3374 524 : for (int nBlockYOff = 0; eErr == CE_None && nBlockYOff < nTilesY;
3375 : nBlockYOff++)
3376 : {
3377 680 : for (int nBlockXOff = 0; eErr == CE_None && nBlockXOff < nTilesX;
3378 : nBlockXOff++)
3379 : {
3380 406 : const int nWidthToRead =
3381 406 : std::min(nBlockXSize, nXSize - nBlockXOff * nBlockXSize);
3382 406 : const int nHeightToRead =
3383 406 : std::min(nBlockYSize, nYSize - nBlockYOff * nBlockYSize);
3384 :
3385 406 : if (bUseIOThread)
3386 : {
3387 : // Wait for previous background I/O task to be finished
3388 100 : oPool.WaitCompletion();
3389 100 : eErr = job.eErr;
3390 :
3391 : // Swap buffers
3392 100 : std::swap(pabyBackgroundBuffer, pabyActiveBuffer);
3393 :
3394 : // Prepare for next I/O task
3395 100 : int nNextBlockXOff = nBlockXOff + 1;
3396 100 : int nNextBlockYOff = nBlockYOff;
3397 100 : if (nNextBlockXOff == nTilesX)
3398 : {
3399 26 : nNextBlockXOff = 0;
3400 26 : nNextBlockYOff++;
3401 : }
3402 100 : if (nNextBlockYOff != nTilesY)
3403 : {
3404 90 : job.nXOff = nNextBlockXOff * nBlockXSize;
3405 90 : job.nYOff = nNextBlockYOff * nBlockYSize;
3406 90 : job.nWidthToRead =
3407 90 : std::min(nBlockXSize, nXSize - job.nXOff);
3408 90 : job.nHeightToRead =
3409 90 : std::min(nBlockYSize, nYSize - job.nYOff);
3410 90 : job.pBuffer = pabyBackgroundBuffer;
3411 :
3412 : // Submit next job
3413 90 : oPool.SubmitJob(ReadRasterFunction, &job);
3414 : }
3415 : }
3416 : else
3417 : {
3418 306 : job.nXOff = nBlockXOff * nBlockXSize;
3419 306 : job.nYOff = nBlockYOff * nBlockYSize;
3420 306 : job.nWidthToRead = nWidthToRead;
3421 306 : job.nHeightToRead = nHeightToRead;
3422 306 : ReadRasterFunction(&job);
3423 306 : eErr = job.eErr;
3424 : }
3425 :
3426 406 : if (b1BitAlpha)
3427 : {
3428 64987 : for (int i = 0; i < nWidthToRead * nHeightToRead; i++)
3429 : {
3430 64984 : if (pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
3431 64984 : nHeightToRead +
3432 : i])
3433 25040 : pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
3434 25040 : nHeightToRead +
3435 25040 : i] = 1;
3436 : else
3437 39944 : pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
3438 39944 : nHeightToRead +
3439 39944 : i] = 0;
3440 : }
3441 : }
3442 406 : if (eErr == CE_None)
3443 : {
3444 406 : if (bYCBCR420)
3445 : {
3446 : int j, i;
3447 202 : for (j = 0; j < nHeightToRead; j++)
3448 : {
3449 27000 : for (i = 0; i < nWidthToRead; i++)
3450 : {
3451 26800 : int R = pabyActiveBuffer[j * nWidthToRead + i];
3452 26800 : int G = pabyActiveBuffer[nHeightToRead *
3453 26800 : nWidthToRead +
3454 26800 : j * nWidthToRead + i];
3455 26800 : int B = pabyActiveBuffer[2 * nHeightToRead *
3456 26800 : nWidthToRead +
3457 26800 : j * nWidthToRead + i];
3458 26800 : int Y =
3459 26800 : (int)(0.299 * R + 0.587 * G + 0.114 * B);
3460 26800 : int Cb =
3461 26800 : CLAMP_0_255((int)(-0.1687 * R - 0.3313 * G +
3462 26800 : 0.5 * B + 128));
3463 26800 : int Cr =
3464 26800 : CLAMP_0_255((int)(0.5 * R - 0.4187 * G -
3465 26800 : 0.0813 * B + 128));
3466 26800 : pYUV420Buffer[j * nWidthToRead + i] = (GByte)Y;
3467 26800 : pYUV420Buffer[nHeightToRead * nWidthToRead +
3468 26800 : ((j / 2) * ((nWidthToRead) / 2) +
3469 26800 : i / 2)] = (GByte)Cb;
3470 26800 : pYUV420Buffer[5 * nHeightToRead * nWidthToRead /
3471 26800 : 4 +
3472 26800 : ((j / 2) * ((nWidthToRead) / 2) +
3473 26800 : i / 2)] = (GByte)Cr;
3474 26800 : if (nBands == 4)
3475 : {
3476 24300 : pYUV420Buffer[3 * nHeightToRead *
3477 24300 : nWidthToRead / 2 +
3478 24300 : j * nWidthToRead + i] =
3479 24300 : (GByte)pabyActiveBuffer
3480 24300 : [3 * nHeightToRead * nWidthToRead +
3481 24300 : j * nWidthToRead + i];
3482 : }
3483 : }
3484 : }
3485 :
3486 2 : int nBytesToWrite =
3487 2 : 3 * nWidthToRead * nHeightToRead / 2;
3488 2 : if (nBands == 4)
3489 1 : nBytesToWrite += nBlockXSize * nBlockYSize;
3490 :
3491 2 : if (!localctx.compressTile(iTile, pYUV420Buffer,
3492 : nBytesToWrite))
3493 : {
3494 0 : CPLError(CE_Failure, CPLE_AppDefined,
3495 : "compress tile failed");
3496 0 : eErr = CE_Failure;
3497 : }
3498 : }
3499 : else
3500 : {
3501 404 : if (!localctx.compressTile(iTile, pabyActiveBuffer,
3502 404 : nWidthToRead *
3503 404 : nHeightToRead * nBands *
3504 : nDataTypeSize))
3505 : {
3506 0 : CPLError(CE_Failure, CPLE_AppDefined,
3507 : "compress tile failed");
3508 0 : eErr = CE_Failure;
3509 : }
3510 : }
3511 : }
3512 :
3513 406 : if (!pfnProgress((iTile + 1) * 1.0 / (nTilesX * nTilesY),
3514 : nullptr, pProgressData))
3515 0 : eErr = CE_Failure;
3516 :
3517 406 : iTile++;
3518 : }
3519 : }
3520 :
3521 250 : if (bUseIOThread && eErr == CE_Failure)
3522 : {
3523 : // Wait for previous background I/O task to be finished
3524 : // before freeing buffers (pTempBuffer, etc.)
3525 0 : oPool.WaitCompletion();
3526 : }
3527 :
3528 250 : VSIFree(pTempBuffer);
3529 250 : VSIFree(pYUV420Buffer);
3530 :
3531 250 : if (eErr != CE_None)
3532 : {
3533 0 : localctx.free();
3534 0 : VSIFCloseL(fp);
3535 0 : delete poGMLJP2Box;
3536 0 : return nullptr;
3537 : }
3538 :
3539 250 : if (!localctx.finishCompress())
3540 : {
3541 10 : localctx.free();
3542 10 : VSIFCloseL(fp);
3543 10 : delete poGMLJP2Box;
3544 10 : return nullptr;
3545 : }
3546 240 : localctx.free();
3547 : }
3548 :
3549 : /* -------------------------------------------------------------------- */
3550 : /* Patch JP2C box length and add trailing JP2 boxes */
3551 : /* -------------------------------------------------------------------- */
3552 247 : bool bRet = true;
3553 464 : if (eCodecFormat == CODEC::cvtenum(JP2_CODEC_JP2) &&
3554 217 : !CPLFetchBool(papszOptions, "JP2C_LENGTH_ZERO",
3555 : false) /* debug option */)
3556 : {
3557 216 : vsi_l_offset nEndJP2C = VSIFTellL(fp);
3558 216 : GUIntBig nBoxSize = nEndJP2C - nStartJP2C;
3559 216 : if (bUseXLBoxes)
3560 : {
3561 1 : VSIFSeekL(fp, nStartJP2C + 8, SEEK_SET);
3562 1 : CPL_MSBPTR64(&nBoxSize);
3563 1 : if (VSIFWriteL(&nBoxSize, 8, 1, fp) != 1)
3564 0 : bRet = false;
3565 : }
3566 : else
3567 : {
3568 215 : GUInt32 nBoxSize32 = (GUInt32)nBoxSize;
3569 215 : if ((vsi_l_offset)nBoxSize32 != nBoxSize)
3570 : {
3571 : /* Should not happen hopefully */
3572 0 : if ((bGeoreferencingCompatOfGeoJP2 || poGMLJP2Box) &&
3573 : bGeoBoxesAfter)
3574 : {
3575 0 : CPLError(CE_Warning, CPLE_AppDefined,
3576 : "Cannot write GMLJP2/GeoJP2 boxes as codestream "
3577 : "is unexpectedly > 4GB");
3578 0 : bGeoreferencingCompatOfGeoJP2 = FALSE;
3579 0 : delete poGMLJP2Box;
3580 0 : poGMLJP2Box = nullptr;
3581 : }
3582 : }
3583 : else
3584 : {
3585 215 : VSIFSeekL(fp, nStartJP2C, SEEK_SET);
3586 215 : CPL_MSBPTR32(&nBoxSize32);
3587 215 : if (VSIFWriteL(&nBoxSize32, 4, 1, fp) != 1)
3588 0 : bRet = false;
3589 : }
3590 : }
3591 216 : VSIFSeekL(fp, 0, SEEK_END);
3592 :
3593 216 : if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
3594 : {
3595 14 : if (!WriteIPRBox(fp, poSrcDS))
3596 0 : bRet = false;
3597 : }
3598 :
3599 216 : if (bGeoBoxesAfter)
3600 : {
3601 11 : if (poGMLJP2Box != nullptr)
3602 : {
3603 5 : if (!WriteBox(fp, poGMLJP2Box))
3604 0 : bRet = false;
3605 : }
3606 :
3607 11 : if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
3608 : {
3609 3 : if (!CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
3610 : {
3611 3 : if (!WriteXMLBoxes(fp, poSrcDS))
3612 0 : bRet = false;
3613 : }
3614 3 : if (!WriteGDALMetadataBox(fp, poSrcDS, papszOptions))
3615 0 : bRet = false;
3616 : }
3617 :
3618 11 : if (bGeoJP2Option && bGeoreferencingCompatOfGeoJP2)
3619 : {
3620 5 : GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
3621 5 : if (!WriteBox(fp, poBox))
3622 0 : bRet = false;
3623 5 : delete poBox;
3624 : }
3625 :
3626 14 : if (CPLFetchBool(papszOptions, "WRITE_METADATA", false) &&
3627 3 : !CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
3628 : {
3629 3 : if (!WriteXMPBox(fp, poSrcDS))
3630 0 : bRet = false;
3631 : }
3632 : }
3633 : }
3634 :
3635 247 : if (VSIFCloseL(fp) != 0)
3636 0 : bRet = false;
3637 247 : delete poGMLJP2Box;
3638 247 : if (!bRet)
3639 0 : return nullptr;
3640 :
3641 : /* -------------------------------------------------------------------- */
3642 : /* Re-open dataset, and copy any auxiliary pam information. */
3643 : /* -------------------------------------------------------------------- */
3644 :
3645 247 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
3646 247 : auto poDS = (JP2OPJLikeDataset *)JP2OPJLikeDataset::Open(&oOpenInfo);
3647 :
3648 247 : if (poDS)
3649 : {
3650 244 : poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT & (~GCIF_METADATA));
3651 :
3652 : /* Only write relevant metadata to PAM, and if needed */
3653 244 : if (!CPLFetchBool(papszOptions, "WRITE_METADATA", false))
3654 : {
3655 230 : char **papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
3656 : papszSrcMD =
3657 230 : CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, nullptr);
3658 230 : papszSrcMD = CSLSetNameValue(papszSrcMD, "Corder", nullptr);
3659 321 : for (char **papszSrcMDIter = papszSrcMD;
3660 321 : papszSrcMDIter && *papszSrcMDIter;)
3661 : {
3662 : /* Remove entries like KEY= (without value) */
3663 91 : if ((*papszSrcMDIter)[0] &&
3664 91 : (*papszSrcMDIter)[strlen((*papszSrcMDIter)) - 1] == '=')
3665 : {
3666 37 : CPLFree(*papszSrcMDIter);
3667 37 : memmove(papszSrcMDIter, papszSrcMDIter + 1,
3668 : sizeof(char *) *
3669 37 : (CSLCount(papszSrcMDIter + 1) + 1));
3670 : }
3671 : else
3672 54 : ++papszSrcMDIter;
3673 : }
3674 230 : char **papszMD = CSLDuplicate(poDS->GetMetadata());
3675 230 : papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
3676 245 : if (papszSrcMD && papszSrcMD[0] != nullptr &&
3677 15 : CSLCount(papszSrcMD) != CSLCount(papszMD))
3678 : {
3679 9 : poDS->SetMetadata(papszSrcMD);
3680 : }
3681 230 : CSLDestroy(papszSrcMD);
3682 230 : CSLDestroy(papszMD);
3683 : }
3684 : }
3685 :
3686 247 : return poDS;
3687 : }
3688 :
3689 : #ifdef unused
3690 : template <typename CODEC, typename BASE>
3691 : void GDALRegisterJP2(const std::string &libraryName,
3692 : const std::string &driverName)
3693 :
3694 : {
3695 : if (!GDAL_CHECK_VERSION((driverName + " driver").c_str()))
3696 : return;
3697 :
3698 : if (GDALGetDriverByName(driverName.c_str()) != nullptr)
3699 : return;
3700 :
3701 : GDALDriver *poDriver = new GDALDriver();
3702 : poDriver->SetDescription(driverName.c_str());
3703 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
3704 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
3705 : poDriver->SetMetadataItem(
3706 : GDAL_DMD_LONGNAME,
3707 : ("JPEG-2000 driver based on " + libraryName + " library").c_str());
3708 :
3709 : poDriver->SetMetadataItem(
3710 : GDAL_DMD_HELPTOPIC,
3711 : ("drivers/raster/jp2" + CPLString(libraryName).tolower() + ".html")
3712 : .c_str());
3713 : poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/jp2");
3714 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "jp2");
3715 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "jp2 j2k");
3716 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
3717 : "Byte Int16 UInt16 Int32 UInt32");
3718 :
3719 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
3720 : BASE::setMetaData(poDriver);
3721 :
3722 : poDriver->pfnIdentify = JP2OPJLikeDataset<CODEC, BASE>::Identify;
3723 : poDriver->pfnOpen = JP2OPJLikeDataset<CODEC, BASE>::Open;
3724 : poDriver->pfnCreateCopy = JP2OPJLikeDataset<CODEC, BASE>::CreateCopy;
3725 :
3726 : GetGDALDriverManager()->RegisterDriver(poDriver);
3727 : }
3728 : #endif
|