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