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