• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.92
/frmts/png/pngdataset.cpp
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
22,579✔
80
{
81
    return nBitDepth == 8 && !bInterlaced && nRasterXSize <= 512 &&
22,392✔
82
           nRasterYSize <= 512 &&
22,048✔
83
           CPLTestBool(
22,045✔
84
               CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")) &&
44,972✔
85
           CPLTestBool(CPLGetConfigOption("GDAL_PNG_SINGLE_BLOCK", "YES"));
44,537✔
86
}
87
#endif
88

89
/************************************************************************/
90
/*                           PNGRasterBand()                            */
91
/************************************************************************/
92

93
PNGRasterBand::PNGRasterBand(PNGDataset *poDSIn, int nBandIn)
22,579✔
94
    : bHaveNoData(FALSE), dfNoDataValue(-1)
22,579✔
95
{
96
    poDS = poDSIn;
22,579✔
97
    nBand = nBandIn;
22,579✔
98

99
    if (poDSIn->nBitDepth == 16)
22,579✔
100
        eDataType = GDT_UInt16;
179✔
101
    else
102
        eDataType = GDT_Byte;
22,400✔
103

104
    nBlockXSize = poDSIn->nRasterXSize;
22,579✔
105
#ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
106
    if (poDSIn->IsCompatibleOfSingleBlock())
22,579✔
107
    {
108
        nBlockYSize = poDSIn->nRasterYSize;
21,868✔
109
    }
110
    else
111
#endif
112
    {
113
        nBlockYSize = 1;
712✔
114
    }
115

116
#ifdef SUPPORT_CREATE
117
    reset_band_provision_flags();
118
#endif
119
}
22,580✔
120

121
/************************************************************************/
122
/*                             IReadBlock()                             */
123
/************************************************************************/
124

125
CPLErr PNGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
19,070✔
126

127
{
128
#ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
129
    if (nBlockYSize > 1)
19,070✔
130
    {
131
        GDALRasterIOExtraArg sExtraArg;
132
        INIT_RASTERIO_EXTRA_ARG(sExtraArg);
127✔
133
        const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
127✔
134
        return IRasterIO(GF_Read, 0, 0, nRasterXSize, nRasterYSize, pImage,
254✔
135
                         nRasterXSize, nRasterYSize, eDataType, nDTSize,
136
                         static_cast<GSpacing>(nDTSize) * nRasterXSize,
127✔
137
                         &sExtraArg);
127✔
138
    }
139
#endif
140

141
    PNGDataset *poGDS = cpl::down_cast<PNGDataset *>(poDS);
18,943✔
142
    int nPixelSize;
143
    CPLAssert(nBlockXOff == 0);
18,943✔
144

145
    if (poGDS->nBitDepth == 16)
18,943✔
146
        nPixelSize = 2;
11,003✔
147
    else
148
        nPixelSize = 1;
7,940✔
149

150
    const int nXSize = GetXSize();
18,943✔
151
    if (poGDS->fpImage == nullptr)
18,943✔
152
    {
153
        memset(pImage, 0, cpl::fits_on<int>(nPixelSize * nXSize));
×
154
        return CE_None;
×
155
    }
156

157
    // Load the desired scanline into the working buffer.
158
    CPLErr eErr = poGDS->LoadScanline(nBlockYOff);
18,943✔
159
    if (eErr != CE_None)
18,943✔
160
        return eErr;
2✔
161

162
    const int nPixelOffset = poGDS->nBands * nPixelSize;
18,941✔
163

164
    GByte *pabyScanline =
18,941✔
165
        poGDS->pabyBuffer +
18,941✔
166
        (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize +
18,941✔
167
        nPixelSize * (nBand - 1);
18,941✔
168

169
    // Transfer between the working buffer and the caller's buffer.
170
    if (nPixelSize == nPixelOffset)
18,941✔
171
        memcpy(pImage, pabyScanline, cpl::fits_on<int>(nPixelSize * nXSize));
10,687✔
172
    else if (nPixelSize == 1)
8,254✔
173
    {
174
        for (int i = 0; i < nXSize; i++)
3,737,490✔
175
            reinterpret_cast<GByte *>(pImage)[i] =
3,730,210✔
176
                pabyScanline[i * nPixelOffset];
3,730,210✔
177
    }
178
    else
179
    {
180
        CPLAssert(nPixelSize == 2);
976✔
181
        for (int i = 0; i < nXSize; i++)
21,648✔
182
        {
183
            reinterpret_cast<GUInt16 *>(pImage)[i] =
20,672✔
184
                *reinterpret_cast<GUInt16 *>(pabyScanline + i * nPixelOffset);
20,672✔
185
        }
186
    }
187

188
    // Forcibly load the other bands associated with this scanline.
189
    for (int iBand = 1; iBand < poGDS->GetRasterCount(); iBand++)
42,347✔
190
    {
191
        GDALRasterBlock *poBlock =
192
            poGDS->GetRasterBand(iBand + 1)->GetLockedBlockRef(nBlockXOff,
23,406✔
193
                                                               nBlockYOff);
23,406✔
194
        if (poBlock != nullptr)
23,406✔
195
            poBlock->DropLock();
23,406✔
196
    }
197

198
    return CE_None;
18,941✔
199
}
200

201
/************************************************************************/
202
/*                       GetColorInterpretation()                       */
203
/************************************************************************/
204

205
GDALColorInterp PNGRasterBand::GetColorInterpretation()
332✔
206

207
{
208
    PNGDataset *poGDS = reinterpret_cast<PNGDataset *>(poDS);
332✔
209

210
    if (poGDS->nColorType == PNG_COLOR_TYPE_GRAY)
332✔
211
        return GCI_GrayIndex;
16✔
212

213
    else if (poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
316✔
214
    {
215
        if (nBand == 1)
29✔
216
            return GCI_GrayIndex;
8✔
217
        else
218
            return GCI_AlphaBand;
21✔
219
    }
220

221
    else if (poGDS->nColorType == PNG_COLOR_TYPE_PALETTE)
287✔
222
        return GCI_PaletteIndex;
43✔
223

224
    else if (poGDS->nColorType == PNG_COLOR_TYPE_RGB ||
244✔
225
             poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA)
202✔
226
    {
227
        if (nBand == 1)
244✔
228
            return GCI_RedBand;
37✔
229
        else if (nBand == 2)
207✔
230
            return GCI_GreenBand;
44✔
231
        else if (nBand == 3)
163✔
232
            return GCI_BlueBand;
35✔
233
        else
234
            return GCI_AlphaBand;
128✔
235
    }
236
    else
237
        return GCI_GrayIndex;
×
238
}
239

240
/************************************************************************/
241
/*                           GetColorTable()                            */
242
/************************************************************************/
243

244
GDALColorTable *PNGRasterBand::GetColorTable()
569✔
245

246
{
247
    PNGDataset *poGDS = reinterpret_cast<PNGDataset *>(poDS);
569✔
248

249
    if (nBand == 1)
569✔
250
        return poGDS->poColorTable;
545✔
251

252
    return nullptr;
24✔
253
}
254

255
/************************************************************************/
256
/*                           SetNoDataValue()                           */
257
/************************************************************************/
258

259
CPLErr PNGRasterBand::SetNoDataValue(double dfNewValue)
283✔
260

261
{
262
    bHaveNoData = TRUE;
283✔
263
    dfNoDataValue = dfNewValue;
283✔
264

265
    return CE_None;
283✔
266
}
267

268
/************************************************************************/
269
/*                           GetNoDataValue()                           */
270
/************************************************************************/
271

272
double PNGRasterBand::GetNoDataValue(int *pbSuccess)
163✔
273

274
{
275
    if (bHaveNoData)
163✔
276
    {
277
        if (pbSuccess != nullptr)
34✔
278
            *pbSuccess = bHaveNoData;
30✔
279
        return dfNoDataValue;
34✔
280
    }
281

282
    return GDALPamRasterBand::GetNoDataValue(pbSuccess);
129✔
283
}
284

285
/************************************************************************/
286
/* ==================================================================== */
287
/*                             PNGDataset                               */
288
/* ==================================================================== */
289
/************************************************************************/
290

291
/************************************************************************/
292
/*                             PNGDataset()                             */
293
/************************************************************************/
294

295
PNGDataset::PNGDataset()
8,106✔
296
{
297
    memset(&sSetJmpContext, 0, sizeof(sSetJmpContext));
8,106✔
298
}
8,106✔
299

300
/************************************************************************/
301
/*                            ~PNGDataset()                             */
302
/************************************************************************/
303

304
PNGDataset::~PNGDataset()
16,212✔
305

306
{
307
    PNGDataset::FlushCache(true);
8,106✔
308

309
    if (hPNG != nullptr)
8,106✔
310
        png_destroy_read_struct(&hPNG, &psPNGInfo, nullptr);
7,742✔
311

312
    if (fpImage)
8,106✔
313
        VSIFCloseL(fpImage);
7,742✔
314

315
    if (poColorTable != nullptr)
8,106✔
316
        delete poColorTable;
383✔
317
}
16,212✔
318

319
/************************************************************************/
320
/*                         LoadWholeImage()                             */
321
/************************************************************************/
322

323
#ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
324

325
#ifdef HAVE_SSE2
326
#include "filter_sse2_intrinsics.c"
327
#endif
328

329
#if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
330
__attribute__((optimize("tree-vectorize"))) static inline void
331
AddVectors(const GByte *CPL_RESTRICT pabyInputLine,
332
           GByte *CPL_RESTRICT pabyOutputLine, int nSize)
333
{
334
    for (int iX = 0; iX < nSize; ++iX)
335
        pabyOutputLine[iX] =
336
            static_cast<GByte>(pabyInputLine[iX] + pabyOutputLine[iX]);
337
}
338

339
__attribute__((optimize("tree-vectorize"))) static inline void
340
AddVectors(const GByte *CPL_RESTRICT pabyInputLine1,
341
           const GByte *CPL_RESTRICT pabyInputLine2,
342
           GByte *CPL_RESTRICT pabyOutputLine, int nSize)
343
{
344
    for (int iX = 0; iX < nSize; ++iX)
345
        pabyOutputLine[iX] =
346
            static_cast<GByte>(pabyInputLine1[iX] + pabyInputLine2[iX]);
347
}
348
#endif  //  defined(__GNUC__) && !defined(__SSE2__)
349

350
CPLErr PNGDataset::LoadWholeImage(void *pSingleBuffer, GSpacing nPixelSpace,
3,216✔
351
                                  GSpacing nLineSpace, GSpacing nBandSpace,
352
                                  void *apabyBuffers[4])
353
{
354
    if (fpImage == nullptr)
3,216✔
355
    {
356
        for (int iY = 0; iY < nRasterYSize; ++iY)
21✔
357
        {
358
            if (pSingleBuffer)
20✔
359
            {
360
                GByte *pabyDest =
20✔
361
                    static_cast<GByte *>(pSingleBuffer) + iY * nLineSpace;
20✔
362
                for (int x = 0; x < nRasterXSize; ++x)
420✔
363
                {
364
                    for (int iBand = 0; iBand < nBands; iBand++)
800✔
365
                    {
366
                        pabyDest[(x * nPixelSpace) + iBand * nBandSpace] = 0;
400✔
367
                    }
368
                }
369
            }
370
            else
371
            {
372
                for (int iBand = 0; iBand < nBands; iBand++)
×
373
                {
374
                    GByte *l_pabyBuffer =
×
375
                        static_cast<GByte *>(apabyBuffers[iBand]) +
×
376
                        iY * nRasterXSize;
×
377
                    memset(l_pabyBuffer, 0, nRasterXSize);
×
378
                }
379
            }
380
        }
381
        return CE_None;
1✔
382
    }
383

384
    const bool bCanUseDeinterleave =
3,215✔
385
        (nBands == 3 || nBands == 4) &&
4,827✔
386
        (apabyBuffers != nullptr ||
1,612✔
387
         (nPixelSpace == 1 &&
1,448✔
388
          nBandSpace == static_cast<GSpacing>(nRasterXSize) * nRasterYSize));
1,448✔
389

390
    // Below should work without SSE2, but the lack of optimized
391
    // filters can sometimes make it slower than regular optimized libpng,
392
    // so restrict to when SSE2 is available.
393

394
    // CPLDebug("PNG", "Using libdeflate optimization");
395

396
    char szChunkName[5] = {0};
3,215✔
397
    bool bError = false;
3,215✔
398

399
    // We try to read the zlib compressed data into pData, if there is
400
    // enough room for that
401
    size_t nDataSize = 0;
3,215✔
402
    std::vector<GByte> abyCompressedData;  // keep in this scope
6,430✔
403
    GByte *pabyCompressedData = static_cast<GByte *>(pSingleBuffer);
3,215✔
404
    size_t nCompressedDataSize = 0;
3,215✔
405
    if (pSingleBuffer)
3,215✔
406
    {
407
        if (nPixelSpace == nBands && nLineSpace == nPixelSpace * nRasterXSize &&
2,655✔
408
            (nBands == 1 || nBandSpace == 1))
422✔
409
        {
410
            nDataSize =
422✔
411
                static_cast<size_t>(nRasterXSize) * nRasterYSize * nBands;
422✔
412
        }
413
        else if (nPixelSpace == 1 && nLineSpace == nRasterXSize &&
2,233✔
414
                 nBandSpace ==
415
                     static_cast<GSpacing>(nRasterXSize) * nRasterYSize)
1,962✔
416
        {
417
            nDataSize =
1,962✔
418
                static_cast<size_t>(nRasterXSize) * nRasterYSize * nBands;
1,962✔
419
        }
420
    }
421

422
    const auto nPosBefore = VSIFTellL(fpImage);
3,215✔
423
    VSIFSeekL(fpImage, 8, SEEK_SET);
3,215✔
424
    // Iterate over PNG chunks
425
    while (true)
426
    {
427
        uint32_t nChunkSize;
428
        if (VSIFReadL(&nChunkSize, sizeof(nChunkSize), 1, fpImage) == 0)
13,237✔
429
        {
430
            bError = true;
×
431
            break;
×
432
        }
433
        CPL_MSBPTR32(&nChunkSize);
13,237✔
434
        if (VSIFReadL(szChunkName, 4, 1, fpImage) == 0)
13,237✔
435
        {
436
            bError = true;
×
437
            break;
×
438
        }
439
        if (strcmp(szChunkName, "IDAT") == 0)
13,237✔
440
        {
441
            // CPLDebug("PNG", "IDAT %u %u", unsigned(nCompressedDataSize),
442
            // unsigned(nChunkSize));
443

444
            // There can be several IDAT chunks: concatenate ZLib stream
445
            if (nChunkSize >
11,536✔
446
                std::numeric_limits<size_t>::max() - nCompressedDataSize)
5,768✔
447
            {
448
                CPLError(CE_Failure, CPLE_OutOfMemory,
×
449
                         "Out of memory when reading compressed stream");
450
                bError = true;
×
451
                break;
×
452
            }
453

454
            // Sanity check to avoid allocating too much memory
455
            if (nCompressedDataSize + nChunkSize > 100 * 1024 * 1024)
5,768✔
456
            {
457
                const auto nCurPos = VSIFTellL(fpImage);
×
458
                VSIFSeekL(fpImage, 0, SEEK_END);
×
459
                const auto nSize = VSIFTellL(fpImage);
×
460
                VSIFSeekL(fpImage, nCurPos, SEEK_SET);
×
461
                if (nSize < 100 * 1024 * 1024)
×
462
                {
463
                    CPLError(CE_Failure, CPLE_OutOfMemory,
×
464
                             "Attempt at reading more data than available in "
465
                             "compressed stream");
466
                    bError = true;
×
467
                    break;
×
468
                }
469
            }
470

471
            if (nCompressedDataSize + nChunkSize > nDataSize)
5,768✔
472
            {
473
                const bool bVectorEmptyBefore = abyCompressedData.empty();
2,990✔
474
                // unlikely situation: would mean that the zlib compressed
475
                // data is longer than the decompressed image
476
                try
477
                {
478
                    abyCompressedData.resize(nCompressedDataSize + nChunkSize);
2,990✔
479
                    pabyCompressedData = abyCompressedData.data();
2,990✔
480
                }
481
                catch (const std::exception &)
×
482
                {
483
                    CPLError(CE_Failure, CPLE_OutOfMemory,
×
484
                             "Out of memory when allocating compressed stream");
485
                    bError = true;
×
486
                    break;
×
487
                }
488
                if (bVectorEmptyBefore && pSingleBuffer &&
2,990✔
489
                    nCompressedDataSize > 0)
490
                {
491
                    memcpy(pabyCompressedData, pSingleBuffer,
×
492
                           nCompressedDataSize);
493
                }
494
            }
495
            VSIFReadL(pabyCompressedData + nCompressedDataSize, nChunkSize, 1,
5,768✔
496
                      fpImage);
497
            nCompressedDataSize += nChunkSize;
5,768✔
498
        }
499
        else if (strcmp(szChunkName, "IEND") == 0)
7,469✔
500
            break;
3,215✔
501
        else
502
        {
503
            // CPLDebug("PNG", "Skipping chunk %s of size %u", szChunkName,
504
            // nChunkSize);
505
            VSIFSeekL(fpImage, nChunkSize, SEEK_CUR);
4,254✔
506
        }
507
        VSIFSeekL(fpImage, 4, SEEK_CUR);  // CRC
10,022✔
508
    }
10,022✔
509
    VSIFSeekL(fpImage, nPosBefore, SEEK_SET);
3,215✔
510
    if (bError)
3,215✔
511
        return CE_Failure;
×
512

513
    const int nSamplesPerLine = nRasterXSize * nBands;
3,215✔
514
    size_t nOutBytes;
515
    constexpr int FILTER_TYPE_BYTE = 1;
3,215✔
516
    const size_t nZlibDecompressedSize = static_cast<size_t>(nRasterYSize) *
3,215✔
517
                                         (FILTER_TYPE_BYTE + nSamplesPerLine);
3,215✔
518
    GByte *pabyZlibDecompressed =
519
        static_cast<GByte *>(VSI_MALLOC_VERBOSE(nZlibDecompressedSize));
3,215✔
520
    if (pabyZlibDecompressed == nullptr)
3,215✔
521
    {
522
        return CE_Failure;
×
523
    }
524

525
    if (CPLZLibInflate(pabyCompressedData, nCompressedDataSize,
3,215✔
526
                       pabyZlibDecompressed, nZlibDecompressedSize,
527
                       &nOutBytes) == nullptr)
3,215✔
528
    {
529
        CPLError(CE_Failure, CPLE_AppDefined, "CPLZLibInflate() failed");
×
530
        CPLFree(pabyZlibDecompressed);
×
531
        return CE_Failure;
×
532
    }
533

534
    GByte *pabyOutputBuffer;
535
    std::vector<GByte> abyTemp;
6,430✔
536
    std::vector<GByte> abyLineUp;
6,430✔
537

538
    if (pSingleBuffer != nullptr && nPixelSpace == nBands &&
3,215✔
539
        nLineSpace == nPixelSpace * nRasterXSize &&
432✔
540
        (nBands == 1 || nBandSpace == 1))
422✔
541
    {
542
        pabyOutputBuffer = static_cast<GByte *>(pSingleBuffer);
422✔
543
    }
544
    else
545
    {
546
        abyTemp.resize(nSamplesPerLine);
2,793✔
547
        pabyOutputBuffer = abyTemp.data();
2,793✔
548
    }
549

550
    for (int iY = 0; iY < nRasterYSize; ++iY)
379,777✔
551
    {
552
        // Cf http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
553
        // CPLDebug("PNG", "Line %d, filter type = %d", iY, nFilterType);
554
        const GByte *CPL_RESTRICT pabyInputLine =
376,562✔
555
            pabyZlibDecompressed +
556
            static_cast<size_t>(iY) * (FILTER_TYPE_BYTE + nSamplesPerLine);
376,562✔
557
        const GByte nFilterType = pabyInputLine[0];
376,562✔
558
        pabyInputLine++;
376,562✔
559
        GByte *const CPL_RESTRICT pabyOutputLine =
560
            abyTemp.empty()
376,562✔
561
                ? pabyOutputBuffer + static_cast<size_t>(iY) * nSamplesPerLine
376,548✔
562
                : abyTemp.data();
286,163✔
563
        if (nFilterType == 0)
376,544✔
564
        {
565
            // Filter type 0: None
566
            memcpy(pabyOutputLine, pabyInputLine, nSamplesPerLine);
127,503✔
567
        }
568
        else if (nFilterType == 1)
249,041✔
569
        {
570
            // Filter type 1: Sub (horizontal differencing)
571
#ifdef HAVE_SSE2
572
            if (nBands == 3)
12,727✔
573
            {
574
                png_row_info row_info;
575
                memset(&row_info, 0, sizeof(row_info));
4,432✔
576
                row_info.rowbytes = nSamplesPerLine;
4,432✔
577

578
                gdal_png_read_filter_row_sub3_sse2(&row_info, pabyInputLine,
4,432✔
579
                                                   pabyOutputLine);
580
            }
581
            else if (nBands == 4)
8,295✔
582
            {
583
                png_row_info row_info;
584
                memset(&row_info, 0, sizeof(row_info));
6,721✔
585
                row_info.rowbytes = nSamplesPerLine;
6,721✔
586

587
                gdal_png_read_filter_row_sub4_sse2(&row_info, pabyInputLine,
6,721✔
588
                                                   pabyOutputLine);
589
            }
590
            else
591
#endif
592
            {
593
                int iX;
594
                for (iX = 0; iX < nBands; ++iX)
4,333✔
595
                    pabyOutputLine[iX] = pabyInputLine[iX];
2,759✔
596
#if !defined(HAVE_SSE2)
597
                if (nBands == 3)
598
                {
599
                    GByte nLast0 = pabyOutputLine[0];
600
                    GByte nLast1 = pabyOutputLine[1];
601
                    GByte nLast2 = pabyOutputLine[2];
602
                    for (; iX + 5 < nSamplesPerLine; iX += 6)
603
                    {
604
                        nLast0 =
605
                            static_cast<GByte>(nLast0 + pabyInputLine[iX + 0]);
606
                        nLast1 =
607
                            static_cast<GByte>(nLast1 + pabyInputLine[iX + 1]);
608
                        nLast2 =
609
                            static_cast<GByte>(nLast2 + pabyInputLine[iX + 2]);
610
                        pabyOutputLine[iX + 0] = nLast0;
611
                        pabyOutputLine[iX + 1] = nLast1;
612
                        pabyOutputLine[iX + 2] = nLast2;
613
                        nLast0 =
614
                            static_cast<GByte>(nLast0 + pabyInputLine[iX + 3]);
615
                        nLast1 =
616
                            static_cast<GByte>(nLast1 + pabyInputLine[iX + 4]);
617
                        nLast2 =
618
                            static_cast<GByte>(nLast2 + pabyInputLine[iX + 5]);
619
                        pabyOutputLine[iX + 3] = nLast0;
620
                        pabyOutputLine[iX + 4] = nLast1;
621
                        pabyOutputLine[iX + 5] = nLast2;
622
                    }
623
                }
624
                else if (nBands == 4)
625
                {
626
                    GByte nLast0 = pabyOutputLine[0];
627
                    GByte nLast1 = pabyOutputLine[1];
628
                    GByte nLast2 = pabyOutputLine[2];
629
                    GByte nLast3 = pabyOutputLine[3];
630
                    for (; iX + 7 < nSamplesPerLine; iX += 8)
631
                    {
632
                        nLast0 =
633
                            static_cast<GByte>(nLast0 + pabyInputLine[iX + 0]);
634
                        nLast1 =
635
                            static_cast<GByte>(nLast1 + pabyInputLine[iX + 1]);
636
                        nLast2 =
637
                            static_cast<GByte>(nLast2 + pabyInputLine[iX + 2]);
638
                        nLast3 =
639
                            static_cast<GByte>(nLast3 + pabyInputLine[iX + 3]);
640
                        pabyOutputLine[iX + 0] = nLast0;
641
                        pabyOutputLine[iX + 1] = nLast1;
642
                        pabyOutputLine[iX + 2] = nLast2;
643
                        pabyOutputLine[iX + 3] = nLast3;
644
                        nLast0 =
645
                            static_cast<GByte>(nLast0 + pabyInputLine[iX + 4]);
646
                        nLast1 =
647
                            static_cast<GByte>(nLast1 + pabyInputLine[iX + 5]);
648
                        nLast2 =
649
                            static_cast<GByte>(nLast2 + pabyInputLine[iX + 6]);
650
                        nLast3 =
651
                            static_cast<GByte>(nLast3 + pabyInputLine[iX + 7]);
652
                        pabyOutputLine[iX + 4] = nLast0;
653
                        pabyOutputLine[iX + 5] = nLast1;
654
                        pabyOutputLine[iX + 6] = nLast2;
655
                        pabyOutputLine[iX + 7] = nLast3;
656
                    }
657
                }
658
#endif
659
                for (; iX < nSamplesPerLine; ++iX)
196,272✔
660
                    pabyOutputLine[iX] = static_cast<GByte>(
194,698✔
661
                        pabyInputLine[iX] + pabyOutputLine[iX - nBands]);
194,698✔
662
            }
663
        }
664
        else if (nFilterType == 2)
236,314✔
665
        {
666
            // Filter type 2: Up (vertical differencing)
667
            if (iY == 0)
89,718✔
668
            {
669
                memcpy(pabyOutputLine, pabyInputLine, nSamplesPerLine);
×
670
            }
671
            else
672
            {
673
                if (abyTemp.empty())
89,718✔
674
                {
675
                    const GByte *CPL_RESTRICT pabyOutputLineUp =
22,547✔
676
                        pabyOutputBuffer +
677
                        (static_cast<size_t>(iY) - 1) * nSamplesPerLine;
22,547✔
678
#if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
679
                    AddVectors(pabyInputLine, pabyOutputLineUp, pabyOutputLine,
680
                               nSamplesPerLine);
681
#else
682
                    int iX;
683
#ifdef HAVE_SSE2
684
                    for (iX = 0; iX + 31 < nSamplesPerLine; iX += 32)
215,847✔
685
                    {
686
                        auto in =
687
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
193,300✔
688
                                pabyInputLine + iX));
193,300✔
689
                        auto in2 =
690
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
193,300✔
691
                                pabyInputLine + iX + 16));
193,300✔
692
                        auto up =
693
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
193,300✔
694
                                pabyOutputLineUp + iX));
193,300✔
695
                        auto up2 =
696
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
193,300✔
697
                                pabyOutputLineUp + iX + 16));
193,300✔
698
                        in = _mm_add_epi8(in, up);
193,300✔
699
                        in2 = _mm_add_epi8(in2, up2);
193,300✔
700
                        _mm_storeu_si128(
193,300✔
701
                            reinterpret_cast<__m128i *>(pabyOutputLine + iX),
193,300✔
702
                            in);
703
                        _mm_storeu_si128(reinterpret_cast<__m128i *>(
193,300✔
704
                                             pabyOutputLine + iX + 16),
193,300✔
705
                                         in2);
706
                    }
707
#endif
708
                    for (; iX < nSamplesPerLine; ++iX)
26,461✔
709
                        pabyOutputLine[iX] = static_cast<GByte>(
3,914✔
710
                            pabyInputLine[iX] + pabyOutputLineUp[iX]);
3,914✔
711
#endif
712
                }
713
                else
714
                {
715
#if defined(__GNUC__) && !defined(__SSE2__) && !defined(USE_NEON_OPTIMIZATIONS)
716
                    AddVectors(pabyInputLine, pabyOutputLine, nSamplesPerLine);
717
#else
718
                    int iX;
719
#ifdef HAVE_SSE2
720
                    for (iX = 0; iX + 31 < nSamplesPerLine; iX += 32)
1,488,180✔
721
                    {
722
                        auto in =
723
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
1,421,020✔
724
                                pabyInputLine + iX));
1,421,020✔
725
                        auto in2 =
726
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
1,421,020✔
727
                                pabyInputLine + iX + 16));
1,421,020✔
728
                        auto out =
729
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
2,842,030✔
730
                                pabyOutputLine + iX));
1,421,020✔
731
                        auto out2 =
732
                            _mm_loadu_si128(reinterpret_cast<const __m128i *>(
1,421,020✔
733
                                pabyOutputLine + iX + 16));
1,421,020✔
734
                        out = _mm_add_epi8(out, in);
1,421,020✔
735
                        out2 = _mm_add_epi8(out2, in2);
1,421,020✔
736
                        _mm_storeu_si128(
1,421,020✔
737
                            reinterpret_cast<__m128i *>(pabyOutputLine + iX),
1,421,020✔
738
                            out);
739
                        _mm_storeu_si128(reinterpret_cast<__m128i *>(
1,421,020✔
740
                                             pabyOutputLine + iX + 16),
1,421,020✔
741
                                         out2);
742
                    }
743
#endif
744
                    for (; iX < nSamplesPerLine; ++iX)
236,066✔
745
                        pabyOutputLine[iX] = static_cast<GByte>(
168,896✔
746
                            pabyOutputLine[iX] + pabyInputLine[iX]);
168,896✔
747
#endif
748
                }
749
            }
750
        }
751
        else if (nFilterType == 3)
146,596✔
752
        {
753
            // Filter type 3: Average
754
            if (iY == 0)
10,867✔
755
            {
756
                for (int iX = 0; iX < nBands; ++iX)
166✔
757
                {
758
                    pabyOutputLine[iX] = pabyInputLine[iX];
114✔
759
                }
760
                for (int iX = nBands; iX < nSamplesPerLine; ++iX)
1,522✔
761
                {
762
                    pabyOutputLine[iX] = static_cast<GByte>(
1,470✔
763
                        pabyInputLine[iX] + pabyOutputLine[iX - nBands] / 2);
1,470✔
764
                }
765
            }
766
            else
767
            {
768
#ifdef HAVE_SSE2
769
                if (nBands == 3)
10,815✔
770
                {
771
                    png_row_info row_info;
772
                    memset(&row_info, 0, sizeof(row_info));
5,541✔
773
                    row_info.rowbytes = nSamplesPerLine;
5,541✔
774
                    if (!abyTemp.empty())
5,541✔
775
                        abyLineUp = abyTemp;
5,541✔
776
                    const GByte *const pabyOutputLineUp =
777
                        abyTemp.empty()
5,541✔
778
                            ? pabyOutputBuffer + (static_cast<size_t>(iY) - 1) *
5,541✔
779
                                                     nSamplesPerLine
×
780
                            : abyLineUp.data();
5,541✔
781

782
                    gdal_png_read_filter_row_avg3_sse2(&row_info, pabyInputLine,
5,541✔
783
                                                       pabyOutputLine,
784
                                                       pabyOutputLineUp);
785
                }
786
                else if (nBands == 4)
5,274✔
787
                {
788
                    png_row_info row_info;
789
                    memset(&row_info, 0, sizeof(row_info));
4,581✔
790
                    row_info.rowbytes = nSamplesPerLine;
4,581✔
791
                    if (!abyTemp.empty())
4,581✔
792
                        abyLineUp = abyTemp;
4,459✔
793
                    const GByte *const pabyOutputLineUp =
794
                        abyTemp.empty()
4,581✔
795
                            ? pabyOutputBuffer + (static_cast<size_t>(iY) - 1) *
4,581✔
796
                                                     nSamplesPerLine
122✔
797
                            : abyLineUp.data();
4,459✔
798

799
                    gdal_png_read_filter_row_avg4_sse2(&row_info, pabyInputLine,
4,581✔
800
                                                       pabyOutputLine,
801
                                                       pabyOutputLineUp);
802
                }
803
                else
804
#endif
805
                    if (abyTemp.empty())
693✔
806
                {
807
                    const GByte *CPL_RESTRICT pabyOutputLineUp =
222✔
808
                        pabyOutputBuffer +
809
                        (static_cast<size_t>(iY) - 1) * nSamplesPerLine;
222✔
810
                    for (int iX = 0; iX < nBands; ++iX)
444✔
811
                    {
812
                        pabyOutputLine[iX] = static_cast<GByte>(
222✔
813
                            pabyInputLine[iX] + pabyOutputLineUp[iX] / 2);
222✔
814
                    }
815
                    for (int iX = nBands; iX < nSamplesPerLine; ++iX)
39,384✔
816
                    {
817
                        pabyOutputLine[iX] = static_cast<GByte>(
39,162✔
818
                            pabyInputLine[iX] + (pabyOutputLine[iX - nBands] +
39,162✔
819
                                                 pabyOutputLineUp[iX]) /
39,162✔
820
                                                    2);
821
                    }
822
                }
823
                else
824
                {
825
                    for (int iX = 0; iX < nBands; ++iX)
1,410✔
826
                    {
827
                        pabyOutputLine[iX] = static_cast<GByte>(
939✔
828
                            pabyInputLine[iX] + pabyOutputLine[iX] / 2);
939✔
829
                    }
830
                    for (int iX = nBands; iX < nSamplesPerLine; ++iX)
15,276✔
831
                    {
832
                        pabyOutputLine[iX] = static_cast<GByte>(
14,805✔
833
                            pabyInputLine[iX] +
14,805✔
834
                            (pabyOutputLine[iX - nBands] + pabyOutputLine[iX]) /
14,805✔
835
                                2);
836
                    }
837
                }
838
            }
839
        }
840
        else if (nFilterType == 4)
135,729✔
841
        {
842
            // Filter type 4: Paeth
843
            if (iY == 0)
135,733✔
844
            {
845
                for (int iX = 0; iX < nSamplesPerLine; ++iX)
158,620✔
846
                {
847
                    GByte a = iX < nBands ? 0 : pabyOutputLine[iX - nBands];
158,464✔
848
                    pabyOutputLine[iX] =
158,464✔
849
                        static_cast<GByte>(pabyInputLine[iX] + a);
158,464✔
850
                }
851
            }
852
            else
853
            {
854
                if (!abyTemp.empty())
135,577✔
855
                    abyLineUp = abyTemp;
94,218✔
856
                const GByte *const pabyOutputLineUp =
857
                    abyTemp.empty()
135,547✔
858
                        ? pabyOutputBuffer +
135,554✔
859
                              (static_cast<size_t>(iY) - 1) * nSamplesPerLine
41,360✔
860
                        : abyLineUp.data();
94,194✔
861
#ifdef HAVE_SSE2
862
                if (nBands == 3)
135,554✔
863
                {
864
                    png_row_info row_info;
865
                    memset(&row_info, 0, sizeof(row_info));
80,952✔
866
                    row_info.rowbytes = nSamplesPerLine;
80,952✔
867
                    gdal_png_read_filter_row_paeth3_sse2(
80,952✔
868
                        &row_info, pabyInputLine, pabyOutputLine,
869
                        pabyOutputLineUp);
870
                }
871
                else if (nBands == 4)
54,602✔
872
                {
873
                    png_row_info row_info;
874
                    memset(&row_info, 0, sizeof(row_info));
48,180✔
875
                    row_info.rowbytes = nSamplesPerLine;
48,180✔
876
                    gdal_png_read_filter_row_paeth4_sse2(
48,180✔
877
                        &row_info, pabyInputLine, pabyOutputLine,
878
                        pabyOutputLineUp);
879
                }
880
                else
881
#endif
882
                {
883
                    int iX = 0;
6,422✔
884
                    for (; iX < nBands; ++iX)
16,387✔
885
                    {
886
                        GByte b = pabyOutputLineUp[iX];
9,965✔
887
                        pabyOutputLine[iX] =
9,965✔
888
                            static_cast<GByte>(pabyInputLine[iX] + b);
9,965✔
889
                    }
890
                    for (; iX < nSamplesPerLine; ++iX)
1,323,560✔
891
                    {
892
                        GByte a = pabyOutputLine[iX - nBands];
1,317,140✔
893
                        GByte b = pabyOutputLineUp[iX];
1,317,140✔
894
                        GByte c = pabyOutputLineUp[iX - nBands];
1,317,140✔
895
                        int p_minus_a = b - c;
1,317,140✔
896
                        int p_minus_b = a - c;
1,317,140✔
897
                        int p_minus_c = p_minus_a + p_minus_b;
1,317,140✔
898
                        int pa = std::abs(p_minus_a);
1,317,140✔
899
                        int pb = std::abs(p_minus_b);
1,317,140✔
900
                        int pc = std::abs(p_minus_c);
1,317,140✔
901
                        if (pa <= pb && pa <= pc)
1,317,140✔
902
                            pabyOutputLine[iX] =
1,147,330✔
903
                                static_cast<GByte>(pabyInputLine[iX] + a);
1,147,330✔
904
                        else if (pb <= pc)
169,801✔
905
                            pabyOutputLine[iX] =
122,576✔
906
                                static_cast<GByte>(pabyInputLine[iX] + b);
122,576✔
907
                        else
908
                            pabyOutputLine[iX] =
47,225✔
909
                                static_cast<GByte>(pabyInputLine[iX] + c);
47,225✔
910
                    }
911
                }
912
            }
913
        }
914
        else
915
        {
UNCOV
916
            CPLError(CE_Failure, CPLE_NotSupported, "Invalid filter type %d",
×
917
                     nFilterType);
918
            CPLFree(pabyZlibDecompressed);
×
919
            return CE_Failure;
×
920
        }
921

922
        if (!abyTemp.empty())
376,562✔
923
        {
924
            if (pSingleBuffer)
286,166✔
925
            {
926
                GByte *pabyDest =
152,241✔
927
                    static_cast<GByte *>(pSingleBuffer) + iY * nLineSpace;
152,241✔
928
                if (bCanUseDeinterleave)
152,241✔
929
                {
930
                    // Cache friendly way for typical band interleaved case.
931
                    void *apDestBuffers[4];
932
                    apDestBuffers[0] = pabyDest;
45,007✔
933
                    apDestBuffers[1] = pabyDest + nBandSpace;
45,007✔
934
                    apDestBuffers[2] = pabyDest + 2 * nBandSpace;
45,007✔
935
                    apDestBuffers[3] = pabyDest + 3 * nBandSpace;
45,007✔
936
                    GDALDeinterleave(pabyOutputLine, GDT_Byte, nBands,
45,007✔
937
                                     apDestBuffers, GDT_Byte, nRasterXSize);
45,007✔
938
                }
939
                else if (nPixelSpace <= nBands && nBandSpace > nBands)
107,234✔
940
                {
941
                    // Cache friendly way for typical band interleaved case.
942
                    for (int iBand = 0; iBand < nBands; iBand++)
105,222✔
943
                    {
944
                        GByte *pabyDest2 = pabyDest + iBand * nBandSpace;
70,148✔
945
                        const GByte *pabyScanline2 = pabyOutputLine + iBand;
70,148✔
946
                        GDALCopyWords(pabyScanline2, GDT_Byte, nBands,
70,148✔
947
                                      pabyDest2, GDT_Byte,
948
                                      static_cast<int>(nPixelSpace),
949
                                      nRasterXSize);
950
                    }
35,074✔
951
                }
952
                else
953
                {
954
                    // Generic method
955
                    for (int x = 0; x < nRasterXSize; ++x)
18,895,300✔
956
                    {
957
                        for (int iBand = 0; iBand < nBands; iBand++)
38,694,900✔
958
                        {
959
                            pabyDest[(x * nPixelSpace) + iBand * nBandSpace] =
19,871,700✔
960
                                pabyOutputLine[x * nBands + iBand];
19,871,700✔
961
                        }
962
                    }
963
                }
964
            }
965
            else
966
            {
967
                GByte *apabyDestBuffers[4];
968
                for (int iBand = 0; iBand < nBands; iBand++)
557,093✔
969
                {
970
                    apabyDestBuffers[iBand] =
423,168✔
971
                        static_cast<GByte *>(apabyBuffers[iBand]) +
423,168✔
972
                        iY * nRasterXSize;
423,168✔
973
                }
974
                if (bCanUseDeinterleave)
133,925✔
975
                {
976
                    // Cache friendly way for typical band interleaved case.
977
                    GDALDeinterleave(
130,526✔
978
                        pabyOutputLine, GDT_Byte, nBands,
979
                        reinterpret_cast<void **>(apabyDestBuffers), GDT_Byte,
980
                        nRasterXSize);
130,526✔
981
                }
982
                else
983
                {
984
                    // Generic method
985
                    for (int x = 0; x < nRasterXSize; ++x)
921,407✔
986
                    {
987
                        for (int iBand = 0; iBand < nBands; iBand++)
2,754,020✔
988
                        {
989
                            apabyDestBuffers[iBand][x] =
1,836,020✔
990
                                pabyOutputLine[x * nBands + iBand];
1,836,020✔
991
                        }
992
                    }
993
                }
994
            }
995
        }
996
    }
997

998
    CPLFree(pabyZlibDecompressed);
3,215✔
999

1000
    return CE_None;
3,215✔
1001
}
1002

1003
#endif  // ENABLE_WHOLE_IMAGE_OPTIMIZATION
1004

1005
/************************************************************************/
1006
/*                             IRasterIO()                              */
1007
/************************************************************************/
1008

1009
CPLErr PNGDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
4,030✔
1010
                             int nXSize, int nYSize, void *pData, int nBufXSize,
1011
                             int nBufYSize, GDALDataType eBufType,
1012
                             int nBandCount, BANDMAP_TYPE panBandMap,
1013
                             GSpacing nPixelSpace, GSpacing nLineSpace,
1014
                             GSpacing nBandSpace,
1015
                             GDALRasterIOExtraArg *psExtraArg)
1016

1017
{
1018
    // Coverity says that we cannot pass a nullptr to IRasterIO.
1019
    if (panBandMap == nullptr)
4,030✔
1020
    {
1021
        return CE_Failure;
×
1022
    }
1023

1024
    if ((eRWFlag == GF_Read) && (nBandCount == nBands) && (nXOff == 0) &&
4,030✔
1025
        (nYOff == 0) && (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
2,702✔
1026
        (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
2,698✔
1027
        (eBufType == GDT_Byte) &&
2,624✔
1028
        (eBufType == GetRasterBand(1)->GetRasterDataType()) &&
2,624✔
1029
        (pData != nullptr) && IsAllBands(nBands, panBandMap))
8,060✔
1030
    {
1031
#ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
1032
        // Below should work without SSE2, but the lack of optimized
1033
        // filters can sometimes make it slower than regular optimized libpng,
1034
        // so restrict to when SSE2 is available.
1035

1036
        if (!bInterlaced && nBitDepth == 8 &&
5,240✔
1037
            CPLTestBool(
2,616✔
1038
                CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")))
1039
        {
1040
            return LoadWholeImage(pData, nPixelSpace, nLineSpace, nBandSpace,
2,608✔
1041
                                  nullptr);
2,608✔
1042
        }
1043
        else if (cpl::down_cast<PNGRasterBand *>(papoBands[0])->nBlockYSize > 1)
16✔
1044
        {
1045
            // Below code requires scanline access in
1046
            // PNGRasterBand::IReadBlock()
1047
        }
1048
        else
1049
#endif  // ENABLE_WHOLE_IMAGE_OPTIMIZATION
1050

1051
            // Pixel interleaved case.
1052
            if (nBandSpace == 1)
16✔
1053
            {
1054
                for (int y = 0; y < nYSize; ++y)
302✔
1055
                {
1056
                    CPLErr tmpError = LoadScanline(y);
300✔
1057
                    if (tmpError != CE_None)
300✔
1058
                        return tmpError;
×
1059
                    const GByte *pabyScanline =
300✔
1060
                        pabyBuffer + (y - nBufferStartLine) * nBands * nXSize;
300✔
1061
                    if (nPixelSpace == nBandSpace * nBandCount)
300✔
1062
                    {
1063
                        memcpy(&(static_cast<GByte *>(pData)[(y * nLineSpace)]),
150✔
1064
                               pabyScanline,
1065
                               cpl::fits_on<int>(nBandCount * nXSize));
150✔
1066
                    }
1067
                    else
1068
                    {
1069
                        for (int x = 0; x < nXSize; ++x)
24,450✔
1070
                        {
1071
                            memcpy(&(static_cast<GByte *>(
24,300✔
1072
                                       pData)[(y * nLineSpace) +
24,300✔
1073
                                              (x * nPixelSpace)]),
24,300✔
1074
                                   &(pabyScanline[x * nBandCount]), nBandCount);
24,300✔
1075
                        }
1076
                    }
1077
                }
1078
                return CE_None;
2✔
1079
            }
1080
            else
1081
            {
1082
                const bool bCanUseDeinterleave =
14✔
1083
                    (nBands == 3 || nBands == 4) && nPixelSpace == 1 &&
19✔
1084
                    nBandSpace ==
1085
                        static_cast<GSpacing>(nRasterXSize) * nRasterYSize;
5✔
1086

1087
                for (int y = 0; y < nYSize; ++y)
966✔
1088
                {
1089
                    CPLErr tmpError = LoadScanline(y);
952✔
1090
                    if (tmpError != CE_None)
952✔
1091
                        return tmpError;
×
1092
                    const GByte *pabyScanline =
952✔
1093
                        pabyBuffer + (y - nBufferStartLine) * nBands * nXSize;
952✔
1094
                    GByte *pabyDest =
952✔
1095
                        static_cast<GByte *>(pData) + y * nLineSpace;
952✔
1096
                    if (bCanUseDeinterleave)
952✔
1097
                    {
1098
                        // Cache friendly way for typical band interleaved case.
1099
                        void *apDestBuffers[4];
1100
                        apDestBuffers[0] = pabyDest;
176✔
1101
                        apDestBuffers[1] = pabyDest + nBandSpace;
176✔
1102
                        apDestBuffers[2] = pabyDest + 2 * nBandSpace;
176✔
1103
                        apDestBuffers[3] = pabyDest + 3 * nBandSpace;
176✔
1104
                        GDALDeinterleave(pabyScanline, GDT_Byte, nBands,
176✔
1105
                                         apDestBuffers, GDT_Byte, nRasterXSize);
176✔
1106
                    }
1107
                    else if (nPixelSpace <= nBands && nBandSpace > nBands)
776✔
1108
                    {
1109
                        // Cache friendly way for typical band interleaved case.
1110
                        for (int iBand = 0; iBand < nBands; iBand++)
39✔
1111
                        {
1112
                            GByte *pabyDest2 = pabyDest + iBand * nBandSpace;
26✔
1113
                            const GByte *pabyScanline2 = pabyScanline + iBand;
26✔
1114
                            GDALCopyWords(pabyScanline2, GDT_Byte, nBands,
26✔
1115
                                          pabyDest2, GDT_Byte,
1116
                                          static_cast<int>(nPixelSpace),
1117
                                          nXSize);
1118
                        }
13✔
1119
                    }
1120
                    else
1121
                    {
1122
                        // Generic method
1123
                        for (int x = 0; x < nXSize; ++x)
124,884✔
1124
                        {
1125
                            for (int iBand = 0; iBand < nBands; iBand++)
248,242✔
1126
                            {
1127
                                pabyDest[(x * nPixelSpace) +
124,121✔
1128
                                         iBand * nBandSpace] =
124,121✔
1129
                                    pabyScanline[x * nBands + iBand];
124,121✔
1130
                            }
1131
                        }
1132
                    }
1133
                }
1134
                return CE_None;
14✔
1135
            }
1136
    }
1137

1138
    return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1,406✔
1139
                                     pData, nBufXSize, nBufYSize, eBufType,
1140
                                     nBandCount, panBandMap, nPixelSpace,
1141
                                     nLineSpace, nBandSpace, psExtraArg);
1,406✔
1142
}
1143

1144
/************************************************************************/
1145
/*                             IRasterIO()                              */
1146
/************************************************************************/
1147

1148
CPLErr PNGRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
5,683✔
1149
                                int nXSize, int nYSize, void *pData,
1150
                                int nBufXSize, int nBufYSize,
1151
                                GDALDataType eBufType, GSpacing nPixelSpace,
1152
                                GSpacing nLineSpace,
1153
                                GDALRasterIOExtraArg *psExtraArg)
1154

1155
{
1156
#ifdef ENABLE_WHOLE_IMAGE_OPTIMIZATION
1157
    auto poGDS = cpl::down_cast<PNGDataset *>(poDS);
5,683✔
1158
    if ((eRWFlag == GF_Read) && (nXOff == 0) && (nYOff == 0) &&
5,683✔
1159
        (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
2,050✔
1160
        (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
2,009✔
1161
        (eBufType == GDT_Byte) && (eBufType == eDataType))
1,643✔
1162
    {
1163
        bool bBlockAlreadyLoaded = false;
1,643✔
1164
        if (nBlockYSize > 1)
1,643✔
1165
        {
1166
            auto poBlock = TryGetLockedBlockRef(0, 0);
1,505✔
1167
            if (poBlock != nullptr)
1,505✔
1168
            {
1169
                bBlockAlreadyLoaded = poBlock->GetDataRef() != pData;
1,033✔
1170
                poBlock->DropLock();
1,033✔
1171
            }
1172
        }
1173

1174
        if (bBlockAlreadyLoaded)
1,643✔
1175
        {
1176
            // will got to general case
1177
        }
1178
        else if (poGDS->nBands == 1 && !poGDS->bInterlaced &&
54✔
1179
                 poGDS->nBitDepth == 8 &&
841✔
1180
                 CPLTestBool(
54✔
1181
                     CPLGetConfigOption("GDAL_PNG_WHOLE_IMAGE_OPTIM", "YES")))
1182
        {
1183
            return poGDS->LoadWholeImage(pData, nPixelSpace, nLineSpace, 0,
48✔
1184
                                         nullptr);
48✔
1185
        }
1186
        else if (nBlockYSize > 1)
685✔
1187
        {
1188
            void *apabyBuffers[4];
1189
            GDALRasterBlock *apoBlocks[4] = {nullptr, nullptr, nullptr,
560✔
1190
                                             nullptr};
1191
            CPLErr eErr = CE_None;
560✔
1192
            bool bNeedToUseDefaultCase = true;
560✔
1193
            for (int i = 0; i < poGDS->nBands; ++i)
2,327✔
1194
            {
1195
                if (i + 1 == nBand && nPixelSpace == 1 &&
1,767✔
1196
                    nLineSpace == nRasterXSize)
560✔
1197
                {
1198
                    bNeedToUseDefaultCase = false;
558✔
1199
                    apabyBuffers[i] = pData;
558✔
1200
                }
1201
                else
1202
                {
1203
                    apoBlocks[i] =
1,209✔
1204
                        poGDS->GetRasterBand(i + 1)->GetLockedBlockRef(0, 0,
1,209✔
1205
                                                                       TRUE);
1,209✔
1206
                    apabyBuffers[i] =
1,209✔
1207
                        apoBlocks[i] ? apoBlocks[i]->GetDataRef() : nullptr;
1,209✔
1208
                    if (apabyBuffers[i] == nullptr)
1,209✔
1209
                        eErr = CE_Failure;
×
1210
                }
1211
            }
1212
            if (eErr == CE_None)
560✔
1213
            {
1214
                eErr = poGDS->LoadWholeImage(nullptr, 0, 0, 0, apabyBuffers);
560✔
1215
            }
1216
            for (int i = 0; i < poGDS->nBands; ++i)
2,327✔
1217
            {
1218
                if (apoBlocks[i])
1,767✔
1219
                    apoBlocks[i]->DropLock();
1,209✔
1220
            }
1221
            if (eErr != CE_None || !bNeedToUseDefaultCase)
560✔
1222
                return eErr;
558✔
1223
        }
1224
    }
1225
#endif
1226
    return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
5,077✔
1227
                                        pData, nBufXSize, nBufYSize, eBufType,
1228
                                        nPixelSpace, nLineSpace, psExtraArg);
5,077✔
1229
}
1230

1231
/************************************************************************/
1232
/*                          GetGeoTransform()                           */
1233
/************************************************************************/
1234

1235
CPLErr PNGDataset::GetGeoTransform(GDALGeoTransform &gt) const
130✔
1236

1237
{
1238
    const_cast<PNGDataset *>(this)->LoadWorldFile();
130✔
1239

1240
    if (bGeoTransformValid)
130✔
1241
    {
1242
        gt = m_gt;
3✔
1243
        return CE_None;
3✔
1244
    }
1245

1246
    return GDALPamDataset::GetGeoTransform(gt);
127✔
1247
}
1248

1249
/************************************************************************/
1250
/*                             FlushCache()                             */
1251
/*                                                                      */
1252
/*      We override this so we can also flush out local TIFF strip      */
1253
/*      cache if need be.                                               */
1254
/************************************************************************/
1255

1256
CPLErr PNGDataset::FlushCache(bool bAtClosing)
8,118✔
1257

1258
{
1259
    const CPLErr eErr = GDALPamDataset::FlushCache(bAtClosing);
8,118✔
1260

1261
    if (pabyBuffer != nullptr)
8,118✔
1262
    {
1263
        CPLFree(pabyBuffer);
230✔
1264
        pabyBuffer = nullptr;
230✔
1265
        nBufferStartLine = 0;
230✔
1266
        nBufferLines = 0;
230✔
1267
    }
1268
    return eErr;
8,118✔
1269
}
1270

1271
#ifdef DISABLE_CRC_CHECK
1272
/************************************************************************/
1273
/*                     PNGDatasetDisableCRCCheck()                      */
1274
/************************************************************************/
1275

1276
static void PNGDatasetDisableCRCCheck(png_structp hPNG)
1277
{
1278
    hPNG->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
1279
    hPNG->flags |= PNG_FLAG_CRC_CRITICAL_IGNORE;
1280

1281
    hPNG->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
1282
    hPNG->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN;
1283
}
1284
#endif
1285

1286
/************************************************************************/
1287
/*                              Restart()                               */
1288
/*                                                                      */
1289
/*      Restart reading from the beginning of the file.                 */
1290
/************************************************************************/
1291

1292
void PNGDataset::Restart()
16✔
1293

1294
{
1295
    png_destroy_read_struct(&hPNG, &psPNGInfo, nullptr);
16✔
1296

1297
    hPNG =
16✔
1298
        png_create_read_struct(PNG_LIBPNG_VER_STRING, this, nullptr, nullptr);
16✔
1299

1300
#ifdef DISABLE_CRC_CHECK
1301
    PNGDatasetDisableCRCCheck(hPNG);
1302
#endif
1303

1304
    png_set_error_fn(hPNG, &sSetJmpContext, png_gdal_error, png_gdal_warning);
16✔
1305
    if (setjmp(sSetJmpContext) != 0)
16✔
1306
        return;
×
1307

1308
    psPNGInfo = png_create_info_struct(hPNG);
16✔
1309

1310
    VSIFSeekL(fpImage, 0, SEEK_SET);
16✔
1311
    png_set_read_fn(hPNG, fpImage, png_vsi_read_data);
16✔
1312
    png_read_info(hPNG, psPNGInfo);
16✔
1313

1314
    if (nBitDepth < 8)
16✔
1315
        png_set_packing(hPNG);
×
1316

1317
    nLastLineRead = -1;
16✔
1318
}
1319

1320
/************************************************************************/
1321
/*                        safe_png_read_image()                         */
1322
/************************************************************************/
1323

1324
static bool safe_png_read_image(png_structp hPNG, png_bytep *png_rows,
13✔
1325
                                jmp_buf sSetJmpContext)
1326
{
1327
    if (setjmp(sSetJmpContext) != 0)
13✔
1328
        return false;
×
1329
    png_read_image(hPNG, png_rows);
13✔
1330
    return true;
13✔
1331
}
1332

1333
/************************************************************************/
1334
/*                        LoadInterlacedChunk()                         */
1335
/************************************************************************/
1336

1337
CPLErr PNGDataset::LoadInterlacedChunk(int iLine)
13✔
1338

1339
{
1340
    const int nPixelOffset =
1341
        (nBitDepth == 16) ? 2 * GetRasterCount() : GetRasterCount();
13✔
1342

1343
    // What is the biggest chunk we can safely operate on?
1344
    constexpr int MAX_PNG_CHUNK_BYTES = 100000000;
13✔
1345

1346
    int nMaxChunkLines =
1347
        std::max(1, MAX_PNG_CHUNK_BYTES / (nPixelOffset * GetRasterXSize()));
13✔
1348

1349
    if (nMaxChunkLines > GetRasterYSize())
13✔
1350
        nMaxChunkLines = GetRasterYSize();
13✔
1351

1352
    // Allocate chunk buffer if we don't already have it from a previous
1353
    // request.
1354
    nBufferLines = nMaxChunkLines;
13✔
1355
    if (nMaxChunkLines + iLine > GetRasterYSize())
13✔
1356
        nBufferStartLine = GetRasterYSize() - nMaxChunkLines;
×
1357
    else
1358
        nBufferStartLine = iLine;
13✔
1359

1360
    if (pabyBuffer == nullptr)
13✔
1361
    {
1362
        pabyBuffer = reinterpret_cast<GByte *>(VSI_MALLOC3_VERBOSE(
13✔
1363
            nPixelOffset, GetRasterXSize(), nMaxChunkLines));
1364

1365
        if (pabyBuffer == nullptr)
13✔
1366
        {
1367
            return CE_Failure;
×
1368
        }
1369
#ifdef notdef
1370
        if (nMaxChunkLines < GetRasterYSize())
1371
            CPLDebug("PNG",
1372
                     "Interlaced file being handled in %d line chunks.\n"
1373
                     "Performance is likely to be quite poor.",
1374
                     nMaxChunkLines);
1375
#endif
1376
    }
1377

1378
    // Do we need to restart reading? We do this if we aren't on the first
1379
    // attempt to read the image.
1380
    if (nLastLineRead != -1)
13✔
1381
    {
1382
        Restart();
×
1383
    }
1384

1385
    // Allocate and populate rows array. We create a row for each row in the
1386
    // image but use our dummy line for rows not in the target window.
1387
    png_bytep dummy_row = reinterpret_cast<png_bytep>(
1388
        CPLMalloc(cpl::fits_on<int>(nPixelOffset * GetRasterXSize())));
13✔
1389
    png_bytep *png_rows = reinterpret_cast<png_bytep *>(
1390
        CPLMalloc(sizeof(png_bytep) * GetRasterYSize()));
13✔
1391

1392
    for (int i = 0; i < GetRasterYSize(); i++)
1,833✔
1393
    {
1394
        if (i >= nBufferStartLine && i < nBufferStartLine + nBufferLines)
1,820✔
1395
            png_rows[i] = pabyBuffer + (i - nBufferStartLine) * nPixelOffset *
3,640✔
1396
                                           GetRasterXSize();
1,820✔
1397
        else
1398
            png_rows[i] = dummy_row;
×
1399
    }
1400

1401
    bool bRet = safe_png_read_image(hPNG, png_rows, sSetJmpContext);
13✔
1402

1403
    // Do swap on LSB machines. 16-bit PNG data is stored in MSB format.
1404
    if (bRet && nBitDepth == 16
13✔
1405
#ifdef CPL_LSB
1406
        && !m_bByteOrderIsLittleEndian
1✔
1407
#else
1408
        && m_bByteOrderIsLittleEndian
1409
#endif
1410
    )
1411
    {
1412
        for (int i = 0; i < GetRasterYSize(); i++)
21✔
1413
        {
1414
            if (i >= nBufferStartLine && i < nBufferStartLine + nBufferLines)
20✔
1415
            {
1416
                GDALSwapWords(png_rows[i], 2,
20✔
1417
                              GetRasterXSize() * GetRasterCount(), 2);
20✔
1418
            }
1419
        }
1420
    }
1421

1422
    CPLFree(png_rows);
13✔
1423
    CPLFree(dummy_row);
13✔
1424
    if (!bRet)
13✔
1425
        return CE_Failure;
×
1426

1427
    nLastLineRead = nBufferStartLine + nBufferLines - 1;
13✔
1428

1429
    return CE_None;
13✔
1430
}
1431

1432
/************************************************************************/
1433
/*                        safe_png_read_rows()                          */
1434
/************************************************************************/
1435

1436
static bool safe_png_read_rows(png_structp hPNG, png_bytep row,
12,208✔
1437
                               jmp_buf sSetJmpContext)
1438
{
1439
    if (setjmp(sSetJmpContext) != 0)
12,208✔
1440
        return false;
2✔
1441
    png_read_rows(hPNG, &row, nullptr, 1);
12,208✔
1442
    return true;
12,206✔
1443
}
1444

1445
/************************************************************************/
1446
/*                            LoadScanline()                            */
1447
/************************************************************************/
1448

1449
CPLErr PNGDataset::LoadScanline(int nLine)
20,195✔
1450

1451
{
1452
    CPLAssert(nLine >= 0 && nLine < GetRasterYSize());
20,195✔
1453

1454
    if (nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines)
20,195✔
1455
        return CE_None;
8,094✔
1456

1457
    const int nPixelOffset =
1458
        (nBitDepth == 16) ? 2 * GetRasterCount() : GetRasterCount();
12,101✔
1459

1460
    // If the file is interlaced, we load the entire image into memory using the
1461
    // high-level API.
1462
    if (bInterlaced)
12,101✔
1463
        return LoadInterlacedChunk(nLine);
13✔
1464

1465
    // Ensure we have space allocated for one scanline.
1466
    if (pabyBuffer == nullptr)
12,088✔
1467
        pabyBuffer = reinterpret_cast<GByte *>(
217✔
1468
            CPLMalloc(cpl::fits_on<int>(nPixelOffset * GetRasterXSize())));
217✔
1469

1470
    // Otherwise we just try to read the requested row. Do we need to rewind and
1471
    // start over?
1472
    if (nLine <= nLastLineRead)
12,088✔
1473
    {
1474
        Restart();
16✔
1475
    }
1476

1477
    // Read till we get the desired row.
1478
    png_bytep row = pabyBuffer;
12,088✔
1479
    const GUInt32 nErrorCounter = CPLGetErrorCounter();
12,088✔
1480
    while (nLine > nLastLineRead)
24,294✔
1481
    {
1482
        if (!safe_png_read_rows(hPNG, row, sSetJmpContext))
12,208✔
1483
        {
1484
            CPLError(CE_Failure, CPLE_AppDefined,
2✔
1485
                     "Error while reading row %d%s", nLine,
1486
                     (nErrorCounter != CPLGetErrorCounter())
2✔
1487
                         ? CPLSPrintf(": %s", CPLGetLastErrorMsg())
2✔
1488
                         : "");
1489
            return CE_Failure;
2✔
1490
        }
1491
        nLastLineRead++;
12,206✔
1492
    }
1493

1494
    nBufferStartLine = nLine;
12,086✔
1495
    nBufferLines = 1;
12,086✔
1496

1497
    // Do swap on LSB machines. 16-bit PNG data is stored in MSB format.
1498
    if (nBitDepth == 16
12,086✔
1499
#ifdef CPL_LSB
1500
        && !m_bByteOrderIsLittleEndian
10,337✔
1501
#else
1502
        && m_bByteOrderIsLittleEndian
1503
#endif
1504
    )
1505
    {
1506
        GDALSwapWords(row, 2, GetRasterXSize() * GetRasterCount(), 2);
10,336✔
1507
    }
1508

1509
    return CE_None;
12,086✔
1510
}
1511

1512
/************************************************************************/
1513
/*                          CollectMetadata()                           */
1514
/*                                                                      */
1515
/*      We normally do this after reading up to the image, but be       */
1516
/*      forewarned: we can miss text chunks this way.                   */
1517
/*                                                                      */
1518
/*      We turn each PNG text chunk into one metadata item.  It         */
1519
/*      might be nice to preserve language information though we        */
1520
/*      don't try to now.                                               */
1521
/************************************************************************/
1522

1523
void PNGDataset::CollectMetadata()
7,741✔
1524

1525
{
1526
    if (nBitDepth < 8)
7,741✔
1527
    {
1528
        for (int iBand = 0; iBand < nBands; iBand++)
16✔
1529
        {
1530
            GetRasterBand(iBand + 1)->SetMetadataItem(
16✔
1531
                "NBITS", CPLString().Printf("%d", nBitDepth),
16✔
1532
                "IMAGE_STRUCTURE");
8✔
1533
        }
1534
    }
1535

1536
    int nTextCount;
1537
    png_textp text_ptr;
1538
    if (png_get_text(hPNG, psPNGInfo, &text_ptr, &nTextCount) == 0)
7,741✔
1539
        return;
7,735✔
1540

1541
    for (int iText = 0; iText < nTextCount; iText++)
14✔
1542
    {
1543
        char *pszTag = CPLStrdup(text_ptr[iText].key);
8✔
1544

1545
        for (int i = 0; pszTag[i] != '\0'; i++)
66✔
1546
        {
1547
            if (pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':')
58✔
1548
                pszTag[i] = '_';
×
1549
        }
1550

1551
        GDALDataset::SetMetadataItem(pszTag, text_ptr[iText].text);
8✔
1552
        CPLFree(pszTag);
8✔
1553
    }
1554
}
1555

1556
/************************************************************************/
1557
/*                       CollectXMPMetadata()                           */
1558
/************************************************************************/
1559

1560
// See §2.1.5 of
1561
// http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf.
1562

1563
void PNGDataset::CollectXMPMetadata()
13✔
1564

1565
{
1566
    if (fpImage == nullptr || bHasReadXMPMetadata)
13✔
1567
        return;
×
1568

1569
    // Save current position to avoid disturbing PNG stream decoding.
1570
    const vsi_l_offset nCurOffset = VSIFTellL(fpImage);
13✔
1571

1572
    vsi_l_offset nOffset = 8;
13✔
1573
    VSIFSeekL(fpImage, nOffset, SEEK_SET);
13✔
1574

1575
    // Loop over chunks.
1576
    while (true)
1577
    {
1578
        int nLength;
1579

1580
        if (VSIFReadL(&nLength, 4, 1, fpImage) != 1)
74✔
1581
            break;
×
1582
        nOffset += 4;
74✔
1583
        CPL_MSBPTR32(&nLength);
74✔
1584
        if (nLength <= 0)
74✔
1585
            break;
12✔
1586

1587
        char pszChunkType[5];
1588
        if (VSIFReadL(pszChunkType, 4, 1, fpImage) != 1)
62✔
1589
            break;
×
1590
        nOffset += 4;
62✔
1591
        pszChunkType[4] = 0;
62✔
1592

1593
        if (strcmp(pszChunkType, "iTXt") == 0 && nLength > 22 &&
62✔
1594
            // Does not make sense to have a XMP content larger than 10 MB
1595
            // (XMP in JPEG must fit in 65 KB...)
1596
            nLength < 10 * 1024 * 1024)
1✔
1597
        {
1598
            char *pszContent = reinterpret_cast<char *>(VSIMalloc(nLength + 1));
1✔
1599
            if (pszContent == nullptr)
1✔
1600
                break;
×
1601
            if (VSIFReadL(pszContent, nLength, 1, fpImage) != 1)
1✔
1602
            {
1603
                VSIFree(pszContent);
×
1604
                break;
×
1605
            }
1606
            nOffset += nLength;
1✔
1607
            pszContent[nLength] = '\0';
1✔
1608
            if (memcmp(pszContent, "XML:com.adobe.xmp\0\0\0\0\0", 22) == 0)
1✔
1609
            {
1610
                // Avoid setting the PAM dirty bit just for that.
1611
                const int nOldPamFlags = nPamFlags;
1✔
1612

1613
                char *apszMDList[2] = {pszContent + 22, nullptr};
1✔
1614
                SetMetadata(apszMDList, "xml:XMP");
1✔
1615

1616
                // cppcheck-suppress redundantAssignment
1617
                nPamFlags = nOldPamFlags;
1✔
1618

1619
                VSIFree(pszContent);
1✔
1620

1621
                break;
1✔
1622
            }
1623
            else
1624
            {
1625
                VSIFree(pszContent);
×
1626
            }
×
1627
        }
1628
        else
1629
        {
1630
            nOffset += nLength;
61✔
1631
            VSIFSeekL(fpImage, nOffset, SEEK_SET);
61✔
1632
        }
1633

1634
        nOffset += 4;
61✔
1635
        int nCRC;
1636
        if (VSIFReadL(&nCRC, 4, 1, fpImage) != 1)
61✔
1637
            break;
×
1638
    }
61✔
1639

1640
    VSIFSeekL(fpImage, nCurOffset, SEEK_SET);
13✔
1641

1642
    bHasReadXMPMetadata = TRUE;
13✔
1643
}
1644

1645
/************************************************************************/
1646
/*                           LoadICCProfile()                           */
1647
/************************************************************************/
1648

1649
void PNGDataset::LoadICCProfile()
24✔
1650
{
1651
    if (hPNG == nullptr || bHasReadICCMetadata)
24✔
1652
        return;
7✔
1653
    bHasReadICCMetadata = TRUE;
24✔
1654

1655
    png_charp pszProfileName;
1656
    png_uint_32 nProfileLength;
1657
    png_bytep pProfileData;
1658
    int nCompressionType;
1659

1660
    // Avoid setting the PAM dirty bit just for that.
1661
    int nOldPamFlags = nPamFlags;
24✔
1662

1663
    if (png_get_iCCP(hPNG, psPNGInfo, &pszProfileName, &nCompressionType,
24✔
1664
                     &pProfileData, &nProfileLength) != 0)
24✔
1665
    {
1666
        // Escape the profile.
1667
        char *pszBase64Profile =
1668
            CPLBase64Encode(static_cast<int>(nProfileLength),
5✔
1669
                            reinterpret_cast<const GByte *>(pProfileData));
1670

1671
        // Set ICC profile metadata.
1672
        SetMetadataItem("SOURCE_ICC_PROFILE", pszBase64Profile,
5✔
1673
                        "COLOR_PROFILE");
5✔
1674
        SetMetadataItem("SOURCE_ICC_PROFILE_NAME", pszProfileName,
5✔
1675
                        "COLOR_PROFILE");
5✔
1676

1677
        nPamFlags = nOldPamFlags;
5✔
1678

1679
        CPLFree(pszBase64Profile);
5✔
1680

1681
        return;
5✔
1682
    }
1683

1684
    int nsRGBIntent;
1685
    if (png_get_sRGB(hPNG, psPNGInfo, &nsRGBIntent) != 0)
19✔
1686
    {
1687
        SetMetadataItem("SOURCE_ICC_PROFILE_NAME", "sRGB", "COLOR_PROFILE");
2✔
1688

1689
        nPamFlags = nOldPamFlags;
2✔
1690

1691
        return;
2✔
1692
    }
1693

1694
    double dfGamma;
1695
    bool bGammaAvailable = false;
17✔
1696
    if (png_get_valid(hPNG, psPNGInfo, PNG_INFO_gAMA))
17✔
1697
    {
1698
        bGammaAvailable = true;
6✔
1699

1700
        png_get_gAMA(hPNG, psPNGInfo, &dfGamma);
6✔
1701

1702
        SetMetadataItem("PNG_GAMMA", CPLString().Printf("%.9f", dfGamma),
6✔
1703
                        "COLOR_PROFILE");
6✔
1704
    }
1705

1706
    // Check that both cHRM and gAMA are available.
1707
    if (bGammaAvailable && png_get_valid(hPNG, psPNGInfo, PNG_INFO_cHRM))
17✔
1708
    {
1709
        double dfaWhitepoint[2];
1710
        double dfaCHR[6];
1711

1712
        png_get_cHRM(hPNG, psPNGInfo, &dfaWhitepoint[0], &dfaWhitepoint[1],
4✔
1713
                     &dfaCHR[0], &dfaCHR[1], &dfaCHR[2], &dfaCHR[3], &dfaCHR[4],
1714
                     &dfaCHR[5]);
1715

1716
        // Set all the colorimetric metadata.
1717
        SetMetadataItem(
4✔
1718
            "SOURCE_PRIMARIES_RED",
1719
            CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[0], dfaCHR[1]),
8✔
1720
            "COLOR_PROFILE");
4✔
1721
        SetMetadataItem(
4✔
1722
            "SOURCE_PRIMARIES_GREEN",
1723
            CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[2], dfaCHR[3]),
8✔
1724
            "COLOR_PROFILE");
4✔
1725
        SetMetadataItem(
4✔
1726
            "SOURCE_PRIMARIES_BLUE",
1727
            CPLString().Printf("%.9f, %.9f, 1.0", dfaCHR[4], dfaCHR[5]),
8✔
1728
            "COLOR_PROFILE");
4✔
1729

1730
        SetMetadataItem("SOURCE_WHITEPOINT",
4✔
1731
                        CPLString().Printf("%.9f, %.9f, 1.0", dfaWhitepoint[0],
4✔
1732
                                           dfaWhitepoint[1]),
4✔
1733
                        "COLOR_PROFILE");
4✔
1734
    }
1735

1736
    nPamFlags = nOldPamFlags;
17✔
1737
}
1738

1739
/************************************************************************/
1740
/*                      GetMetadataDomainList()                         */
1741
/************************************************************************/
1742

1743
char **PNGDataset::GetMetadataDomainList()
3✔
1744
{
1745
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3✔
1746
                                   TRUE, "xml:XMP", "COLOR_PROFILE", nullptr);
3✔
1747
}
1748

1749
/************************************************************************/
1750
/*                           GetMetadata()                              */
1751
/************************************************************************/
1752

1753
char **PNGDataset::GetMetadata(const char *pszDomain)
109✔
1754
{
1755
    if (fpImage == nullptr)
109✔
1756
        return nullptr;
×
1757
    if (eAccess == GA_ReadOnly && !bHasReadXMPMetadata &&
109✔
1758
        pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP"))
79✔
1759
        CollectXMPMetadata();
13✔
1760
    if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
109✔
1761
        pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
49✔
1762
        LoadICCProfile();
13✔
1763
    return GDALPamDataset::GetMetadata(pszDomain);
109✔
1764
}
1765

1766
/************************************************************************/
1767
/*                       GetMetadataItem()                              */
1768
/************************************************************************/
1769
const char *PNGDataset::GetMetadataItem(const char *pszName,
708✔
1770
                                        const char *pszDomain)
1771
{
1772
    if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
708✔
1773
        pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
184✔
1774
        LoadICCProfile();
11✔
1775
    return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
708✔
1776
}
1777

1778
/************************************************************************/
1779
/*                                Open()                                */
1780
/************************************************************************/
1781

1782
GDALDataset *PNGDataset::Open(GDALOpenInfo *poOpenInfo)
7,742✔
1783

1784
{
1785
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1786
    // During fuzzing, do not use Identify to reject crazy content.
1787
    if (!PNGDriverIdentify(poOpenInfo))
7,742✔
1788
        return nullptr;
1✔
1789
#else
1790
    if (poOpenInfo->fpL == nullptr)
1791
        return nullptr;
1792
#endif
1793

1794
    if (poOpenInfo->eAccess == GA_Update)
7,742✔
1795
    {
1796
        ReportUpdateNotSupportedByDriver("PNG");
×
1797
        return nullptr;
×
1798
    }
1799

1800
    // Create a corresponding GDALDataset.
1801
    PNGDataset *poDS = new PNGDataset();
7,742✔
1802
    return OpenStage2(poOpenInfo, poDS);
7,742✔
1803
}
1804

1805
GDALDataset *PNGDataset::OpenStage2(GDALOpenInfo *poOpenInfo, PNGDataset *&poDS)
7,742✔
1806

1807
{
1808
    poDS->fpImage = poOpenInfo->fpL;
7,742✔
1809
    poOpenInfo->fpL = nullptr;
7,742✔
1810
    poDS->eAccess = poOpenInfo->eAccess;
7,742✔
1811

1812
    poDS->hPNG =
15,484✔
1813
        png_create_read_struct(PNG_LIBPNG_VER_STRING, poDS, nullptr, nullptr);
7,742✔
1814
    if (poDS->hPNG == nullptr)
7,742✔
1815
    {
1816
        int version = static_cast<int>(png_access_version_number());
×
1817
        CPLError(CE_Failure, CPLE_NotSupported,
×
1818
                 "The PNG driver failed to access libpng with version '%s',"
1819
                 " library is actually version '%d'.\n",
1820
                 PNG_LIBPNG_VER_STRING, version);
1821
        delete poDS;
×
1822
        return nullptr;
×
1823
    }
1824

1825
#ifdef DISABLE_CRC_CHECK
1826
    PNGDatasetDisableCRCCheck(poDS->hPNG);
1827
#endif
1828

1829
    poDS->psPNGInfo = png_create_info_struct(poDS->hPNG);
7,742✔
1830

1831
    // Set up error handling.
1832
    png_set_error_fn(poDS->hPNG, &poDS->sSetJmpContext, png_gdal_error,
7,742✔
1833
                     png_gdal_warning);
1834

1835
    if (setjmp(poDS->sSetJmpContext) != 0)
7,742✔
1836
    {
1837
        delete poDS;
1✔
1838
        return nullptr;
1✔
1839
    }
1840

1841
    // Read pre-image data after ensuring the file is rewound.
1842
    // We should likely do a setjmp() here.
1843

1844
    png_set_read_fn(poDS->hPNG, poDS->fpImage, png_vsi_read_data);
7,742✔
1845
    png_read_info(poDS->hPNG, poDS->psPNGInfo);
7,742✔
1846

1847
    // Capture some information from the file that is of interest.
1848
    poDS->nRasterXSize =
7,740✔
1849
        static_cast<int>(png_get_image_width(poDS->hPNG, poDS->psPNGInfo));
7,740✔
1850
    poDS->nRasterYSize =
7,739✔
1851
        static_cast<int>(png_get_image_height(poDS->hPNG, poDS->psPNGInfo));
7,740✔
1852

1853
    poDS->nBands = png_get_channels(poDS->hPNG, poDS->psPNGInfo);
7,739✔
1854
    poDS->nBitDepth = png_get_bit_depth(poDS->hPNG, poDS->psPNGInfo);
7,740✔
1855
    poDS->bInterlaced = png_get_interlace_type(poDS->hPNG, poDS->psPNGInfo) !=
7,740✔
1856
                        PNG_INTERLACE_NONE;
1857

1858
    poDS->nColorType = png_get_color_type(poDS->hPNG, poDS->psPNGInfo);
7,739✔
1859

1860
    if (poDS->nColorType == PNG_COLOR_TYPE_PALETTE && poDS->nBands > 1)
7,739✔
1861
    {
1862
        CPLDebug("GDAL",
×
1863
                 "PNG Driver got %d from png_get_channels(),\n"
1864
                 "but this kind of image (paletted) can only have one band.\n"
1865
                 "Correcting and continuing, but this may indicate a bug!",
1866
                 poDS->nBands);
1867
        poDS->nBands = 1;
×
1868
    }
1869

1870
    // We want to treat 1-, 2-, and 4-bit images as eight bit. This call causes
1871
    // libpng to unpack the image.
1872
    if (poDS->nBitDepth < 8)
7,739✔
1873
        png_set_packing(poDS->hPNG);
8✔
1874

1875
    // Create band information objects.
1876
    for (int iBand = 0; iBand < poDS->nBands; iBand++)
29,176✔
1877
        poDS->SetBand(iBand + 1, new PNGRasterBand(poDS, iBand + 1));
21,435✔
1878

1879
    // Is there a palette?  Note: we should also read back and apply
1880
    // transparency values if available.
1881
    if (poDS->nColorType == PNG_COLOR_TYPE_PALETTE)
7,741✔
1882
    {
1883
        png_color *pasPNGPalette = nullptr;
383✔
1884
        int nColorCount = 0;
383✔
1885

1886
        if (png_get_PLTE(poDS->hPNG, poDS->psPNGInfo, &pasPNGPalette,
383✔
1887
                         &nColorCount) == 0)
383✔
1888
            nColorCount = 0;
×
1889

1890
        unsigned char *trans = nullptr;
383✔
1891
        png_color_16 *trans_values = nullptr;
383✔
1892
        int num_trans = 0;
383✔
1893
        png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
383✔
1894
                     &trans_values);
1895

1896
        poDS->poColorTable = new GDALColorTable();
383✔
1897

1898
        GDALColorEntry oEntry;
1899
        int nNoDataIndex = -1;
383✔
1900
        for (int iColor = nColorCount - 1; iColor >= 0; iColor--)
26,009✔
1901
        {
1902
            oEntry.c1 = pasPNGPalette[iColor].red;
25,626✔
1903
            oEntry.c2 = pasPNGPalette[iColor].green;
25,626✔
1904
            oEntry.c3 = pasPNGPalette[iColor].blue;
25,626✔
1905

1906
            if (iColor < num_trans)
25,626✔
1907
            {
1908
                oEntry.c4 = trans[iColor];
3,441✔
1909
                if (oEntry.c4 == 0)
3,441✔
1910
                {
1911
                    if (nNoDataIndex == -1)
266✔
1912
                        nNoDataIndex = iColor;
266✔
1913
                    else
1914
                        nNoDataIndex = -2;
×
1915
                }
1916
            }
1917
            else
1918
                oEntry.c4 = 255;
22,185✔
1919

1920
            poDS->poColorTable->SetColorEntry(iColor, &oEntry);
25,626✔
1921
        }
1922

1923
        // Special hack to use an index as the no data value, as long as it is
1924
        // the only transparent color in the palette.
1925
        if (nNoDataIndex > -1)
383✔
1926
        {
1927
            poDS->GetRasterBand(1)->SetNoDataValue(nNoDataIndex);
266✔
1928
        }
1929
    }
1930

1931
    // Check for transparency values in greyscale images.
1932
    if (poDS->nColorType == PNG_COLOR_TYPE_GRAY)
7,741✔
1933
    {
1934
        png_color_16 *trans_values = nullptr;
402✔
1935
        unsigned char *trans;
1936
        int num_trans;
1937

1938
        if (png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
402✔
1939
                         &trans_values) != 0 &&
407✔
1940
            trans_values != nullptr)
5✔
1941
        {
1942
            poDS->GetRasterBand(1)->SetNoDataValue(trans_values->gray);
5✔
1943
        }
1944
    }
1945

1946
    // Check for nodata color for RGB images.
1947
    if (poDS->nColorType == PNG_COLOR_TYPE_RGB)
7,741✔
1948
    {
1949
        png_color_16 *trans_values = nullptr;
5,468✔
1950
        unsigned char *trans;
1951
        int num_trans;
1952

1953
        if (png_get_tRNS(poDS->hPNG, poDS->psPNGInfo, &trans, &num_trans,
5,468✔
1954
                         &trans_values) != 0 &&
5,472✔
1955
            trans_values != nullptr)
4✔
1956
        {
1957
            CPLString oNDValue;
8✔
1958

1959
            oNDValue.Printf("%d %d %d", trans_values->red, trans_values->green,
4✔
1960
                            trans_values->blue);
4✔
1961
            poDS->SetMetadataItem("NODATA_VALUES", oNDValue.c_str());
4✔
1962

1963
            poDS->GetRasterBand(1)->SetNoDataValue(trans_values->red);
4✔
1964
            poDS->GetRasterBand(2)->SetNoDataValue(trans_values->green);
4✔
1965
            poDS->GetRasterBand(3)->SetNoDataValue(trans_values->blue);
4✔
1966
        }
1967
    }
1968

1969
    // Extract any text chunks as "metadata."
1970
    poDS->CollectMetadata();
7,741✔
1971

1972
    // More metadata.
1973
    if (poDS->nBands > 1)
7,741✔
1974
    {
1975
        poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
6,956✔
1976
    }
1977

1978
    // Initialize any PAM information.
1979
    poDS->SetDescription(poOpenInfo->pszFilename);
7,739✔
1980
    poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
7,739✔
1981

1982
    // Open overviews.
1983
    poDS->oOvManager.Initialize(poDS, poOpenInfo);
7,741✔
1984

1985
    // Used by JPEG FLIR
1986
    poDS->m_bByteOrderIsLittleEndian = CPLTestBool(CSLFetchNameValueDef(
7,739✔
1987
        poOpenInfo->papszOpenOptions, "BYTE_ORDER_LITTLE_ENDIAN", "NO"));
7,738✔
1988

1989
    return poDS;
7,741✔
1990
}
1991

1992
/************************************************************************/
1993
/*                        LoadWorldFile()                               */
1994
/************************************************************************/
1995

1996
void PNGDataset::LoadWorldFile()
175✔
1997
{
1998
    if (bHasTriedLoadWorldFile)
175✔
1999
        return;
5✔
2000
    bHasTriedLoadWorldFile = TRUE;
170✔
2001

2002
    char *pszWldFilename = nullptr;
170✔
2003
    bGeoTransformValid =
170✔
2004
        GDALReadWorldFile2(GetDescription(), nullptr, m_gt,
170✔
2005
                           oOvManager.GetSiblingFiles(), &pszWldFilename);
170✔
2006

2007
    if (!bGeoTransformValid)
170✔
2008
        bGeoTransformValid =
170✔
2009
            GDALReadWorldFile2(GetDescription(), ".wld", m_gt,
170✔
2010
                               oOvManager.GetSiblingFiles(), &pszWldFilename);
170✔
2011

2012
    if (pszWldFilename)
170✔
2013
    {
2014
        osWldFilename = pszWldFilename;
3✔
2015
        CPLFree(pszWldFilename);
3✔
2016
    }
2017
}
2018

2019
/************************************************************************/
2020
/*                            GetFileList()                             */
2021
/************************************************************************/
2022

2023
char **PNGDataset::GetFileList()
45✔
2024

2025
{
2026
    char **papszFileList = GDALPamDataset::GetFileList();
45✔
2027

2028
    LoadWorldFile();
45✔
2029

2030
    if (!osWldFilename.empty() &&
46✔
2031
        CSLFindString(papszFileList, osWldFilename) == -1)
1✔
2032
    {
2033
        papszFileList = CSLAddString(papszFileList, osWldFilename);
1✔
2034
    }
2035

2036
    return papszFileList;
45✔
2037
}
2038

2039
/************************************************************************/
2040
/*                          WriteMetadataAsText()                       */
2041
/************************************************************************/
2042

2043
static bool IsASCII(const char *pszStr)
3✔
2044
{
2045
    for (int i = 0; pszStr[i] != '\0'; i++)
28✔
2046
    {
2047
        if (reinterpret_cast<GByte *>(const_cast<char *>(pszStr))[i] >= 128)
25✔
2048
            return false;
×
2049
    }
2050
    return true;
3✔
2051
}
2052

2053
static bool safe_png_set_text(jmp_buf sSetJmpContext, png_structp png_ptr,
3✔
2054
                              png_infop info_ptr, png_const_textp text_ptr,
2055
                              int num_text)
2056
{
2057
    if (setjmp(sSetJmpContext) != 0)
3✔
2058
    {
2059
        return false;
×
2060
    }
2061
    png_set_text(png_ptr, info_ptr, text_ptr, num_text);
3✔
2062
    return true;
3✔
2063
}
2064

2065
void PNGDataset::WriteMetadataAsText(jmp_buf sSetJmpContext, png_structp hPNG,
3✔
2066
                                     png_infop psPNGInfo, const char *pszKey,
2067
                                     const char *pszValue)
2068
{
2069
    png_text sText;
2070
    memset(&sText, 0, sizeof(png_text));
3✔
2071
    sText.compression = PNG_TEXT_COMPRESSION_NONE;
3✔
2072
    sText.key = const_cast<png_charp>(pszKey);
3✔
2073
    sText.text = const_cast<png_charp>(pszValue);
3✔
2074

2075
    // UTF-8 values should be written in iTXt, whereas TEXT should be LATIN-1.
2076
    if (!IsASCII(pszValue) && CPLIsUTF8(pszValue, -1))
3✔
2077
        sText.compression = PNG_ITXT_COMPRESSION_NONE;
×
2078

2079
    safe_png_set_text(sSetJmpContext, hPNG, psPNGInfo, &sText, 1);
3✔
2080
}
3✔
2081

2082
static bool safe_png_set_IHDR(jmp_buf sSetJmpContext, png_structp png_ptr,
4,554✔
2083
                              png_infop info_ptr, png_uint_32 width,
2084
                              png_uint_32 height, int bit_depth, int color_type,
2085
                              int interlace_type, int compression_type,
2086
                              int filter_type)
2087
{
2088
    if (setjmp(sSetJmpContext) != 0)
4,554✔
2089
    {
2090
        return false;
×
2091
    }
2092
    png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
4,554✔
2093
                 interlace_type, compression_type, filter_type);
2094
    return true;
4,554✔
2095
}
2096

2097
static bool safe_png_set_compression_level(jmp_buf sSetJmpContext,
3,986✔
2098
                                           png_structp png_ptr, int level)
2099
{
2100
    if (setjmp(sSetJmpContext) != 0)
3,986✔
2101
    {
2102
        return false;
×
2103
    }
2104
    png_set_compression_level(png_ptr, level);
3,986✔
2105
    return true;
3,986✔
2106
}
2107

2108
static bool safe_png_set_tRNS(jmp_buf sSetJmpContext, png_structp png_ptr,
8✔
2109
                              png_infop info_ptr, png_const_bytep trans,
2110
                              int num_trans, png_color_16p trans_values)
2111
{
2112
    if (setjmp(sSetJmpContext) != 0)
8✔
2113
    {
2114
        return false;
×
2115
    }
2116
    png_set_tRNS(png_ptr, info_ptr, trans, num_trans, trans_values);
8✔
2117
    return true;
8✔
2118
}
2119

2120
static bool safe_png_set_iCCP(jmp_buf sSetJmpContext, png_structp png_ptr,
2✔
2121
                              png_infop info_ptr, png_const_charp name,
2122
                              int compression_type, png_const_bytep profile,
2123
                              png_uint_32 proflen)
2124
{
2125
    if (setjmp(sSetJmpContext) != 0)
2✔
2126
    {
2127
        return false;
×
2128
    }
2129
    png_set_iCCP(png_ptr, info_ptr, name, compression_type, profile, proflen);
2✔
2130
    return true;
2✔
2131
}
2132

2133
static bool safe_png_set_PLTE(jmp_buf sSetJmpContext, png_structp png_ptr,
33✔
2134
                              png_infop info_ptr, png_const_colorp palette,
2135
                              int num_palette)
2136
{
2137
    if (setjmp(sSetJmpContext) != 0)
33✔
2138
    {
2139
        return false;
×
2140
    }
2141
    png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
33✔
2142
    return true;
33✔
2143
}
2144

2145
static bool safe_png_write_info(jmp_buf sSetJmpContext, png_structp png_ptr,
4,554✔
2146
                                png_infop info_ptr)
2147
{
2148
    if (setjmp(sSetJmpContext) != 0)
4,554✔
2149
    {
2150
        return false;
5✔
2151
    }
2152
    png_write_info(png_ptr, info_ptr);
4,554✔
2153
    return true;
4,549✔
2154
}
2155

2156
static bool safe_png_write_rows(jmp_buf sSetJmpContext, png_structp png_ptr,
225,051✔
2157
                                png_bytepp row, png_uint_32 num_rows)
2158
{
2159
    if (setjmp(sSetJmpContext) != 0)
225,051✔
2160
    {
2161
        return false;
4✔
2162
    }
2163
    png_write_rows(png_ptr, row, num_rows);
225,052✔
2164
    return true;
225,049✔
2165
}
2166

2167
static bool safe_png_write_end(jmp_buf sSetJmpContext, png_structp png_ptr,
4,549✔
2168
                               png_infop info_ptr)
2169
{
2170
    if (setjmp(sSetJmpContext) != 0)
4,549✔
2171
    {
2172
        return false;
7✔
2173
    }
2174
    png_write_end(png_ptr, info_ptr);
4,549✔
2175
    return true;
4,542✔
2176
}
2177

2178
/************************************************************************/
2179
/*                             CreateCopy()                             */
2180
/************************************************************************/
2181

2182
GDALDataset *PNGDataset::CreateCopy(const char *pszFilename,
4,568✔
2183
                                    GDALDataset *poSrcDS, int bStrict,
2184
                                    char **papszOptions,
2185
                                    GDALProgressFunc pfnProgress,
2186
                                    void *pProgressData)
2187

2188
{
2189
    // Perform some rudimentary checks.
2190
    const int nBands = poSrcDS->GetRasterCount();
4,568✔
2191
    if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
4,568✔
2192
    {
2193
        CPLError(CE_Failure, CPLE_NotSupported,
2✔
2194
                 "PNG driver doesn't support %d bands.  Must be 1 (grey),\n"
2195
                 "2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n",
2196
                 nBands);
2197

2198
        return nullptr;
2✔
2199
    }
2200

2201
    if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte &&
4,633✔
2202
        poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16)
67✔
2203
    {
2204
        CPLError(
9✔
2205
            (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
2206
            "PNG driver doesn't support data type %s. "
2207
            "Only eight bit (Byte) and sixteen bit (UInt16) bands supported. "
2208
            "%s\n",
2209
            GDALGetDataTypeName(poSrcDS->GetRasterBand(1)->GetRasterDataType()),
2210
            (bStrict) ? "" : "Defaulting to Byte");
2211

2212
        if (bStrict)
9✔
2213
            return nullptr;
9✔
2214
    }
2215

2216
    // Create the dataset.
2217
    VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
4,557✔
2218
    if (fpImage == nullptr)
4,557✔
2219
    {
2220
        CPLError(CE_Failure, CPLE_OpenFailed,
3✔
2221
                 "Unable to create png file %s: %s\n", pszFilename,
2222
                 VSIStrerror(errno));
3✔
2223
        return nullptr;
3✔
2224
    }
2225

2226
    // Initialize PNG access to the file.
2227
    jmp_buf sSetJmpContext;
2228

2229
    png_structp hPNG =
2230
        png_create_write_struct(PNG_LIBPNG_VER_STRING, &sSetJmpContext,
4,554✔
2231
                                png_gdal_error, png_gdal_warning);
4,554✔
2232
    png_infop psPNGInfo = png_create_info_struct(hPNG);
4,554✔
2233

2234
    // Set up some parameters.
2235
    int nColorType = 0;
4,554✔
2236

2237
    if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr)
4,554✔
2238
        nColorType = PNG_COLOR_TYPE_GRAY;
150✔
2239
    else if (nBands == 1)
4,404✔
2240
        nColorType = PNG_COLOR_TYPE_PALETTE;
33✔
2241
    else if (nBands == 2)
4,371✔
2242
        nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
267✔
2243
    else if (nBands == 3)
4,104✔
2244
        nColorType = PNG_COLOR_TYPE_RGB;
3,862✔
2245
    else if (nBands == 4)
242✔
2246
        nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
242✔
2247

2248
    int nBitDepth;
2249
    GDALDataType eType;
2250
    if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16)
4,554✔
2251
    {
2252
        eType = GDT_Byte;
4,496✔
2253
        nBitDepth = 8;
4,496✔
2254
        if (nBands == 1)
4,496✔
2255
        {
2256
            const char *pszNbits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
128✔
2257
                "NBITS", "IMAGE_STRUCTURE");
128✔
2258
            if (pszNbits != nullptr)
128✔
2259
            {
2260
                nBitDepth = atoi(pszNbits);
3✔
2261
                if (!(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4))
3✔
2262
                    nBitDepth = 8;
×
2263
            }
2264
        }
2265
    }
2266
    else
2267
    {
2268
        eType = GDT_UInt16;
58✔
2269
        nBitDepth = 16;
58✔
2270
    }
2271

2272
    const char *pszNbits = CSLFetchNameValue(papszOptions, "NBITS");
4,554✔
2273
    if (eType == GDT_Byte && pszNbits != nullptr)
4,554✔
2274
    {
2275
        nBitDepth = atoi(pszNbits);
13✔
2276
        if (!(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4 ||
13✔
2277
              nBitDepth == 8))
2278
        {
2279
            CPLError(CE_Warning, CPLE_NotSupported,
1✔
2280
                     "Invalid bit depth. Using 8");
2281
            nBitDepth = 8;
1✔
2282
        }
2283
    }
2284

2285
    png_set_write_fn(hPNG, fpImage, png_vsi_write_data, png_vsi_flush);
4,554✔
2286

2287
    const int nXSize = poSrcDS->GetRasterXSize();
4,554✔
2288
    const int nYSize = poSrcDS->GetRasterYSize();
4,554✔
2289

2290
    if (!safe_png_set_IHDR(sSetJmpContext, hPNG, psPNGInfo, nXSize, nYSize,
4,554✔
2291
                           nBitDepth, nColorType, PNG_INTERLACE_NONE,
2292
                           PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE))
2293
    {
2294
        VSIFCloseL(fpImage);
×
2295
        png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2296
        return nullptr;
×
2297
    }
2298

2299
    // Do we want to control the compression level?
2300
    const char *pszLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
4,554✔
2301

2302
    if (pszLevel)
4,554✔
2303
    {
2304
        const int nLevel = atoi(pszLevel);
3,986✔
2305
        if (nLevel < 1 || nLevel > 9)
3,986✔
2306
        {
2307
            CPLError(CE_Failure, CPLE_AppDefined,
×
2308
                     "Illegal ZLEVEL value '%s', should be 1-9.", pszLevel);
2309
            VSIFCloseL(fpImage);
×
2310
            png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2311
            return nullptr;
×
2312
        }
2313

2314
        if (!safe_png_set_compression_level(sSetJmpContext, hPNG, nLevel))
3,986✔
2315
        {
2316
            VSIFCloseL(fpImage);
×
2317
            png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2318
            return nullptr;
×
2319
        }
2320
    }
2321

2322
    // Try to handle nodata values as a tRNS block (note that for paletted
2323
    // images, we save the effect to apply as part of palette).
2324
    png_color_16 sTRNSColor;
2325

2326
    // Gray nodata.
2327
    if (nColorType == PNG_COLOR_TYPE_GRAY)
4,554✔
2328
    {
2329
        int bHaveNoData = FALSE;
150✔
2330
        const double dfNoDataValue =
2331
            poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
150✔
2332

2333
        if (bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536)
150✔
2334
        {
2335
            sTRNSColor.gray = static_cast<png_uint_16>(dfNoDataValue);
3✔
2336
            if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr, 0,
3✔
2337
                                   &sTRNSColor))
2338
            {
2339
                VSIFCloseL(fpImage);
×
2340
                png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2341
                return nullptr;
×
2342
            }
2343
        }
2344
    }
2345

2346
    // RGB nodata.
2347
    if (nColorType == PNG_COLOR_TYPE_RGB)
4,554✔
2348
    {
2349
        // First try to use the NODATA_VALUES metadata item.
2350
        if (poSrcDS->GetMetadataItem("NODATA_VALUES") != nullptr)
3,862✔
2351
        {
2352
            char **papszValues =
2353
                CSLTokenizeString(poSrcDS->GetMetadataItem("NODATA_VALUES"));
1✔
2354

2355
            if (CSLCount(papszValues) >= 3)
1✔
2356
            {
2357
                sTRNSColor.red = static_cast<png_uint_16>(atoi(papszValues[0]));
1✔
2358
                sTRNSColor.green =
1✔
2359
                    static_cast<png_uint_16>(atoi(papszValues[1]));
1✔
2360
                sTRNSColor.blue =
1✔
2361
                    static_cast<png_uint_16>(atoi(papszValues[2]));
1✔
2362
                if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr,
1✔
2363
                                       0, &sTRNSColor))
2364
                {
2365
                    VSIFCloseL(fpImage);
×
2366
                    png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2367
                    CSLDestroy(papszValues);
×
2368
                    return nullptr;
×
2369
                }
2370
            }
2371

2372
            CSLDestroy(papszValues);
1✔
2373
        }
2374
        // Otherwise, get the nodata value from the bands.
2375
        else
2376
        {
2377
            int bHaveNoDataRed = FALSE;
3,861✔
2378
            const double dfNoDataValueRed =
2379
                poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoDataRed);
3,861✔
2380

2381
            int bHaveNoDataGreen = FALSE;
3,861✔
2382
            const double dfNoDataValueGreen =
2383
                poSrcDS->GetRasterBand(2)->GetNoDataValue(&bHaveNoDataGreen);
3,861✔
2384

2385
            int bHaveNoDataBlue = FALSE;
3,861✔
2386
            const double dfNoDataValueBlue =
2387
                poSrcDS->GetRasterBand(3)->GetNoDataValue(&bHaveNoDataBlue);
3,861✔
2388

2389
            if ((bHaveNoDataRed && dfNoDataValueRed >= 0 &&
3,861✔
2390
                 dfNoDataValueRed < 65536) &&
×
2391
                (bHaveNoDataGreen && dfNoDataValueGreen >= 0 &&
×
2392
                 dfNoDataValueGreen < 65536) &&
×
2393
                (bHaveNoDataBlue && dfNoDataValueBlue >= 0 &&
×
2394
                 dfNoDataValueBlue < 65536))
2395
            {
2396
                sTRNSColor.red = static_cast<png_uint_16>(dfNoDataValueRed);
×
2397
                sTRNSColor.green = static_cast<png_uint_16>(dfNoDataValueGreen);
×
2398
                sTRNSColor.blue = static_cast<png_uint_16>(dfNoDataValueBlue);
×
2399
                if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, nullptr,
×
2400
                                       0, &sTRNSColor))
2401
                {
2402
                    VSIFCloseL(fpImage);
×
2403
                    png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2404
                    return nullptr;
×
2405
                }
2406
            }
2407
        }
2408
    }
2409

2410
    // Copy color profile data.
2411
    const char *pszICCProfile =
2412
        CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
4,554✔
2413
    const char *pszICCProfileName =
2414
        CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE_NAME");
4,554✔
2415
    if (pszICCProfileName == nullptr)
4,554✔
2416
        pszICCProfileName = poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE_NAME",
4,553✔
2417
                                                     "COLOR_PROFILE");
4,553✔
2418

2419
    if (pszICCProfile == nullptr)
4,554✔
2420
        pszICCProfile =
2421
            poSrcDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
4,553✔
2422

2423
    if ((pszICCProfileName != nullptr) && EQUAL(pszICCProfileName, "sRGB"))
4,554✔
2424
    {
2425
        pszICCProfile = nullptr;
1✔
2426

2427
        // assumes this can't fail ?
2428
        png_set_sRGB(hPNG, psPNGInfo, PNG_sRGB_INTENT_PERCEPTUAL);
1✔
2429
    }
2430

2431
    if (pszICCProfile != nullptr)
4,554✔
2432
    {
2433
        char *pEmbedBuffer = CPLStrdup(pszICCProfile);
2✔
2434
        png_uint_32 nEmbedLen =
2435
            CPLBase64DecodeInPlace(reinterpret_cast<GByte *>(pEmbedBuffer));
2✔
2436
        const char *pszLocalICCProfileName =
2✔
2437
            (pszICCProfileName != nullptr) ? pszICCProfileName : "ICC Profile";
2✔
2438

2439
        if (!safe_png_set_iCCP(
2✔
2440
                sSetJmpContext, hPNG, psPNGInfo, pszLocalICCProfileName, 0,
2441
                reinterpret_cast<png_const_bytep>(pEmbedBuffer), nEmbedLen))
2442
        {
2443
            CPLFree(pEmbedBuffer);
×
2444
            VSIFCloseL(fpImage);
×
2445
            png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2446
            return nullptr;
×
2447
        }
2448

2449
        CPLFree(pEmbedBuffer);
2✔
2450
    }
2451
    else if ((pszICCProfileName == nullptr) ||
4,552✔
2452
             !EQUAL(pszICCProfileName, "sRGB"))
1✔
2453
    {
2454
        // Output gamma, primaries and whitepoint.
2455
        const char *pszGamma = CSLFetchNameValue(papszOptions, "PNG_GAMMA");
4,551✔
2456
        if (pszGamma == nullptr)
4,551✔
2457
            pszGamma = poSrcDS->GetMetadataItem("PNG_GAMMA", "COLOR_PROFILE");
4,549✔
2458

2459
        if (pszGamma != nullptr)
4,551✔
2460
        {
2461
            double dfGamma = CPLAtof(pszGamma);
4✔
2462
            // assumes this can't fail ?
2463
            png_set_gAMA(hPNG, psPNGInfo, dfGamma);
4✔
2464
        }
2465

2466
        const char *pszPrimariesRed =
2467
            CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_RED");
4,551✔
2468
        if (pszPrimariesRed == nullptr)
4,551✔
2469
            pszPrimariesRed = poSrcDS->GetMetadataItem("SOURCE_PRIMARIES_RED",
4,550✔
2470
                                                       "COLOR_PROFILE");
4,550✔
2471
        const char *pszPrimariesGreen =
2472
            CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_GREEN");
4,551✔
2473
        if (pszPrimariesGreen == nullptr)
4,551✔
2474
            pszPrimariesGreen = poSrcDS->GetMetadataItem(
4,550✔
2475
                "SOURCE_PRIMARIES_GREEN", "COLOR_PROFILE");
4,550✔
2476
        const char *pszPrimariesBlue =
2477
            CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_BLUE");
4,551✔
2478
        if (pszPrimariesBlue == nullptr)
4,551✔
2479
            pszPrimariesBlue = poSrcDS->GetMetadataItem("SOURCE_PRIMARIES_BLUE",
4,550✔
2480
                                                        "COLOR_PROFILE");
4,550✔
2481
        const char *pszWhitepoint =
2482
            CSLFetchNameValue(papszOptions, "SOURCE_WHITEPOINT");
4,551✔
2483
        if (pszWhitepoint == nullptr)
4,551✔
2484
            pszWhitepoint =
2485
                poSrcDS->GetMetadataItem("SOURCE_WHITEPOINT", "COLOR_PROFILE");
4,550✔
2486

2487
        if ((pszPrimariesRed != nullptr) && (pszPrimariesGreen != nullptr) &&
4,551✔
2488
            (pszPrimariesBlue != nullptr) && (pszWhitepoint != nullptr))
2✔
2489
        {
2490
            bool bOk = true;
2✔
2491
            double faColour[8] = {0.0};
2✔
2492
            char **apapszTokenList[4] = {nullptr};
2✔
2493

2494
            apapszTokenList[0] = CSLTokenizeString2(pszWhitepoint, ",",
2✔
2495
                                                    CSLT_ALLOWEMPTYTOKENS |
2496
                                                        CSLT_STRIPLEADSPACES |
2497
                                                        CSLT_STRIPENDSPACES);
2498
            apapszTokenList[1] = CSLTokenizeString2(pszPrimariesRed, ",",
2✔
2499
                                                    CSLT_ALLOWEMPTYTOKENS |
2500
                                                        CSLT_STRIPLEADSPACES |
2501
                                                        CSLT_STRIPENDSPACES);
2502
            apapszTokenList[2] = CSLTokenizeString2(pszPrimariesGreen, ",",
2✔
2503
                                                    CSLT_ALLOWEMPTYTOKENS |
2504
                                                        CSLT_STRIPLEADSPACES |
2505
                                                        CSLT_STRIPENDSPACES);
2506
            apapszTokenList[3] = CSLTokenizeString2(pszPrimariesBlue, ",",
2✔
2507
                                                    CSLT_ALLOWEMPTYTOKENS |
2508
                                                        CSLT_STRIPLEADSPACES |
2509
                                                        CSLT_STRIPENDSPACES);
2510

2511
            if ((CSLCount(apapszTokenList[0]) == 3) &&
2✔
2512
                (CSLCount(apapszTokenList[1]) == 3) &&
2✔
2513
                (CSLCount(apapszTokenList[2]) == 3) &&
6✔
2514
                (CSLCount(apapszTokenList[3]) == 3))
2✔
2515
            {
2516
                for (int i = 0; i < 4; i++)
10✔
2517
                {
2518
                    for (int j = 0; j < 3; j++)
32✔
2519
                    {
2520
                        const double v = CPLAtof(apapszTokenList[i][j]);
24✔
2521

2522
                        if (j == 2)
24✔
2523
                        {
2524
                            /* Last term of xyY colour must be 1.0 */
2525
                            if (v != 1.0)
8✔
2526
                            {
2527
                                bOk = false;
×
2528
                                break;
×
2529
                            }
2530
                        }
2531
                        else
2532
                        {
2533
                            faColour[i * 2 + j] = v;
16✔
2534
                        }
2535
                    }
2536
                    if (!bOk)
8✔
2537
                        break;
×
2538
                }
2539

2540
                if (bOk)
2✔
2541
                {
2542
                    // assumes this can't fail ?
2543
                    png_set_cHRM(hPNG, psPNGInfo, faColour[0], faColour[1],
2✔
2544
                                 faColour[2], faColour[3], faColour[4],
2545
                                 faColour[5], faColour[6], faColour[7]);
2546
                }
2547
            }
2548

2549
            CSLDestroy(apapszTokenList[0]);
2✔
2550
            CSLDestroy(apapszTokenList[1]);
2✔
2551
            CSLDestroy(apapszTokenList[2]);
2✔
2552
            CSLDestroy(apapszTokenList[3]);
2✔
2553
        }
2554
    }
2555

2556
    // Write the palette if there is one. Technically, it may be possible to
2557
    // write 16-bit palettes for PNG, but for now, this is omitted.
2558
    if (nColorType == PNG_COLOR_TYPE_PALETTE)
4,554✔
2559
    {
2560
        int bHaveNoData = FALSE;
33✔
2561
        double dfNoDataValue =
2562
            poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
33✔
2563

2564
        GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
33✔
2565

2566
        int nEntryCount = poCT->GetColorEntryCount();
33✔
2567
        int nMaxEntryCount = 1 << nBitDepth;
33✔
2568
        if (nEntryCount > nMaxEntryCount)
33✔
2569
            nEntryCount = nMaxEntryCount;
×
2570

2571
        png_color *pasPNGColors = reinterpret_cast<png_color *>(
2572
            CPLMalloc(sizeof(png_color) * nEntryCount));
33✔
2573

2574
        GDALColorEntry sEntry;
2575
        bool bFoundTrans = false;
33✔
2576
        for (int iColor = 0; iColor < nEntryCount; iColor++)
7,007✔
2577
        {
2578
            poCT->GetColorEntryAsRGB(iColor, &sEntry);
6,974✔
2579
            if (sEntry.c4 != 255)
6,974✔
2580
                bFoundTrans = true;
266✔
2581

2582
            pasPNGColors[iColor].red = static_cast<png_byte>(sEntry.c1);
6,974✔
2583
            pasPNGColors[iColor].green = static_cast<png_byte>(sEntry.c2);
6,974✔
2584
            pasPNGColors[iColor].blue = static_cast<png_byte>(sEntry.c3);
6,974✔
2585
        }
2586

2587
        if (!safe_png_set_PLTE(sSetJmpContext, hPNG, psPNGInfo, pasPNGColors,
33✔
2588
                               nEntryCount))
2589
        {
2590
            CPLFree(pasPNGColors);
×
2591
            VSIFCloseL(fpImage);
×
2592
            png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2593
            return nullptr;
×
2594
        }
2595

2596
        CPLFree(pasPNGColors);
33✔
2597

2598
        // If we have transparent elements in the palette, we need to write a
2599
        // transparency block.
2600
        if (bFoundTrans || bHaveNoData)
33✔
2601
        {
2602
            unsigned char *pabyAlpha =
2603
                reinterpret_cast<unsigned char *>(CPLMalloc(nEntryCount));
4✔
2604

2605
            for (int iColor = 0; iColor < nEntryCount; iColor++)
571✔
2606
            {
2607
                poCT->GetColorEntryAsRGB(iColor, &sEntry);
567✔
2608
                pabyAlpha[iColor] = static_cast<unsigned char>(sEntry.c4);
567✔
2609

2610
                if (bHaveNoData && iColor == static_cast<int>(dfNoDataValue))
567✔
2611
                    pabyAlpha[iColor] = 0;
3✔
2612
            }
2613

2614
            if (!safe_png_set_tRNS(sSetJmpContext, hPNG, psPNGInfo, pabyAlpha,
4✔
2615
                                   nEntryCount, nullptr))
2616
            {
2617
                CPLFree(pabyAlpha);
×
2618
                VSIFCloseL(fpImage);
×
2619
                png_destroy_write_struct(&hPNG, &psPNGInfo);
×
2620
                return nullptr;
×
2621
            }
2622

2623
            CPLFree(pabyAlpha);
4✔
2624
        }
2625
    }
2626

2627
    // Add text info.
2628
    // These are predefined keywords. See "4.2.7 tEXt Textual data" of
2629
    // http://www.w3.org/TR/PNG-Chunks.html for more information.
2630
    const char *apszKeywords[] = {"Title",      "Author",        "Description",
4,554✔
2631
                                  "Copyright",  "Creation Time", "Software",
2632
                                  "Disclaimer", "Warning",       "Source",
2633
                                  "Comment",    nullptr};
2634
    const bool bWriteMetadataAsText = CPLTestBool(
4,554✔
2635
        CSLFetchNameValueDef(papszOptions, "WRITE_METADATA_AS_TEXT", "FALSE"));
2636
    for (int i = 0; apszKeywords[i] != nullptr; i++)
50,094✔
2637
    {
2638
        const char *pszKey = apszKeywords[i];
45,540✔
2639
        const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
45,540✔
2640
        if (pszValue == nullptr && bWriteMetadataAsText)
45,540✔
2641
            pszValue = poSrcDS->GetMetadataItem(pszKey);
9✔
2642
        if (pszValue != nullptr)
45,540✔
2643
        {
2644
            WriteMetadataAsText(sSetJmpContext, hPNG, psPNGInfo, pszKey,
2✔
2645
                                pszValue);
2646
        }
2647
    }
2648
    if (bWriteMetadataAsText)
4,554✔
2649
    {
2650
        char **papszSrcMD = poSrcDS->GetMetadata();
1✔
2651
        for (; papszSrcMD && *papszSrcMD; papszSrcMD++)
4✔
2652
        {
2653
            char *pszKey = nullptr;
3✔
2654
            const char *pszValue = CPLParseNameValue(*papszSrcMD, &pszKey);
3✔
2655
            if (pszKey && pszValue)
3✔
2656
            {
2657
                if (CSLFindString(const_cast<char **>(apszKeywords), pszKey) <
3✔
2658
                        0 &&
1✔
2659
                    !EQUAL(pszKey, "AREA_OR_POINT") &&
4✔
2660
                    !EQUAL(pszKey, "NODATA_VALUES"))
1✔
2661
                {
2662
                    WriteMetadataAsText(sSetJmpContext, hPNG, psPNGInfo, pszKey,
1✔
2663
                                        pszValue);
2664
                }
2665
                CPLFree(pszKey);
3✔
2666
            }
2667
        }
2668
    }
2669

2670
    // Write the PNG info.
2671
    if (!safe_png_write_info(sSetJmpContext, hPNG, psPNGInfo))
4,554✔
2672
    {
2673
        VSIFCloseL(fpImage);
5✔
2674
        png_destroy_write_struct(&hPNG, &psPNGInfo);
5✔
2675
        return nullptr;
5✔
2676
    }
2677

2678
    if (nBitDepth < 8)
4,549✔
2679
    {
2680
        // Assumes this can't fail
2681
        png_set_packing(hPNG);
6✔
2682
    }
2683

2684
    // Loop over the image, copying image data.
2685
    CPLErr eErr = CE_None;
4,549✔
2686
    const int nWordSize = GDALGetDataTypeSizeBytes(eType);
4,549✔
2687

2688
    GByte *pabyScanline = reinterpret_cast<GByte *>(
2689
        CPLMalloc(cpl::fits_on<int>(nBands * nXSize * nWordSize)));
4,549✔
2690

2691
    for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
229,605✔
2692
    {
2693
        png_bytep row = pabyScanline;
225,056✔
2694

2695
        eErr = poSrcDS->RasterIO(
450,108✔
2696
            GF_Read, 0, iLine, nXSize, 1, pabyScanline, nXSize, 1, eType,
2697
            nBands, nullptr, static_cast<GSpacing>(nBands) * nWordSize,
225,056✔
2698
            static_cast<GSpacing>(nBands) * nXSize * nWordSize, nWordSize,
225,056✔
2699
            nullptr);
2700

2701
#ifdef CPL_LSB
2702
        if (nBitDepth == 16)
225,052✔
2703
            GDALSwapWords(row, 2, nXSize * nBands, 2);
13,397✔
2704
#endif
2705
        if (eErr == CE_None)
225,051✔
2706
        {
2707
            if (!safe_png_write_rows(sSetJmpContext, hPNG, &row, 1))
225,051✔
2708
            {
2709
                eErr = CE_Failure;
4✔
2710
            }
2711
        }
2712

2713
        if (eErr == CE_None &&
450,105✔
2714
            !pfnProgress((iLine + 1) / static_cast<double>(nYSize), nullptr,
225,049✔
2715
                         pProgressData))
2716
        {
2717
            eErr = CE_Failure;
1✔
2718
            CPLError(CE_Failure, CPLE_UserInterrupt,
1✔
2719
                     "User terminated CreateCopy()");
2720
        }
2721
    }
2722

2723
    CPLFree(pabyScanline);
4,549✔
2724

2725
    if (!safe_png_write_end(sSetJmpContext, hPNG, psPNGInfo))
4,549✔
2726
    {
2727
        eErr = CE_Failure;
7✔
2728
    }
2729
    png_destroy_write_struct(&hPNG, &psPNGInfo);
4,549✔
2730

2731
    VSIFCloseL(fpImage);
4,549✔
2732

2733
    if (eErr != CE_None)
4,549✔
2734
        return nullptr;
7✔
2735

2736
    // Do we need a world file?
2737
    if (CPLFetchBool(papszOptions, "WORLDFILE", false))
4,542✔
2738
    {
2739
        GDALGeoTransform gt;
×
2740
        if (poSrcDS->GetGeoTransform(gt) == CE_None)
×
2741
            GDALWriteWorldFile(pszFilename, "wld", gt.data());
×
2742
    }
2743

2744
    // Re-open dataset and copy any auxiliary PAM information.
2745

2746
    /* If writing to stdout, we can't reopen it, so return */
2747
    /* a fake dataset to make the caller happy */
2748
    if (CPLTestBool(CPLGetConfigOption("GDAL_OPEN_AFTER_COPY", "YES")))
4,542✔
2749
    {
2750
        CPLPushErrorHandler(CPLQuietErrorHandler);
4,179✔
2751
        GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
4,179✔
2752
        PNGDataset *poDS =
2753
            reinterpret_cast<PNGDataset *>(PNGDataset::Open(&oOpenInfo));
4,179✔
2754
        CPLPopErrorHandler();
4,179✔
2755
        if (poDS)
4,179✔
2756
        {
2757
            int nFlags = GCIF_PAM_DEFAULT & ~GCIF_METADATA;
4,178✔
2758
            poDS->CloneInfo(poSrcDS, nFlags);
4,178✔
2759

2760
            char **papszExcludedDomains =
2761
                CSLAddString(nullptr, "COLOR_PROFILE");
4,178✔
2762
            if (bWriteMetadataAsText)
4,178✔
2763
                papszExcludedDomains = CSLAddString(papszExcludedDomains, "");
1✔
2764
            GDALDriver::DefaultCopyMetadata(poSrcDS, poDS, papszOptions,
4,178✔
2765
                                            papszExcludedDomains);
2766
            CSLDestroy(papszExcludedDomains);
4,178✔
2767

2768
            return poDS;
4,178✔
2769
        }
2770
        CPLErrorReset();
1✔
2771
    }
2772

2773
    PNGDataset *poPNG_DS = new PNGDataset();
364✔
2774
    poPNG_DS->nRasterXSize = nXSize;
364✔
2775
    poPNG_DS->nRasterYSize = nYSize;
364✔
2776
    poPNG_DS->nBitDepth = nBitDepth;
364✔
2777
    for (int i = 0; i < nBands; i++)
1,508✔
2778
        poPNG_DS->SetBand(i + 1, new PNGRasterBand(poPNG_DS, i + 1));
1,144✔
2779
    return poPNG_DS;
364✔
2780
}
2781

2782
/************************************************************************/
2783
/*                         png_vsi_read_data()                          */
2784
/*                                                                      */
2785
/*      Read data callback through VSI.                                 */
2786
/************************************************************************/
2787
static void png_vsi_read_data(png_structp png_ptr, png_bytep data,
67,951✔
2788
                              png_size_t length)
2789

2790
{
2791
    // fread() returns 0 on error, so it is OK to store this in a png_size_t
2792
    // instead of an int, which is what fread() actually returns.
2793
    const png_size_t check = static_cast<png_size_t>(
2794
        VSIFReadL(data, 1, length,
135,898✔
2795
                  reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr))));
67,951✔
2796

2797
    if (check != length)
67,948✔
2798
        png_error(png_ptr, "Read Error");
3✔
2799
}
67,945✔
2800

2801
/************************************************************************/
2802
/*                         png_vsi_write_data()                         */
2803
/************************************************************************/
2804

2805
static void png_vsi_write_data(png_structp png_ptr, png_bytep data,
53,428✔
2806
                               png_size_t length)
2807
{
2808
    const size_t check = VSIFWriteL(
106,856✔
2809
        data, 1, length, reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr)));
53,428✔
2810

2811
    if (check != length)
53,428✔
2812
        png_error(png_ptr, "Write Error");
10✔
2813
}
53,418✔
2814

2815
/************************************************************************/
2816
/*                           png_vsi_flush()                            */
2817
/************************************************************************/
2818
static void png_vsi_flush(png_structp png_ptr)
×
2819
{
2820
    VSIFFlushL(reinterpret_cast<VSILFILE *>(png_get_io_ptr(png_ptr)));
×
2821
}
×
2822

2823
/************************************************************************/
2824
/*                           png_gdal_error()                           */
2825
/************************************************************************/
2826

2827
static void png_gdal_error(png_structp png_ptr, const char *error_message)
19✔
2828
{
2829
    CPLError(CE_Failure, CPLE_AppDefined, "libpng: %s", error_message);
19✔
2830

2831
    // Use longjmp instead of a C++ exception, because libpng is generally not
2832
    // built as C++ and so will not honor unwind semantics.
2833

2834
    jmp_buf *psSetJmpContext =
2835
        reinterpret_cast<jmp_buf *>(png_get_error_ptr(png_ptr));
19✔
2836
    if (psSetJmpContext)
19✔
2837
    {
2838
        longjmp(*psSetJmpContext, 1);
19✔
2839
    }
2840
}
×
2841

2842
/************************************************************************/
2843
/*                          png_gdal_warning()                          */
2844
/************************************************************************/
2845

2846
static void png_gdal_warning(CPL_UNUSED png_structp png_ptr,
×
2847
                             const char *error_message)
2848
{
2849
    CPLError(CE_Warning, CPLE_AppDefined, "libpng: %s", error_message);
×
2850
}
×
2851

2852
/************************************************************************/
2853
/*                          GDALRegister_PNG()                          */
2854
/************************************************************************/
2855

2856
void GDALRegister_PNG()
58✔
2857

2858
{
2859
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
58✔
2860
        return;
×
2861

2862
    GDALDriver *poDriver = new GDALDriver();
58✔
2863
    PNGDriverSetCommonMetadata(poDriver);
58✔
2864

2865
    poDriver->pfnOpen = PNGDataset::Open;
58✔
2866
    poDriver->pfnCreateCopy = PNGDataset::CreateCopy;
58✔
2867
#ifdef SUPPORT_CREATE
2868
    poDriver->pfnCreate = PNGDataset::Create;
2869
#endif
2870

2871
    GetGDALDriverManager()->RegisterDriver(poDriver);
58✔
2872
}
2873

2874
#ifdef SUPPORT_CREATE
2875
/************************************************************************/
2876
/*                         IWriteBlock()                                */
2877
/************************************************************************/
2878

2879
CPLErr PNGRasterBand::IWriteBlock(int x, int y, void *pvData)
2880
{
2881
    PNGDataset &ds = *reinterpret_cast<PNGDataset *>(poDS);
2882

2883
    // Write the block (or consolidate into multichannel block) and then write.
2884

2885
    const GDALDataType dt = GetRasterDataType();
2886
    const size_t wordsize = ds.m_nBitDepth / 8;
2887
    GDALCopyWords(pvData, dt, wordsize,
2888
                  ds.m_pabyBuffer + (nBand - 1) * wordsize, dt,
2889
                  ds.nBands * wordsize, nBlockXSize);
2890

2891
    // See if we have all the bands.
2892
    m_bBandProvided[nBand - 1] = TRUE;
2893
    for (size_t i = 0; i < static_cast<size_t>(ds.nBands); i++)
2894
    {
2895
        if (!m_bBandProvided[i])
2896
            return CE_None;
2897
    }
2898

2899
    // We received all the bands, so reset band flags and write pixels out.
2900
    this->reset_band_provision_flags();
2901

2902
    // If it is the first block, write out the file header.
2903
    if (x == 0 && y == 0)
2904
    {
2905
        CPLErr err = ds.write_png_header();
2906
        if (err != CE_None)
2907
            return err;
2908
    }
2909

2910
#ifdef CPL_LSB
2911
    if (ds.m_nBitDepth == 16)
2912
        GDALSwapWords(ds.m_pabyBuffer, 2, nBlockXSize * ds.nBands, 2);
2913
#endif
2914
    png_write_rows(ds.m_hPNG, &ds.m_pabyBuffer, 1);
2915

2916
    return CE_None;
2917
}
2918

2919
/************************************************************************/
2920
/*                          SetGeoTransform()                           */
2921
/************************************************************************/
2922

2923
CPLErr PNGDataset::SetGeoTransform(const GDALGeoTransform &gt)
2924
{
2925
    m_gt = gt;
2926

2927
    if (m_pszFilename)
2928
    {
2929
        if (GDALWriteWorldFile(m_pszFilename, "wld", m_gt.data()) == FALSE)
2930
        {
2931
            CPLError(CE_Failure, CPLE_FileIO, "Can't write world file.");
2932
            return CE_Failure;
2933
        }
2934
    }
2935

2936
    return CE_None;
2937
}
2938

2939
/************************************************************************/
2940
/*                           SetColorTable()                            */
2941
/************************************************************************/
2942

2943
CPLErr PNGRasterBand::SetColorTable(GDALColorTable *poCT)
2944
{
2945
    if (poCT == NULL)
2946
        return CE_Failure;
2947

2948
    // We get called even for grayscale files, since some formats need a palette
2949
    // even then. PNG doesn't, so if a gray palette is given, just ignore it.
2950

2951
    GDALColorEntry sEntry;
2952
    for (size_t i = 0; i < static_cast<size_t>(poCT->GetColorEntryCount()); i++)
2953
    {
2954
        poCT->GetColorEntryAsRGB(i, &sEntry);
2955
        if (sEntry.c1 != sEntry.c2 || sEntry.c1 != sEntry.c3)
2956
        {
2957
            CPLErr err = GDALPamRasterBand::SetColorTable(poCT);
2958
            if (err != CE_None)
2959
                return err;
2960

2961
            PNGDataset &ds = *reinterpret_cast<PNGDataset *>(poDS);
2962
            ds.m_nColorType = PNG_COLOR_TYPE_PALETTE;
2963
            break;
2964
            // band::IWriteBlock will emit color table as part of the header
2965
            // preceding the first block write.
2966
        }
2967
    }
2968

2969
    return CE_None;
2970
}
2971

2972
/************************************************************************/
2973
/*                  PNGDataset::write_png_header()                      */
2974
/************************************************************************/
2975

2976
CPLErr PNGDataset::write_png_header()
2977
{
2978
    // Initialize PNG access to the file.
2979
    m_hPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
2980
                                     png_gdal_error, png_gdal_warning);
2981

2982
    m_psPNGInfo = png_create_info_struct(m_hPNG);
2983

2984
    png_set_write_fn(m_hPNG, m_fpImage, png_vsi_write_data, png_vsi_flush);
2985

2986
    png_set_IHDR(m_hPNG, m_psPNGInfo, nRasterXSize, nRasterYSize, m_nBitDepth,
2987
                 m_nColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
2988
                 PNG_FILTER_TYPE_DEFAULT);
2989

2990
    png_set_compression_level(m_hPNG, Z_BEST_COMPRESSION);
2991

2992
    // png_set_swap_alpha(m_hPNG); // Use RGBA order, not ARGB.
2993

2994
    // Try to handle nodata values as a tRNS block (note that for paletted
2995
    // images, we save the effect to apply as part of the palette).
2996
    // m_bHaveNoData = FALSE;
2997
    // m_dfNoDataValue = -1;
2998
    png_color_16 sTRNSColor;
2999

3000
    int bHaveNoData = FALSE;
3001
    double dfNoDataValue = -1;
3002

3003
    if (m_nColorType == PNG_COLOR_TYPE_GRAY)
3004
    {
3005
        dfNoDataValue = GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
3006

3007
        if (bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536)
3008
        {
3009
            sTRNSColor.gray = static_cast<png_uint_16>(dfNoDataValue);
3010
            png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3011
        }
3012
    }
3013

3014
    // RGB nodata.
3015
    if (nColorType == PNG_COLOR_TYPE_RGB)
3016
    {
3017
        // First, try to use the NODATA_VALUES metadata item.
3018
        if (GetMetadataItem("NODATA_VALUES") != NULL)
3019
        {
3020
            char **papszValues =
3021
                CSLTokenizeString(GetMetadataItem("NODATA_VALUES"));
3022

3023
            if (CSLCount(papszValues) >= 3)
3024
            {
3025
                sTRNSColor.red = static_cast<png_uint_16>(atoi(papszValues[0]));
3026
                sTRNSColor.green =
3027
                    static_cast<png_uint_16>(atoi(papszValues[1]));
3028
                sTRNSColor.blue =
3029
                    static_cast<png_uint_16>(atoi(papszValues[2]));
3030
                png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3031
            }
3032

3033
            CSLDestroy(papszValues);
3034
        }
3035
        // Otherwise, get the nodata value from the bands.
3036
        else
3037
        {
3038
            int bHaveNoDataRed = FALSE;
3039
            const double dfNoDataValueRed =
3040
                GetRasterBand(1)->GetNoDataValue(&bHaveNoDataRed);
3041

3042
            int bHaveNoDataGreen = FALSE;
3043
            const double dfNoDataValueGreen =
3044
                GetRasterBand(2)->GetNoDataValue(&bHaveNoDataGreen);
3045

3046
            int bHaveNoDataBlue = FALSE;
3047
            const double dfNoDataValueBlue =
3048
                GetRasterBand(3)->GetNoDataValue(&bHaveNoDataBlue);
3049

3050
            if ((bHaveNoDataRed && dfNoDataValueRed >= 0 &&
3051
                 dfNoDataValueRed < 65536) &&
3052
                (bHaveNoDataGreen && dfNoDataValueGreen >= 0 &&
3053
                 dfNoDataValueGreen < 65536) &&
3054
                (bHaveNoDataBlue && dfNoDataValueBlue >= 0 &&
3055
                 dfNoDataValueBlue < 65536))
3056
            {
3057
                sTRNSColor.red = static_cast<png_uint_16>(dfNoDataValueRed);
3058
                sTRNSColor.green = static_cast<png_uint_16>(dfNoDataValueGreen);
3059
                sTRNSColor.blue = static_cast<png_uint_16>(dfNoDataValueBlue);
3060
                png_set_tRNS(m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor);
3061
            }
3062
        }
3063
    }
3064

3065
    // Write the palette if there is one. Technically, it may be possible
3066
    // to write 16-bit palettes for PNG, but for now, doing so is omitted.
3067
    if (nColorType == PNG_COLOR_TYPE_PALETTE)
3068
    {
3069
        GDALColorTable *poCT = GetRasterBand(1)->GetColorTable();
3070

3071
        int bHaveNoData = FALSE;
3072
        double dfNoDataValue = GetRasterBand(1)->GetNoDataValue(&bHaveNoData);
3073

3074
        m_pasPNGColors = reinterpret_cast<png_color *>(
3075
            CPLMalloc(sizeof(png_color) * poCT->GetColorEntryCount()));
3076

3077
        GDALColorEntry sEntry;
3078
        bool bFoundTrans = false;
3079
        for (int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++)
3080
        {
3081
            poCT->GetColorEntryAsRGB(iColor, &sEntry);
3082
            if (sEntry.c4 != 255)
3083
                bFoundTrans = true;
3084

3085
            m_pasPNGColors[iColor].red = static_cast<png_byte>(sEntry.c1);
3086
            m_pasPNGColors[iColor].green = static_cast<png_byte>(sEntry.c2);
3087
            m_pasPNGColors[iColor].blue = static_cast<png_byte>(sEntry.c3);
3088
        }
3089

3090
        png_set_PLTE(m_hPNG, m_psPNGInfo, m_pasPNGColors,
3091
                     poCT->GetColorEntryCount());
3092

3093
        // If we have transparent elements in the palette, we need to write a
3094
        // transparency block.
3095
        if (bFoundTrans || bHaveNoData)
3096
        {
3097
            m_pabyAlpha = reinterpret_cast<unsigned char *>(
3098
                CPLMalloc(poCT->GetColorEntryCount()));
3099

3100
            for (int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++)
3101
            {
3102
                poCT->GetColorEntryAsRGB(iColor, &sEntry);
3103
                m_pabyAlpha[iColor] = static_cast<unsigned char>(sEntry.c4);
3104

3105
                if (bHaveNoData && iColor == static_cast<int>(dfNoDataValue))
3106
                    m_pabyAlpha[iColor] = 0;
3107
            }
3108

3109
            png_set_tRNS(m_hPNG, m_psPNGInfo, m_pabyAlpha,
3110
                         poCT->GetColorEntryCount(), NULL);
3111
        }
3112
    }
3113

3114
    png_write_info(m_hPNG, m_psPNGInfo);
3115
    return CE_None;
3116
}
3117

3118
/************************************************************************/
3119
/*                               Create()                               */
3120
/************************************************************************/
3121

3122
GDALDataset *PNGDataset::Create(const char *pszFilename, int nXSize, int nYSize,
3123
                                int nBands, GDALDataType eType,
3124
                                char **papszOptions)
3125
{
3126
    if (eType != GDT_Byte && eType != GDT_UInt16)
3127
    {
3128
        CPLError(
3129
            CE_Failure, CPLE_AppDefined,
3130
            "Attempt to create PNG dataset with an illegal\n"
3131
            "data type (%s), only Byte and UInt16 supported by the format.\n",
3132
            GDALGetDataTypeName(eType));
3133

3134
        return NULL;
3135
    }
3136

3137
    if (nBands < 1 || nBands > 4)
3138
    {
3139
        CPLError(CE_Failure, CPLE_NotSupported,
3140
                 "PNG driver doesn't support %d bands. "
3141
                 "Must be 1 (gray/indexed color),\n"
3142
                 "2 (gray+alpha), 3 (rgb) or 4 (rgba) bands.\n",
3143
                 nBands);
3144

3145
        return NULL;
3146
    }
3147

3148
    // Bands are:
3149
    // 1: Grayscale or indexed color.
3150
    // 2: Gray plus alpha.
3151
    // 3: RGB.
3152
    // 4: RGB plus alpha.
3153

3154
    if (nXSize < 1 || nYSize < 1)
3155
    {
3156
        CPLError(CE_Failure, CPLE_NotSupported,
3157
                 "Specified pixel dimensions (% d x %d) are bad.\n", nXSize,
3158
                 nYSize);
3159
    }
3160

3161
    // Set up some parameters.
3162
    PNGDataset *poDS = new PNGDataset();
3163

3164
    poDS->nRasterXSize = nXSize;
3165
    poDS->nRasterYSize = nYSize;
3166
    poDS->eAccess = GA_Update;
3167
    poDS->nBands = nBands;
3168

3169
    switch (nBands)
3170
    {
3171
        case 1:
3172
            poDS->m_nColorType = PNG_COLOR_TYPE_GRAY;
3173
            break;  // If a non-gray palette is set, we'll change this.
3174

3175
        case 2:
3176
            poDS->m_nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
3177
            break;
3178

3179
        case 3:
3180
            poDS->m_nColorType = PNG_COLOR_TYPE_RGB;
3181
            break;
3182

3183
        case 4:
3184
            poDS->m_nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
3185
            break;
3186
    }
3187

3188
    poDS->m_nBitDepth = (eType == GDT_Byte ? 8 : 16);
3189

3190
    poDS->m_pabyBuffer = reinterpret_cast<GByte *>(
3191
        CPLMalloc(nBands * nXSize * poDS->m_nBitDepth / 8));
3192

3193
    // Create band information objects.
3194
    for (int iBand = 1; iBand <= poDS->nBands; iBand++)
3195
        poDS->SetBand(iBand, new PNGRasterBand(poDS, iBand));
3196

3197
    // Do we need a world file?
3198
    if (CPLFetchBool(papszOptions, "WORLDFILE", false))
3199
        poDS->m_bGeoTransformValid = TRUE;
3200

3201
    // Create the file.
3202

3203
    poDS->m_fpImage = VSIFOpenL(pszFilename, "wb");
3204
    if (poDS->m_fpImage == NULL)
3205
    {
3206
        CPLError(CE_Failure, CPLE_OpenFailed,
3207
                 "Unable to create PNG file %s: %s\n", pszFilename,
3208
                 VSIStrerror(errno));
3209
        delete poDS;
3210
        return NULL;
3211
    }
3212

3213
    poDS->m_pszFilename = CPLStrdup(pszFilename);
3214

3215
    return poDS;
3216
}
3217

3218
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc