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