Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PNG Driver
4 : * Purpose: Implement GDAL PNG Support
5 : * Author: Frank Warmerdam, warmerda@home.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2000, Frank Warmerdam
9 : * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ******************************************************************************
13 : *
14 : * ISSUES:
15 : * o CollectMetadata() will only capture TEXT chunks before the image
16 : * data as the code is currently structured.
17 : * o Interlaced images are read entirely into memory for use. This is
18 : * bad for large images.
19 : * o Image reading is always strictly sequential. Reading backwards will
20 : * cause the file to be rewound, and access started again from the
21 : * beginning.
22 : * o 16 bit alpha values are not scaled by to eight bit.
23 : *
24 : */
25 :
26 : #include "pngdataset.h"
27 : #include "pngdrivercore.h"
28 :
29 : #include "cpl_string.h"
30 : #include "gdal_frmts.h"
31 : #include "gdal_pam.h"
32 :
33 : #if defined(__clang__)
34 : #pragma clang diagnostic push
35 : #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
36 : #endif
37 :
38 : #include "png.h"
39 :
40 : #if defined(__clang__)
41 : #pragma clang diagnostic pop
42 : #endif
43 :
44 : #include <csetjmp>
45 :
46 : #include <algorithm>
47 : #include <limits>
48 :
49 : // Note: Callers must provide blocks in increasing Y order.
50 : // Disclaimer (E. Rouault): this code is not production ready at all. A lot of
51 : // issues remain: uninitialized variables, unclosed files, lack of proper
52 : // multiband handling, and an inability to read and write at the same time. Do
53 : // not use it unless you're ready to fix it.
54 :
55 : // Define SUPPORT_CREATE to enable use of the Create() call.
56 : // #define SUPPORT_CREATE
57 :
58 : #ifdef _MSC_VER
59 : #pragma warning(disable : 4611)
60 : #endif
61 :
62 : static void png_vsi_read_data(png_structp png_ptr, png_bytep data,
63 : png_size_t length);
64 :
65 : static void png_vsi_write_data(png_structp png_ptr, png_bytep data,
66 : png_size_t length);
67 :
68 : static void png_vsi_flush(png_structp png_ptr);
69 :
70 : static void png_gdal_error(png_structp png_ptr, const char *error_message);
71 : static void png_gdal_warning(png_structp png_ptr, const char *error_message);
72 :
73 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
74 :
75 : /************************************************************************/
76 : /* IsCompatibleOfSingleBlock() */
77 : /************************************************************************/
78 :
79 23362 : bool PNGDataset::IsCompatibleOfSingleBlock() const
80 : {
81 23174 : return nBitDepth == 8 && !bInterlaced && nRasterXSize <= 512 &&
82 22828 : nRasterYSize <= 512 &&
83 22827 : CPLTestBool(
84 46537 : CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")) &&
85 46103 : CPLTestBool(CPLGetConfigOption("GDAL_PNG_SINGLE_BLOCK", "YES"));
86 : }
87 : #endif
88 :
89 : /************************************************************************/
90 : /* PNGRasterBand() */
91 : /************************************************************************/
92 :
93 23362 : PNGRasterBand::PNGRasterBand(PNGDataset *poDSIn, int nBandIn)
94 23362 : : bHaveNoData(FALSE), dfNoDataValue(-1)
95 : {
96 23363 : poDS = poDSIn;
97 23363 : nBand = nBandIn;
98 :
99 23363 : if (poDSIn->nBitDepth == 16)
100 179 : eDataType = GDT_UInt16;
101 : else
102 23184 : eDataType = GDT_Byte;
103 :
104 23363 : nBlockXSize = poDSIn->nRasterXSize;
105 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
106 23363 : if (poDSIn->IsCompatibleOfSingleBlock())
107 : {
108 22651 : nBlockYSize = poDSIn->nRasterYSize;
109 : }
110 : else
111 : #endif
112 : {
113 712 : nBlockYSize = 1;
114 : }
115 :
116 : #ifdef SUPPORT_CREATE
117 : reset_band_provision_flags();
118 : #endif
119 23363 : }
120 :
121 : /************************************************************************/
122 : /* IReadBlock() */
123 : /************************************************************************/
124 :
125 13120 : CPLErr PNGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
126 :
127 : {
128 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
129 13120 : if (nBlockYSize > 1)
130 : {
131 : GDALRasterIOExtraArg sExtraArg;
132 127 : INIT_RASTERIO_EXTRA_ARG(sExtraArg);
133 127 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
134 254 : return IRasterIO(GF_Read, 0, 0, nRasterXSize, nRasterYSize, pImage,
135 : nRasterXSize, nRasterYSize, eDataType, nDTSize,
136 127 : static_cast<GSpacing>(nDTSize) * nRasterXSize,
137 127 : &sExtraArg);
138 : }
139 : #endif
140 :
141 12993 : PNGDataset *poGDS = cpl::down_cast<PNGDataset *>(poDS);
142 12993 : CPLAssert(nBlockXOff == 0);
143 :
144 12993 : const int nPixelSize = (poGDS->nBitDepth == 16) ? 2 : 1;
145 :
146 12993 : if (poGDS->fpImage == nullptr)
147 : {
148 0 : memset(pImage, 0, cpl::fits_on<int>(nPixelSize * nRasterXSize));
149 0 : return CE_None;
150 : }
151 :
152 : // Load the desired scanline into the working buffer.
153 12993 : CPLErr eErr = poGDS->LoadScanline(nBlockYOff);
154 12993 : if (eErr != CE_None)
155 2 : return eErr;
156 :
157 12991 : const int nPixelOffset = poGDS->nBands * nPixelSize;
158 :
159 : const auto CopyToDstBuffer =
160 7608490 : [this, nPixelOffset, nPixelSize](const GByte *pabyScanline, void *pDest)
161 : {
162 : // Transfer between the working buffer and the caller's buffer.
163 19331 : if (nPixelSize == nPixelOffset)
164 10687 : memcpy(pDest, pabyScanline,
165 10687 : cpl::fits_on<int>(nPixelSize * nRasterXSize));
166 8644 : else if (nPixelSize == 1)
167 : {
168 3761940 : for (int i = 0; i < nRasterXSize; i++)
169 3754510 : reinterpret_cast<GByte *>(pDest)[i] =
170 3754510 : pabyScanline[i * nPixelOffset];
171 : }
172 : else
173 : {
174 1216 : CPLAssert(nPixelSize == 2);
175 26688 : for (int i = 0; i < nRasterXSize; i++)
176 : {
177 25472 : reinterpret_cast<GUInt16 *>(pDest)[i] =
178 25472 : *reinterpret_cast<const GUInt16 *>(pabyScanline +
179 25472 : i * nPixelOffset);
180 : }
181 : }
182 32322 : };
183 :
184 12991 : const GByte *const pabySrcBufferFirstBand =
185 12991 : poGDS->pabyBuffer +
186 12991 : (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nRasterXSize;
187 12991 : CopyToDstBuffer(pabySrcBufferFirstBand + nPixelSize * (nBand - 1), pImage);
188 :
189 : // Forcibly load the other bands associated with this scanline.
190 32322 : for (int iBand = 1; iBand <= poGDS->GetRasterCount(); iBand++)
191 : {
192 19331 : if (iBand != nBand)
193 : {
194 6340 : auto poIterBand = poGDS->GetRasterBand(iBand);
195 : GDALRasterBlock *poBlock =
196 6340 : poIterBand->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
197 6340 : if (poBlock != nullptr)
198 : {
199 : // Block already cached
200 0 : poBlock->DropLock();
201 0 : continue;
202 : }
203 :
204 : // Instantiate the block
205 : poBlock =
206 6340 : poIterBand->GetLockedBlockRef(nBlockXOff, nBlockYOff, TRUE);
207 6340 : if (poBlock == nullptr)
208 : {
209 0 : continue;
210 : }
211 :
212 6340 : CopyToDstBuffer(pabySrcBufferFirstBand + nPixelSize * (iBand - 1),
213 : poBlock->GetDataRef());
214 :
215 6340 : poBlock->DropLock();
216 : }
217 : }
218 :
219 12991 : return CE_None;
220 : }
221 :
222 : /************************************************************************/
223 : /* GetColorInterpretation() */
224 : /************************************************************************/
225 :
226 332 : GDALColorInterp PNGRasterBand::GetColorInterpretation()
227 :
228 : {
229 332 : PNGDataset *poGDS = reinterpret_cast<PNGDataset *>(poDS);
230 :
231 332 : if (poGDS->nColorType == PNG_COLOR_TYPE_GRAY)
232 16 : return GCI_GrayIndex;
233 :
234 316 : else if (poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
235 : {
236 29 : if (nBand == 1)
237 8 : return GCI_GrayIndex;
238 : else
239 21 : return GCI_AlphaBand;
240 : }
241 :
242 287 : else if (poGDS->nColorType == PNG_COLOR_TYPE_PALETTE)
243 43 : return GCI_PaletteIndex;
244 :
245 244 : else if (poGDS->nColorType == PNG_COLOR_TYPE_RGB ||
246 202 : poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA)
247 : {
248 244 : if (nBand == 1)
249 37 : return GCI_RedBand;
250 207 : else if (nBand == 2)
251 44 : return GCI_GreenBand;
252 163 : else if (nBand == 3)
253 35 : return GCI_BlueBand;
254 : else
255 128 : return GCI_AlphaBand;
256 : }
257 : else
258 0 : return GCI_GrayIndex;
259 : }
260 :
261 : /************************************************************************/
262 : /* GetColorTable() */
263 : /************************************************************************/
264 :
265 569 : GDALColorTable *PNGRasterBand::GetColorTable()
266 :
267 : {
268 569 : PNGDataset *poGDS = reinterpret_cast<PNGDataset *>(poDS);
269 :
270 569 : if (nBand == 1)
271 545 : return poGDS->poColorTable;
272 :
273 24 : return nullptr;
274 : }
275 :
276 : /************************************************************************/
277 : /* SetNoDataValue() */
278 : /************************************************************************/
279 :
280 283 : CPLErr PNGRasterBand::SetNoDataValue(double dfNewValue)
281 :
282 : {
283 283 : bHaveNoData = TRUE;
284 283 : dfNoDataValue = dfNewValue;
285 :
286 283 : return CE_None;
287 : }
288 :
289 : /************************************************************************/
290 : /* GetNoDataValue() */
291 : /************************************************************************/
292 :
293 163 : double PNGRasterBand::GetNoDataValue(int *pbSuccess)
294 :
295 : {
296 163 : if (bHaveNoData)
297 : {
298 34 : if (pbSuccess != nullptr)
299 30 : *pbSuccess = bHaveNoData;
300 34 : return dfNoDataValue;
301 : }
302 :
303 129 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
304 : }
305 :
306 : /************************************************************************/
307 : /* ==================================================================== */
308 : /* PNGDataset */
309 : /* ==================================================================== */
310 : /************************************************************************/
311 :
312 : /************************************************************************/
313 : /* PNGDataset() */
314 : /************************************************************************/
315 :
316 8367 : PNGDataset::PNGDataset()
317 : {
318 8367 : memset(&sSetJmpContext, 0, sizeof(sSetJmpContext));
319 8367 : }
320 :
321 : /************************************************************************/
322 : /* ~PNGDataset() */
323 : /************************************************************************/
324 :
325 16734 : PNGDataset::~PNGDataset()
326 :
327 : {
328 8367 : PNGDataset::FlushCache(true);
329 :
330 8367 : if (hPNG != nullptr)
331 7918 : png_destroy_read_struct(&hPNG, &psPNGInfo, nullptr);
332 :
333 8367 : if (fpImage)
334 7918 : VSIFCloseL(fpImage);
335 :
336 8367 : if (poColorTable != nullptr)
337 383 : delete poColorTable;
338 16734 : }
339 :
340 : /************************************************************************/
341 : /* LoadWholeImage() */
342 : /************************************************************************/
343 :
344 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
345 :
346 : #ifdef HAVE_SSE2
347 : #include "filter_sse2_intrinsics.c"
348 : #endif
349 :
350 : #if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
351 : __attribute__((optimize("tree-vectorize"))) static inline void
352 : AddVectors(const GByte *CPL_RESTRICT pabyInputLine,
353 : GByte *CPL_RESTRICT pabyOutputLine, int nSize)
354 : {
355 : for (int iX = 0; iX < nSize; ++iX)
356 : pabyOutputLine[iX] =
357 : static_cast<GByte>(pabyInputLine[iX] + pabyOutputLine[iX]);
358 : }
359 :
360 : __attribute__((optimize("tree-vectorize"))) static inline void
361 : AddVectors(const GByte *CPL_RESTRICT pabyInputLine1,
362 : const GByte *CPL_RESTRICT pabyInputLine2,
363 : GByte *CPL_RESTRICT pabyOutputLine, int nSize)
364 : {
365 : for (int iX = 0; iX < nSize; ++iX)
366 : pabyOutputLine[iX] =
367 : static_cast<GByte>(pabyInputLine1[iX] + pabyInputLine2[iX]);
368 : }
369 : #endif // defined(__GNUC__) && !defined(__SSE2__)
370 :
371 3368 : CPLErr PNGDataset::LoadWholeImage(void *pSingleBuffer, GSpacing nPixelSpace,
372 : GSpacing nLineSpace, GSpacing nBandSpace,
373 : void *apabyBuffers[4])
374 : {
375 3368 : if (fpImage == nullptr)
376 : {
377 21 : for (int iY = 0; iY < nRasterYSize; ++iY)
378 : {
379 20 : if (pSingleBuffer)
380 : {
381 20 : GByte *pabyDest =
382 20 : static_cast<GByte *>(pSingleBuffer) + iY * nLineSpace;
383 420 : for (int x = 0; x < nRasterXSize; ++x)
384 : {
385 800 : for (int iBand = 0; iBand < nBands; iBand++)
386 : {
387 400 : pabyDest[(x * nPixelSpace) + iBand * nBandSpace] = 0;
388 : }
389 : }
390 : }
391 : else
392 : {
393 0 : for (int iBand = 0; iBand < nBands; iBand++)
394 : {
395 0 : GByte *l_pabyBuffer =
396 0 : static_cast<GByte *>(apabyBuffers[iBand]) +
397 0 : iY * nRasterXSize;
398 0 : memset(l_pabyBuffer, 0, nRasterXSize);
399 : }
400 : }
401 : }
402 1 : return CE_None;
403 : }
404 :
405 3367 : const bool bCanUseDeinterleave =
406 4979 : (nBands == 3 || nBands == 4) &&
407 1612 : (apabyBuffers != nullptr ||
408 1448 : (nPixelSpace == 1 &&
409 1448 : nBandSpace == static_cast<GSpacing>(nRasterXSize) * nRasterYSize));
410 :
411 : // Below should work without SSE2, but the lack of optimized
412 : // filters can sometimes make it slower than regular optimized libpng,
413 : // so restrict to when SSE2 is available.
414 :
415 : // CPLDebug("PNG", "Using libdeflate optimization");
416 :
417 3367 : char szChunkName[5] = {0};
418 3367 : bool bError = false;
419 :
420 : // We try to read the zlib compressed data into pData, if there is
421 : // enough room for that
422 3367 : size_t nDataSize = 0;
423 6734 : std::vector<GByte> abyCompressedData; // keep in this scope
424 3367 : GByte *pabyCompressedData = static_cast<GByte *>(pSingleBuffer);
425 3367 : size_t nCompressedDataSize = 0;
426 3367 : if (pSingleBuffer)
427 : {
428 2655 : if (nPixelSpace == nBands && nLineSpace == nPixelSpace * nRasterXSize &&
429 422 : (nBands == 1 || nBandSpace == 1))
430 : {
431 422 : nDataSize =
432 422 : static_cast<size_t>(nRasterXSize) * nRasterYSize * nBands;
433 : }
434 2233 : else if (nPixelSpace == 1 && nLineSpace == nRasterXSize &&
435 : nBandSpace ==
436 1962 : static_cast<GSpacing>(nRasterXSize) * nRasterYSize)
437 : {
438 1962 : nDataSize =
439 1962 : static_cast<size_t>(nRasterXSize) * nRasterYSize * nBands;
440 : }
441 : }
442 :
443 3367 : const auto nPosBefore = VSIFTellL(fpImage);
444 3367 : VSIFSeekL(fpImage, 8, SEEK_SET);
445 : // Iterate over PNG chunks
446 : while (true)
447 : {
448 : uint32_t nChunkSize;
449 14265 : if (VSIFReadL(&nChunkSize, sizeof(nChunkSize), 1, fpImage) == 0)
450 : {
451 0 : bError = true;
452 0 : break;
453 : }
454 14265 : CPL_MSBPTR32(&nChunkSize);
455 14265 : if (VSIFReadL(szChunkName, 4, 1, fpImage) == 0)
456 : {
457 0 : bError = true;
458 0 : break;
459 : }
460 14265 : if (strcmp(szChunkName, "IDAT") == 0)
461 : {
462 : // CPLDebug("PNG", "IDAT %u %u", unsigned(nCompressedDataSize),
463 : // unsigned(nChunkSize));
464 :
465 : // There can be several IDAT chunks: concatenate ZLib stream
466 12984 : if (nChunkSize >
467 6492 : std::numeric_limits<size_t>::max() - nCompressedDataSize)
468 : {
469 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
470 : "Out of memory when reading compressed stream");
471 0 : bError = true;
472 0 : break;
473 : }
474 :
475 : // Sanity check to avoid allocating too much memory
476 6492 : if (nCompressedDataSize + nChunkSize > 100 * 1024 * 1024)
477 : {
478 0 : const auto nCurPos = VSIFTellL(fpImage);
479 0 : VSIFSeekL(fpImage, 0, SEEK_END);
480 0 : const auto nSize = VSIFTellL(fpImage);
481 0 : VSIFSeekL(fpImage, nCurPos, SEEK_SET);
482 0 : if (nSize < 100 * 1024 * 1024)
483 : {
484 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
485 : "Attempt at reading more data than available in "
486 : "compressed stream");
487 0 : bError = true;
488 0 : break;
489 : }
490 : }
491 :
492 6492 : if (nCompressedDataSize + nChunkSize > nDataSize)
493 : {
494 3714 : const bool bVectorEmptyBefore = abyCompressedData.empty();
495 : // unlikely situation: would mean that the zlib compressed
496 : // data is longer than the decompressed image
497 : try
498 : {
499 3714 : abyCompressedData.resize(nCompressedDataSize + nChunkSize);
500 3714 : pabyCompressedData = abyCompressedData.data();
501 : }
502 0 : catch (const std::exception &)
503 : {
504 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
505 : "Out of memory when allocating compressed stream");
506 0 : bError = true;
507 0 : break;
508 : }
509 3714 : if (bVectorEmptyBefore && pSingleBuffer &&
510 : nCompressedDataSize > 0)
511 : {
512 0 : memcpy(pabyCompressedData, pSingleBuffer,
513 : nCompressedDataSize);
514 : }
515 : }
516 6492 : VSIFReadL(pabyCompressedData + nCompressedDataSize, nChunkSize, 1,
517 : fpImage);
518 6492 : nCompressedDataSize += nChunkSize;
519 : }
520 7773 : else if (strcmp(szChunkName, "IEND") == 0)
521 3367 : break;
522 : else
523 : {
524 : // CPLDebug("PNG", "Skipping chunk %s of size %u", szChunkName,
525 : // nChunkSize);
526 4406 : VSIFSeekL(fpImage, nChunkSize, SEEK_CUR);
527 : }
528 10898 : VSIFSeekL(fpImage, 4, SEEK_CUR); // CRC
529 10898 : }
530 3367 : VSIFSeekL(fpImage, nPosBefore, SEEK_SET);
531 3367 : if (bError)
532 0 : return CE_Failure;
533 :
534 3367 : const int nSamplesPerLine = nRasterXSize * nBands;
535 : size_t nOutBytes;
536 3367 : constexpr int FILTER_TYPE_BYTE = 1;
537 3367 : const size_t nZlibDecompressedSize = static_cast<size_t>(nRasterYSize) *
538 3367 : (FILTER_TYPE_BYTE + nSamplesPerLine);
539 : GByte *pabyZlibDecompressed =
540 3367 : static_cast<GByte *>(VSI_MALLOC_VERBOSE(nZlibDecompressedSize));
541 3367 : if (pabyZlibDecompressed == nullptr)
542 : {
543 0 : return CE_Failure;
544 : }
545 :
546 3367 : if (CPLZLibInflate(pabyCompressedData, nCompressedDataSize,
547 : pabyZlibDecompressed, nZlibDecompressedSize,
548 3367 : &nOutBytes) == nullptr)
549 : {
550 0 : CPLError(CE_Failure, CPLE_AppDefined, "CPLZLibInflate() failed");
551 0 : CPLFree(pabyZlibDecompressed);
552 0 : return CE_Failure;
553 : }
554 :
555 : GByte *pabyOutputBuffer;
556 6734 : std::vector<GByte> abyTemp;
557 6734 : std::vector<GByte> abyLineUp;
558 :
559 3367 : if (pSingleBuffer != nullptr && nPixelSpace == nBands &&
560 432 : nLineSpace == nPixelSpace * nRasterXSize &&
561 422 : (nBands == 1 || nBandSpace == 1))
562 : {
563 422 : pabyOutputBuffer = static_cast<GByte *>(pSingleBuffer);
564 : }
565 : else
566 : {
567 2945 : abyTemp.resize(nSamplesPerLine);
568 2945 : pabyOutputBuffer = abyTemp.data();
569 : }
570 :
571 418838 : for (int iY = 0; iY < nRasterYSize; ++iY)
572 : {
573 : // Cf http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
574 : // CPLDebug("PNG", "Line %d, filter type = %d", iY, nFilterType);
575 415471 : const GByte *CPL_RESTRICT pabyInputLine =
576 : pabyZlibDecompressed +
577 415471 : static_cast<size_t>(iY) * (FILTER_TYPE_BYTE + nSamplesPerLine);
578 415471 : const GByte nFilterType = pabyInputLine[0];
579 415471 : pabyInputLine++;
580 : GByte *const CPL_RESTRICT pabyOutputLine =
581 415471 : abyTemp.empty()
582 415452 : ? pabyOutputBuffer + static_cast<size_t>(iY) * nSamplesPerLine
583 325067 : : abyTemp.data();
584 415440 : if (nFilterType == 0)
585 : {
586 : // Filter type 0: None
587 127503 : memcpy(pabyOutputLine, pabyInputLine, nSamplesPerLine);
588 : }
589 287937 : else if (nFilterType == 1)
590 : {
591 : // Filter type 1: Sub (horizontal differencing)
592 : #ifdef HAVE_SSE2
593 12880 : if (nBands == 3)
594 : {
595 : png_row_info row_info;
596 4585 : memset(&row_info, 0, sizeof(row_info));
597 4585 : row_info.rowbytes = nSamplesPerLine;
598 :
599 4585 : gdal_png_read_filter_row_sub3_sse2(&row_info, pabyInputLine,
600 : pabyOutputLine);
601 : }
602 8295 : else if (nBands == 4)
603 : {
604 : png_row_info row_info;
605 6721 : memset(&row_info, 0, sizeof(row_info));
606 6721 : row_info.rowbytes = nSamplesPerLine;
607 :
608 6721 : gdal_png_read_filter_row_sub4_sse2(&row_info, pabyInputLine,
609 : pabyOutputLine);
610 : }
611 : else
612 : #endif
613 : {
614 : int iX;
615 4333 : for (iX = 0; iX < nBands; ++iX)
616 2759 : pabyOutputLine[iX] = pabyInputLine[iX];
617 : #if !defined(HAVE_SSE2)
618 : if (nBands == 3)
619 : {
620 : GByte nLast0 = pabyOutputLine[0];
621 : GByte nLast1 = pabyOutputLine[1];
622 : GByte nLast2 = pabyOutputLine[2];
623 : for (; iX + 5 < nSamplesPerLine; iX += 6)
624 : {
625 : nLast0 =
626 : static_cast<GByte>(nLast0 + pabyInputLine[iX + 0]);
627 : nLast1 =
628 : static_cast<GByte>(nLast1 + pabyInputLine[iX + 1]);
629 : nLast2 =
630 : static_cast<GByte>(nLast2 + pabyInputLine[iX + 2]);
631 : pabyOutputLine[iX + 0] = nLast0;
632 : pabyOutputLine[iX + 1] = nLast1;
633 : pabyOutputLine[iX + 2] = nLast2;
634 : nLast0 =
635 : static_cast<GByte>(nLast0 + pabyInputLine[iX + 3]);
636 : nLast1 =
637 : static_cast<GByte>(nLast1 + pabyInputLine[iX + 4]);
638 : nLast2 =
639 : static_cast<GByte>(nLast2 + pabyInputLine[iX + 5]);
640 : pabyOutputLine[iX + 3] = nLast0;
641 : pabyOutputLine[iX + 4] = nLast1;
642 : pabyOutputLine[iX + 5] = nLast2;
643 : }
644 : }
645 : else if (nBands == 4)
646 : {
647 : GByte nLast0 = pabyOutputLine[0];
648 : GByte nLast1 = pabyOutputLine[1];
649 : GByte nLast2 = pabyOutputLine[2];
650 : GByte nLast3 = pabyOutputLine[3];
651 : for (; iX + 7 < nSamplesPerLine; iX += 8)
652 : {
653 : nLast0 =
654 : static_cast<GByte>(nLast0 + pabyInputLine[iX + 0]);
655 : nLast1 =
656 : static_cast<GByte>(nLast1 + pabyInputLine[iX + 1]);
657 : nLast2 =
658 : static_cast<GByte>(nLast2 + pabyInputLine[iX + 2]);
659 : nLast3 =
660 : static_cast<GByte>(nLast3 + pabyInputLine[iX + 3]);
661 : pabyOutputLine[iX + 0] = nLast0;
662 : pabyOutputLine[iX + 1] = nLast1;
663 : pabyOutputLine[iX + 2] = nLast2;
664 : pabyOutputLine[iX + 3] = nLast3;
665 : nLast0 =
666 : static_cast<GByte>(nLast0 + pabyInputLine[iX + 4]);
667 : nLast1 =
668 : static_cast<GByte>(nLast1 + pabyInputLine[iX + 5]);
669 : nLast2 =
670 : static_cast<GByte>(nLast2 + pabyInputLine[iX + 6]);
671 : nLast3 =
672 : static_cast<GByte>(nLast3 + pabyInputLine[iX + 7]);
673 : pabyOutputLine[iX + 4] = nLast0;
674 : pabyOutputLine[iX + 5] = nLast1;
675 : pabyOutputLine[iX + 6] = nLast2;
676 : pabyOutputLine[iX + 7] = nLast3;
677 : }
678 : }
679 : #endif
680 196272 : for (; iX < nSamplesPerLine; ++iX)
681 194698 : pabyOutputLine[iX] = static_cast<GByte>(
682 194698 : pabyInputLine[iX] + pabyOutputLine[iX - nBands]);
683 : }
684 : }
685 275057 : else if (nFilterType == 2)
686 : {
687 : // Filter type 2: Up (vertical differencing)
688 99280 : if (iY == 0)
689 : {
690 0 : memcpy(pabyOutputLine, pabyInputLine, nSamplesPerLine);
691 : }
692 : else
693 : {
694 99280 : if (abyTemp.empty())
695 : {
696 22547 : const GByte *CPL_RESTRICT pabyOutputLineUp =
697 : pabyOutputBuffer +
698 22547 : (static_cast<size_t>(iY) - 1) * nSamplesPerLine;
699 : #if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
700 : AddVectors(pabyInputLine, pabyOutputLineUp, pabyOutputLine,
701 : nSamplesPerLine);
702 : #else
703 : int iX;
704 : #ifdef HAVE_SSE2
705 215847 : for (iX = 0; iX + 31 < nSamplesPerLine; iX += 32)
706 : {
707 : auto in =
708 193300 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
709 193300 : pabyInputLine + iX));
710 : auto in2 =
711 193300 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
712 193300 : pabyInputLine + iX + 16));
713 : auto up =
714 193300 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
715 193300 : pabyOutputLineUp + iX));
716 : auto up2 =
717 193300 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
718 193300 : pabyOutputLineUp + iX + 16));
719 193300 : in = _mm_add_epi8(in, up);
720 193300 : in2 = _mm_add_epi8(in2, up2);
721 193300 : _mm_storeu_si128(
722 193300 : reinterpret_cast<__m128i *>(pabyOutputLine + iX),
723 : in);
724 193300 : _mm_storeu_si128(reinterpret_cast<__m128i *>(
725 193300 : pabyOutputLine + iX + 16),
726 : in2);
727 : }
728 : #endif
729 26461 : for (; iX < nSamplesPerLine; ++iX)
730 3914 : pabyOutputLine[iX] = static_cast<GByte>(
731 3914 : pabyInputLine[iX] + pabyOutputLineUp[iX]);
732 : #endif
733 : }
734 : else
735 : {
736 : #if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
737 : AddVectors(pabyInputLine, pabyOutputLine, nSamplesPerLine);
738 : #else
739 : int iX;
740 : #ifdef HAVE_SSE2
741 1726340 : for (iX = 0; iX + 31 < nSamplesPerLine; iX += 32)
742 : {
743 : auto in =
744 1649600 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
745 1649600 : pabyInputLine + iX));
746 : auto in2 =
747 1649600 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
748 1649600 : pabyInputLine + iX + 16));
749 : auto out =
750 3299210 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
751 1649600 : pabyOutputLine + iX));
752 : auto out2 =
753 1649600 : _mm_loadu_si128(reinterpret_cast<const __m128i *>(
754 1649600 : pabyOutputLine + iX + 16));
755 1649600 : out = _mm_add_epi8(out, in);
756 1649600 : out2 = _mm_add_epi8(out2, in2);
757 1649600 : _mm_storeu_si128(
758 1649600 : reinterpret_cast<__m128i *>(pabyOutputLine + iX),
759 : out);
760 1649600 : _mm_storeu_si128(reinterpret_cast<__m128i *>(
761 1649600 : pabyOutputLine + iX + 16),
762 : out2);
763 : }
764 : #endif
765 245630 : for (; iX < nSamplesPerLine; ++iX)
766 168896 : pabyOutputLine[iX] = static_cast<GByte>(
767 168896 : pabyOutputLine[iX] + pabyInputLine[iX]);
768 : #endif
769 : }
770 : }
771 : }
772 175777 : else if (nFilterType == 3)
773 : {
774 : // Filter type 3: Average
775 11196 : if (iY == 0)
776 : {
777 166 : for (int iX = 0; iX < nBands; ++iX)
778 : {
779 114 : pabyOutputLine[iX] = pabyInputLine[iX];
780 : }
781 1522 : for (int iX = nBands; iX < nSamplesPerLine; ++iX)
782 : {
783 1470 : pabyOutputLine[iX] = static_cast<GByte>(
784 1470 : pabyInputLine[iX] + pabyOutputLine[iX - nBands] / 2);
785 : }
786 : }
787 : else
788 : {
789 : #ifdef HAVE_SSE2
790 11144 : if (nBands == 3)
791 : {
792 : png_row_info row_info;
793 5870 : memset(&row_info, 0, sizeof(row_info));
794 5870 : row_info.rowbytes = nSamplesPerLine;
795 5870 : if (!abyTemp.empty())
796 5870 : abyLineUp = abyTemp;
797 : const GByte *const pabyOutputLineUp =
798 5870 : abyTemp.empty()
799 5870 : ? pabyOutputBuffer + (static_cast<size_t>(iY) - 1) *
800 0 : nSamplesPerLine
801 5870 : : abyLineUp.data();
802 :
803 5870 : gdal_png_read_filter_row_avg3_sse2(&row_info, pabyInputLine,
804 : pabyOutputLine,
805 : pabyOutputLineUp);
806 : }
807 5274 : else if (nBands == 4)
808 : {
809 : png_row_info row_info;
810 4581 : memset(&row_info, 0, sizeof(row_info));
811 4581 : row_info.rowbytes = nSamplesPerLine;
812 4581 : if (!abyTemp.empty())
813 4459 : abyLineUp = abyTemp;
814 : const GByte *const pabyOutputLineUp =
815 4581 : abyTemp.empty()
816 4581 : ? pabyOutputBuffer + (static_cast<size_t>(iY) - 1) *
817 122 : nSamplesPerLine
818 4459 : : abyLineUp.data();
819 :
820 4581 : gdal_png_read_filter_row_avg4_sse2(&row_info, pabyInputLine,
821 : pabyOutputLine,
822 : pabyOutputLineUp);
823 : }
824 : else
825 : #endif
826 693 : if (abyTemp.empty())
827 : {
828 222 : const GByte *CPL_RESTRICT pabyOutputLineUp =
829 : pabyOutputBuffer +
830 222 : (static_cast<size_t>(iY) - 1) * nSamplesPerLine;
831 444 : for (int iX = 0; iX < nBands; ++iX)
832 : {
833 222 : pabyOutputLine[iX] = static_cast<GByte>(
834 222 : pabyInputLine[iX] + pabyOutputLineUp[iX] / 2);
835 : }
836 39384 : for (int iX = nBands; iX < nSamplesPerLine; ++iX)
837 : {
838 39162 : pabyOutputLine[iX] = static_cast<GByte>(
839 39162 : pabyInputLine[iX] + (pabyOutputLine[iX - nBands] +
840 39162 : pabyOutputLineUp[iX]) /
841 : 2);
842 : }
843 : }
844 : else
845 : {
846 1410 : for (int iX = 0; iX < nBands; ++iX)
847 : {
848 939 : pabyOutputLine[iX] = static_cast<GByte>(
849 939 : pabyInputLine[iX] + pabyOutputLine[iX] / 2);
850 : }
851 15276 : for (int iX = nBands; iX < nSamplesPerLine; ++iX)
852 : {
853 14805 : pabyOutputLine[iX] = static_cast<GByte>(
854 14805 : pabyInputLine[iX] +
855 14805 : (pabyOutputLine[iX - nBands] + pabyOutputLine[iX]) /
856 : 2);
857 : }
858 : }
859 : }
860 : }
861 164581 : else if (nFilterType == 4)
862 : {
863 : // Filter type 4: Paeth
864 164580 : if (iY == 0)
865 : {
866 158620 : for (int iX = 0; iX < nSamplesPerLine; ++iX)
867 : {
868 158464 : GByte a = iX < nBands ? 0 : pabyOutputLine[iX - nBands];
869 158464 : pabyOutputLine[iX] =
870 158464 : static_cast<GByte>(pabyInputLine[iX] + a);
871 : }
872 : }
873 : else
874 : {
875 164424 : if (!abyTemp.empty())
876 123068 : abyLineUp = abyTemp;
877 : const GByte *const pabyOutputLineUp =
878 164405 : abyTemp.empty()
879 164410 : ? pabyOutputBuffer +
880 41360 : (static_cast<size_t>(iY) - 1) * nSamplesPerLine
881 123050 : : abyLineUp.data();
882 : #ifdef HAVE_SSE2
883 164428 : if (nBands == 3)
884 : {
885 : png_row_info row_info;
886 109826 : memset(&row_info, 0, sizeof(row_info));
887 109826 : row_info.rowbytes = nSamplesPerLine;
888 109826 : gdal_png_read_filter_row_paeth3_sse2(
889 : &row_info, pabyInputLine, pabyOutputLine,
890 : pabyOutputLineUp);
891 : }
892 54602 : else if (nBands == 4)
893 : {
894 : png_row_info row_info;
895 48180 : memset(&row_info, 0, sizeof(row_info));
896 48180 : row_info.rowbytes = nSamplesPerLine;
897 48180 : gdal_png_read_filter_row_paeth4_sse2(
898 : &row_info, pabyInputLine, pabyOutputLine,
899 : pabyOutputLineUp);
900 : }
901 : else
902 : #endif
903 : {
904 6422 : int iX = 0;
905 16387 : for (; iX < nBands; ++iX)
906 : {
907 9965 : GByte b = pabyOutputLineUp[iX];
908 9965 : pabyOutputLine[iX] =
909 9965 : static_cast<GByte>(pabyInputLine[iX] + b);
910 : }
911 1323560 : for (; iX < nSamplesPerLine; ++iX)
912 : {
913 1317140 : GByte a = pabyOutputLine[iX - nBands];
914 1317140 : GByte b = pabyOutputLineUp[iX];
915 1317140 : GByte c = pabyOutputLineUp[iX - nBands];
916 1317140 : int p_minus_a = b - c;
917 1317140 : int p_minus_b = a - c;
918 1317140 : int p_minus_c = p_minus_a + p_minus_b;
919 1317140 : int pa = std::abs(p_minus_a);
920 1317140 : int pb = std::abs(p_minus_b);
921 1317140 : int pc = std::abs(p_minus_c);
922 1317140 : if (pa <= pb && pa <= pc)
923 1147330 : pabyOutputLine[iX] =
924 1147330 : static_cast<GByte>(pabyInputLine[iX] + a);
925 169801 : else if (pb <= pc)
926 122576 : pabyOutputLine[iX] =
927 122576 : static_cast<GByte>(pabyInputLine[iX] + b);
928 : else
929 47225 : pabyOutputLine[iX] =
930 47225 : static_cast<GByte>(pabyInputLine[iX] + c);
931 : }
932 : }
933 : }
934 : }
935 : else
936 : {
937 1 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid filter type %d",
938 : nFilterType);
939 0 : CPLFree(pabyZlibDecompressed);
940 0 : return CE_Failure;
941 : }
942 :
943 415460 : if (!abyTemp.empty())
944 : {
945 325069 : if (pSingleBuffer)
946 : {
947 152241 : GByte *pabyDest =
948 152241 : static_cast<GByte *>(pSingleBuffer) + iY * nLineSpace;
949 152241 : if (bCanUseDeinterleave)
950 : {
951 : // Cache friendly way for typical band interleaved case.
952 : void *apDestBuffers[4];
953 45007 : apDestBuffers[0] = pabyDest;
954 45007 : apDestBuffers[1] = pabyDest + nBandSpace;
955 45007 : apDestBuffers[2] = pabyDest + 2 * nBandSpace;
956 45007 : apDestBuffers[3] = pabyDest + 3 * nBandSpace;
957 45007 : GDALDeinterleave(pabyOutputLine, GDT_Byte, nBands,
958 45007 : apDestBuffers, GDT_Byte, nRasterXSize);
959 : }
960 107234 : else if (nPixelSpace <= nBands && nBandSpace > nBands)
961 : {
962 : // Cache friendly way for typical band interleaved case.
963 105222 : for (int iBand = 0; iBand < nBands; iBand++)
964 : {
965 70148 : GByte *pabyDest2 = pabyDest + iBand * nBandSpace;
966 70148 : const GByte *pabyScanline2 = pabyOutputLine + iBand;
967 70148 : GDALCopyWords(pabyScanline2, GDT_Byte, nBands,
968 : pabyDest2, GDT_Byte,
969 : static_cast<int>(nPixelSpace),
970 : nRasterXSize);
971 35074 : }
972 : }
973 : else
974 : {
975 : // Generic method
976 18895300 : for (int x = 0; x < nRasterXSize; ++x)
977 : {
978 38694900 : for (int iBand = 0; iBand < nBands; iBand++)
979 : {
980 19871700 : pabyDest[(x * nPixelSpace) + iBand * nBandSpace] =
981 19871700 : pabyOutputLine[x * nBands + iBand];
982 : }
983 : }
984 : }
985 : }
986 : else
987 : {
988 : GByte *apabyDestBuffers[4];
989 712690 : for (int iBand = 0; iBand < nBands; iBand++)
990 : {
991 539862 : apabyDestBuffers[iBand] =
992 539862 : static_cast<GByte *>(apabyBuffers[iBand]) +
993 539862 : iY * nRasterXSize;
994 : }
995 172828 : if (bCanUseDeinterleave)
996 : {
997 : // Cache friendly way for typical band interleaved case.
998 169436 : GDALDeinterleave(
999 : pabyOutputLine, GDT_Byte, nBands,
1000 : reinterpret_cast<void **>(apabyDestBuffers), GDT_Byte,
1001 169436 : nRasterXSize);
1002 : }
1003 : else
1004 : {
1005 : // Generic method
1006 921400 : for (int x = 0; x < nRasterXSize; ++x)
1007 : {
1008 2754020 : for (int iBand = 0; iBand < nBands; iBand++)
1009 : {
1010 1836020 : apabyDestBuffers[iBand][x] =
1011 1836020 : pabyOutputLine[x * nBands + iBand];
1012 : }
1013 : }
1014 : }
1015 : }
1016 : }
1017 : }
1018 :
1019 3367 : CPLFree(pabyZlibDecompressed);
1020 :
1021 3367 : return CE_None;
1022 : }
1023 :
1024 : #endif // ENABLE_WHOLE_IMAGE_OPTIMIZATION
1025 :
1026 : /************************************************************************/
1027 : /* IRasterIO() */
1028 : /************************************************************************/
1029 :
1030 4030 : CPLErr PNGDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1031 : int nXSize, int nYSize, void *pData, int nBufXSize,
1032 : int nBufYSize, GDALDataType eBufType,
1033 : int nBandCount, BANDMAP_TYPE panBandMap,
1034 : GSpacing nPixelSpace, GSpacing nLineSpace,
1035 : GSpacing nBandSpace,
1036 : GDALRasterIOExtraArg *psExtraArg)
1037 :
1038 : {
1039 : // Coverity says that we cannot pass a nullptr to IRasterIO.
1040 4030 : if (panBandMap == nullptr)
1041 : {
1042 0 : return CE_Failure;
1043 : }
1044 :
1045 4030 : if ((eRWFlag == GF_Read) && (nBandCount == nBands) && (nXOff == 0) &&
1046 2702 : (nYOff == 0) && (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
1047 2698 : (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
1048 2624 : (eBufType == GDT_Byte) &&
1049 2624 : (eBufType == GetRasterBand(1)->GetRasterDataType()) &&
1050 8060 : (pData != nullptr) && IsAllBands(nBands, panBandMap))
1051 : {
1052 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
1053 : // Below should work without SSE2, but the lack of optimized
1054 : // filters can sometimes make it slower than regular optimized libpng,
1055 : // so restrict to when SSE2 is available.
1056 :
1057 5240 : if (!bInterlaced && nBitDepth == 8 &&
1058 2616 : CPLTestBool(
1059 : CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")))
1060 : {
1061 2608 : return LoadWholeImage(pData, nPixelSpace, nLineSpace, nBandSpace,
1062 2608 : nullptr);
1063 : }
1064 16 : else if (cpl::down_cast<PNGRasterBand *>(papoBands[0])->nBlockYSize > 1)
1065 : {
1066 : // Below code requires scanline access in
1067 : // PNGRasterBand::IReadBlock()
1068 : }
1069 : else
1070 : #endif // ENABLE_WHOLE_IMAGE_OPTIMIZATION
1071 :
1072 : // Pixel interleaved case.
1073 16 : if (nBandSpace == 1)
1074 : {
1075 302 : for (int y = 0; y < nYSize; ++y)
1076 : {
1077 300 : CPLErr tmpError = LoadScanline(y);
1078 300 : if (tmpError != CE_None)
1079 0 : return tmpError;
1080 300 : const GByte *pabyScanline =
1081 300 : pabyBuffer + (y - nBufferStartLine) * nBands * nXSize;
1082 300 : if (nPixelSpace == nBandSpace * nBandCount)
1083 : {
1084 150 : memcpy(&(static_cast<GByte *>(pData)[(y * nLineSpace)]),
1085 : pabyScanline,
1086 150 : cpl::fits_on<int>(nBandCount * nXSize));
1087 : }
1088 : else
1089 : {
1090 24450 : for (int x = 0; x < nXSize; ++x)
1091 : {
1092 24300 : memcpy(&(static_cast<GByte *>(
1093 24300 : pData)[(y * nLineSpace) +
1094 24300 : (x * nPixelSpace)]),
1095 24300 : &(pabyScanline[x * nBandCount]), nBandCount);
1096 : }
1097 : }
1098 : }
1099 2 : return CE_None;
1100 : }
1101 : else
1102 : {
1103 14 : const bool bCanUseDeinterleave =
1104 19 : (nBands == 3 || nBands == 4) && nPixelSpace == 1 &&
1105 : nBandSpace ==
1106 5 : static_cast<GSpacing>(nRasterXSize) * nRasterYSize;
1107 :
1108 966 : for (int y = 0; y < nYSize; ++y)
1109 : {
1110 952 : CPLErr tmpError = LoadScanline(y);
1111 952 : if (tmpError != CE_None)
1112 0 : return tmpError;
1113 952 : const GByte *pabyScanline =
1114 952 : pabyBuffer + (y - nBufferStartLine) * nBands * nXSize;
1115 952 : GByte *pabyDest =
1116 952 : static_cast<GByte *>(pData) + y * nLineSpace;
1117 952 : if (bCanUseDeinterleave)
1118 : {
1119 : // Cache friendly way for typical band interleaved case.
1120 : void *apDestBuffers[4];
1121 176 : apDestBuffers[0] = pabyDest;
1122 176 : apDestBuffers[1] = pabyDest + nBandSpace;
1123 176 : apDestBuffers[2] = pabyDest + 2 * nBandSpace;
1124 176 : apDestBuffers[3] = pabyDest + 3 * nBandSpace;
1125 176 : GDALDeinterleave(pabyScanline, GDT_Byte, nBands,
1126 176 : apDestBuffers, GDT_Byte, nRasterXSize);
1127 : }
1128 776 : else if (nPixelSpace <= nBands && nBandSpace > nBands)
1129 : {
1130 : // Cache friendly way for typical band interleaved case.
1131 39 : for (int iBand = 0; iBand < nBands; iBand++)
1132 : {
1133 26 : GByte *pabyDest2 = pabyDest + iBand * nBandSpace;
1134 26 : const GByte *pabyScanline2 = pabyScanline + iBand;
1135 26 : GDALCopyWords(pabyScanline2, GDT_Byte, nBands,
1136 : pabyDest2, GDT_Byte,
1137 : static_cast<int>(nPixelSpace),
1138 : nXSize);
1139 13 : }
1140 : }
1141 : else
1142 : {
1143 : // Generic method
1144 124884 : for (int x = 0; x < nXSize; ++x)
1145 : {
1146 248242 : for (int iBand = 0; iBand < nBands; iBand++)
1147 : {
1148 124121 : pabyDest[(x * nPixelSpace) +
1149 124121 : iBand * nBandSpace] =
1150 124121 : pabyScanline[x * nBands + iBand];
1151 : }
1152 : }
1153 : }
1154 : }
1155 14 : return CE_None;
1156 : }
1157 : }
1158 :
1159 1406 : return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1160 : pData, nBufXSize, nBufYSize, eBufType,
1161 : nBandCount, panBandMap, nPixelSpace,
1162 1406 : nLineSpace, nBandSpace, psExtraArg);
1163 : }
1164 :
1165 : /************************************************************************/
1166 : /* IRasterIO() */
1167 : /************************************************************************/
1168 :
1169 6138 : CPLErr PNGRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1170 : int nXSize, int nYSize, void *pData,
1171 : int nBufXSize, int nBufYSize,
1172 : GDALDataType eBufType, GSpacing nPixelSpace,
1173 : GSpacing nLineSpace,
1174 : GDALRasterIOExtraArg *psExtraArg)
1175 :
1176 : {
1177 : #ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
1178 6138 : auto poGDS = cpl::down_cast<PNGDataset *>(poDS);
1179 6139 : if ((eRWFlag == GF_Read) && (nXOff == 0) && (nYOff == 0) &&
1180 2506 : (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
1181 2465 : (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
1182 2098 : (eBufType == GDT_Byte) && (eBufType == eDataType))
1183 : {
1184 2098 : bool bBlockAlreadyLoaded = false;
1185 2098 : if (nBlockYSize > 1)
1186 : {
1187 1960 : auto poBlock = TryGetLockedBlockRef(0, 0);
1188 1961 : if (poBlock != nullptr)
1189 : {
1190 1337 : bBlockAlreadyLoaded = poBlock->GetDataRef() != pData;
1191 1337 : poBlock->DropLock();
1192 : }
1193 : }
1194 :
1195 2099 : if (bBlockAlreadyLoaded)
1196 : {
1197 : // will got to general case
1198 : }
1199 54 : else if (poGDS->nBands == 1 && !poGDS->bInterlaced &&
1200 993 : poGDS->nBitDepth == 8 &&
1201 54 : CPLTestBool(
1202 : CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")))
1203 : {
1204 48 : return poGDS->LoadWholeImage(pData, nPixelSpace, nLineSpace, 0,
1205 48 : nullptr);
1206 : }
1207 837 : else if (nBlockYSize > 1)
1208 : {
1209 : void *apabyBuffers[4];
1210 712 : GDALRasterBlock *apoBlocks[4] = {nullptr, nullptr, nullptr,
1211 : nullptr};
1212 712 : CPLErr eErr = CE_None;
1213 712 : bool bNeedToUseDefaultCase = true;
1214 2935 : for (int i = 0; i < poGDS->nBands; ++i)
1215 : {
1216 2223 : if (i + 1 == nBand && nPixelSpace == 1 &&
1217 712 : nLineSpace == nRasterXSize)
1218 : {
1219 710 : bNeedToUseDefaultCase = false;
1220 710 : apabyBuffers[i] = pData;
1221 : }
1222 : else
1223 : {
1224 1513 : apoBlocks[i] =
1225 1513 : poGDS->GetRasterBand(i + 1)->GetLockedBlockRef(0, 0,
1226 1513 : TRUE);
1227 1513 : apabyBuffers[i] =
1228 1513 : apoBlocks[i] ? apoBlocks[i]->GetDataRef() : nullptr;
1229 1513 : if (apabyBuffers[i] == nullptr)
1230 0 : eErr = CE_Failure;
1231 : }
1232 : }
1233 712 : if (eErr == CE_None)
1234 : {
1235 712 : eErr = poGDS->LoadWholeImage(nullptr, 0, 0, 0, apabyBuffers);
1236 : }
1237 2935 : for (int i = 0; i < poGDS->nBands; ++i)
1238 : {
1239 2223 : if (apoBlocks[i])
1240 1513 : apoBlocks[i]->DropLock();
1241 : }
1242 712 : if (eErr != CE_None || !bNeedToUseDefaultCase)
1243 710 : return eErr;
1244 : }
1245 : }
1246 : #endif
1247 5382 : return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1248 : pData, nBufXSize, nBufYSize, eBufType,
1249 5381 : nPixelSpace, nLineSpace, psExtraArg);
1250 : }
1251 :
1252 : /************************************************************************/
1253 : /* GetGeoTransform() */
1254 : /************************************************************************/
1255 :
1256 130 : CPLErr PNGDataset::GetGeoTransform(GDALGeoTransform >) const
1257 :
1258 : {
1259 130 : const_cast<PNGDataset *>(this)->LoadWorldFile();
1260 :
1261 130 : if (bGeoTransformValid)
1262 : {
1263 3 : gt = m_gt;
1264 3 : return CE_None;
1265 : }
1266 :
1267 127 : return GDALPamDataset::GetGeoTransform(gt);
1268 : }
1269 :
1270 : /************************************************************************/
1271 : /* FlushCache() */
1272 : /* */
1273 : /* We override this so we can also flush out local TIFF strip */
1274 : /* cache if need be. */
1275 : /************************************************************************/
1276 :
1277 8379 : CPLErr PNGDataset::FlushCache(bool bAtClosing)
1278 :
1279 : {
1280 8379 : const CPLErr eErr = GDALPamDataset::FlushCache(bAtClosing);
1281 :
1282 8379 : if (pabyBuffer != nullptr)
1283 : {
1284 230 : CPLFree(pabyBuffer);
1285 230 : pabyBuffer = nullptr;
1286 230 : nBufferStartLine = 0;
1287 230 : nBufferLines = 0;
1288 : }
1289 8379 : return eErr;
1290 : }
1291 :
1292 : #ifdef DISABLE_CRC_CHECK
1293 : /************************************************************************/
1294 : /* PNGDatasetDisableCRCCheck() */
1295 : /************************************************************************/
1296 :
1297 : static void PNGDatasetDisableCRCCheck(png_structp hPNG)
1298 : {
1299 : hPNG->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
1300 : hPNG->flags |= PNG_FLAG_CRC_CRITICAL_IGNORE;
1301 :
1302 : hPNG->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
1303 : hPNG->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN;
1304 : }
1305 : #endif
1306 :
1307 : /************************************************************************/
1308 : /* Restart() */
1309 : /* */
1310 : /* Restart reading from the beginning of the file. */
1311 : /************************************************************************/
1312 :
1313 0 : void PNGDataset::Restart()
1314 :
1315 : {
1316 0 : if (!m_bHasRewind)
1317 : {
1318 0 : m_bHasRewind = true;
1319 0 : CPLDebug("PNG", "Restart decompression from top (emitted once)");
1320 : }
1321 :
1322 0 : png_destroy_read_struct(&hPNG, &psPNGInfo, nullptr);
1323 :
1324 0 : hPNG =
1325 0 : png_create_read_struct(PNG_LIBPNG_VER_STRING, this, nullptr, nullptr);
1326 :
1327 : #ifdef DISABLE_CRC_CHECK
1328 : PNGDatasetDisableCRCCheck(hPNG);
1329 : #endif
1330 :
1331 0 : png_set_error_fn(hPNG, &sSetJmpContext, png_gdal_error, png_gdal_warning);
1332 0 : if (setjmp(sSetJmpContext) != 0)
1333 0 : return;
1334 :
1335 0 : psPNGInfo = png_create_info_struct(hPNG);
1336 :
1337 0 : VSIFSeekL(fpImage, 0, SEEK_SET);
1338 0 : png_set_read_fn(hPNG, fpImage, png_vsi_read_data);
1339 0 : png_read_info(hPNG, psPNGInfo);
1340 :
1341 0 : if (nBitDepth < 8)
1342 0 : png_set_packing(hPNG);
1343 :
1344 0 : nLastLineRead = -1;
1345 : }
1346 :
1347 : /************************************************************************/
1348 : /* safe_png_read_image() */
1349 : /************************************************************************/
1350 :
1351 13 : static bool safe_png_read_image(png_structp hPNG, png_bytep *png_rows,
1352 : jmp_buf sSetJmpContext)
1353 : {
1354 13 : if (setjmp(sSetJmpContext) != 0)
1355 0 : return false;
1356 13 : png_read_image(hPNG, png_rows);
1357 13 : return true;
1358 : }
1359 :
1360 : /************************************************************************/
1361 : /* LoadInterlacedChunk() */
1362 : /************************************************************************/
1363 :
1364 13 : CPLErr PNGDataset::LoadInterlacedChunk(int iLine)
1365 :
1366 : {
1367 : const int nPixelOffset =
1368 13 : (nBitDepth == 16) ? 2 * GetRasterCount() : GetRasterCount();
1369 :
1370 : // What is the biggest chunk we can safely operate on?
1371 13 : constexpr int MAX_PNG_CHUNK_BYTES = 100000000;
1372 :
1373 : int nMaxChunkLines =
1374 13 : std::max(1, MAX_PNG_CHUNK_BYTES / (nPixelOffset * GetRasterXSize()));
1375 :
1376 13 : if (nMaxChunkLines > GetRasterYSize())
1377 13 : nMaxChunkLines = GetRasterYSize();
1378 :
1379 : // Allocate chunk buffer if we don't already have it from a previous
1380 : // request.
1381 13 : nBufferLines = nMaxChunkLines;
1382 13 : if (nMaxChunkLines + iLine > GetRasterYSize())
1383 0 : nBufferStartLine = GetRasterYSize() - nMaxChunkLines;
1384 : else
1385 13 : nBufferStartLine = iLine;
1386 :
1387 13 : if (pabyBuffer == nullptr)
1388 : {
1389 13 : pabyBuffer = reinterpret_cast<GByte *>(VSI_MALLOC3_VERBOSE(
1390 : nPixelOffset, GetRasterXSize(), nMaxChunkLines));
1391 :
1392 13 : if (pabyBuffer == nullptr)
1393 : {
1394 0 : return CE_Failure;
1395 : }
1396 : #ifdef notdef
1397 : if (nMaxChunkLines < GetRasterYSize())
1398 : CPLDebug("PNG",
1399 : "Interlaced file being handled in %d line chunks.\n"
1400 : "Performance is likely to be quite poor.",
1401 : nMaxChunkLines);
1402 : #endif
1403 : }
1404 :
1405 : // Do we need to restart reading? We do this if we aren't on the first
1406 : // attempt to read the image.
1407 13 : if (nLastLineRead != -1)
1408 : {
1409 0 : Restart();
1410 : }
1411 :
1412 : // Allocate and populate rows array. We create a row for each row in the
1413 : // image but use our dummy line for rows not in the target window.
1414 : png_bytep dummy_row = reinterpret_cast<png_bytep>(
1415 13 : CPLMalloc(cpl::fits_on<int>(nPixelOffset * GetRasterXSize())));
1416 : png_bytep *png_rows = reinterpret_cast<png_bytep *>(
1417 13 : CPLMalloc(sizeof(png_bytep) * GetRasterYSize()));
1418 :
1419 1833 : for (int i = 0; i < GetRasterYSize(); i++)
1420 : {
1421 1820 : if (i >= nBufferStartLine && i < nBufferStartLine + nBufferLines)
1422 3640 : png_rows[i] = pabyBuffer + (i - nBufferStartLine) * nPixelOffset *
1423 1820 : GetRasterXSize();
1424 : else
1425 0 : png_rows[i] = dummy_row;
1426 : }
1427 :
1428 13 : bool bRet = safe_png_read_image(hPNG, png_rows, sSetJmpContext);
1429 :
1430 : // Do swap on LSB machines. 16-bit PNG data is stored in MSB format.
1431 13 : if (bRet && nBitDepth == 16
1432 : #ifdef CPL_LSB
1433 1 : && !m_bByteOrderIsLittleEndian
1434 : #else
1435 : && m_bByteOrderIsLittleEndian
1436 : #endif
1437 : )
1438 : {
1439 21 : for (int i = 0; i < GetRasterYSize(); i++)
1440 : {
1441 20 : if (i >= nBufferStartLine && i < nBufferStartLine + nBufferLines)
1442 : {
1443 20 : GDALSwapWords(png_rows[i], 2,
1444 20 : GetRasterXSize() * GetRasterCount(), 2);
1445 : }
1446 : }
1447 : }
1448 :
1449 13 : CPLFree(png_rows);
1450 13 : CPLFree(dummy_row);
1451 13 : if (!bRet)
1452 0 : return CE_Failure;
1453 :
1454 13 : nLastLineRead = nBufferStartLine + nBufferLines - 1;
1455 :
1456 13 : return CE_None;
1457 : }
1458 :
1459 : /************************************************************************/
1460 : /* safe_png_read_rows() */
1461 : /************************************************************************/
1462 :
1463 12095 : static bool safe_png_read_rows(png_structp hPNG, png_bytep row,
1464 : jmp_buf sSetJmpContext)
1465 : {
1466 12095 : if (setjmp(sSetJmpContext) != 0)
1467 2 : return false;
1468 12095 : png_read_rows(hPNG, &row, nullptr, 1);
1469 12093 : return true;
1470 : }
1471 :
1472 : /************************************************************************/
1473 : /* LoadScanline() */
1474 : /************************************************************************/
1475 :
1476 14245 : CPLErr PNGDataset::LoadScanline(int nLine)
1477 :
1478 : {
1479 14245 : CPLAssert(nLine >= 0 && nLine < GetRasterYSize());
1480 :
1481 14245 : if (nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines)
1482 2257 : return CE_None;
1483 :
1484 : const int nPixelOffset =
1485 11988 : (nBitDepth == 16) ? 2 * GetRasterCount() : GetRasterCount();
1486 :
1487 : // If the file is interlaced, we load the entire image into memory using the
1488 : // high-level API.
1489 11988 : if (bInterlaced)
1490 13 : return LoadInterlacedChunk(nLine);
1491 :
1492 : // Ensure we have space allocated for one scanline.
1493 11975 : if (pabyBuffer == nullptr)
1494 217 : pabyBuffer = reinterpret_cast<GByte *>(
1495 217 : CPLMalloc(cpl::fits_on<int>(nPixelOffset * GetRasterXSize())));
1496 :
1497 : // Otherwise we just try to read the requested row. Do we need to rewind and
1498 : // start over?
1499 11975 : if (nLine <= nLastLineRead)
1500 : {
1501 0 : Restart();
1502 : }
1503 :
1504 : // Read till we get the desired row.
1505 11975 : png_bytep row = pabyBuffer;
1506 11975 : const GUInt32 nErrorCounter = CPLGetErrorCounter();
1507 24068 : while (nLine > nLastLineRead)
1508 : {
1509 12095 : if (!safe_png_read_rows(hPNG, row, sSetJmpContext))
1510 : {
1511 2 : CPLError(CE_Failure, CPLE_AppDefined,
1512 : "Error while reading row %d%s", nLine,
1513 2 : (nErrorCounter != CPLGetErrorCounter())
1514 2 : ? CPLSPrintf(": %s", CPLGetLastErrorMsg())
1515 : : "");
1516 2 : return CE_Failure;
1517 : }
1518 12093 : nLastLineRead++;
1519 : }
1520 :
1521 11973 : nBufferStartLine = nLine;
1522 11973 : nBufferLines = 1;
1523 :
1524 : // Do swap on LSB machines. 16-bit PNG data is stored in MSB format.
1525 11973 : if (nBitDepth == 16
1526 : #ifdef CPL_LSB
1527 10317 : && !m_bByteOrderIsLittleEndian
1528 : #else
1529 : && m_bByteOrderIsLittleEndian
1530 : #endif
1531 : )
1532 : {
1533 10316 : GDALSwapWords(row, 2, GetRasterXSize() * GetRasterCount(), 2);
1534 : }
1535 :
1536 11973 : return CE_None;
1537 : }
1538 :
1539 : /************************************************************************/
1540 : /* CollectMetadata() */
1541 : /* */
1542 : /* We normally do this after reading up to the image, but be */
1543 : /* forewarned: we can miss text chunks this way. */
1544 : /* */
1545 : /* We turn each PNG text chunk into one metadata item. It */
1546 : /* might be nice to preserve language information though we */
1547 : /* don't try to now. */
1548 : /************************************************************************/
1549 :
1550 7917 : void PNGDataset::CollectMetadata()
1551 :
1552 : {
1553 7917 : if (nBitDepth < 8)
1554 : {
1555 16 : for (int iBand = 0; iBand < nBands; iBand++)
1556 : {
1557 16 : GetRasterBand(iBand + 1)->SetMetadataItem(
1558 16 : "NBITS", CPLString().Printf("%d", nBitDepth),
1559 8 : "IMAGE_STRUCTURE");
1560 : }
1561 : }
1562 :
1563 : int nTextCount;
1564 : png_textp text_ptr;
1565 7917 : if (png_get_text(hPNG, psPNGInfo, &text_ptr, &nTextCount) == 0)
1566 7908 : return;
1567 :
1568 14 : for (int iText = 0; iText < nTextCount; iText++)
1569 : {
1570 8 : char *pszTag = CPLStrdup(text_ptr[iText].key);
1571 :
1572 66 : for (int i = 0; pszTag[i] != '\0'; i++)
1573 : {
1574 58 : if (pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':')
1575 0 : pszTag[i] = '_';
1576 : }
1577 :
1578 8 : GDALDataset::SetMetadataItem(pszTag, text_ptr[iText].text);
1579 8 : CPLFree(pszTag);
1580 : }
1581 : }
1582 :
1583 : /************************************************************************/
1584 : /* CollectXMPMetadata() */
1585 : /************************************************************************/
1586 :
1587 : // See §2.1.5 of
1588 : // http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf.
1589 :
1590 13 : void PNGDataset::CollectXMPMetadata()
1591 :
1592 : {
1593 13 : if (fpImage == nullptr || bHasReadXMPMetadata)
1594 0 : return;
1595 :
1596 : // Save current position to avoid disturbing PNG stream decoding.
1597 13 : const vsi_l_offset nCurOffset = VSIFTellL(fpImage);
1598 :
1599 13 : vsi_l_offset nOffset = 8;
1600 13 : VSIFSeekL(fpImage, nOffset, SEEK_SET);
1601 :
1602 : // Loop over chunks.
1603 : while (true)
1604 : {
1605 : int nLength;
1606 :
1607 74 : if (VSIFReadL(&nLength, 4, 1, fpImage) != 1)
1608 0 : break;
1609 74 : nOffset += 4;
1610 74 : CPL_MSBPTR32(&nLength);
1611 74 : if (nLength <= 0)
1612 12 : break;
1613 :
1614 : char pszChunkType[5];
1615 62 : if (VSIFReadL(pszChunkType, 4, 1, fpImage) != 1)
1616 0 : break;
1617 62 : nOffset += 4;
1618 62 : pszChunkType[4] = 0;
1619 :
1620 62 : if (strcmp(pszChunkType, "iTXt") == 0 && nLength > 22 &&
1621 : // Does not make sense to have a XMP content larger than 10 MB
1622 : // (XMP in JPEG must fit in 65 KB...)
1623 1 : nLength < 10 * 1024 * 1024)
1624 : {
1625 1 : char *pszContent = reinterpret_cast<char *>(VSIMalloc(nLength + 1));
1626 1 : if (pszContent == nullptr)
1627 0 : break;
1628 1 : if (VSIFReadL(pszContent, nLength, 1, fpImage) != 1)
1629 : {
1630 0 : VSIFree(pszContent);
1631 0 : break;
1632 : }
1633 1 : nOffset += nLength;
1634 1 : pszContent[nLength] = '\0';
1635 1 : if (memcmp(pszContent, "XML:com.adobe.xmp\0\0\0\0\0", 22) == 0)
1636 : {
1637 : // Avoid setting the PAM dirty bit just for that.
1638 1 : const int nOldPamFlags = nPamFlags;
1639 :
1640 1 : char *apszMDList[2] = {pszContent + 22, nullptr};
1641 1 : SetMetadata(apszMDList, "xml:XMP");
1642 :
1643 : // cppcheck-suppress redundantAssignment
1644 1 : nPamFlags = nOldPamFlags;
1645 :
1646 1 : VSIFree(pszContent);
1647 :
1648 1 : break;
1649 : }
1650 : else
1651 : {
1652 0 : VSIFree(pszContent);
1653 0 : }
1654 : }
1655 : else
1656 : {
1657 61 : nOffset += nLength;
1658 61 : VSIFSeekL(fpImage, nOffset, SEEK_SET);
1659 : }
1660 :
1661 61 : nOffset += 4;
1662 : int nCRC;
1663 61 : if (VSIFReadL(&nCRC, 4, 1, fpImage) != 1)
1664 0 : break;
1665 61 : }
1666 :
1667 13 : VSIFSeekL(fpImage, nCurOffset, SEEK_SET);
1668 :
1669 13 : bHasReadXMPMetadata = TRUE;
1670 : }
1671 :
1672 : /************************************************************************/
1673 : /* LoadICCProfile() */
1674 : /************************************************************************/
1675 :
1676 24 : void PNGDataset::LoadICCProfile()
1677 : {
1678 24 : if (hPNG == nullptr || bHasReadICCMetadata)
1679 7 : return;
1680 24 : bHasReadICCMetadata = TRUE;
1681 :
1682 : png_charp pszProfileName;
1683 : png_uint_32 nProfileLength;
1684 : png_bytep pProfileData;
1685 : int nCompressionType;
1686 :
1687 : // Avoid setting the PAM dirty bit just for that.
1688 24 : int nOldPamFlags = nPamFlags;
1689 :
1690 24 : if (png_get_iCCP(hPNG, psPNGInfo, &pszProfileName, &nCompressionType,
1691 24 : &pProfileData, &nProfileLength) != 0)
1692 : {
1693 : // Escape the profile.
1694 : char *pszBase64Profile =
1695 5 : CPLBase64Encode(static_cast<int>(nProfileLength),
1696 : reinterpret_cast<const GByte *>(pProfileData));
1697 :
1698 : // Set ICC profile metadata.
1699 5 : SetMetadataItem("SOURCE_ICC_PROFILE", pszBase64Profile,
1700 5 : "COLOR_PROFILE");
1701 5 : SetMetadataItem("SOURCE_ICC_PROFILE_NAME", pszProfileName,
1702 5 : "COLOR_PROFILE");
1703 :
1704 5 : nPamFlags = nOldPamFlags;
1705 :
1706 5 : CPLFree(pszBase64Profile);
1707 :
1708 5 : return;
1709 : }
1710 :
1711 : int nsRGBIntent;
1712 19 : if (png_get_sRGB(hPNG, psPNGInfo, &nsRGBIntent) != 0)
1713 : {
1714 2 : SetMetadataItem("SOURCE_ICC_PROFILE_NAME", "sRGB", "COLOR_PROFILE");
1715 :
1716 2 : nPamFlags = nOldPamFlags;
1717 :
1718 2 : return;
1719 : }
1720 :
1721 : double dfGamma;
1722 17 : bool bGammaAvailable = false;
1723 17 : if (png_get_valid(hPNG, psPNGInfo, PNG_INFO_gAMA))
1724 : {
1725 6 : bGammaAvailable = true;
1726 :
1727 6 : png_get_gAMA(hPNG, psPNGInfo, &dfGamma);
1728 :
1729 6 : SetMetadataItem("PNG_GAMMA", CPLString().Printf("%.9f", dfGamma),
1730 6 : "COLOR_PROFILE");
1731 : }
1732 :
1733 : // Check that both cHRM and gAMA are available.
1734 17 : if (bGammaAvailable && png_get_valid(hPNG, psPNGInfo, PNG_INFO_cHRM))
1735 : {
1736 : double dfaWhitepoint[2];
1737 : double dfaCHR[6];
1738 :
1739 4 : png_get_cHRM(hPNG, psPNGInfo, &dfaWhitepoint[0], &dfaWhitepoint[1],
1740 : &dfaCHR[0], &dfaCHR[1], &dfaCHR[2], &dfaCHR[3], &dfaCHR[4],
1741 : &dfaCHR[5]);
1742 :
1743 : // Set all the colorimetric metadata.
1744 4 : SetMetadataItem(
1745 : "SOURCE_PRIMARIES_RED",
1746 8 : CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[0], dfaCHR[1]),
1747 4 : "COLOR_PROFILE");
1748 4 : SetMetadataItem(
1749 : "SOURCE_PRIMARIES_GREEN",
1750 8 : CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[2], dfaCHR[3]),
1751 4 : "COLOR_PROFILE");
1752 4 : SetMetadataItem(
1753 : "SOURCE_PRIMARIES_BLUE",
1754 8 : CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[4], dfaCHR[5]),
1755 4 : "COLOR_PROFILE");
1756 :
1757 4 : SetMetadataItem("SOURCE_WHITEPOINT",
1758 4 : CPLString().Printf("%.9f, %.9f, 1.0", dfaWhitepoint[0],
1759 4 : dfaWhitepoint[1]),
1760 4 : "COLOR_PROFILE");
1761 : }
1762 :
1763 17 : nPamFlags = nOldPamFlags;
1764 : }
1765 :
1766 : /************************************************************************/
1767 : /* GetMetadataDomainList() */
1768 : /************************************************************************/
1769 :
1770 3 : char **PNGDataset::GetMetadataDomainList()
1771 : {
1772 3 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
1773 3 : TRUE, "xml:XMP", "COLOR_PROFILE", nullptr);
1774 : }
1775 :
1776 : /************************************************************************/
1777 : /* GetMetadata() */
1778 : /************************************************************************/
1779 :
1780 109 : char **PNGDataset::GetMetadata(const char *pszDomain)
1781 : {
1782 109 : if (fpImage == nullptr)
1783 0 : return nullptr;
1784 109 : if (eAccess == GA_ReadOnly && !bHasReadXMPMetadata &&
1785 79 : pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP"))
1786 13 : CollectXMPMetadata();
1787 109 : if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
1788 49 : pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
1789 13 : LoadICCProfile();
1790 109 : return GDALPamDataset::GetMetadata(pszDomain);
1791 : }
1792 :
1793 : /************************************************************************/
1794 : /* GetMetadataItem() */
1795 : /************************************************************************/
1796 708 : const char *PNGDataset::GetMetadataItem(const char *pszName,
1797 : const char *pszDomain)
1798 : {
1799 708 : if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
1800 184 : pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
1801 11 : LoadICCProfile();
1802 708 : return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
1803 : }
1804 :
1805 : /************************************************************************/
1806 : /* Open() */
1807 : /************************************************************************/
1808 :
1809 7919 : GDALDataset *PNGDataset::Open(GDALOpenInfo *poOpenInfo)
1810 :
1811 : {
1812 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1813 : // During fuzzing, do not use Identify to reject crazy content.
1814 7919 : if (!PNGDriverIdentify(poOpenInfo))
1815 1 : return nullptr;
1816 : #else
1817 : if (poOpenInfo->fpL == nullptr)
1818 : return nullptr;
1819 : #endif
1820 :
1821 7918 : if (poOpenInfo->eAccess == GA_Update)
1822 : {
1823 0 : ReportUpdateNotSupportedByDriver("PNG");
1824 0 : return nullptr;
1825 : }
1826 :
1827 : // Create a corresponding GDALDataset.
1828 7918 : PNGDataset *poDS = new PNGDataset();
1829 7916 : return OpenStage2(poOpenInfo, poDS);
1830 : }
1831 :
1832 7917 : GDALDataset *PNGDataset::OpenStage2(GDALOpenInfo *poOpenInfo, PNGDataset *&poDS)
1833 :
1834 : {
1835 7917 : poDS->fpImage = poOpenInfo->fpL;
1836 7917 : poOpenInfo->fpL = nullptr;
1837 7917 : poDS->eAccess = poOpenInfo->eAccess;
1838 :
1839 15834 : poDS->hPNG =
1840 7917 : png_create_read_struct(PNG_LIBPNG_VER_STRING, poDS, nullptr, nullptr);
1841 7917 : if (poDS->hPNG == nullptr)
1842 : {
1843 0 : int version = static_cast<int>(png_access_version_number());
1844 0 : CPLError(CE_Failure, CPLE_NotSupported,
1845 : "The PNG driver failed to access libpng with version '%s',"
1846 : " library is actually version '%d'.\n",
1847 : PNG_LIBPNG_VER_STRING, version);
1848 0 : delete poDS;
1849 0 : return nullptr;
1850 : }
1851 :
1852 : #ifdef DISABLE_CRC_CHECK
1853 : PNGDatasetDisableCRCCheck(poDS->hPNG);
1854 : #endif
1855 :
1856 7917 : poDS->psPNGInfo = png_create_info_struct(poDS->hPNG);
1857 :
1858 : // Set up error handling.
1859 7918 : png_set_error_fn(poDS->hPNG, &poDS->sSetJmpContext, png_gdal_error,
1860 : png_gdal_warning);
1861 :
1862 7918 : if (setjmp(poDS->sSetJmpContext) != 0)
1863 : {
1864 1 : delete poDS;
1865 1 : return nullptr;
1866 : }
1867 :
1868 : // Read pre-image data after ensuring the file is rewound.
1869 : // We should likely do a setjmp() here.
1870 :
1871 7918 : png_set_read_fn(poDS->hPNG, poDS->fpImage, png_vsi_read_data);
1872 7918 : png_read_info(poDS->hPNG, poDS->psPNGInfo);
1873 :
1874 : // Capture some information from the file that is of interest.
1875 7914 : poDS->nRasterXSize =
1876 7914 : static_cast<int>(png_get_image_width(poDS->hPNG, poDS->psPNGInfo));
1877 7916 : poDS->nRasterYSize =
1878 7914 : static_cast<int>(png_get_image_height(poDS->hPNG, poDS->psPNGInfo));
1879 :
1880 7916 : poDS->nBands = png_get_channels(poDS->hPNG, poDS->psPNGInfo);
1881 7915 : poDS->nBitDepth = png_get_bit_depth(poDS->hPNG, poDS->psPNGInfo);
1882 7916 : poDS->bInterlaced = png_get_interlace_type(poDS->hPNG, poDS->psPNGInfo) !=
1883 : PNG_INTERLACE_NONE;
1884 :
1885 7917 : poDS->nColorType = png_get_color_type(poDS->hPNG, poDS->psPNGInfo);
1886 :
1887 7915 : if (poDS->nColorType == PNG_COLOR_TYPE_PALETTE && poDS->nBands > 1)
1888 : {
1889 0 : CPLDebug("GDAL",
1890 : "PNG Driver got %d from png_get_channels(),\n"
1891 : "but this kind of image (paletted) can only have one band.\n"
1892 : "Correcting and continuing, but this may indicate a bug!",
1893 : poDS->nBands);
1894 0 : poDS->nBands = 1;
1895 : }
1896 :
1897 : // We want to treat 1-, 2-, and 4-bit images as eight bit. This call causes
1898 : // libpng to unpack the image.
1899 7915 : if (poDS->nBitDepth < 8)
1900 8 : png_set_packing(poDS->hPNG);
1901 :
1902 : // Create band information objects.
1903 29875 : for (int iBand = 0; iBand < poDS->nBands; iBand++)
1904 21958 : poDS->SetBand(iBand + 1, new PNGRasterBand(poDS, iBand + 1));
1905 :
1906 : // Is there a palette? Note: we should also read back and apply
1907 : // transparency values if available.
1908 7917 : if (poDS->nColorType == PNG_COLOR_TYPE_PALETTE)
1909 : {
1910 383 : png_color *pasPNGPalette = nullptr;
1911 383 : int nColorCount = 0;
1912 :
1913 383 : if (png_get_PLTE(poDS->hPNG, poDS->psPNGInfo, &pasPNGPalette,
1914 383 : &nColorCount) == 0)
1915 0 : nColorCount = 0;
1916 :
1917 383 : unsigned char *trans = nullptr;
1918 383 : png_color_16 *trans_values = nullptr;
1919 383 : int num_trans = 0;
1920 383 : png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
1921 : &trans_values);
1922 :
1923 383 : poDS->poColorTable = new GDALColorTable();
1924 :
1925 : GDALColorEntry oEntry;
1926 383 : int nNoDataIndex = -1;
1927 26009 : for (int iColor = nColorCount - 1; iColor >= 0; iColor--)
1928 : {
1929 25626 : oEntry.c1 = pasPNGPalette[iColor].red;
1930 25626 : oEntry.c2 = pasPNGPalette[iColor].green;
1931 25626 : oEntry.c3 = pasPNGPalette[iColor].blue;
1932 :
1933 25626 : if (iColor < num_trans)
1934 : {
1935 3441 : oEntry.c4 = trans[iColor];
1936 3441 : if (oEntry.c4 == 0)
1937 : {
1938 266 : if (nNoDataIndex == -1)
1939 266 : nNoDataIndex = iColor;
1940 : else
1941 0 : nNoDataIndex = -2;
1942 : }
1943 : }
1944 : else
1945 22185 : oEntry.c4 = 255;
1946 :
1947 25626 : poDS->poColorTable->SetColorEntry(iColor, &oEntry);
1948 : }
1949 :
1950 : // Special hack to use an index as the no data value, as long as it is
1951 : // the only transparent color in the palette.
1952 383 : if (nNoDataIndex > -1)
1953 : {
1954 266 : poDS->GetRasterBand(1)->SetNoDataValue(nNoDataIndex);
1955 : }
1956 : }
1957 :
1958 : // Check for transparency values in greyscale images.
1959 7917 : if (poDS->nColorType == PNG_COLOR_TYPE_GRAY)
1960 : {
1961 402 : png_color_16 *trans_values = nullptr;
1962 : unsigned char *trans;
1963 : int num_trans;
1964 :
1965 402 : if (png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
1966 407 : &trans_values) != 0 &&
1967 5 : trans_values != nullptr)
1968 : {
1969 5 : poDS->GetRasterBand(1)->SetNoDataValue(trans_values->gray);
1970 : }
1971 : }
1972 :
1973 : // Check for nodata color for RGB images.
1974 7917 : if (poDS->nColorType == PNG_COLOR_TYPE_RGB)
1975 : {
1976 5645 : png_color_16 *trans_values = nullptr;
1977 : unsigned char *trans;
1978 : int num_trans;
1979 :
1980 5645 : if (png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
1981 5649 : &trans_values) != 0 &&
1982 4 : trans_values != nullptr)
1983 : {
1984 8 : CPLString oNDValue;
1985 :
1986 4 : oNDValue.Printf("%d %d %d", trans_values->red, trans_values->green,
1987 4 : trans_values->blue);
1988 4 : poDS->SetMetadataItem("NODATA_VALUES", oNDValue.c_str());
1989 :
1990 4 : poDS->GetRasterBand(1)->SetNoDataValue(trans_values->red);
1991 4 : poDS->GetRasterBand(2)->SetNoDataValue(trans_values->green);
1992 4 : poDS->GetRasterBand(3)->SetNoDataValue(trans_values->blue);
1993 : }
1994 : }
1995 :
1996 : // Extract any text chunks as "metadata."
1997 7917 : poDS->CollectMetadata();
1998 :
1999 : // More metadata.
2000 7914 : if (poDS->nBands > 1)
2001 : {
2002 7130 : poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2003 : }
2004 :
2005 : // Initialize any PAM information.
2006 7913 : poDS->SetDescription(poOpenInfo->pszFilename);
2007 7913 : poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
2008 :
2009 : // Open overviews.
2010 7915 : poDS->oOvManager.Initialize(poDS, poOpenInfo);
2011 :
2012 : // Used by JPEG FLIR
2013 7914 : poDS->m_bByteOrderIsLittleEndian = CPLTestBool(CSLFetchNameValueDef(
2014 7913 : poOpenInfo->papszOpenOptions, "BYTE_ORDER_LITTLE_ENDIAN", "NO"));
2015 :
2016 7915 : return poDS;
2017 : }
2018 :
2019 : /************************************************************************/
2020 : /* LoadWorldFile() */
2021 : /************************************************************************/
2022 :
2023 175 : void PNGDataset::LoadWorldFile()
2024 : {
2025 175 : if (bHasTriedLoadWorldFile)
2026 5 : return;
2027 170 : bHasTriedLoadWorldFile = TRUE;
2028 :
2029 170 : char *pszWldFilename = nullptr;
2030 170 : bGeoTransformValid =
2031 170 : GDALReadWorldFile2(GetDescription(), nullptr, m_gt,
2032 170 : oOvManager.GetSiblingFiles(), &pszWldFilename);
2033 :
2034 170 : if (!bGeoTransformValid)
2035 170 : bGeoTransformValid =
2036 170 : GDALReadWorldFile2(GetDescription(), ".wld", m_gt,
2037 170 : oOvManager.GetSiblingFiles(), &pszWldFilename);
2038 :
2039 170 : if (pszWldFilename)
2040 : {
2041 3 : osWldFilename = pszWldFilename;
2042 3 : CPLFree(pszWldFilename);
2043 : }
2044 : }
2045 :
2046 : /************************************************************************/
2047 : /* GetFileList() */
2048 : /************************************************************************/
2049 :
2050 45 : char **PNGDataset::GetFileList()
2051 :
2052 : {
2053 45 : char **papszFileList = GDALPamDataset::GetFileList();
2054 :
2055 45 : LoadWorldFile();
2056 :
2057 46 : if (!osWldFilename.empty() &&
2058 1 : CSLFindString(papszFileList, osWldFilename) == -1)
2059 : {
2060 1 : papszFileList = CSLAddString(papszFileList, osWldFilename);
2061 : }
2062 :
2063 45 : return papszFileList;
2064 : }
2065 :
2066 : /************************************************************************/
2067 : /* WriteMetadataAsText() */
2068 : /************************************************************************/
2069 :
2070 3 : static bool IsASCII(const char *pszStr)
2071 : {
2072 28 : for (int i = 0; pszStr[i] != '\0'; i++)
2073 : {
2074 25 : if (reinterpret_cast<GByte *>(const_cast<char *>(pszStr))[i] >= 128)
2075 0 : return false;
2076 : }
2077 3 : return true;
2078 : }
2079 :
2080 3 : static bool safe_png_set_text(jmp_buf sSetJmpContext, png_structp png_ptr,
2081 : png_infop info_ptr, png_const_textp text_ptr,
2082 : int num_text)
2083 : {
2084 3 : if (setjmp(sSetJmpContext) != 0)
2085 : {
2086 0 : return false;
2087 : }
2088 3 : png_set_text(png_ptr, info_ptr, text_ptr, num_text);
2089 3 : return true;
2090 : }
2091 :
2092 3 : void PNGDataset::WriteMetadataAsText(jmp_buf sSetJmpContext, png_structp hPNG,
2093 : png_infop psPNGInfo, const char *pszKey,
2094 : const char *pszValue)
2095 : {
2096 : png_text sText;
2097 3 : memset(&sText, 0, sizeof(png_text));
2098 3 : sText.compression = PNG_TEXT_COMPRESSION_NONE;
2099 3 : sText.key = const_cast<png_charp>(pszKey);
2100 3 : sText.text = const_cast<png_charp>(pszValue);
2101 :
2102 : // UTF-8 values should be written in iTXt, whereas TEXT should be LATIN-1.
2103 3 : if (!IsASCII(pszValue) && CPLIsUTF8(pszValue, -1))
2104 0 : sText.compression = PNG_ITXT_COMPRESSION_NONE;
2105 :
2106 3 : safe_png_set_text(sSetJmpContext, hPNG, psPNGInfo, &sText, 1);
2107 3 : }
2108 :
2109 4639 : static bool safe_png_set_IHDR(jmp_buf sSetJmpContext, png_structp png_ptr,
2110 : png_infop info_ptr, png_uint_32 width,
2111 : png_uint_32 height, int bit_depth, int color_type,
2112 : int interlace_type, int compression_type,
2113 : int filter_type)
2114 : {
2115 4639 : if (setjmp(sSetJmpContext) != 0)
2116 : {
2117 0 : return false;
2118 : }
2119 4639 : png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
2120 : interlace_type, compression_type, filter_type);
2121 4639 : return true;
2122 : }
2123 :
2124 3986 : static bool safe_png_set_compression_level(jmp_buf sSetJmpContext,
2125 : png_structp png_ptr, int level)
2126 : {
2127 3986 : if (setjmp(sSetJmpContext) != 0)
2128 : {
2129 0 : return false;
2130 : }
2131 3986 : png_set_compression_level(png_ptr, level);
2132 3986 : return true;
2133 : }
2134 :
2135 8 : static bool safe_png_set_tRNS(jmp_buf sSetJmpContext, png_structp png_ptr,
2136 : png_infop info_ptr, png_const_bytep trans,
2137 : int num_trans, png_color_16p trans_values)
2138 : {
2139 8 : if (setjmp(sSetJmpContext) != 0)
2140 : {
2141 0 : return false;
2142 : }
2143 8 : png_set_tRNS(png_ptr, info_ptr, trans, num_trans, trans_values);
2144 8 : return true;
2145 : }
2146 :
2147 2 : static bool safe_png_set_iCCP(jmp_buf sSetJmpContext, png_structp png_ptr,
2148 : png_infop info_ptr, png_const_charp name,
2149 : int compression_type, png_const_bytep profile,
2150 : png_uint_32 proflen)
2151 : {
2152 2 : if (setjmp(sSetJmpContext) != 0)
2153 : {
2154 0 : return false;
2155 : }
2156 2 : png_set_iCCP(png_ptr, info_ptr, name, compression_type, profile, proflen);
2157 2 : return true;
2158 : }
2159 :
2160 33 : static bool safe_png_set_PLTE(jmp_buf sSetJmpContext, png_structp png_ptr,
2161 : png_infop info_ptr, png_const_colorp palette,
2162 : int num_palette)
2163 : {
2164 33 : if (setjmp(sSetJmpContext) != 0)
2165 : {
2166 0 : return false;
2167 : }
2168 33 : png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
2169 33 : return true;
2170 : }
2171 :
2172 4639 : static bool safe_png_write_info(jmp_buf sSetJmpContext, png_structp png_ptr,
2173 : png_infop info_ptr)
2174 : {
2175 4639 : if (setjmp(sSetJmpContext) != 0)
2176 : {
2177 5 : return false;
2178 : }
2179 4639 : png_write_info(png_ptr, info_ptr);
2180 4634 : return true;
2181 : }
2182 :
2183 246815 : static bool safe_png_write_rows(jmp_buf sSetJmpContext, png_structp png_ptr,
2184 : png_bytepp row, png_uint_32 num_rows)
2185 : {
2186 246815 : if (setjmp(sSetJmpContext) != 0)
2187 : {
2188 4 : return false;
2189 : }
2190 246815 : png_write_rows(png_ptr, row, num_rows);
2191 246811 : return true;
2192 : }
2193 :
2194 4634 : static bool safe_png_write_end(jmp_buf sSetJmpContext, png_structp png_ptr,
2195 : png_infop info_ptr)
2196 : {
2197 4634 : if (setjmp(sSetJmpContext) != 0)
2198 : {
2199 7 : return false;
2200 : }
2201 4634 : png_write_end(png_ptr, info_ptr);
2202 4627 : return true;
2203 : }
2204 :
2205 : /************************************************************************/
2206 : /* CreateCopy() */
2207 : /************************************************************************/
2208 :
2209 4653 : GDALDataset *PNGDataset::CreateCopy(const char *pszFilename,
2210 : GDALDataset *poSrcDS, int bStrict,
2211 : char **papszOptions,
2212 : GDALProgressFunc pfnProgress,
2213 : void *pProgressData)
2214 :
2215 : {
2216 : // Perform some rudimentary checks.
2217 4653 : const int nBands = poSrcDS->GetRasterCount();
2218 4653 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
2219 : {
2220 2 : CPLError(CE_Failure, CPLE_NotSupported,
2221 : "PNG driver doesn't support %d bands. Must be 1 (grey),\n"
2222 : "2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n",
2223 : nBands);
2224 :
2225 2 : return nullptr;
2226 : }
2227 :
2228 4718 : if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte &&
2229 67 : poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16)
2230 : {
2231 9 : CPLError(
2232 : (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
2233 : "PNG driver doesn't support data type %s. "
2234 : "Only eight bit (Byte) and sixteen bit (UInt16) bands supported. "
2235 : "%s\n",
2236 : GDALGetDataTypeName(poSrcDS->GetRasterBand(1)->GetRasterDataType()),
2237 : (bStrict) ? "" : "Defaulting to Byte");
2238 :
2239 9 : if (bStrict)
2240 9 : return nullptr;
2241 : }
2242 :
2243 : // Create the dataset.
2244 4642 : VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
2245 4642 : if (fpImage == nullptr)
2246 : {
2247 3 : CPLError(CE_Failure, CPLE_OpenFailed,
2248 : "Unable to create png file %s: %s\n", pszFilename,
2249 3 : VSIStrerror(errno));
2250 3 : return nullptr;
2251 : }
2252 :
2253 : // Initialize PNG access to the file.
2254 : jmp_buf sSetJmpContext;
2255 :
2256 : png_structp hPNG =
2257 4639 : png_create_write_struct(PNG_LIBPNG_VER_STRING, &sSetJmpContext,
2258 4639 : png_gdal_error, png_gdal_warning);
2259 4639 : png_infop psPNGInfo = png_create_info_struct(hPNG);
2260 :
2261 : // Set up some parameters.
2262 4639 : int nColorType = 0;
2263 :
2264 4639 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr)
2265 150 : nColorType = PNG_COLOR_TYPE_GRAY;
2266 4489 : else if (nBands == 1)
2267 33 : nColorType = PNG_COLOR_TYPE_PALETTE;
2268 4456 : else if (nBands == 2)
2269 267 : nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
2270 4189 : else if (nBands == 3)
2271 3947 : nColorType = PNG_COLOR_TYPE_RGB;
2272 242 : else if (nBands == 4)
2273 242 : nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
2274 :
2275 : int nBitDepth;
2276 : GDALDataType eType;
2277 4639 : if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16)
2278 : {
2279 4581 : eType = GDT_Byte;
2280 4581 : nBitDepth = 8;
2281 4581 : if (nBands == 1)
2282 : {
2283 128 : const char *pszNbits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
2284 128 : "NBITS", "IMAGE_STRUCTURE");
2285 128 : if (pszNbits != nullptr)
2286 : {
2287 3 : nBitDepth = atoi(pszNbits);
2288 3 : if (!(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4))
2289 0 : nBitDepth = 8;
2290 : }
2291 : }
2292 : }
2293 : else
2294 : {
2295 58 : eType = GDT_UInt16;
2296 58 : nBitDepth = 16;
2297 : }
2298 :
2299 4639 : const char *pszNbits = CSLFetchNameValue(papszOptions, "NBITS");
2300 4639 : if (eType == GDT_Byte && pszNbits != nullptr)
2301 : {
2302 13 : nBitDepth = atoi(pszNbits);
2303 13 : if (!(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4 ||
2304 : nBitDepth == 8))
2305 : {
2306 1 : CPLError(CE_Warning, CPLE_NotSupported,
2307 : "Invalid bit depth. Using 8");
2308 1 : nBitDepth = 8;
2309 : }
2310 : }
2311 :
2312 4639 : png_set_write_fn(hPNG, fpImage, png_vsi_write_data, png_vsi_flush);
2313 :
2314 4639 : const int nXSize = poSrcDS->GetRasterXSize();
2315 4639 : const int nYSize = poSrcDS->GetRasterYSize();
2316 :
2317 4639 : if (!safe_png_set_IHDR(sSetJmpContext, hPNG, psPNGInfo, nXSize, nYSize,
2318 : nBitDepth, nColorType, PNG_INTERLACE_NONE,
2319 : PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE))
2320 : {
2321 0 : VSIFCloseL(fpImage);
2322 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2323 0 : return nullptr;
2324 : }
2325 :
2326 : // Do we want to control the compression level?
2327 4639 : const char *pszLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
2328 :
2329 4639 : if (pszLevel)
2330 : {
2331 3986 : const int nLevel = atoi(pszLevel);
2332 3986 : if (nLevel < 1 || nLevel > 9)
2333 : {
2334 0 : CPLError(CE_Failure, CPLE_AppDefined,
2335 : "Illegal ZLEVEL value '%s', should be 1-9.", pszLevel);
2336 0 : VSIFCloseL(fpImage);
2337 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2338 0 : return nullptr;
2339 : }
2340 :
2341 3986 : if (!safe_png_set_compression_level(sSetJmpContext, hPNG, nLevel))
2342 : {
2343 0 : VSIFCloseL(fpImage);
2344 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2345 0 : return nullptr;
2346 : }
2347 : }
2348 :
2349 : // Try to handle nodata values as a tRNS block (note that for paletted
2350 : // images, we save the effect to apply as part of palette).
2351 : png_color_16 sTRNSColor;
2352 :
2353 : // Gray nodata.
2354 4639 : if (nColorType == PNG_COLOR_TYPE_GRAY)
2355 : {
2356 150 : int bHaveNoData = FALSE;
2357 : const double dfNoDataValue =
2358 150 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
2359 :
2360 150 : if (bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536)
2361 : {
2362 3 : sTRNSColor.gray = static_cast<png_uint_16>(dfNoDataValue);
2363 3 : if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr, 0,
2364 : &sTRNSColor))
2365 : {
2366 0 : VSIFCloseL(fpImage);
2367 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2368 0 : return nullptr;
2369 : }
2370 : }
2371 : }
2372 :
2373 : // RGB nodata.
2374 4639 : if (nColorType == PNG_COLOR_TYPE_RGB)
2375 : {
2376 : // First try to use the NODATA_VALUES metadata item.
2377 3947 : if (poSrcDS->GetMetadataItem("NODATA_VALUES") != nullptr)
2378 : {
2379 : char **papszValues =
2380 1 : CSLTokenizeString(poSrcDS->GetMetadataItem("NODATA_VALUES"));
2381 :
2382 1 : if (CSLCount(papszValues) >= 3)
2383 : {
2384 1 : sTRNSColor.red = static_cast<png_uint_16>(atoi(papszValues[0]));
2385 1 : sTRNSColor.green =
2386 1 : static_cast<png_uint_16>(atoi(papszValues[1]));
2387 1 : sTRNSColor.blue =
2388 1 : static_cast<png_uint_16>(atoi(papszValues[2]));
2389 1 : if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr,
2390 : 0, &sTRNSColor))
2391 : {
2392 0 : VSIFCloseL(fpImage);
2393 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2394 0 : CSLDestroy(papszValues);
2395 0 : return nullptr;
2396 : }
2397 : }
2398 :
2399 1 : CSLDestroy(papszValues);
2400 : }
2401 : // Otherwise, get the nodata value from the bands.
2402 : else
2403 : {
2404 3946 : int bHaveNoDataRed = FALSE;
2405 : const double dfNoDataValueRed =
2406 3946 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoDataRed);
2407 :
2408 3946 : int bHaveNoDataGreen = FALSE;
2409 : const double dfNoDataValueGreen =
2410 3946 : poSrcDS->GetRasterBand(2)->GetNoDataValue(&bHaveNoDataGreen);
2411 :
2412 3946 : int bHaveNoDataBlue = FALSE;
2413 : const double dfNoDataValueBlue =
2414 3946 : poSrcDS->GetRasterBand(3)->GetNoDataValue(&bHaveNoDataBlue);
2415 :
2416 3946 : if ((bHaveNoDataRed && dfNoDataValueRed >= 0 &&
2417 0 : dfNoDataValueRed < 65536) &&
2418 0 : (bHaveNoDataGreen && dfNoDataValueGreen >= 0 &&
2419 0 : dfNoDataValueGreen < 65536) &&
2420 0 : (bHaveNoDataBlue && dfNoDataValueBlue >= 0 &&
2421 : dfNoDataValueBlue < 65536))
2422 : {
2423 0 : sTRNSColor.red = static_cast<png_uint_16>(dfNoDataValueRed);
2424 0 : sTRNSColor.green = static_cast<png_uint_16>(dfNoDataValueGreen);
2425 0 : sTRNSColor.blue = static_cast<png_uint_16>(dfNoDataValueBlue);
2426 0 : if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr,
2427 : 0, &sTRNSColor))
2428 : {
2429 0 : VSIFCloseL(fpImage);
2430 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2431 0 : return nullptr;
2432 : }
2433 : }
2434 : }
2435 : }
2436 :
2437 : // Copy color profile data.
2438 : const char *pszICCProfile =
2439 4639 : CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
2440 : const char *pszICCProfileName =
2441 4639 : CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE_NAME");
2442 4639 : if (pszICCProfileName == nullptr)
2443 4638 : pszICCProfileName = poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE_NAME",
2444 4638 : "COLOR_PROFILE");
2445 :
2446 4639 : if (pszICCProfile == nullptr)
2447 : pszICCProfile =
2448 4638 : poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
2449 :
2450 4639 : if ((pszICCProfileName != nullptr) && EQUAL(pszICCProfileName, "sRGB"))
2451 : {
2452 1 : pszICCProfile = nullptr;
2453 :
2454 : // assumes this can't fail ?
2455 1 : png_set_sRGB(hPNG, psPNGInfo, PNG_sRGB_INTENT_PERCEPTUAL);
2456 : }
2457 :
2458 4639 : if (pszICCProfile != nullptr)
2459 : {
2460 2 : char *pEmbedBuffer = CPLStrdup(pszICCProfile);
2461 : png_uint_32 nEmbedLen =
2462 2 : CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pEmbedBuffer));
2463 2 : const char *pszLocalICCProfileName =
2464 2 : (pszICCProfileName != nullptr) ? pszICCProfileName : "ICC Profile";
2465 :
2466 2 : if (!safe_png_set_iCCP(
2467 : sSetJmpContext, hPNG, psPNGInfo, pszLocalICCProfileName, 0,
2468 : reinterpret_cast<png_const_bytep>(pEmbedBuffer), nEmbedLen))
2469 : {
2470 0 : CPLFree(pEmbedBuffer);
2471 0 : VSIFCloseL(fpImage);
2472 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2473 0 : return nullptr;
2474 : }
2475 :
2476 2 : CPLFree(pEmbedBuffer);
2477 : }
2478 4637 : else if ((pszICCProfileName == nullptr) ||
2479 1 : !EQUAL(pszICCProfileName, "sRGB"))
2480 : {
2481 : // Output gamma, primaries and whitepoint.
2482 4636 : const char *pszGamma = CSLFetchNameValue(papszOptions, "PNG_GAMMA");
2483 4636 : if (pszGamma == nullptr)
2484 4634 : pszGamma = poSrcDS->GetMetadataItem("PNG_GAMMA", "COLOR_PROFILE");
2485 :
2486 4636 : if (pszGamma != nullptr)
2487 : {
2488 4 : double dfGamma = CPLAtof(pszGamma);
2489 : // assumes this can't fail ?
2490 4 : png_set_gAMA(hPNG, psPNGInfo, dfGamma);
2491 : }
2492 :
2493 : const char *pszPrimariesRed =
2494 4636 : CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_RED");
2495 4636 : if (pszPrimariesRed == nullptr)
2496 4635 : pszPrimariesRed = poSrcDS->GetMetadataItem("SOURCE_PRIMARIES_RED",
2497 4635 : "COLOR_PROFILE");
2498 : const char *pszPrimariesGreen =
2499 4636 : CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_GREEN");
2500 4636 : if (pszPrimariesGreen == nullptr)
2501 4635 : pszPrimariesGreen = poSrcDS->GetMetadataItem(
2502 4635 : "SOURCE_PRIMARIES_GREEN", "COLOR_PROFILE");
2503 : const char *pszPrimariesBlue =
2504 4636 : CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_BLUE");
2505 4636 : if (pszPrimariesBlue == nullptr)
2506 4635 : pszPrimariesBlue = poSrcDS->GetMetadataItem("SOURCE_PRIMARIES_BLUE",
2507 4635 : "COLOR_PROFILE");
2508 : const char *pszWhitepoint =
2509 4636 : CSLFetchNameValue(papszOptions, "SOURCE_WHITEPOINT");
2510 4636 : if (pszWhitepoint == nullptr)
2511 : pszWhitepoint =
2512 4635 : poSrcDS->GetMetadataItem("SOURCE_WHITEPOINT", "COLOR_PROFILE");
2513 :
2514 4636 : if ((pszPrimariesRed != nullptr) && (pszPrimariesGreen != nullptr) &&
2515 2 : (pszPrimariesBlue != nullptr) && (pszWhitepoint != nullptr))
2516 : {
2517 2 : bool bOk = true;
2518 2 : double faColour[8] = {0.0};
2519 2 : char **apapszTokenList[4] = {nullptr};
2520 :
2521 2 : apapszTokenList[0] = CSLTokenizeString2(pszWhitepoint, ",",
2522 : CSLT_ALLOWEMPTYTOKENS |
2523 : CSLT_STRIPLEADSPACES |
2524 : CSLT_STRIPENDSPACES);
2525 2 : apapszTokenList[1] = CSLTokenizeString2(pszPrimariesRed, ",",
2526 : CSLT_ALLOWEMPTYTOKENS |
2527 : CSLT_STRIPLEADSPACES |
2528 : CSLT_STRIPENDSPACES);
2529 2 : apapszTokenList[2] = CSLTokenizeString2(pszPrimariesGreen, ",",
2530 : CSLT_ALLOWEMPTYTOKENS |
2531 : CSLT_STRIPLEADSPACES |
2532 : CSLT_STRIPENDSPACES);
2533 2 : apapszTokenList[3] = CSLTokenizeString2(pszPrimariesBlue, ",",
2534 : CSLT_ALLOWEMPTYTOKENS |
2535 : CSLT_STRIPLEADSPACES |
2536 : CSLT_STRIPENDSPACES);
2537 :
2538 2 : if ((CSLCount(apapszTokenList[0]) == 3) &&
2539 2 : (CSLCount(apapszTokenList[1]) == 3) &&
2540 6 : (CSLCount(apapszTokenList[2]) == 3) &&
2541 2 : (CSLCount(apapszTokenList[3]) == 3))
2542 : {
2543 10 : for (int i = 0; i < 4; i++)
2544 : {
2545 32 : for (int j = 0; j < 3; j++)
2546 : {
2547 24 : const double v = CPLAtof(apapszTokenList[i][j]);
2548 :
2549 24 : if (j == 2)
2550 : {
2551 : /* Last term of xyY colour must be 1.0 */
2552 8 : if (v != 1.0)
2553 : {
2554 0 : bOk = false;
2555 0 : break;
2556 : }
2557 : }
2558 : else
2559 : {
2560 16 : faColour[i * 2 + j] = v;
2561 : }
2562 : }
2563 8 : if (!bOk)
2564 0 : break;
2565 : }
2566 :
2567 2 : if (bOk)
2568 : {
2569 : // assumes this can't fail ?
2570 2 : png_set_cHRM(hPNG, psPNGInfo, faColour[0], faColour[1],
2571 : faColour[2], faColour[3], faColour[4],
2572 : faColour[5], faColour[6], faColour[7]);
2573 : }
2574 : }
2575 :
2576 2 : CSLDestroy(apapszTokenList[0]);
2577 2 : CSLDestroy(apapszTokenList[1]);
2578 2 : CSLDestroy(apapszTokenList[2]);
2579 2 : CSLDestroy(apapszTokenList[3]);
2580 : }
2581 : }
2582 :
2583 : // Write the palette if there is one. Technically, it may be possible to
2584 : // write 16-bit palettes for PNG, but for now, this is omitted.
2585 4639 : if (nColorType == PNG_COLOR_TYPE_PALETTE)
2586 : {
2587 33 : int bHaveNoData = FALSE;
2588 : double dfNoDataValue =
2589 33 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
2590 :
2591 33 : GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
2592 :
2593 33 : int nEntryCount = poCT->GetColorEntryCount();
2594 33 : int nMaxEntryCount = 1 << nBitDepth;
2595 33 : if (nEntryCount > nMaxEntryCount)
2596 0 : nEntryCount = nMaxEntryCount;
2597 :
2598 : png_color *pasPNGColors = reinterpret_cast<png_color *>(
2599 33 : CPLMalloc(sizeof(png_color) * nEntryCount));
2600 :
2601 : GDALColorEntry sEntry;
2602 33 : bool bFoundTrans = false;
2603 7007 : for (int iColor = 0; iColor < nEntryCount; iColor++)
2604 : {
2605 6974 : poCT->GetColorEntryAsRGB(iColor, &sEntry);
2606 6974 : if (sEntry.c4 != 255)
2607 266 : bFoundTrans = true;
2608 :
2609 6974 : pasPNGColors[iColor].red = static_cast<png_byte>(sEntry.c1);
2610 6974 : pasPNGColors[iColor].green = static_cast<png_byte>(sEntry.c2);
2611 6974 : pasPNGColors[iColor].blue = static_cast<png_byte>(sEntry.c3);
2612 : }
2613 :
2614 33 : if (!safe_png_set_PLTE(sSetJmpContext, hPNG, psPNGInfo, pasPNGColors,
2615 : nEntryCount))
2616 : {
2617 0 : CPLFree(pasPNGColors);
2618 0 : VSIFCloseL(fpImage);
2619 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2620 0 : return nullptr;
2621 : }
2622 :
2623 33 : CPLFree(pasPNGColors);
2624 :
2625 : // If we have transparent elements in the palette, we need to write a
2626 : // transparency block.
2627 33 : if (bFoundTrans || bHaveNoData)
2628 : {
2629 : unsigned char *pabyAlpha =
2630 4 : reinterpret_cast<unsigned char *>(CPLMalloc(nEntryCount));
2631 :
2632 571 : for (int iColor = 0; iColor < nEntryCount; iColor++)
2633 : {
2634 567 : poCT->GetColorEntryAsRGB(iColor, &sEntry);
2635 567 : pabyAlpha[iColor] = static_cast<unsigned char>(sEntry.c4);
2636 :
2637 567 : if (bHaveNoData && iColor == static_cast<int>(dfNoDataValue))
2638 3 : pabyAlpha[iColor] = 0;
2639 : }
2640 :
2641 4 : if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, pabyAlpha,
2642 : nEntryCount, nullptr))
2643 : {
2644 0 : CPLFree(pabyAlpha);
2645 0 : VSIFCloseL(fpImage);
2646 0 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2647 0 : return nullptr;
2648 : }
2649 :
2650 4 : CPLFree(pabyAlpha);
2651 : }
2652 : }
2653 :
2654 : // Add text info.
2655 : // These are predefined keywords. See "4.2.7 tEXt Textual data" of
2656 : // http://www.w3.org/TR/PNG-Chunks.html for more information.
2657 4639 : const char *apszKeywords[] = {"Title", "Author", "Description",
2658 : "Copyright", "Creation Time", "Software",
2659 : "Disclaimer", "Warning", "Source",
2660 : "Comment", nullptr};
2661 4639 : const bool bWriteMetadataAsText = CPLTestBool(
2662 : CSLFetchNameValueDef(papszOptions, "WRITE_METADATA_AS_TEXT", "FALSE"));
2663 51029 : for (int i = 0; apszKeywords[i] != nullptr; i++)
2664 : {
2665 46390 : const char *pszKey = apszKeywords[i];
2666 46390 : const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
2667 46390 : if (pszValue == nullptr && bWriteMetadataAsText)
2668 9 : pszValue = poSrcDS->GetMetadataItem(pszKey);
2669 46390 : if (pszValue != nullptr)
2670 : {
2671 2 : WriteMetadataAsText(sSetJmpContext, hPNG, psPNGInfo, pszKey,
2672 : pszValue);
2673 : }
2674 : }
2675 4639 : if (bWriteMetadataAsText)
2676 : {
2677 1 : char **papszSrcMD = poSrcDS->GetMetadata();
2678 4 : for (; papszSrcMD && *papszSrcMD; papszSrcMD++)
2679 : {
2680 3 : char *pszKey = nullptr;
2681 3 : const char *pszValue = CPLParseNameValue(*papszSrcMD, &pszKey);
2682 3 : if (pszKey && pszValue)
2683 : {
2684 3 : if (CSLFindString(const_cast<char **>(apszKeywords), pszKey) <
2685 1 : 0 &&
2686 4 : !EQUAL(pszKey, "AREA_OR_POINT") &&
2687 1 : !EQUAL(pszKey, "NODATA_VALUES"))
2688 : {
2689 1 : WriteMetadataAsText(sSetJmpContext, hPNG, psPNGInfo, pszKey,
2690 : pszValue);
2691 : }
2692 3 : CPLFree(pszKey);
2693 : }
2694 : }
2695 : }
2696 :
2697 : // Write the PNG info.
2698 4639 : if (!safe_png_write_info(sSetJmpContext, hPNG, psPNGInfo))
2699 : {
2700 5 : VSIFCloseL(fpImage);
2701 5 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2702 5 : return nullptr;
2703 : }
2704 :
2705 4634 : if (nBitDepth < 8)
2706 : {
2707 : // Assumes this can't fail
2708 6 : png_set_packing(hPNG);
2709 : }
2710 :
2711 : // Loop over the image, copying image data.
2712 4634 : CPLErr eErr = CE_None;
2713 4634 : const int nWordSize = GDALGetDataTypeSizeBytes(eType);
2714 :
2715 : GByte *pabyScanline = reinterpret_cast<GByte *>(
2716 4634 : CPLMalloc(cpl::fits_on<int>(nBands * nXSize * nWordSize)));
2717 :
2718 251447 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
2719 : {
2720 246812 : png_bytep row = pabyScanline;
2721 :
2722 493628 : eErr = poSrcDS->RasterIO(
2723 : GF_Read, 0, iLine, nXSize, 1, pabyScanline, nXSize, 1, eType,
2724 246812 : nBands, nullptr, static_cast<GSpacing>(nBands) * nWordSize,
2725 246812 : static_cast<GSpacing>(nBands) * nXSize * nWordSize, nWordSize,
2726 : nullptr);
2727 :
2728 : #ifdef CPL_LSB
2729 246816 : if (nBitDepth == 16)
2730 13397 : GDALSwapWords(row, 2, nXSize * nBands, 2);
2731 : #endif
2732 246816 : if (eErr == CE_None)
2733 : {
2734 246815 : if (!safe_png_write_rows(sSetJmpContext, hPNG, &row, 1))
2735 : {
2736 4 : eErr = CE_Failure;
2737 : }
2738 : }
2739 :
2740 493625 : if (eErr == CE_None &&
2741 246811 : !pfnProgress((iLine + 1) / static_cast<double>(nYSize), nullptr,
2742 : pProgressData))
2743 : {
2744 1 : eErr = CE_Failure;
2745 1 : CPLError(CE_Failure, CPLE_UserInterrupt,
2746 : "User terminated CreateCopy()");
2747 : }
2748 : }
2749 :
2750 4635 : CPLFree(pabyScanline);
2751 :
2752 4634 : if (!safe_png_write_end(sSetJmpContext, hPNG, psPNGInfo))
2753 : {
2754 7 : eErr = CE_Failure;
2755 : }
2756 4634 : png_destroy_write_struct(&hPNG, &psPNGInfo);
2757 :
2758 4634 : VSIFCloseL(fpImage);
2759 :
2760 4634 : if (eErr != CE_None)
2761 7 : return nullptr;
2762 :
2763 : // Do we need a world file?
2764 4627 : if (CPLFetchBool(papszOptions, "WORLDFILE", false))
2765 : {
2766 0 : GDALGeoTransform gt;
2767 0 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
2768 0 : GDALWriteWorldFile(pszFilename, "wld", gt.data());
2769 : }
2770 :
2771 : // Re-open dataset and copy any auxiliary PAM information.
2772 :
2773 : /* If writing to stdout, we can't reopen it, so return */
2774 : /* a fake dataset to make the caller happy */
2775 4627 : if (CPLTestBool(CPLGetConfigOption("GDAL_OPEN_AFTER_COPY", "YES")))
2776 : {
2777 4179 : CPLPushErrorHandler(CPLQuietErrorHandler);
2778 4179 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
2779 : PNGDataset *poDS =
2780 4179 : reinterpret_cast<PNGDataset *>(PNGDataset::Open(&oOpenInfo));
2781 4179 : CPLPopErrorHandler();
2782 4179 : if (poDS)
2783 : {
2784 4178 : int nFlags = GCIF_PAM_DEFAULT & ~GCIF_METADATA;
2785 4178 : poDS->CloneInfo(poSrcDS, nFlags);
2786 :
2787 : char **papszExcludedDomains =
2788 4178 : CSLAddString(nullptr, "COLOR_PROFILE");
2789 4178 : if (bWriteMetadataAsText)
2790 1 : papszExcludedDomains = CSLAddString(papszExcludedDomains, "");
2791 4178 : GDALDriver::DefaultCopyMetadata(poSrcDS, poDS, papszOptions,
2792 : papszExcludedDomains);
2793 4178 : CSLDestroy(papszExcludedDomains);
2794 :
2795 4178 : return poDS;
2796 : }
2797 1 : CPLErrorReset();
2798 : }
2799 :
2800 449 : PNGDataset *poPNG_DS = new PNGDataset();
2801 449 : poPNG_DS->nRasterXSize = nXSize;
2802 449 : poPNG_DS->nRasterYSize = nYSize;
2803 449 : poPNG_DS->nBitDepth = nBitDepth;
2804 1848 : for (int i = 0; i < nBands; i++)
2805 1399 : poPNG_DS->SetBand(i + 1, new PNGRasterBand(poPNG_DS, i + 1));
2806 449 : return poPNG_DS;
2807 : }
2808 :
2809 : /************************************************************************/
2810 : /* png_vsi_read_data() */
2811 : /* */
2812 : /* Read data callback through VSI. */
2813 : /************************************************************************/
2814 68717 : static void png_vsi_read_data(png_structp png_ptr, png_bytep data,
2815 : png_size_t length)
2816 :
2817 : {
2818 : // fread() returns 0 on error, so it is OK to store this in a png_size_t
2819 : // instead of an int, which is what fread() actually returns.
2820 : const png_size_t check = static_cast<png_size_t>(
2821 137427 : VSIFReadL(data, 1, length,
2822 68717 : reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr))));
2823 :
2824 68716 : if (check != length)
2825 3 : png_error(png_ptr, "Read Error");
2826 68713 : }
2827 :
2828 : /************************************************************************/
2829 : /* png_vsi_write_data() */
2830 : /************************************************************************/
2831 :
2832 55144 : static void png_vsi_write_data(png_structp png_ptr, png_bytep data,
2833 : png_size_t length)
2834 : {
2835 110288 : const size_t check = VSIFWriteL(
2836 55144 : data, 1, length, reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr)));
2837 :
2838 55144 : if (check != length)
2839 10 : png_error(png_ptr, "Write Error");
2840 55134 : }
2841 :
2842 : /************************************************************************/
2843 : /* png_vsi_flush() */
2844 : /************************************************************************/
2845 0 : static void png_vsi_flush(png_structp png_ptr)
2846 : {
2847 0 : VSIFFlushL(reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr)));
2848 0 : }
2849 :
2850 : /************************************************************************/
2851 : /* png_gdal_error() */
2852 : /************************************************************************/
2853 :
2854 19 : static void png_gdal_error(png_structp png_ptr, const char *error_message)
2855 : {
2856 19 : CPLError(CE_Failure, CPLE_AppDefined, "libpng: %s", error_message);
2857 :
2858 : // Use longjmp instead of a C++ exception, because libpng is generally not
2859 : // built as C++ and so will not honor unwind semantics.
2860 :
2861 : jmp_buf *psSetJmpContext =
2862 19 : reinterpret_cast<jmp_buf *>(png_get_error_ptr(png_ptr));
2863 19 : if (psSetJmpContext)
2864 : {
2865 19 : longjmp(*psSetJmpContext, 1);
2866 : }
2867 0 : }
2868 :
2869 : /************************************************************************/
2870 : /* png_gdal_warning() */
2871 : /************************************************************************/
2872 :
2873 0 : static void png_gdal_warning(CPL_UNUSED png_structp png_ptr,
2874 : const char *error_message)
2875 : {
2876 0 : CPLError(CE_Warning, CPLE_AppDefined, "libpng: %s", error_message);
2877 0 : }
2878 :
2879 : /************************************************************************/
2880 : /* GDALRegister_PNG() */
2881 : /************************************************************************/
2882 :
2883 58 : void GDALRegister_PNG()
2884 :
2885 : {
2886 58 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
2887 0 : return;
2888 :
2889 58 : GDALDriver *poDriver = new GDALDriver();
2890 58 : PNGDriverSetCommonMetadata(poDriver);
2891 :
2892 58 : poDriver->pfnOpen = PNGDataset::Open;
2893 58 : poDriver->pfnCreateCopy = PNGDataset::CreateCopy;
2894 : #ifdef SUPPORT_CREATE
2895 : poDriver->pfnCreate = PNGDataset::Create;
2896 : #endif
2897 :
2898 58 : GetGDALDriverManager()->RegisterDriver(poDriver);
2899 : }
2900 :
2901 : #ifdef SUPPORT_CREATE
2902 : /************************************************************************/
2903 : /* IWriteBlock() */
2904 : /************************************************************************/
2905 :
2906 : CPLErr PNGRasterBand::IWriteBlock(int x, int y, void *pvData)
2907 : {
2908 : PNGDataset &ds = *reinterpret_cast<PNGDataset *>(poDS);
2909 :
2910 : // Write the block (or consolidate into multichannel block) and then write.
2911 :
2912 : const GDALDataType dt = GetRasterDataType();
2913 : const size_t wordsize = ds.m_nBitDepth / 8;
2914 : GDALCopyWords(pvData, dt, wordsize,
2915 : ds.m_pabyBuffer + (nBand - 1) * wordsize, dt,
2916 : ds.nBands * wordsize, nBlockXSize);
2917 :
2918 : // See if we have all the bands.
2919 : m_bBandProvided[nBand - 1] = TRUE;
2920 : for (size_t i = 0; i < static_cast<size_t>(ds.nBands); i++)
2921 : {
2922 : if (!m_bBandProvided[i])
2923 : return CE_None;
2924 : }
2925 :
2926 : // We received all the bands, so reset band flags and write pixels out.
2927 : this->reset_band_provision_flags();
2928 :
2929 : // If it is the first block, write out the file header.
2930 : if (x == 0 && y == 0)
2931 : {
2932 : CPLErr err = ds.write_png_header();
2933 : if (err != CE_None)
2934 : return err;
2935 : }
2936 :
2937 : #ifdef CPL_LSB
2938 : if (ds.m_nBitDepth == 16)
2939 : GDALSwapWords(ds.m_pabyBuffer, 2, nBlockXSize * ds.nBands, 2);
2940 : #endif
2941 : png_write_rows(ds.m_hPNG, &ds.m_pabyBuffer, 1);
2942 :
2943 : return CE_None;
2944 : }
2945 :
2946 : /************************************************************************/
2947 : /* SetGeoTransform() */
2948 : /************************************************************************/
2949 :
2950 : CPLErr PNGDataset::SetGeoTransform(const GDALGeoTransform >)
2951 : {
2952 : m_gt = gt;
2953 :
2954 : if (m_pszFilename)
2955 : {
2956 : if (GDALWriteWorldFile(m_pszFilename, "wld", m_gt.data()) == FALSE)
2957 : {
2958 : CPLError(CE_Failure, CPLE_FileIO, "Can't write world file.");
2959 : return CE_Failure;
2960 : }
2961 : }
2962 :
2963 : return CE_None;
2964 : }
2965 :
2966 : /************************************************************************/
2967 : /* SetColorTable() */
2968 : /************************************************************************/
2969 :
2970 : CPLErr PNGRasterBand::SetColorTable(GDALColorTable *poCT)
2971 : {
2972 : if (poCT == NULL)
2973 : return CE_Failure;
2974 :
2975 : // We get called even for grayscale files, since some formats need a palette
2976 : // even then. PNG doesn't, so if a gray palette is given, just ignore it.
2977 :
2978 : GDALColorEntry sEntry;
2979 : for (size_t i = 0; i < static_cast<size_t>(poCT->GetColorEntryCount()); i++)
2980 : {
2981 : poCT->GetColorEntryAsRGB(i, &sEntry);
2982 : if (sEntry.c1 != sEntry.c2 || sEntry.c1 != sEntry.c3)
2983 : {
2984 : CPLErr err = GDALPamRasterBand::SetColorTable(poCT);
2985 : if (err != CE_None)
2986 : return err;
2987 :
2988 : PNGDataset &ds = *reinterpret_cast<PNGDataset *>(poDS);
2989 : ds.m_nColorType = PNG_COLOR_TYPE_PALETTE;
2990 : break;
2991 : // band::IWriteBlock will emit color table as part of the header
2992 : // preceding the first block write.
2993 : }
2994 : }
2995 :
2996 : return CE_None;
2997 : }
2998 :
2999 : /************************************************************************/
3000 : /* PNGDataset::write_png_header() */
3001 : /************************************************************************/
3002 :
3003 : CPLErr PNGDataset::write_png_header()
3004 : {
3005 : // Initialize PNG access to the file.
3006 : m_hPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
3007 : png_gdal_error, png_gdal_warning);
3008 :
3009 : m_psPNGInfo = png_create_info_struct(m_hPNG);
3010 :
3011 : png_set_write_fn(m_hPNG, m_fpImage, png_vsi_write_data, png_vsi_flush);
3012 :
3013 : png_set_IHDR(m_hPNG, m_psPNGInfo, nRasterXSize, nRasterYSize, m_nBitDepth,
3014 : m_nColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
3015 : PNG_FILTER_TYPE_DEFAULT);
3016 :
3017 : png_set_compression_level(m_hPNG, Z_BEST_COMPRESSION);
3018 :
3019 : // png_set_swap_alpha(m_hPNG); // Use RGBA order, not ARGB.
3020 :
3021 : // Try to handle nodata values as a tRNS block (note that for paletted
3022 : // images, we save the effect to apply as part of the palette).
3023 : // m_bHaveNoData = FALSE;
3024 : // m_dfNoDataValue = -1;
3025 : png_color_16 sTRNSColor;
3026 :
3027 : int bHaveNoData = FALSE;
3028 : double dfNoDataValue = -1;
3029 :
3030 : if (m_nColorType == PNG_COLOR_TYPE_GRAY)
3031 : {
3032 : dfNoDataValue = GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
3033 :
3034 : if (bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536)
3035 : {
3036 : sTRNSColor.gray = static_cast<png_uint_16>(dfNoDataValue);
3037 : png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3038 : }
3039 : }
3040 :
3041 : // RGB nodata.
3042 : if (nColorType == PNG_COLOR_TYPE_RGB)
3043 : {
3044 : // First, try to use the NODATA_VALUES metadata item.
3045 : if (GetMetadataItem("NODATA_VALUES") != NULL)
3046 : {
3047 : char **papszValues =
3048 : CSLTokenizeString(GetMetadataItem("NODATA_VALUES"));
3049 :
3050 : if (CSLCount(papszValues) >= 3)
3051 : {
3052 : sTRNSColor.red = static_cast<png_uint_16>(atoi(papszValues[0]));
3053 : sTRNSColor.green =
3054 : static_cast<png_uint_16>(atoi(papszValues[1]));
3055 : sTRNSColor.blue =
3056 : static_cast<png_uint_16>(atoi(papszValues[2]));
3057 : png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3058 : }
3059 :
3060 : CSLDestroy(papszValues);
3061 : }
3062 : // Otherwise, get the nodata value from the bands.
3063 : else
3064 : {
3065 : int bHaveNoDataRed = FALSE;
3066 : const double dfNoDataValueRed =
3067 : GetRasterBand(1)->GetNoDataValue(&bHaveNoDataRed);
3068 :
3069 : int bHaveNoDataGreen = FALSE;
3070 : const double dfNoDataValueGreen =
3071 : GetRasterBand(2)->GetNoDataValue(&bHaveNoDataGreen);
3072 :
3073 : int bHaveNoDataBlue = FALSE;
3074 : const double dfNoDataValueBlue =
3075 : GetRasterBand(3)->GetNoDataValue(&bHaveNoDataBlue);
3076 :
3077 : if ((bHaveNoDataRed && dfNoDataValueRed >= 0 &&
3078 : dfNoDataValueRed < 65536) &&
3079 : (bHaveNoDataGreen && dfNoDataValueGreen >= 0 &&
3080 : dfNoDataValueGreen < 65536) &&
3081 : (bHaveNoDataBlue && dfNoDataValueBlue >= 0 &&
3082 : dfNoDataValueBlue < 65536))
3083 : {
3084 : sTRNSColor.red = static_cast<png_uint_16>(dfNoDataValueRed);
3085 : sTRNSColor.green = static_cast<png_uint_16>(dfNoDataValueGreen);
3086 : sTRNSColor.blue = static_cast<png_uint_16>(dfNoDataValueBlue);
3087 : png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3088 : }
3089 : }
3090 : }
3091 :
3092 : // Write the palette if there is one. Technically, it may be possible
3093 : // to write 16-bit palettes for PNG, but for now, doing so is omitted.
3094 : if (nColorType == PNG_COLOR_TYPE_PALETTE)
3095 : {
3096 : GDALColorTable *poCT = GetRasterBand(1)->GetColorTable();
3097 :
3098 : int bHaveNoData = FALSE;
3099 : double dfNoDataValue = GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
3100 :
3101 : m_pasPNGColors = reinterpret_cast<png_color *>(
3102 : CPLMalloc(sizeof(png_color) * poCT->GetColorEntryCount()));
3103 :
3104 : GDALColorEntry sEntry;
3105 : bool bFoundTrans = false;
3106 : for (int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++)
3107 : {
3108 : poCT->GetColorEntryAsRGB(iColor, &sEntry);
3109 : if (sEntry.c4 != 255)
3110 : bFoundTrans = true;
3111 :
3112 : m_pasPNGColors[iColor].red = static_cast<png_byte>(sEntry.c1);
3113 : m_pasPNGColors[iColor].green = static_cast<png_byte>(sEntry.c2);
3114 : m_pasPNGColors[iColor].blue = static_cast<png_byte>(sEntry.c3);
3115 : }
3116 :
3117 : png_set_PLTE(m_hPNG, m_psPNGInfo, m_pasPNGColors,
3118 : poCT->GetColorEntryCount());
3119 :
3120 : // If we have transparent elements in the palette, we need to write a
3121 : // transparency block.
3122 : if (bFoundTrans || bHaveNoData)
3123 : {
3124 : m_pabyAlpha = reinterpret_cast<unsigned char *>(
3125 : CPLMalloc(poCT->GetColorEntryCount()));
3126 :
3127 : for (int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++)
3128 : {
3129 : poCT->GetColorEntryAsRGB(iColor, &sEntry);
3130 : m_pabyAlpha[iColor] = static_cast<unsigned char>(sEntry.c4);
3131 :
3132 : if (bHaveNoData && iColor == static_cast<int>(dfNoDataValue))
3133 : m_pabyAlpha[iColor] = 0;
3134 : }
3135 :
3136 : png_set_tRNS(m_hPNG, m_psPNGInfo, m_pabyAlpha,
3137 : poCT->GetColorEntryCount(), NULL);
3138 : }
3139 : }
3140 :
3141 : png_write_info(m_hPNG, m_psPNGInfo);
3142 : return CE_None;
3143 : }
3144 :
3145 : /************************************************************************/
3146 : /* Create() */
3147 : /************************************************************************/
3148 :
3149 : GDALDataset *PNGDataset::Create(const char *pszFilename, int nXSize, int nYSize,
3150 : int nBands, GDALDataType eType,
3151 : char **papszOptions)
3152 : {
3153 : if (eType != GDT_Byte && eType != GDT_UInt16)
3154 : {
3155 : CPLError(
3156 : CE_Failure, CPLE_AppDefined,
3157 : "Attempt to create PNG dataset with an illegal\n"
3158 : "data type (%s), only Byte and UInt16 supported by the format.\n",
3159 : GDALGetDataTypeName(eType));
3160 :
3161 : return NULL;
3162 : }
3163 :
3164 : if (nBands < 1 || nBands > 4)
3165 : {
3166 : CPLError(CE_Failure, CPLE_NotSupported,
3167 : "PNG driver doesn't support %d bands. "
3168 : "Must be 1 (gray/indexed color),\n"
3169 : "2 (gray+alpha), 3 (rgb) or 4 (rgba) bands.\n",
3170 : nBands);
3171 :
3172 : return NULL;
3173 : }
3174 :
3175 : // Bands are:
3176 : // 1: Grayscale or indexed color.
3177 : // 2: Gray plus alpha.
3178 : // 3: RGB.
3179 : // 4: RGB plus alpha.
3180 :
3181 : if (nXSize < 1 || nYSize < 1)
3182 : {
3183 : CPLError(CE_Failure, CPLE_NotSupported,
3184 : "Specified pixel dimensions (% d x %d) are bad.\n", nXSize,
3185 : nYSize);
3186 : }
3187 :
3188 : // Set up some parameters.
3189 : PNGDataset *poDS = new PNGDataset();
3190 :
3191 : poDS->nRasterXSize = nXSize;
3192 : poDS->nRasterYSize = nYSize;
3193 : poDS->eAccess = GA_Update;
3194 : poDS->nBands = nBands;
3195 :
3196 : switch (nBands)
3197 : {
3198 : case 1:
3199 : poDS->m_nColorType = PNG_COLOR_TYPE_GRAY;
3200 : break; // If a non-gray palette is set, we'll change this.
3201 :
3202 : case 2:
3203 : poDS->m_nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
3204 : break;
3205 :
3206 : case 3:
3207 : poDS->m_nColorType = PNG_COLOR_TYPE_RGB;
3208 : break;
3209 :
3210 : case 4:
3211 : poDS->m_nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
3212 : break;
3213 : }
3214 :
3215 : poDS->m_nBitDepth = (eType == GDT_Byte ? 8 : 16);
3216 :
3217 : poDS->m_pabyBuffer = reinterpret_cast<GByte *>(
3218 : CPLMalloc(nBands * nXSize * poDS->m_nBitDepth / 8));
3219 :
3220 : // Create band information objects.
3221 : for (int iBand = 1; iBand <= poDS->nBands; iBand++)
3222 : poDS->SetBand(iBand, new PNGRasterBand(poDS, iBand));
3223 :
3224 : // Do we need a world file?
3225 : if (CPLFetchBool(papszOptions, "WORLDFILE", false))
3226 : poDS->m_bGeoTransformValid = TRUE;
3227 :
3228 : // Create the file.
3229 :
3230 : poDS->m_fpImage = VSIFOpenL(pszFilename, "wb");
3231 : if (poDS->m_fpImage == NULL)
3232 : {
3233 : CPLError(CE_Failure, CPLE_OpenFailed,
3234 : "Unable to create PNG file %s: %s\n", pszFilename,
3235 : VSIStrerror(errno));
3236 : delete poDS;
3237 : return NULL;
3238 : }
3239 :
3240 : poDS->m_pszFilename = CPLStrdup(pszFilename);
3241 :
3242 : return poDS;
3243 : }
3244 :
3245 : #endif
|