Coverage Report

Created: 2025-06-09 07:02

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