1 /******************************************************************************
2  *
3  * Project:  PNG Driver
4  * Purpose:  Implement GDAL PNG Support
5  * Author:   Frank Warmerdam, warmerda@home.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2000, Frank Warmerdam
9  * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ******************************************************************************
29  *
30  * ISSUES:
31  *  o CollectMetadata() will only capture TEXT chunks before the image
32  *    data as the code is currently structured.
33  *  o Interlaced images are read entirely into memory for use.  This is
34  *    bad for large images.
35  *  o Image reading is always strictly sequential.  Reading backwards will
36  *    cause the file to be rewound, and access started again from the
37  *    beginning.
38  *  o 16 bit alpha values are not scaled by to eight bit.
39  *
40  */
41 
42 #include "pngdataset.h"
43 
44 #include "cpl_string.h"
45 #include "gdal_frmts.h"
46 #include "gdal_pam.h"
47 #include "png.h"
48 
49 #include <csetjmp>
50 
51 #include <algorithm>
52 
53 CPL_CVSID("$Id: pngdataset.cpp f6099e5ed704166bf5cc113a053dd1b2725cb391 2020-03-22 11:20:10 +0100 Kai Pastor $")
54 
55 // Note: Callers must provide blocks in increasing Y order.
56 // Disclaimer (E. Rouault): this code is not production ready at all. A lot of
57 // issues remain: uninitialized variables, unclosed files, lack of proper
58 // multiband handling, and an inability to read and write at the same time. Do
59 // not use it unless you're ready to fix it.
60 
61 // Define SUPPORT_CREATE to enable use of the Create() call.
62 // #define SUPPORT_CREATE
63 
64 #ifdef _MSC_VER
65 #  pragma warning(disable:4611)
66 #endif
67 
68 static void
69 png_vsi_read_data(png_structp png_ptr, png_bytep data, png_size_t length);
70 
71 static void
72 png_vsi_write_data(png_structp png_ptr, png_bytep data, png_size_t length);
73 
74 static void png_vsi_flush(png_structp png_ptr);
75 
76 static void png_gdal_error( png_structp png_ptr, const char *error_message );
77 static void png_gdal_warning( png_structp png_ptr, const char *error_message );
78 
79 
80 /************************************************************************/
81 /*                           PNGRasterBand()                            */
82 /************************************************************************/
83 
PNGRasterBand(PNGDataset * poDSIn,int nBandIn)84 PNGRasterBand::PNGRasterBand( PNGDataset *poDSIn, int nBandIn ) :
85     bHaveNoData(FALSE),
86     dfNoDataValue(-1)
87 {
88     poDS = poDSIn;
89     nBand = nBandIn;
90 
91     if( poDSIn->nBitDepth == 16 )
92         eDataType = GDT_UInt16;
93     else
94         eDataType = GDT_Byte;
95 
96     nBlockXSize = poDSIn->nRasterXSize;
97     nBlockYSize = 1;
98 
99 #ifdef SUPPORT_CREATE
100     reset_band_provision_flags();
101 #endif
102 }
103 
104 /************************************************************************/
105 /*                             IReadBlock()                             */
106 /************************************************************************/
107 
IReadBlock(int nBlockXOff,int nBlockYOff,void * pImage)108 CPLErr PNGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
109                                   void * pImage )
110 
111 {
112     PNGDataset *poGDS = reinterpret_cast<PNGDataset *>( poDS );
113     int nPixelSize;
114 
115     CPLAssert( nBlockXOff == 0 );
116 
117     if( poGDS->nBitDepth == 16 )
118         nPixelSize = 2;
119     else
120         nPixelSize = 1;
121 
122     const int nXSize = GetXSize();
123     if (poGDS->fpImage == nullptr)
124     {
125         memset( pImage, 0, nPixelSize * nXSize );
126         return CE_None;
127     }
128 
129     // Load the desired scanline into the working buffer.
130     CPLErr eErr = poGDS->LoadScanline( nBlockYOff );
131     if( eErr != CE_None )
132         return eErr;
133 
134     const int nPixelOffset = poGDS->nBands * nPixelSize;
135 
136     GByte *pabyScanline = poGDS->pabyBuffer
137         + (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize
138         + nPixelSize * (nBand - 1);
139 
140     // Transfer between the working buffer and the caller's buffer.
141     if( nPixelSize == nPixelOffset )
142         memcpy( pImage, pabyScanline, nPixelSize * nXSize );
143     else if( nPixelSize == 1 )
144     {
145         for( int i = 0; i < nXSize; i++ )
146             reinterpret_cast<GByte *>( pImage )[i] = pabyScanline[i*nPixelOffset];
147     }
148     else
149     {
150         CPLAssert( nPixelSize == 2 );
151         for( int i = 0; i < nXSize; i++ )
152         {
153             reinterpret_cast<GUInt16 *>( pImage )[i] =
154                 *reinterpret_cast<GUInt16 *>( pabyScanline+i*nPixelOffset );
155         }
156     }
157 
158     // Forcibly load the other bands associated with this scanline.
159     for(int iBand = 1; iBand < poGDS->GetRasterCount(); iBand++)
160     {
161         GDALRasterBlock *poBlock =
162             poGDS->GetRasterBand(iBand+1)->GetLockedBlockRef(nBlockXOff,nBlockYOff);
163         if( poBlock != nullptr )
164             poBlock->DropLock();
165     }
166 
167     return CE_None;
168 }
169 
170 /************************************************************************/
171 /*                       GetColorInterpretation()                       */
172 /************************************************************************/
173 
GetColorInterpretation()174 GDALColorInterp PNGRasterBand::GetColorInterpretation()
175 
176 {
177     PNGDataset *poGDS = reinterpret_cast<PNGDataset *>( poDS );
178 
179     if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY )
180         return GCI_GrayIndex;
181 
182     else if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA )
183     {
184         if( nBand == 1 )
185             return GCI_GrayIndex;
186         else
187             return GCI_AlphaBand;
188     }
189 
190     else  if( poGDS->nColorType == PNG_COLOR_TYPE_PALETTE )
191         return GCI_PaletteIndex;
192 
193     else  if( poGDS->nColorType == PNG_COLOR_TYPE_RGB
194               || poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA )
195     {
196         if( nBand == 1 )
197             return GCI_RedBand;
198         else if( nBand == 2 )
199             return GCI_GreenBand;
200         else if( nBand == 3 )
201             return GCI_BlueBand;
202         else
203             return GCI_AlphaBand;
204     }
205     else
206         return GCI_GrayIndex;
207 }
208 
209 /************************************************************************/
210 /*                           GetColorTable()                            */
211 /************************************************************************/
212 
GetColorTable()213 GDALColorTable *PNGRasterBand::GetColorTable()
214 
215 {
216     PNGDataset  *poGDS = reinterpret_cast<PNGDataset *>( poDS );
217 
218     if( nBand == 1 )
219         return poGDS->poColorTable;
220 
221     return nullptr;
222 }
223 
224 /************************************************************************/
225 /*                           SetNoDataValue()                           */
226 /************************************************************************/
227 
SetNoDataValue(double dfNewValue)228 CPLErr PNGRasterBand::SetNoDataValue( double dfNewValue )
229 
230 {
231    bHaveNoData = TRUE;
232    dfNoDataValue = dfNewValue;
233 
234    return CE_None;
235 }
236 
237 /************************************************************************/
238 /*                           GetNoDataValue()                           */
239 /************************************************************************/
240 
GetNoDataValue(int * pbSuccess)241 double PNGRasterBand::GetNoDataValue( int *pbSuccess )
242 
243 {
244     if( bHaveNoData )
245     {
246         if( pbSuccess != nullptr )
247             *pbSuccess = bHaveNoData;
248         return dfNoDataValue;
249     }
250 
251     return GDALPamRasterBand::GetNoDataValue( pbSuccess );
252 }
253 
254 /************************************************************************/
255 /* ==================================================================== */
256 /*                             PNGDataset                               */
257 /* ==================================================================== */
258 /************************************************************************/
259 
260 /************************************************************************/
261 /*                             PNGDataset()                             */
262 /************************************************************************/
263 
PNGDataset()264 PNGDataset::PNGDataset() :
265     fpImage(nullptr),
266     hPNG(nullptr),
267     psPNGInfo(nullptr),
268     nBitDepth(8),
269     nColorType(0),
270     bInterlaced(FALSE),
271     nBufferStartLine(0),
272     nBufferLines(0),
273     nLastLineRead(-1),
274     pabyBuffer(nullptr),
275     poColorTable(nullptr),
276     bGeoTransformValid(FALSE),
277     bHasReadXMPMetadata(FALSE),
278     bHasTriedLoadWorldFile(FALSE),
279     bHasReadICCMetadata(FALSE)
280 {
281     adfGeoTransform[0] = 0.0;
282     adfGeoTransform[1] = 1.0;
283     adfGeoTransform[2] = 0.0;
284     adfGeoTransform[3] = 0.0;
285     adfGeoTransform[4] = 0.0;
286     adfGeoTransform[5] = 1.0;
287 
288     memset(&sSetJmpContext, 0, sizeof(sSetJmpContext));
289 }
290 
291 /************************************************************************/
292 /*                            ~PNGDataset()                             */
293 /************************************************************************/
294 
~PNGDataset()295 PNGDataset::~PNGDataset()
296 
297 {
298     PNGDataset::FlushCache();
299 
300     if( hPNG != nullptr )
301         png_destroy_read_struct( &hPNG, &psPNGInfo, nullptr );
302 
303     if( fpImage )
304         VSIFCloseL( fpImage );
305 
306     if( poColorTable != nullptr )
307         delete poColorTable;
308 }
309 
310 /************************************************************************/
311 /*                            IsFullBandMap()                           */
312 /************************************************************************/
313 
IsFullBandMap(int * panBandMap,int nBands)314 static int IsFullBandMap(int *panBandMap, int nBands)
315 {
316     for(int i=0;i<nBands;i++)
317     {
318         if( panBandMap[i] != i + 1 )
319             return FALSE;
320     }
321     return TRUE;
322 }
323 
324 /************************************************************************/
325 /*                             IRasterIO()                              */
326 /************************************************************************/
327 
IRasterIO(GDALRWFlag eRWFlag,int nXOff,int nYOff,int nXSize,int nYSize,void * pData,int nBufXSize,int nBufYSize,GDALDataType eBufType,int nBandCount,int * panBandMap,GSpacing nPixelSpace,GSpacing nLineSpace,GSpacing nBandSpace,GDALRasterIOExtraArg * psExtraArg)328 CPLErr PNGDataset::IRasterIO( GDALRWFlag eRWFlag,
329                               int nXOff, int nYOff, int nXSize, int nYSize,
330                               void *pData, int nBufXSize, int nBufYSize,
331                               GDALDataType eBufType,
332                               int nBandCount, int *panBandMap,
333                               GSpacing nPixelSpace, GSpacing nLineSpace,
334                               GSpacing nBandSpace,
335                               GDALRasterIOExtraArg* psExtraArg )
336 
337 {
338     // Coverity says that we cannot pass a nullptr to IRasterIO.
339     if (panBandMap == nullptr)
340     {
341       return CE_Failure;
342     }
343 
344     if((eRWFlag == GF_Read) &&
345        (nBandCount == nBands) &&
346        (nXOff == 0) && (nYOff == 0) &&
347        (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
348        (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
349        (eBufType == GDT_Byte) &&
350        (eBufType == GetRasterBand(1)->GetRasterDataType()) &&
351        (pData != nullptr) &&
352        IsFullBandMap(panBandMap, nBands))
353     {
354         // Pixel interleaved case.
355         if( nBandSpace == 1 )
356         {
357             for(int y = 0; y < nYSize; ++y)
358             {
359                 CPLErr tmpError = LoadScanline(y);
360                 if(tmpError != CE_None) return tmpError;
361                 const GByte* pabyScanline = pabyBuffer
362                     + (y - nBufferStartLine) * nBands * nXSize;
363                 if( nPixelSpace == nBandSpace * nBandCount )
364                 {
365                     memcpy(&(reinterpret_cast<GByte*>( pData )[(y*nLineSpace)]),
366                            pabyScanline, nBandCount * nXSize);
367                 }
368                 else
369                 {
370                     for(int x = 0; x < nXSize; ++x)
371                     {
372                         memcpy(&(reinterpret_cast<GByte*>(pData)[(y*nLineSpace) + (x*nPixelSpace)]),
373                                (const GByte*)&(pabyScanline[x* nBandCount]), nBandCount);
374                     }
375                 }
376             }
377         }
378         else
379         {
380             for(int y = 0; y < nYSize; ++y)
381             {
382                 CPLErr tmpError = LoadScanline(y);
383                 if(tmpError != CE_None) return tmpError;
384                 const GByte* pabyScanline = pabyBuffer
385                     + (y - nBufferStartLine) * nBands * nXSize;
386                 GByte* pabyDest = reinterpret_cast<GByte *>( pData ) +
387                                                             y*nLineSpace;
388                 if( nPixelSpace <= nBands && nBandSpace > nBands )
389                 {
390                     // Cache friendly way for typical band interleaved case.
391                     for(int iBand=0;iBand<nBands;iBand++)
392                     {
393                         GByte* pabyDest2 = pabyDest + iBand * nBandSpace;
394                         const GByte* pabyScanline2 = pabyScanline + iBand;
395                         GDALCopyWords( pabyScanline2, GDT_Byte, nBands,
396                                        pabyDest2, GDT_Byte,
397                                        static_cast<int>(nPixelSpace),
398                                        nXSize );
399                     }
400                 }
401                 else
402                 {
403                     // Generic method
404                     for(int x = 0; x < nXSize; ++x)
405                     {
406                         for(int iBand=0;iBand<nBands;iBand++)
407                         {
408                             pabyDest[(x*nPixelSpace) + iBand * nBandSpace] =
409                                 pabyScanline[x*nBands+iBand];
410                         }
411                     }
412                 }
413             }
414         }
415 
416         return CE_None;
417     }
418 
419     return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
420                                      pData, nBufXSize, nBufYSize, eBufType,
421                                      nBandCount, panBandMap,
422                                      nPixelSpace, nLineSpace, nBandSpace,
423                                      psExtraArg);
424 }
425 
426 /************************************************************************/
427 /*                          GetGeoTransform()                           */
428 /************************************************************************/
429 
GetGeoTransform(double * padfTransform)430 CPLErr PNGDataset::GetGeoTransform( double * padfTransform )
431 
432 {
433     LoadWorldFile();
434 
435     if( bGeoTransformValid )
436     {
437         memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
438         return CE_None;
439     }
440 
441     return GDALPamDataset::GetGeoTransform( padfTransform );
442 }
443 
444 /************************************************************************/
445 /*                             FlushCache()                             */
446 /*                                                                      */
447 /*      We override this so we can also flush out local TIFF strip      */
448 /*      cache if need be.                                               */
449 /************************************************************************/
450 
FlushCache()451 void PNGDataset::FlushCache()
452 
453 {
454     GDALPamDataset::FlushCache();
455 
456     if( pabyBuffer != nullptr )
457     {
458         CPLFree( pabyBuffer );
459         pabyBuffer = nullptr;
460         nBufferStartLine = 0;
461         nBufferLines = 0;
462     }
463 }
464 
465 #ifdef DISABLE_CRC_CHECK
466 /************************************************************************/
467 /*                     PNGDatasetDisableCRCCheck()                      */
468 /************************************************************************/
469 
PNGDatasetDisableCRCCheck(png_structp hPNG)470 static void PNGDatasetDisableCRCCheck( png_structp hPNG )
471 {
472     hPNG->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK;
473     hPNG->flags |= PNG_FLAG_CRC_CRITICAL_IGNORE;
474 
475     hPNG->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK;
476     hPNG->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN;
477 }
478 #endif
479 
480 /************************************************************************/
481 /*                              Restart()                               */
482 /*                                                                      */
483 /*      Restart reading from the beginning of the file.                 */
484 /************************************************************************/
485 
Restart()486 void PNGDataset::Restart()
487 
488 {
489     png_destroy_read_struct( &hPNG, &psPNGInfo, nullptr );
490 
491     hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, this, nullptr, nullptr );
492 
493 #ifdef DISABLE_CRC_CHECK
494     PNGDatasetDisableCRCCheck( hPNG );
495 #endif
496 
497     png_set_error_fn( hPNG, &sSetJmpContext, png_gdal_error, png_gdal_warning );
498     if( setjmp( sSetJmpContext ) != 0 )
499         return;
500 
501     psPNGInfo = png_create_info_struct( hPNG );
502 
503     VSIFSeekL( fpImage, 0, SEEK_SET );
504     png_set_read_fn( hPNG, fpImage, png_vsi_read_data );
505     png_read_info( hPNG, psPNGInfo );
506 
507     if( nBitDepth < 8 )
508         png_set_packing( hPNG );
509 
510     nLastLineRead = -1;
511 }
512 
513 /************************************************************************/
514 /*                        safe_png_read_image()                         */
515 /************************************************************************/
516 
safe_png_read_image(png_structp hPNG,png_bytep * png_rows,jmp_buf sSetJmpContext)517 static bool safe_png_read_image(png_structp hPNG,
518                                 png_bytep *png_rows,
519                                 jmp_buf     sSetJmpContext)
520 {
521     if( setjmp( sSetJmpContext ) != 0 )
522         return false;
523     png_read_image( hPNG, png_rows );
524     return true;
525 }
526 
527 /************************************************************************/
528 /*                        LoadInterlacedChunk()                         */
529 /************************************************************************/
530 
LoadInterlacedChunk(int iLine)531 CPLErr PNGDataset::LoadInterlacedChunk( int iLine )
532 
533 {
534     const int nPixelOffset =
535         ( nBitDepth == 16 ) ? 2 * GetRasterCount() : GetRasterCount();
536 
537     // What is the biggest chunk we can safely operate on?
538     constexpr int MAX_PNG_CHUNK_BYTES = 100000000;
539 
540     int nMaxChunkLines =
541         std::max(1, MAX_PNG_CHUNK_BYTES / (nPixelOffset * GetRasterXSize()));
542 
543     if( nMaxChunkLines > GetRasterYSize() )
544         nMaxChunkLines = GetRasterYSize();
545 
546     // Allocate chunk buffer if we don't already have it from a previous
547     // request.
548     nBufferLines = nMaxChunkLines;
549     if( nMaxChunkLines + iLine > GetRasterYSize() )
550         nBufferStartLine = GetRasterYSize() - nMaxChunkLines;
551     else
552         nBufferStartLine = iLine;
553 
554     if( pabyBuffer == nullptr )
555     {
556       pabyBuffer = reinterpret_cast<GByte *>(
557           VSI_MALLOC_VERBOSE(nPixelOffset*GetRasterXSize()*nMaxChunkLines) );
558 
559         if( pabyBuffer == nullptr )
560         {
561             return CE_Failure;
562         }
563 #ifdef notdef
564         if( nMaxChunkLines < GetRasterYSize() )
565             CPLDebug( "PNG",
566                       "Interlaced file being handled in %d line chunks.\n"
567                       "Performance is likely to be quite poor.",
568                       nMaxChunkLines );
569 #endif
570     }
571 
572     // Do we need to restart reading? We do this if we aren't on the first
573     // attempt to read the image.
574     if( nLastLineRead != -1 )
575     {
576         Restart();
577     }
578 
579     // Allocate and populate rows array. We create a row for each row in the
580     // image but use our dummy line for rows not in the target window.
581     png_bytep dummy_row = reinterpret_cast<png_bytep>(
582         CPLMalloc(nPixelOffset*GetRasterXSize()) );
583     png_bytep *png_rows
584         = reinterpret_cast<png_bytep *>(
585             CPLMalloc(sizeof(png_bytep) * GetRasterYSize()) );
586 
587     for( int i = 0; i < GetRasterYSize(); i++ )
588     {
589         if( i >= nBufferStartLine && i < nBufferStartLine + nBufferLines )
590             png_rows[i] = pabyBuffer
591                 + (i-nBufferStartLine) * nPixelOffset * GetRasterXSize();
592         else
593             png_rows[i] = dummy_row;
594     }
595 
596     bool bRet = safe_png_read_image( hPNG, png_rows, sSetJmpContext );
597 
598     CPLFree( png_rows );
599     CPLFree( dummy_row );
600     if( !bRet )
601         return CE_Failure;
602 
603     nLastLineRead = nBufferStartLine + nBufferLines - 1;
604 
605     return CE_None;
606 }
607 
608 /************************************************************************/
609 /*                        safe_png_read_rows()                          */
610 /************************************************************************/
611 
safe_png_read_rows(png_structp hPNG,png_bytep row,jmp_buf sSetJmpContext)612 static bool safe_png_read_rows(png_structp hPNG,
613                                 png_bytep  row,
614                                 jmp_buf    sSetJmpContext)
615 {
616     if( setjmp( sSetJmpContext ) != 0 )
617         return false;
618     png_read_rows( hPNG, &row, nullptr, 1 );
619     return true;
620 }
621 
622 /************************************************************************/
623 /*                            LoadScanline()                            */
624 /************************************************************************/
625 
LoadScanline(int nLine)626 CPLErr PNGDataset::LoadScanline( int nLine )
627 
628 {
629     CPLAssert( nLine >= 0 && nLine < GetRasterYSize() );
630 
631     if( nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines)
632         return CE_None;
633 
634     const int nPixelOffset =
635         ( nBitDepth == 16 ) ? 2 * GetRasterCount() : GetRasterCount();
636 
637     // If the file is interlaced, we load the entire image into memory using the
638     // high-level API.
639     if( bInterlaced )
640         return LoadInterlacedChunk( nLine );
641 
642     // Ensure we have space allocated for one scanline.
643     if( pabyBuffer == nullptr )
644         pabyBuffer = reinterpret_cast<GByte *>(
645             CPLMalloc(nPixelOffset * GetRasterXSize() ) );
646 
647     // Otherwise we just try to read the requested row. Do we need to rewind and
648     // start over?
649     if( nLine <= nLastLineRead )
650     {
651         Restart();
652     }
653 
654     // Read till we get the desired row.
655     png_bytep row = pabyBuffer;
656     const GUInt32 nErrorCounter = CPLGetErrorCounter();
657     while( nLine > nLastLineRead )
658     {
659         if( !safe_png_read_rows( hPNG, row, sSetJmpContext ) )
660         {
661             CPLError(CE_Failure, CPLE_AppDefined,
662                      "Error while reading row %d%s", nLine,
663                      (nErrorCounter != CPLGetErrorCounter()) ?
664                         CPLSPrintf(": %s", CPLGetLastErrorMsg()) : "");
665             return CE_Failure;
666         }
667         nLastLineRead++;
668     }
669 
670     nBufferStartLine = nLine;
671     nBufferLines = 1;
672 
673      // Do swap on LSB machines. 16-bit PNG data is stored in MSB format.
674 #ifdef CPL_LSB
675     if( nBitDepth == 16 )
676         GDALSwapWords( row, 2, GetRasterXSize() * GetRasterCount(), 2 );
677 #endif
678 
679     return CE_None;
680 }
681 
682 /************************************************************************/
683 /*                          CollectMetadata()                           */
684 /*                                                                      */
685 /*      We normally do this after reading up to the image, but be       */
686 /*      forewarned: we can miss text chunks this way.                   */
687 /*                                                                      */
688 /*      We turn each PNG text chunk into one metadata item.  It         */
689 /*      might be nice to preserve language information though we        */
690 /*      don't try to now.                                               */
691 /************************************************************************/
692 
CollectMetadata()693 void PNGDataset::CollectMetadata()
694 
695 {
696     if( nBitDepth < 8 )
697     {
698         for( int iBand = 0; iBand < nBands; iBand++ )
699         {
700             GetRasterBand(iBand+1)->SetMetadataItem(
701                 "NBITS", CPLString().Printf( "%d", nBitDepth ),
702                 "IMAGE_STRUCTURE" );
703         }
704     }
705 
706     int nTextCount;
707     png_textp text_ptr;
708     if( png_get_text( hPNG, psPNGInfo, &text_ptr, &nTextCount ) == 0 )
709         return;
710 
711     for( int iText = 0; iText < nTextCount; iText++ )
712     {
713         char *pszTag = CPLStrdup(text_ptr[iText].key);
714 
715         for( int i = 0; pszTag[i] != '\0'; i++ )
716         {
717             if( pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':' )
718                 pszTag[i] = '_';
719         }
720 
721         GDALDataset::SetMetadataItem( pszTag, text_ptr[iText].text );
722         CPLFree( pszTag );
723     }
724 }
725 
726 /************************************************************************/
727 /*                       CollectXMPMetadata()                           */
728 /************************************************************************/
729 
730 // See §2.1.5 of
731 // http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf.
732 
CollectXMPMetadata()733 void PNGDataset::CollectXMPMetadata()
734 
735 {
736     if (fpImage == nullptr || bHasReadXMPMetadata)
737         return;
738 
739     // Save current position to avoid disturbing PNG stream decoding.
740     const vsi_l_offset nCurOffset = VSIFTellL(fpImage);
741 
742     vsi_l_offset nOffset = 8;
743     VSIFSeekL( fpImage, nOffset, SEEK_SET );
744 
745     // Loop over chunks.
746     while( true )
747     {
748         int nLength;
749 
750         if (VSIFReadL( &nLength, 4, 1, fpImage ) != 1)
751             break;
752         nOffset += 4;
753         CPL_MSBPTR32(&nLength);
754         if (nLength <= 0)
755             break;
756 
757         char pszChunkType[5];
758         if (VSIFReadL( pszChunkType, 4, 1, fpImage ) != 1)
759             break;
760         nOffset += 4;
761         pszChunkType[4] = 0;
762 
763         if (strcmp(pszChunkType, "iTXt") == 0 && nLength > 22  &&
764             // Does not make sense to have a XMP content larger than 10 MB
765             // (XMP in JPEG must fit in 65 KB...)
766             nLength < 10 * 1024 * 1024)
767         {
768             char* pszContent = reinterpret_cast<char *>(
769                 VSIMalloc(nLength + 1) );
770             if (pszContent == nullptr)
771                 break;
772             if (VSIFReadL( pszContent, nLength, 1, fpImage) != 1)
773             {
774                 VSIFree(pszContent);
775                 break;
776             }
777             nOffset += nLength;
778             pszContent[nLength] = '\0';
779             if (memcmp(pszContent, "XML:com.adobe.xmp\0\0\0\0\0", 22) == 0)
780             {
781                 // Avoid setting the PAM dirty bit just for that.
782                 int nOldPamFlags = nPamFlags;
783 
784                 char *apszMDList[2] = { pszContent + 22, nullptr };
785                 SetMetadata(apszMDList, "xml:XMP");
786 
787                 nPamFlags = nOldPamFlags;
788 
789                 VSIFree(pszContent);
790 
791                 break;
792             }
793             else
794             {
795                 VSIFree(pszContent);
796             }
797         }
798         else
799         {
800             nOffset += nLength;
801             VSIFSeekL( fpImage, nOffset, SEEK_SET );
802         }
803 
804         nOffset += 4;
805         int nCRC;
806         if (VSIFReadL( &nCRC, 4, 1, fpImage ) != 1)
807             break;
808     }
809 
810     VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
811 
812     bHasReadXMPMetadata = TRUE;
813 }
814 
815 /************************************************************************/
816 /*                           LoadICCProfile()                           */
817 /************************************************************************/
818 
LoadICCProfile()819 void PNGDataset::LoadICCProfile()
820 {
821     if (hPNG == nullptr || bHasReadICCMetadata)
822         return;
823     bHasReadICCMetadata = TRUE;
824 
825     png_charp pszProfileName;
826     png_uint_32 nProfileLength;
827 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
828     png_bytep pProfileData;
829 #else
830     png_charp pProfileData;
831 #endif
832     int nCompressionType;
833 
834     // Avoid setting the PAM dirty bit just for that.
835     int nOldPamFlags = nPamFlags;
836 
837     if (png_get_iCCP(hPNG, psPNGInfo, &pszProfileName,
838        &nCompressionType, &pProfileData, &nProfileLength) != 0)
839     {
840         // Escape the profile.
841         char *pszBase64Profile = CPLBase64Encode(
842             static_cast<int>(nProfileLength), reinterpret_cast<const GByte *>( pProfileData ) );
843 
844         // Set ICC profile metadata.
845         SetMetadataItem( "SOURCE_ICC_PROFILE", pszBase64Profile, "COLOR_PROFILE" );
846         SetMetadataItem( "SOURCE_ICC_PROFILE_NAME", pszProfileName, "COLOR_PROFILE" );
847 
848         nPamFlags = nOldPamFlags;
849 
850         CPLFree(pszBase64Profile);
851 
852         return;
853     }
854 
855     int nsRGBIntent;
856     if (png_get_sRGB(hPNG, psPNGInfo, &nsRGBIntent) != 0)
857     {
858         SetMetadataItem( "SOURCE_ICC_PROFILE_NAME", "sRGB", "COLOR_PROFILE" );
859 
860         nPamFlags = nOldPamFlags;
861 
862         return;
863     }
864 
865     double dfGamma;
866     bool bGammaAvailable = false;
867     if (png_get_valid(hPNG, psPNGInfo, PNG_INFO_gAMA))
868     {
869         bGammaAvailable = true;
870 
871         png_get_gAMA(hPNG,psPNGInfo, &dfGamma);
872 
873         SetMetadataItem( "PNG_GAMMA",
874             CPLString().Printf( "%.9f", dfGamma ) , "COLOR_PROFILE" );
875     }
876 
877     // Check that both cHRM and gAMA are available.
878     if (bGammaAvailable && png_get_valid(hPNG, psPNGInfo, PNG_INFO_cHRM))
879     {
880         double dfaWhitepoint[2];
881         double dfaCHR[6];
882 
883         png_get_cHRM(hPNG, psPNGInfo,
884                     &dfaWhitepoint[0], &dfaWhitepoint[1],
885                     &dfaCHR[0], &dfaCHR[1],
886                     &dfaCHR[2], &dfaCHR[3],
887                     &dfaCHR[4], &dfaCHR[5]);
888 
889         // Set all the colorimetric metadata.
890         SetMetadataItem( "SOURCE_PRIMARIES_RED",
891             CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[0], dfaCHR[1] ) , "COLOR_PROFILE" );
892         SetMetadataItem( "SOURCE_PRIMARIES_GREEN",
893             CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[2], dfaCHR[3] ) , "COLOR_PROFILE" );
894         SetMetadataItem( "SOURCE_PRIMARIES_BLUE",
895             CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[4], dfaCHR[5] ) , "COLOR_PROFILE" );
896 
897         SetMetadataItem( "SOURCE_WHITEPOINT",
898             CPLString().Printf( "%.9f, %.9f, 1.0", dfaWhitepoint[0], dfaWhitepoint[1] ) , "COLOR_PROFILE" );
899     }
900 
901     nPamFlags = nOldPamFlags;
902 }
903 
904 /************************************************************************/
905 /*                      GetMetadataDomainList()                         */
906 /************************************************************************/
907 
GetMetadataDomainList()908 char **PNGDataset::GetMetadataDomainList()
909 {
910     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
911                                    TRUE,
912                                    "xml:XMP", "COLOR_PROFILE", nullptr);
913 }
914 
915 /************************************************************************/
916 /*                           GetMetadata()                              */
917 /************************************************************************/
918 
GetMetadata(const char * pszDomain)919 char  **PNGDataset::GetMetadata( const char * pszDomain )
920 {
921     if (fpImage == nullptr)
922         return nullptr;
923     if (eAccess == GA_ReadOnly && !bHasReadXMPMetadata &&
924         pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP"))
925         CollectXMPMetadata();
926     if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
927         pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
928         LoadICCProfile();
929     return GDALPamDataset::GetMetadata(pszDomain);
930 }
931 
932 /************************************************************************/
933 /*                       GetMetadataItem()                              */
934 /************************************************************************/
GetMetadataItem(const char * pszName,const char * pszDomain)935 const char *PNGDataset::GetMetadataItem( const char * pszName,
936                                          const char * pszDomain )
937 {
938     if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
939         pszDomain != nullptr && EQUAL(pszDomain, "COLOR_PROFILE"))
940         LoadICCProfile();
941     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
942 }
943 
944 /************************************************************************/
945 /*                              Identify()                              */
946 /************************************************************************/
947 
Identify(GDALOpenInfo * poOpenInfo)948 int PNGDataset::Identify( GDALOpenInfo * poOpenInfo )
949 
950 {
951     if( poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 4 )
952         return FALSE;
953 
954     if( png_sig_cmp(poOpenInfo->pabyHeader, static_cast<png_size_t>( 0 ),
955                     poOpenInfo->nHeaderBytes) != 0 )
956         return FALSE;
957 
958     return TRUE;
959 }
960 
961 /************************************************************************/
962 /*                                Open()                                */
963 /************************************************************************/
964 
Open(GDALOpenInfo * poOpenInfo)965 GDALDataset *PNGDataset::Open( GDALOpenInfo * poOpenInfo )
966 
967 {
968 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
969     // During fuzzing, do not use Identify to reject crazy content.
970     if( !Identify( poOpenInfo ) )
971         return nullptr;
972 #else
973     if( poOpenInfo->fpL == nullptr )
974         return nullptr;
975 #endif
976 
977     if( poOpenInfo->eAccess == GA_Update )
978     {
979         CPLError( CE_Failure, CPLE_NotSupported,
980                   "The PNG driver does not support update access to existing"
981                   " datasets.\n" );
982         return nullptr;
983     }
984 
985     // Create a corresponding GDALDataset.
986     PNGDataset *poDS = new PNGDataset();
987     return OpenStage2( poOpenInfo, poDS );
988 }
989 
OpenStage2(GDALOpenInfo * poOpenInfo,PNGDataset * & poDS)990 GDALDataset *PNGDataset::OpenStage2( GDALOpenInfo * poOpenInfo, PNGDataset*& poDS )
991 
992 {
993     poDS->fpImage = poOpenInfo->fpL;
994     poOpenInfo->fpL = nullptr;
995     poDS->eAccess = poOpenInfo->eAccess;
996 
997     poDS->hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, poDS,
998                                          nullptr, nullptr );
999     if (poDS->hPNG == nullptr)
1000     {
1001 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 2) || PNG_LIBPNG_VER_MAJOR > 1
1002         int version = static_cast<int>(png_access_version_number());
1003         CPLError( CE_Failure, CPLE_NotSupported,
1004                   "The PNG driver failed to access libpng with version '%s',"
1005                   " library is actually version '%d'.\n",
1006                   PNG_LIBPNG_VER_STRING, version);
1007 #else
1008         CPLError( CE_Failure, CPLE_NotSupported,
1009                   "The PNG driver failed to in png_create_read_struct().\n"
1010                   "This may be due to version compatibility problems." );
1011 #endif
1012         delete poDS;
1013         return nullptr;
1014     }
1015 
1016 #ifdef DISABLE_CRC_CHECK
1017     PNGDatasetDisableCRCCheck( poDS->hPNG );
1018 #endif
1019 
1020     poDS->psPNGInfo = png_create_info_struct( poDS->hPNG );
1021 
1022     // Set up error handling.
1023     png_set_error_fn( poDS->hPNG, &poDS->sSetJmpContext, png_gdal_error, png_gdal_warning );
1024 
1025     if( setjmp( poDS->sSetJmpContext ) != 0 )
1026     {
1027         delete poDS;
1028         return nullptr;
1029     }
1030 
1031     // Read pre-image data after ensuring the file is rewound.
1032     // We should likely do a setjmp() here.
1033 
1034     png_set_read_fn( poDS->hPNG, poDS->fpImage, png_vsi_read_data );
1035     png_read_info( poDS->hPNG, poDS->psPNGInfo );
1036 
1037     // Capture some information from the file that is of interest.
1038     poDS->nRasterXSize = static_cast<int>(png_get_image_width( poDS->hPNG, poDS->psPNGInfo));
1039     poDS->nRasterYSize = static_cast<int>(png_get_image_height( poDS->hPNG,poDS->psPNGInfo));
1040 
1041     poDS->nBands = png_get_channels( poDS->hPNG, poDS->psPNGInfo );
1042     poDS->nBitDepth = png_get_bit_depth( poDS->hPNG, poDS->psPNGInfo );
1043     poDS->bInterlaced = png_get_interlace_type( poDS->hPNG, poDS->psPNGInfo )
1044         != PNG_INTERLACE_NONE;
1045 
1046     poDS->nColorType = png_get_color_type( poDS->hPNG, poDS->psPNGInfo );
1047 
1048     if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE
1049         && poDS->nBands > 1 )
1050     {
1051         CPLDebug( "GDAL", "PNG Driver got %d from png_get_channels(),\n"
1052                   "but this kind of image (paletted) can only have one band.\n"
1053                   "Correcting and continuing, but this may indicate a bug!",
1054                   poDS->nBands );
1055         poDS->nBands = 1;
1056     }
1057 
1058     // We want to treat 1-, 2-, and 4-bit images as eight bit. This call causes
1059     // libpng to unpack the image.
1060     if( poDS->nBitDepth < 8 )
1061         png_set_packing( poDS->hPNG );
1062 
1063     // Create band information objects.
1064     for( int iBand = 0; iBand < poDS->nBands; iBand++ )
1065         poDS->SetBand( iBand+1, new PNGRasterBand( poDS, iBand+1 ) );
1066 
1067     // Is there a palette?  Note: we should also read back and apply
1068     // transparency values if available.
1069     if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE )
1070     {
1071         png_color *pasPNGPalette = nullptr;
1072         int nColorCount = 0;
1073 
1074         if( png_get_PLTE( poDS->hPNG, poDS->psPNGInfo,
1075                           &pasPNGPalette, &nColorCount ) == 0 )
1076             nColorCount = 0;
1077 
1078         unsigned char *trans = nullptr;
1079         png_color_16 *trans_values = nullptr;
1080         int num_trans = 0;
1081         png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
1082                       &trans, &num_trans, &trans_values );
1083 
1084         poDS->poColorTable = new GDALColorTable();
1085 
1086         GDALColorEntry oEntry;
1087         int nNoDataIndex = -1;
1088         for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
1089         {
1090             oEntry.c1 = pasPNGPalette[iColor].red;
1091             oEntry.c2 = pasPNGPalette[iColor].green;
1092             oEntry.c3 = pasPNGPalette[iColor].blue;
1093 
1094             if( iColor < num_trans )
1095             {
1096                 oEntry.c4 = trans[iColor];
1097                 if( oEntry.c4 == 0 )
1098                 {
1099                     if( nNoDataIndex == -1 )
1100                         nNoDataIndex = iColor;
1101                     else
1102                         nNoDataIndex = -2;
1103                 }
1104             }
1105             else
1106                 oEntry.c4 = 255;
1107 
1108             poDS->poColorTable->SetColorEntry( iColor, &oEntry );
1109         }
1110 
1111         // Special hack to use an index as the no data value, as long as it is
1112         // the only transparent color in the palette.
1113         if( nNoDataIndex > -1 )
1114         {
1115             poDS->GetRasterBand(1)->SetNoDataValue(nNoDataIndex);
1116         }
1117     }
1118 
1119     // Check for transparency values in greyscale images.
1120     if( poDS->nColorType == PNG_COLOR_TYPE_GRAY )
1121     {
1122         png_color_16 *trans_values = nullptr;
1123         unsigned char *trans;
1124         int num_trans;
1125 
1126         if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
1127                           &trans, &num_trans, &trans_values ) != 0
1128             && trans_values != nullptr )
1129         {
1130             poDS->GetRasterBand(1)->SetNoDataValue(trans_values->gray);
1131         }
1132     }
1133 
1134     // Check for nodata color for RGB images.
1135     if( poDS->nColorType == PNG_COLOR_TYPE_RGB )
1136     {
1137         png_color_16 *trans_values = nullptr;
1138         unsigned char *trans;
1139         int num_trans;
1140 
1141         if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
1142                           &trans, &num_trans, &trans_values ) != 0
1143             && trans_values != nullptr )
1144         {
1145             CPLString oNDValue;
1146 
1147             oNDValue.Printf( "%d %d %d",
1148                     trans_values->red,
1149                     trans_values->green,
1150                     trans_values->blue );
1151             poDS->SetMetadataItem( "NODATA_VALUES", oNDValue.c_str() );
1152 
1153             poDS->GetRasterBand(1)->SetNoDataValue(trans_values->red);
1154             poDS->GetRasterBand(2)->SetNoDataValue(trans_values->green);
1155             poDS->GetRasterBand(3)->SetNoDataValue(trans_values->blue);
1156         }
1157     }
1158 
1159     // Extract any text chunks as "metadata."
1160     poDS->CollectMetadata();
1161 
1162     // More metadata.
1163     if( poDS->nBands > 1 )
1164     {
1165         poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
1166     }
1167 
1168     // Initialize any PAM information.
1169     poDS->SetDescription( poOpenInfo->pszFilename );
1170     poDS->TryLoadXML( poOpenInfo->GetSiblingFiles() );
1171 
1172     // Open overviews.
1173     poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename,
1174                                  poOpenInfo->GetSiblingFiles() );
1175 
1176     return poDS;
1177 }
1178 
1179 /************************************************************************/
1180 /*                        LoadWorldFile()                               */
1181 /************************************************************************/
1182 
LoadWorldFile()1183 void PNGDataset::LoadWorldFile()
1184 {
1185     if (bHasTriedLoadWorldFile)
1186         return;
1187     bHasTriedLoadWorldFile = TRUE;
1188 
1189     char* pszWldFilename = nullptr;
1190     bGeoTransformValid =
1191         GDALReadWorldFile2( GetDescription(), nullptr,
1192                             adfGeoTransform, oOvManager.GetSiblingFiles(),
1193                             &pszWldFilename);
1194 
1195     if( !bGeoTransformValid )
1196         bGeoTransformValid =
1197             GDALReadWorldFile2( GetDescription(), ".wld",
1198                                 adfGeoTransform, oOvManager.GetSiblingFiles(),
1199                                 &pszWldFilename);
1200 
1201     if (pszWldFilename)
1202     {
1203         osWldFilename = pszWldFilename;
1204         CPLFree(pszWldFilename);
1205     }
1206 }
1207 
1208 /************************************************************************/
1209 /*                            GetFileList()                             */
1210 /************************************************************************/
1211 
GetFileList()1212 char **PNGDataset::GetFileList()
1213 
1214 {
1215     char **papszFileList = GDALPamDataset::GetFileList();
1216 
1217     LoadWorldFile();
1218 
1219     if (!osWldFilename.empty() &&
1220         CSLFindString(papszFileList, osWldFilename) == -1)
1221     {
1222         papszFileList = CSLAddString( papszFileList, osWldFilename );
1223     }
1224 
1225     return papszFileList;
1226 }
1227 
1228 /************************************************************************/
1229 /*                          WriteMetadataAsText()                       */
1230 /************************************************************************/
1231 
1232 #if defined(PNG_iTXt_SUPPORTED) || ((PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4) || PNG_LIBPNG_VER_MAJOR > 1)
1233 #define HAVE_ITXT_SUPPORT
1234 #endif
1235 
1236 #ifdef HAVE_ITXT_SUPPORT
IsASCII(const char * pszStr)1237 static bool IsASCII(const char* pszStr)
1238 {
1239     for(int i=0;pszStr[i]!='\0';i++)
1240     {
1241         if( reinterpret_cast<GByte *>(
1242             const_cast<char *>( pszStr ) )[i] >= 128 )
1243             return false;
1244     }
1245     return true;
1246 }
1247 #endif
1248 
safe_png_set_text(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr,png_const_textp text_ptr,int num_text)1249 static bool safe_png_set_text(jmp_buf sSetJmpContext,
1250                                    png_structp png_ptr,
1251                                    png_infop info_ptr,
1252 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) || PNG_LIBPNG_VER_MAJOR > 1
1253                                    png_const_textp text_ptr,
1254 #else
1255                                    png_textp text_ptr,
1256 #endif
1257                                    int num_text)
1258 {
1259     if( setjmp( sSetJmpContext ) != 0 )
1260     {
1261         return false;
1262     }
1263     png_set_text(png_ptr, info_ptr, text_ptr, num_text);
1264     return true;
1265 }
1266 
WriteMetadataAsText(jmp_buf sSetJmpContext,png_structp hPNG,png_infop psPNGInfo,const char * pszKey,const char * pszValue)1267 void PNGDataset::WriteMetadataAsText(jmp_buf sSetJmpContext,
1268                                      png_structp hPNG, png_infop psPNGInfo,
1269                                      const char* pszKey, const char* pszValue)
1270 {
1271     png_text sText;
1272     memset(&sText, 0, sizeof(png_text));
1273     sText.compression = PNG_TEXT_COMPRESSION_NONE;
1274     sText.key = (png_charp) pszKey;
1275     sText.text = (png_charp) pszValue;
1276 #ifdef HAVE_ITXT_SUPPORT
1277     // UTF-8 values should be written in iTXt, whereas TEXT should be LATIN-1.
1278     if( !IsASCII(pszValue) && CPLIsUTF8(pszValue, -1) )
1279         sText.compression = PNG_ITXT_COMPRESSION_NONE;
1280 #endif
1281     safe_png_set_text(sSetJmpContext, hPNG, psPNGInfo, &sText, 1);
1282 }
1283 
1284 static
safe_png_set_IHDR(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr,png_uint_32 width,png_uint_32 height,int bit_depth,int color_type,int interlace_type,int compression_type,int filter_type)1285 bool safe_png_set_IHDR(jmp_buf sSetJmpContext,
1286                   png_structp png_ptr, png_infop info_ptr, png_uint_32 width,
1287                   png_uint_32 height, int bit_depth, int color_type,
1288                   int interlace_type, int compression_type, int filter_type)
1289 {
1290     if( setjmp( sSetJmpContext ) != 0 )
1291     {
1292         return false;
1293     }
1294     png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
1295                  color_type, interlace_type, compression_type, filter_type);
1296     return true;
1297 }
1298 
safe_png_set_compression_level(jmp_buf sSetJmpContext,png_structp png_ptr,int level)1299 static bool safe_png_set_compression_level(jmp_buf sSetJmpContext,
1300                                            png_structp png_ptr, int level)
1301 {
1302     if( setjmp( sSetJmpContext ) != 0 )
1303     {
1304         return false;
1305     }
1306     png_set_compression_level(png_ptr, level);
1307     return true;
1308 }
1309 
safe_png_set_tRNS(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr,png_const_bytep trans,int num_trans,png_color_16p trans_values)1310 static bool safe_png_set_tRNS(jmp_buf sSetJmpContext,
1311                               png_structp png_ptr,
1312                               png_infop info_ptr,
1313 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1314                               png_const_bytep trans,
1315 #else
1316                               png_bytep trans,
1317 #endif
1318                               int num_trans,
1319                               png_color_16p trans_values)
1320 {
1321     if( setjmp( sSetJmpContext ) != 0 )
1322     {
1323         return false;
1324     }
1325     png_set_tRNS(png_ptr, info_ptr, trans, num_trans, trans_values);
1326     return true;
1327 }
1328 
safe_png_set_iCCP(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr,png_const_charp name,int compression_type,png_const_bytep profile,png_uint_32 proflen)1329 static bool safe_png_set_iCCP(jmp_buf sSetJmpContext,
1330                               png_structp png_ptr,
1331                               png_infop info_ptr,
1332 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1333                               png_const_charp name,
1334 #else
1335                               png_charp name,
1336 #endif
1337                               int compression_type,
1338 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1339                               png_const_bytep profile,
1340 #else
1341                               png_charp profile,
1342 #endif
1343                               png_uint_32 proflen)
1344 {
1345     if( setjmp( sSetJmpContext ) != 0 )
1346     {
1347         return false;
1348     }
1349     png_set_iCCP(png_ptr, info_ptr, name, compression_type, profile, proflen);
1350     return true;
1351 }
1352 
safe_png_set_PLTE(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr,png_const_colorp palette,int num_palette)1353 static bool safe_png_set_PLTE(jmp_buf sSetJmpContext,
1354                               png_structp png_ptr,
1355                               png_infop info_ptr,
1356 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1357                               png_const_colorp palette,
1358 #else
1359                               png_colorp palette,
1360 #endif
1361                               int num_palette)
1362 {
1363     if( setjmp( sSetJmpContext ) != 0 )
1364     {
1365         return false;
1366     }
1367     png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
1368     return true;
1369 }
1370 
safe_png_write_info(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr)1371 static bool safe_png_write_info(jmp_buf sSetJmpContext,
1372                                 png_structp png_ptr,
1373                                 png_infop info_ptr)
1374 {
1375     if( setjmp( sSetJmpContext ) != 0 )
1376     {
1377         return false;
1378     }
1379     png_write_info(png_ptr, info_ptr);
1380     return true;
1381 }
1382 
safe_png_write_rows(jmp_buf sSetJmpContext,png_structp png_ptr,png_bytepp row,png_uint_32 num_rows)1383 static bool safe_png_write_rows(jmp_buf sSetJmpContext,
1384                                 png_structp png_ptr,
1385                                 png_bytepp row,
1386                                 png_uint_32 num_rows)
1387 {
1388     if( setjmp( sSetJmpContext ) != 0 )
1389     {
1390         return false;
1391     }
1392     png_write_rows(png_ptr, row, num_rows);
1393     return true;
1394 }
1395 
safe_png_write_end(jmp_buf sSetJmpContext,png_structp png_ptr,png_infop info_ptr)1396 static bool safe_png_write_end(jmp_buf sSetJmpContext,
1397                                 png_structp png_ptr,
1398                                 png_infop info_ptr)
1399 {
1400     if( setjmp( sSetJmpContext ) != 0 )
1401     {
1402         return false;
1403     }
1404     png_write_end(png_ptr, info_ptr);
1405     return true;
1406 }
1407 
1408 /************************************************************************/
1409 /*                             CreateCopy()                             */
1410 /************************************************************************/
1411 
1412 GDALDataset *
CreateCopy(const char * pszFilename,GDALDataset * poSrcDS,int bStrict,char ** papszOptions,GDALProgressFunc pfnProgress,void * pProgressData)1413 PNGDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
1414                int bStrict, char ** papszOptions,
1415                GDALProgressFunc pfnProgress, void * pProgressData )
1416 
1417 {
1418     // Perform some rudimentary checks.
1419     const int nBands = poSrcDS->GetRasterCount();
1420     if( nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4 )
1421     {
1422         CPLError( CE_Failure, CPLE_NotSupported,
1423                   "PNG driver doesn't support %d bands.  Must be 1 (grey),\n"
1424                   "2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n",
1425                   nBands );
1426 
1427         return nullptr;
1428     }
1429 
1430     if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte
1431         && poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 )
1432     {
1433         CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
1434                   "PNG driver doesn't support data type %s. "
1435                   "Only eight bit (Byte) and sixteen bit (UInt16) bands supported. %s\n",
1436                   GDALGetDataTypeName(
1437                       poSrcDS->GetRasterBand(1)->GetRasterDataType()),
1438                   (bStrict) ? "" : "Defaulting to Byte" );
1439 
1440         if (bStrict)
1441             return nullptr;
1442     }
1443 
1444     // Create the dataset.
1445     VSILFILE *fpImage = VSIFOpenL( pszFilename, "wb" );
1446     if( fpImage == nullptr )
1447     {
1448         CPLError( CE_Failure, CPLE_OpenFailed,
1449                   "Unable to create png file %s.\n",
1450                   pszFilename );
1451         return nullptr;
1452     }
1453 
1454     // Initialize PNG access to the file.
1455     jmp_buf     sSetJmpContext;
1456 
1457     png_structp hPNG = png_create_write_struct(
1458         PNG_LIBPNG_VER_STRING, &sSetJmpContext, png_gdal_error, png_gdal_warning );
1459     png_infop  psPNGInfo = png_create_info_struct( hPNG );
1460 
1461     // Set up some parameters.
1462     int  nColorType=0;
1463 
1464     if( nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr )
1465         nColorType = PNG_COLOR_TYPE_GRAY;
1466     else if( nBands == 1 )
1467         nColorType = PNG_COLOR_TYPE_PALETTE;
1468     else if( nBands == 2 )
1469         nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
1470     else if( nBands == 3 )
1471         nColorType = PNG_COLOR_TYPE_RGB;
1472     else if( nBands == 4 )
1473         nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
1474 
1475     int nBitDepth;
1476     GDALDataType eType;
1477     if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 )
1478     {
1479         eType = GDT_Byte;
1480         nBitDepth = 8;
1481         if( nBands == 1 )
1482         {
1483             const char* pszNbits = poSrcDS->GetRasterBand(1)->GetMetadataItem(
1484                                                     "NBITS", "IMAGE_STRUCTURE");
1485             if( pszNbits != nullptr )
1486             {
1487                 nBitDepth = atoi(pszNbits);
1488                 if( !(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4) )
1489                     nBitDepth = 8;
1490             }
1491         }
1492     }
1493     else
1494     {
1495         eType = GDT_UInt16;
1496         nBitDepth = 16;
1497     }
1498 
1499     const char* pszNbits = CSLFetchNameValue(papszOptions, "NBITS");
1500     if( eType == GDT_Byte && pszNbits != nullptr )
1501     {
1502         nBitDepth = atoi(pszNbits);
1503         if( !(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4 || nBitDepth == 8) )
1504         {
1505             CPLError(CE_Warning, CPLE_NotSupported, "Invalid bit depth. Using 8");
1506             nBitDepth = 8;
1507         }
1508     }
1509 
1510     png_set_write_fn( hPNG, fpImage, png_vsi_write_data, png_vsi_flush );
1511 
1512     const int nXSize = poSrcDS->GetRasterXSize();
1513     const int nYSize = poSrcDS->GetRasterYSize();
1514 
1515     if( !safe_png_set_IHDR( sSetJmpContext, hPNG, psPNGInfo, nXSize, nYSize,
1516                   nBitDepth, nColorType, PNG_INTERLACE_NONE,
1517                   PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE ) )
1518     {
1519         VSIFCloseL( fpImage );
1520         png_destroy_write_struct( &hPNG, &psPNGInfo );
1521         return nullptr;
1522     }
1523 
1524     // Do we want to control the compression level?
1525     const char *pszLevel = CSLFetchNameValue( papszOptions, "ZLEVEL" );
1526 
1527     if( pszLevel )
1528     {
1529         const int nLevel = atoi(pszLevel);
1530         if( nLevel < 1 || nLevel > 9 )
1531         {
1532             CPLError( CE_Failure, CPLE_AppDefined,
1533                       "Illegal ZLEVEL value '%s', should be 1-9.",
1534                       pszLevel );
1535             VSIFCloseL( fpImage );
1536             png_destroy_write_struct( &hPNG, &psPNGInfo );
1537             return nullptr;
1538         }
1539 
1540         if( !safe_png_set_compression_level( sSetJmpContext, hPNG, nLevel ) )
1541         {
1542             VSIFCloseL( fpImage );
1543             png_destroy_write_struct( &hPNG, &psPNGInfo );
1544             return nullptr;
1545         }
1546     }
1547 
1548     // Try to handle nodata values as a tRNS block (note that for paletted
1549     // images, we save the effect to apply as part of palette).
1550     png_color_16 sTRNSColor;
1551 
1552     // Gray nodata.
1553     if( nColorType == PNG_COLOR_TYPE_GRAY )
1554     {
1555        int bHaveNoData = FALSE;
1556        const double dfNoDataValue
1557            = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1558 
1559        if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 )
1560        {
1561           sTRNSColor.gray = (png_uint_16) dfNoDataValue;
1562           if( !safe_png_set_tRNS( sSetJmpContext, hPNG, psPNGInfo, nullptr, 0, &sTRNSColor ) )
1563           {
1564                 VSIFCloseL( fpImage );
1565                 png_destroy_write_struct( &hPNG, &psPNGInfo );
1566                 return nullptr;
1567           }
1568        }
1569     }
1570 
1571     // RGB nodata.
1572     if( nColorType == PNG_COLOR_TYPE_RGB )
1573     {
1574        // First try to use the NODATA_VALUES metadata item.
1575        if ( poSrcDS->GetMetadataItem( "NODATA_VALUES" ) != nullptr )
1576        {
1577            char **papszValues = CSLTokenizeString(
1578                poSrcDS->GetMetadataItem( "NODATA_VALUES" ) );
1579 
1580            if( CSLCount(papszValues) >= 3 )
1581            {
1582                sTRNSColor.red   = (png_uint_16) atoi(papszValues[0]);
1583                sTRNSColor.green = (png_uint_16) atoi(papszValues[1]);
1584                sTRNSColor.blue  = (png_uint_16) atoi(papszValues[2]);
1585                if( !safe_png_set_tRNS( sSetJmpContext, hPNG, psPNGInfo, nullptr, 0, &sTRNSColor ) )
1586                {
1587                     VSIFCloseL( fpImage );
1588                     png_destroy_write_struct( &hPNG, &psPNGInfo );
1589                     CSLDestroy( papszValues );
1590                     return nullptr;
1591                }
1592            }
1593 
1594            CSLDestroy( papszValues );
1595        }
1596        // Otherwise, get the nodata value from the bands.
1597        else
1598        {
1599           int bHaveNoDataRed = FALSE;
1600           const double dfNoDataValueRed
1601               = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed );
1602 
1603           int bHaveNoDataGreen = FALSE;
1604           const double dfNoDataValueGreen
1605               = poSrcDS->GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen );
1606 
1607           int bHaveNoDataBlue = FALSE;
1608           const double dfNoDataValueBlue
1609               = poSrcDS->GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue );
1610 
1611           if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) &&
1612                ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) &&
1613                ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) )
1614           {
1615              sTRNSColor.red   = static_cast<png_uint_16>( dfNoDataValueRed );
1616              sTRNSColor.green = static_cast<png_uint_16>( dfNoDataValueGreen );
1617              sTRNSColor.blue  = static_cast<png_uint_16>( dfNoDataValueBlue );
1618              if( !safe_png_set_tRNS( sSetJmpContext, hPNG, psPNGInfo, nullptr, 0, &sTRNSColor ) )
1619              {
1620                 VSIFCloseL( fpImage );
1621                 png_destroy_write_struct( &hPNG, &psPNGInfo );
1622                 return nullptr;
1623              }
1624           }
1625        }
1626     }
1627 
1628     // Copy color profile data.
1629     const char *pszICCProfile = CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
1630     const char *pszICCProfileName = CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE_NAME");
1631     if (pszICCProfileName == nullptr)
1632         pszICCProfileName = poSrcDS->GetMetadataItem( "SOURCE_ICC_PROFILE_NAME", "COLOR_PROFILE" );
1633 
1634     if (pszICCProfile == nullptr)
1635         pszICCProfile = poSrcDS->GetMetadataItem( "SOURCE_ICC_PROFILE", "COLOR_PROFILE" );
1636 
1637     if ((pszICCProfileName != nullptr) && EQUAL(pszICCProfileName, "sRGB"))
1638     {
1639         pszICCProfile = nullptr;
1640 
1641         // assumes this can't fail ?
1642         png_set_sRGB(hPNG, psPNGInfo, PNG_sRGB_INTENT_PERCEPTUAL);
1643     }
1644 
1645     if (pszICCProfile != nullptr)
1646     {
1647         char *pEmbedBuffer = CPLStrdup(pszICCProfile);
1648         png_uint_32 nEmbedLen
1649             = CPLBase64DecodeInPlace(reinterpret_cast<GByte *>( pEmbedBuffer ) );
1650         const char* pszLocalICCProfileName = (pszICCProfileName!=nullptr)?pszICCProfileName:"ICC Profile";
1651 
1652         if( !safe_png_set_iCCP( sSetJmpContext, hPNG, psPNGInfo,
1653 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1654             pszLocalICCProfileName,
1655 #else
1656             (png_charp)pszLocalICCProfileName,
1657 #endif
1658             0,
1659 #if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1
1660             (png_const_bytep)pEmbedBuffer,
1661 #else
1662             (png_charp)pEmbedBuffer,
1663 #endif
1664             nEmbedLen) )
1665         {
1666             CPLFree(pEmbedBuffer);
1667             VSIFCloseL( fpImage );
1668             png_destroy_write_struct( &hPNG, &psPNGInfo );
1669             return nullptr;
1670         }
1671 
1672         CPLFree(pEmbedBuffer);
1673     }
1674     else if ((pszICCProfileName == nullptr) || !EQUAL(pszICCProfileName, "sRGB"))
1675     {
1676         // Output gamma, primaries and whitepoint.
1677         const char *pszGamma = CSLFetchNameValue(papszOptions, "PNG_GAMMA");
1678         if (pszGamma == nullptr)
1679             pszGamma = poSrcDS->GetMetadataItem( "PNG_GAMMA", "COLOR_PROFILE" );
1680 
1681         if (pszGamma != nullptr)
1682         {
1683             double dfGamma = CPLAtof(pszGamma);
1684             // assumes this can't fail ?
1685             png_set_gAMA(hPNG, psPNGInfo, dfGamma);
1686         }
1687 
1688         const char *pszPrimariesRed = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_RED");
1689         if (pszPrimariesRed == nullptr)
1690             pszPrimariesRed = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_RED", "COLOR_PROFILE" );
1691         const char *pszPrimariesGreen = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_GREEN");
1692         if (pszPrimariesGreen == nullptr)
1693             pszPrimariesGreen = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_GREEN", "COLOR_PROFILE" );
1694         const char *pszPrimariesBlue = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_BLUE");
1695         if (pszPrimariesBlue == nullptr)
1696             pszPrimariesBlue = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_BLUE", "COLOR_PROFILE" );
1697         const char *pszWhitepoint = CSLFetchNameValue(papszOptions, "SOURCE_WHITEPOINT");
1698         if (pszWhitepoint == nullptr)
1699             pszWhitepoint = poSrcDS->GetMetadataItem( "SOURCE_WHITEPOINT", "COLOR_PROFILE" );
1700 
1701         if ((pszPrimariesRed != nullptr) && (pszPrimariesGreen != nullptr) && (pszPrimariesBlue != nullptr) &&
1702             (pszWhitepoint != nullptr))
1703         {
1704             bool bOk = true;
1705             double faColour[8] = { 0.0 };
1706             char** apapszTokenList[4] = { nullptr };
1707 
1708             apapszTokenList[0] = CSLTokenizeString2( pszWhitepoint, ",",
1709                 CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
1710             apapszTokenList[1] = CSLTokenizeString2( pszPrimariesRed, ",",
1711                 CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
1712             apapszTokenList[2] = CSLTokenizeString2( pszPrimariesGreen, ",",
1713                 CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
1714             apapszTokenList[3] = CSLTokenizeString2( pszPrimariesBlue, ",",
1715                 CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
1716 
1717             if ((CSLCount( apapszTokenList[0] ) == 3) &&
1718                 (CSLCount( apapszTokenList[1] ) == 3) &&
1719                 (CSLCount( apapszTokenList[2] ) == 3) &&
1720                 (CSLCount( apapszTokenList[3] ) == 3))
1721             {
1722                 for( int i = 0; i < 4; i++ )
1723                 {
1724                     for( int j = 0; j < 3; j++ )
1725                     {
1726                         const double v = CPLAtof(apapszTokenList[i][j]);
1727 
1728                         if (j == 2)
1729                         {
1730                             /* Last term of xyY colour must be 1.0 */
1731                             if (v != 1.0)
1732                             {
1733                                 bOk = false;
1734                                 break;
1735                             }
1736                         }
1737                         else
1738                         {
1739                             faColour[i*2 + j] = v;
1740                         }
1741                     }
1742                     if (!bOk)
1743                         break;
1744                 }
1745 
1746                 if (bOk)
1747                 {
1748                     // assumes this can't fail ?
1749                     png_set_cHRM(hPNG, psPNGInfo,
1750                         faColour[0], faColour[1],
1751                         faColour[2], faColour[3],
1752                         faColour[4], faColour[5],
1753                         faColour[6], faColour[7]);
1754                 }
1755             }
1756 
1757             CSLDestroy( apapszTokenList[0] );
1758             CSLDestroy( apapszTokenList[1] );
1759             CSLDestroy( apapszTokenList[2] );
1760             CSLDestroy( apapszTokenList[3] );
1761         }
1762     }
1763 
1764     // Write the palette if there is one. Technically, it may be possible to
1765     // write 16-bit palettes for PNG, but for now, this is omitted.
1766     if( nColorType == PNG_COLOR_TYPE_PALETTE )
1767     {
1768         int bHaveNoData = FALSE;
1769         double dfNoDataValue
1770             = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1771 
1772         GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
1773 
1774         int nEntryCount = poCT->GetColorEntryCount();
1775         int nMaxEntryCount = 1 << nBitDepth;
1776         if( nEntryCount > nMaxEntryCount )
1777             nEntryCount = nMaxEntryCount;
1778 
1779         png_color *pasPNGColors = reinterpret_cast<png_color *>(
1780             CPLMalloc( sizeof(png_color) * nEntryCount ) );
1781 
1782         GDALColorEntry sEntry;
1783         bool bFoundTrans = false;
1784         for( int iColor = 0; iColor < nEntryCount; iColor++ )
1785         {
1786             poCT->GetColorEntryAsRGB( iColor, &sEntry );
1787             if( sEntry.c4 != 255 )
1788                 bFoundTrans = true;
1789 
1790             pasPNGColors[iColor].red = static_cast<png_byte>( sEntry.c1 );
1791             pasPNGColors[iColor].green = static_cast<png_byte>( sEntry.c2 );
1792             pasPNGColors[iColor].blue = static_cast<png_byte>( sEntry.c3 );
1793         }
1794 
1795         if( !safe_png_set_PLTE( sSetJmpContext, hPNG, psPNGInfo, pasPNGColors,
1796                                 nEntryCount ) )
1797         {
1798             CPLFree( pasPNGColors );
1799             VSIFCloseL( fpImage );
1800             png_destroy_write_struct( &hPNG, &psPNGInfo );
1801             return nullptr;
1802         }
1803 
1804         CPLFree( pasPNGColors );
1805 
1806         // If we have transparent elements in the palette, we need to write a
1807         // transparency block.
1808         if( bFoundTrans || bHaveNoData )
1809         {
1810             unsigned char *pabyAlpha
1811                 = reinterpret_cast<unsigned char *>(
1812                     CPLMalloc(nEntryCount) );
1813 
1814             for( int iColor = 0; iColor < nEntryCount; iColor++ )
1815             {
1816                 poCT->GetColorEntryAsRGB( iColor, &sEntry );
1817                 pabyAlpha[iColor] = static_cast<unsigned char>( sEntry.c4 );
1818 
1819                 if( bHaveNoData && iColor == static_cast<int>( dfNoDataValue ) )
1820                     pabyAlpha[iColor] = 0;
1821             }
1822 
1823             if( !safe_png_set_tRNS( sSetJmpContext, hPNG, psPNGInfo, pabyAlpha,
1824                           nEntryCount, nullptr ) )
1825             {
1826                 CPLFree( pabyAlpha );
1827                 VSIFCloseL( fpImage );
1828                 png_destroy_write_struct( &hPNG, &psPNGInfo );
1829                 return nullptr;
1830             }
1831 
1832             CPLFree( pabyAlpha );
1833         }
1834     }
1835 
1836     // Add text info.
1837     // These are predefined keywords. See "4.2.7 tEXt Textual data" of
1838     // http://www.w3.org/TR/PNG-Chunks.html for more information.
1839     const char* apszKeywords[] = { "Title", "Author", "Description", "Copyright",
1840                                    "Creation Time", "Software", "Disclaimer",
1841                                    "Warning", "Source", "Comment", nullptr };
1842     const bool bWriteMetadataAsText = CPLTestBool(
1843         CSLFetchNameValueDef(papszOptions, "WRITE_METADATA_AS_TEXT", "FALSE"));
1844     for(int i=0;apszKeywords[i]!=nullptr;i++)
1845     {
1846         const char* pszKey = apszKeywords[i];
1847         const char* pszValue = CSLFetchNameValue(papszOptions, pszKey);
1848         if( pszValue == nullptr && bWriteMetadataAsText )
1849             pszValue = poSrcDS->GetMetadataItem(pszKey);
1850         if( pszValue != nullptr )
1851         {
1852             WriteMetadataAsText(sSetJmpContext, hPNG, psPNGInfo, pszKey, pszValue);
1853         }
1854     }
1855     if( bWriteMetadataAsText )
1856     {
1857         char** papszSrcMD = poSrcDS->GetMetadata();
1858         for( ; papszSrcMD && *papszSrcMD; papszSrcMD++ )
1859         {
1860             char* pszKey = nullptr;
1861             const char* pszValue = CPLParseNameValue(*papszSrcMD, &pszKey );
1862             if( pszKey && pszValue )
1863             {
1864                 if( CSLFindString(const_cast<char**>( apszKeywords ), pszKey) < 0 &&
1865                     !EQUAL(pszKey, "AREA_OR_POINT") && !EQUAL(pszKey, "NODATA_VALUES") )
1866                 {
1867                     WriteMetadataAsText(sSetJmpContext,hPNG, psPNGInfo, pszKey, pszValue);
1868                 }
1869                 CPLFree(pszKey);
1870             }
1871         }
1872     }
1873 
1874     // Write the PNG info.
1875     if( !safe_png_write_info( sSetJmpContext, hPNG, psPNGInfo ) )
1876     {
1877         VSIFCloseL( fpImage );
1878         png_destroy_write_struct( &hPNG, &psPNGInfo );
1879         return nullptr;
1880     }
1881 
1882     if( nBitDepth < 8 )
1883     {
1884         // Assumes this can't fail
1885         png_set_packing( hPNG );
1886     }
1887 
1888     // Loop over the image, copying image data.
1889     CPLErr      eErr = CE_None;
1890     const int nWordSize = GDALGetDataTypeSize(eType) / 8;
1891 
1892     GByte *pabyScanline = reinterpret_cast<GByte *>(
1893         CPLMalloc( nBands * nXSize * nWordSize ) );
1894 
1895     for( int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++ )
1896     {
1897         png_bytep       row = pabyScanline;
1898 
1899         eErr = poSrcDS->RasterIO( GF_Read, 0, iLine, nXSize, 1,
1900                                   pabyScanline,
1901                                   nXSize, 1, eType,
1902                                   nBands, nullptr,
1903                                   nBands * nWordSize,
1904                                   nBands * nXSize * nWordSize,
1905                                   nWordSize,
1906                                   nullptr );
1907 
1908 #ifdef CPL_LSB
1909         if( nBitDepth == 16 )
1910             GDALSwapWords( row, 2, nXSize * nBands, 2 );
1911 #endif
1912         if( eErr == CE_None )
1913         {
1914             if( !safe_png_write_rows( sSetJmpContext, hPNG, &row, 1 ) )
1915             {
1916                 eErr = CE_Failure;
1917             }
1918         }
1919 
1920         if( eErr == CE_None
1921             && !pfnProgress( (iLine+1) / static_cast<double>( nYSize ),
1922                              nullptr, pProgressData ) )
1923         {
1924             eErr = CE_Failure;
1925             CPLError( CE_Failure, CPLE_UserInterrupt,
1926                       "User terminated CreateCopy()" );
1927         }
1928     }
1929 
1930     CPLFree( pabyScanline );
1931 
1932     if( !safe_png_write_end( sSetJmpContext, hPNG, psPNGInfo ) )
1933     {
1934         eErr = CE_Failure;
1935     }
1936     png_destroy_write_struct( &hPNG, &psPNGInfo );
1937 
1938     VSIFCloseL( fpImage );
1939 
1940     if( eErr != CE_None )
1941         return nullptr;
1942 
1943     // Do we need a world file?
1944     if( CPLFetchBool( papszOptions, "WORLDFILE", false ) )
1945     {
1946       double adfGeoTransform[6];
1947 
1948       if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None )
1949         GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
1950     }
1951 
1952     // Re-open dataset and copy any auxiliary PAM information.
1953 
1954     /* If writing to stdout, we can't reopen it, so return */
1955     /* a fake dataset to make the caller happy */
1956     if( CPLTestBool(CPLGetConfigOption("GDAL_OPEN_AFTER_COPY", "YES")) )
1957     {
1958         CPLPushErrorHandler(CPLQuietErrorHandler);
1959         GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1960         PNGDataset *poDS = reinterpret_cast<PNGDataset *>(
1961             PNGDataset::Open( &oOpenInfo ) );
1962         CPLPopErrorHandler();
1963         if( poDS )
1964         {
1965             int nFlags = GCIF_PAM_DEFAULT;
1966             if( bWriteMetadataAsText )
1967                 nFlags &= ~GCIF_METADATA;
1968             poDS->CloneInfo( poSrcDS, nFlags );
1969             return poDS;
1970         }
1971         CPLErrorReset();
1972     }
1973 
1974     PNGDataset* poPNG_DS = new PNGDataset();
1975     poPNG_DS->nRasterXSize = nXSize;
1976     poPNG_DS->nRasterYSize = nYSize;
1977     poPNG_DS->nBitDepth = nBitDepth;
1978     for(int i=0;i<nBands;i++)
1979         poPNG_DS->SetBand( i+1, new PNGRasterBand( poPNG_DS, i+1) );
1980     return poPNG_DS;
1981 }
1982 
1983 /************************************************************************/
1984 /*                         png_vsi_read_data()                          */
1985 /*                                                                      */
1986 /*      Read data callback through VSI.                                 */
1987 /************************************************************************/
1988 static void
png_vsi_read_data(png_structp png_ptr,png_bytep data,png_size_t length)1989 png_vsi_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
1990 
1991 {
1992     // fread() returns 0 on error, so it is OK to store this in a png_size_t
1993     // instead of an int, which is what fread() actually returns.
1994     const png_size_t check
1995         = static_cast<png_size_t>(
1996             VSIFReadL(data, (png_size_t)1, length,
1997                       reinterpret_cast<VSILFILE *>( png_get_io_ptr(png_ptr) ) ) );
1998 
1999     if (check != length)
2000         png_error(png_ptr, "Read Error");
2001 }
2002 
2003 /************************************************************************/
2004 /*                         png_vsi_write_data()                         */
2005 /************************************************************************/
2006 
2007 static void
png_vsi_write_data(png_structp png_ptr,png_bytep data,png_size_t length)2008 png_vsi_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
2009 {
2010     const size_t check
2011         = VSIFWriteL(data, 1, length, reinterpret_cast<VSILFILE *>(
2012             png_get_io_ptr(png_ptr) ) );
2013 
2014     if (check != length)
2015       png_error(png_ptr, "Write Error");
2016 }
2017 
2018 /************************************************************************/
2019 /*                           png_vsi_flush()                            */
2020 /************************************************************************/
png_vsi_flush(png_structp png_ptr)2021 static void png_vsi_flush(png_structp png_ptr)
2022 {
2023     VSIFFlushL( reinterpret_cast<VSILFILE *>( png_get_io_ptr(png_ptr) ) );
2024 }
2025 
2026 /************************************************************************/
2027 /*                           png_gdal_error()                           */
2028 /************************************************************************/
2029 
png_gdal_error(png_structp png_ptr,const char * error_message)2030 static void png_gdal_error( png_structp png_ptr, const char *error_message )
2031 {
2032     CPLError( CE_Failure, CPLE_AppDefined,
2033               "libpng: %s", error_message );
2034 
2035     // Use longjmp instead of a C++ exception, because libpng is generally not
2036     // built as C++ and so will not honor unwind semantics.
2037 
2038     jmp_buf* psSetJmpContext = reinterpret_cast<jmp_buf *>(
2039         png_get_error_ptr( png_ptr ) );
2040     if (psSetJmpContext)
2041     {
2042         longjmp( *psSetJmpContext, 1 );
2043     }
2044 }
2045 
2046 /************************************************************************/
2047 /*                          png_gdal_warning()                          */
2048 /************************************************************************/
2049 
png_gdal_warning(CPL_UNUSED png_structp png_ptr,const char * error_message)2050 static void png_gdal_warning( CPL_UNUSED png_structp png_ptr,
2051                               const char *error_message )
2052 {
2053     CPLError( CE_Warning, CPLE_AppDefined,
2054               "libpng: %s", error_message );
2055 }
2056 
2057 /************************************************************************/
2058 /*                          GDALRegister_PNG()                          */
2059 /************************************************************************/
2060 
GDALRegister_PNG()2061 void GDALRegister_PNG()
2062 
2063 {
2064     if( GDALGetDriverByName( "PNG" ) != nullptr )
2065         return;
2066 
2067     GDALDriver *poDriver = new GDALDriver();
2068 
2069     poDriver->SetDescription( "PNG" );
2070     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
2071     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
2072                                "Portable Network Graphics" );
2073     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
2074                                "drivers/raster/png.html" );
2075     poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "png" );
2076     poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/png" );
2077 
2078     poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
2079                                "Byte UInt16" );
2080     poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
2081 "<CreationOptionList>\n"
2082 "   <Option name='WORLDFILE' type='boolean' description='Create world file' default='FALSE'/>\n"
2083 "   <Option name='ZLEVEL' type='int' description='DEFLATE compression level 1-9' default='6'/>\n"
2084 "   <Option name='SOURCE_ICC_PROFILE' type='string' description='ICC Profile'/>\n"
2085 "   <Option name='SOURCE_ICC_PROFILE_NAME' type='string' description='ICC Profile name'/>\n"
2086 "   <Option name='SOURCE_PRIMARIES_RED' type='string' description='x,y,1.0 (xyY) red chromaticity'/>\n"
2087 "   <Option name='SOURCE_PRIMARIES_GREEN' type='string' description='x,y,1.0 (xyY) green chromaticity'/>\n"
2088 "   <Option name='SOURCE_PRIMARIES_BLUE' type='string' description='x,y,1.0 (xyY) blue chromaticity'/>\n"
2089 "   <Option name='SOURCE_WHITEPOINT' type='string' description='x,y,1.0 (xyY) whitepoint'/>\n"
2090 "   <Option name='PNG_GAMMA' type='string' description='Gamma'/>\n"
2091 "   <Option name='TITLE' type='string' description='Title'/>\n"
2092 "   <Option name='DESCRIPTION' type='string' description='Description'/>\n"
2093 "   <Option name='COPYRIGHT' type='string' description='Copyright'/>\n"
2094 "   <Option name='COMMENT' type='string' description='Comment'/>\n"
2095 "   <Option name='WRITE_METADATA_AS_TEXT' type='boolean' description='Whether to write source dataset metadata in TEXT chunks' default='FALSE'/>\n"
2096 "   <Option name='NBITS' type='int' description='Force output bit depth: 1, 2 or 4'/>\n"
2097 "</CreationOptionList>\n" );
2098 
2099     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
2100 
2101     poDriver->pfnOpen = PNGDataset::Open;
2102     poDriver->pfnCreateCopy = PNGDataset::CreateCopy;
2103     poDriver->pfnIdentify = PNGDataset::Identify;
2104 #ifdef SUPPORT_CREATE
2105     poDriver->pfnCreate = PNGDataset::Create;
2106 #endif
2107 
2108     GetGDALDriverManager()->RegisterDriver( poDriver );
2109 }
2110 
2111 #ifdef SUPPORT_CREATE
2112 /************************************************************************/
2113 /*                         IWriteBlock()                                */
2114 /************************************************************************/
2115 
IWriteBlock(int x,int y,void * pvData)2116 CPLErr PNGRasterBand::IWriteBlock(int x, int y, void* pvData)
2117 {
2118     PNGDataset& ds = *reinterpret_cast<PNGDataset*>( poDS );
2119 
2120     // Write the block (or consolidate into multichannel block) and then write.
2121 
2122     const GDALDataType dt = GetRasterDataType();
2123     const size_t wordsize = ds.m_nBitDepth / 8;
2124     GDALCopyWords( pvData, dt, wordsize,
2125                    ds.m_pabyBuffer + (nBand-1) * wordsize,
2126                    dt, ds.nBands * wordsize,
2127                    nBlockXSize );
2128 
2129     // See if we have all the bands.
2130     m_bBandProvided[nBand - 1] = TRUE;
2131     for( size_t i = 0; i < static_cast<size_t>( ds.nBands ); i++ )
2132     {
2133         if(!m_bBandProvided[i])
2134             return CE_None;
2135     }
2136 
2137     // We received all the bands, so reset band flags and write pixels out.
2138     this->reset_band_provision_flags();
2139 
2140     // If it is the first block, write out the file header.
2141     if(x == 0 && y == 0)
2142     {
2143         CPLErr err = ds.write_png_header();
2144         if(err != CE_None)
2145             return err;
2146     }
2147 
2148 #ifdef CPL_LSB
2149     if( ds.m_nBitDepth == 16 )
2150         GDALSwapWords( ds.m_pabyBuffer, 2, nBlockXSize * ds.nBands, 2 );
2151 #endif
2152     png_write_rows( ds.m_hPNG, &ds.m_pabyBuffer, 1 );
2153 
2154     return CE_None;
2155 }
2156 
2157 /************************************************************************/
2158 /*                          SetGeoTransform()                           */
2159 /************************************************************************/
2160 
SetGeoTransform(double * padfTransform)2161 CPLErr PNGDataset::SetGeoTransform( double * padfTransform )
2162 {
2163     memcpy( m_adfGeoTransform, padfTransform, sizeof(double) * 6 );
2164 
2165     if ( m_pszFilename )
2166     {
2167         if ( GDALWriteWorldFile( m_pszFilename, "wld", m_adfGeoTransform )
2168              == FALSE )
2169         {
2170             CPLError( CE_Failure, CPLE_FileIO, "Can't write world file." );
2171             return CE_Failure;
2172         }
2173     }
2174 
2175     return CE_None;
2176 }
2177 
2178 /************************************************************************/
2179 /*                           SetColorTable()                            */
2180 /************************************************************************/
2181 
SetColorTable(GDALColorTable * poCT)2182 CPLErr PNGRasterBand::SetColorTable(GDALColorTable* poCT)
2183 {
2184     if( poCT == NULL )
2185         return CE_Failure;
2186 
2187     // We get called even for grayscale files, since some formats need a palette
2188     // even then. PNG doesn't, so if a gray palette is given, just ignore it.
2189 
2190     GDALColorEntry sEntry;
2191     for( size_t i = 0; i < static_cast<size_t>( poCT->GetColorEntryCount() ); i++ )
2192     {
2193         poCT->GetColorEntryAsRGB( i, &sEntry );
2194         if( sEntry.c1 != sEntry.c2 || sEntry.c1 != sEntry.c3)
2195         {
2196             CPLErr err = GDALPamRasterBand::SetColorTable(poCT);
2197             if(err != CE_None)
2198                 return err;
2199 
2200             PNGDataset& ds = *reinterpret_cast<PNGDataset *>( poDS );
2201             ds.m_nColorType = PNG_COLOR_TYPE_PALETTE;
2202             break;
2203             // band::IWriteBlock will emit color table as part of the header
2204             // preceding the first block write.
2205         }
2206     }
2207 
2208     return CE_None;
2209 }
2210 
2211 /************************************************************************/
2212 /*                  PNGDataset::write_png_header()                      */
2213 /************************************************************************/
2214 
write_png_header()2215 CPLErr PNGDataset::write_png_header()
2216 {
2217     // Initialize PNG access to the file.
2218     m_hPNG = png_create_write_struct(
2219         PNG_LIBPNG_VER_STRING, NULL,
2220         png_gdal_error, png_gdal_warning );
2221 
2222     m_psPNGInfo = png_create_info_struct( m_hPNG );
2223 
2224     png_set_write_fn( m_hPNG, m_fpImage, png_vsi_write_data, png_vsi_flush );
2225 
2226     png_set_IHDR( m_hPNG, m_psPNGInfo, nRasterXSize, nRasterYSize,
2227                   m_nBitDepth, m_nColorType, PNG_INTERLACE_NONE,
2228                   PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT );
2229 
2230     png_set_compression_level(m_hPNG, Z_BEST_COMPRESSION);
2231 
2232     // png_set_swap_alpha(m_hPNG); // Use RGBA order, not ARGB.
2233 
2234     // Try to handle nodata values as a tRNS block (note that for paletted
2235     // images, we save the effect to apply as part of the palette).
2236     //m_bHaveNoData = FALSE;
2237     //m_dfNoDataValue = -1;
2238     png_color_16 sTRNSColor;
2239 
2240     int bHaveNoData = FALSE;
2241     double dfNoDataValue = -1;
2242 
2243     if( m_nColorType == PNG_COLOR_TYPE_GRAY )
2244     {
2245         dfNoDataValue = GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
2246 
2247         if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 )
2248         {
2249             sTRNSColor.gray = static_cast<png_uint_16>( dfNoDataValue );
2250             png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
2251         }
2252     }
2253 
2254     // RGB nodata.
2255     if( nColorType == PNG_COLOR_TYPE_RGB )
2256     {
2257         // First, try to use the NODATA_VALUES metadata item.
2258         if ( GetMetadataItem( "NODATA_VALUES" ) != NULL )
2259         {
2260             char **papszValues = CSLTokenizeString(
2261                 GetMetadataItem( "NODATA_VALUES" ) );
2262 
2263             if( CSLCount(papszValues) >= 3 )
2264             {
2265                 sTRNSColor.red   = static_cast<png_uint_16>( atoi(papszValues[0] ) );
2266                 sTRNSColor.green = static_cast<png_uint_16>( atoi(papszValues[1] ) );
2267                 sTRNSColor.blue  = static_cast<png_uint_16>( atoi(papszValues[2] ) );
2268                 png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
2269             }
2270 
2271             CSLDestroy( papszValues );
2272         }
2273         // Otherwise, get the nodata value from the bands.
2274         else
2275         {
2276             int bHaveNoDataRed = FALSE;
2277             const double dfNoDataValueRed
2278                 = GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed );
2279 
2280             int bHaveNoDataGreen = FALSE;
2281             const double dfNoDataValueGreen
2282                 = GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen );
2283 
2284             int bHaveNoDataBlue = FALSE;
2285             const double dfNoDataValueBlue
2286                 = GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue );
2287 
2288             if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) &&
2289                  ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) &&
2290                  ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) )
2291             {
2292                 sTRNSColor.red   = static_cast<png_uint_16>( dfNoDataValueRed );
2293                 sTRNSColor.green = static_cast<png_uint_16>( dfNoDataValueGreen );
2294                 sTRNSColor.blue  = static_cast<png_uint_16>( dfNoDataValueBlue );
2295                 png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
2296             }
2297         }
2298     }
2299 
2300     // Write the palette if there is one. Technically, it may be possible
2301     // to write 16-bit palettes for PNG, but for now, doing so is omitted.
2302     if( nColorType == PNG_COLOR_TYPE_PALETTE )
2303     {
2304         GDALColorTable *poCT = GetRasterBand(1)->GetColorTable();
2305 
2306         int bHaveNoData = FALSE;
2307         double dfNoDataValue = GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
2308 
2309         m_pasPNGColors = reinterpret_cast<png_color *>(
2310             CPLMalloc( sizeof(png_color) * poCT->GetColorEntryCount() ) );
2311 
2312         GDALColorEntry sEntry;
2313         bool bFoundTrans = false;
2314         for( int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
2315         {
2316             poCT->GetColorEntryAsRGB( iColor, &sEntry );
2317             if( sEntry.c4 != 255 )
2318                 bFoundTrans = true;
2319 
2320             m_pasPNGColors[iColor].red   = static_cast<png_byte>( sEntry.c1 );
2321             m_pasPNGColors[iColor].green = static_cast<png_byte>( sEntry.c2 );
2322             m_pasPNGColors[iColor].blue  = static_cast<png_byte>( sEntry.c3 );
2323         }
2324 
2325         png_set_PLTE( m_hPNG, m_psPNGInfo, m_pasPNGColors,
2326                       poCT->GetColorEntryCount() );
2327 
2328         // If we have transparent elements in the palette, we need to write a
2329         // transparency block.
2330         if( bFoundTrans || bHaveNoData )
2331         {
2332             m_pabyAlpha = reinterpret_cast<unsigned char *>(
2333                 CPLMalloc(poCT->GetColorEntryCount() ) );
2334 
2335             for( int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
2336             {
2337                 poCT->GetColorEntryAsRGB( iColor, &sEntry );
2338                 m_pabyAlpha[iColor] = static_cast<unsigned char>( sEntry.c4 );
2339 
2340                 if( bHaveNoData && iColor == static_cast<int>( dfNoDataValue ) )
2341                     m_pabyAlpha[iColor] = 0;
2342             }
2343 
2344             png_set_tRNS( m_hPNG, m_psPNGInfo, m_pabyAlpha,
2345                           poCT->GetColorEntryCount(), NULL );
2346         }
2347     }
2348 
2349     png_write_info( m_hPNG, m_psPNGInfo );
2350     return CE_None;
2351 }
2352 
2353 /************************************************************************/
2354 /*                               Create()                               */
2355 /************************************************************************/
2356 
Create(const char * pszFilename,int nXSize,int nYSize,int nBands,GDALDataType eType,char ** papszOptions)2357 GDALDataset *PNGDataset::Create
2358 (
2359     const char* pszFilename,
2360     int nXSize, int nYSize,
2361     int nBands,
2362     GDALDataType eType,
2363     char **papszOptions
2364 )
2365 {
2366     if( eType != GDT_Byte && eType != GDT_UInt16)
2367     {
2368         CPLError( CE_Failure, CPLE_AppDefined,
2369                   "Attempt to create PNG dataset with an illegal\n"
2370                   "data type (%s), only Byte and UInt16 supported by the format.\n",
2371                   GDALGetDataTypeName(eType) );
2372 
2373         return NULL;
2374     }
2375 
2376     if( nBands < 1 || nBands > 4 )
2377     {
2378         CPLError( CE_Failure, CPLE_NotSupported,
2379                   "PNG driver doesn't support %d bands. "
2380                   "Must be 1 (gray/indexed color),\n"
2381                   "2 (gray+alpha), 3 (rgb) or 4 (rgba) bands.\n",
2382                   nBands );
2383 
2384         return NULL;
2385     }
2386 
2387     // Bands are:
2388     // 1: Grayscale or indexed color.
2389     // 2: Gray plus alpha.
2390     // 3: RGB.
2391     // 4: RGB plus alpha.
2392 
2393     if(nXSize < 1 || nYSize < 1)
2394     {
2395         CPLError( CE_Failure, CPLE_NotSupported,
2396                   "Specified pixel dimensions (% d x %d) are bad.\n",
2397                   nXSize, nYSize );
2398     }
2399 
2400     // Set up some parameters.
2401     PNGDataset* poDS = new PNGDataset();
2402 
2403     poDS->nRasterXSize = nXSize;
2404     poDS->nRasterYSize = nYSize;
2405     poDS->eAccess = GA_Update;
2406     poDS->nBands = nBands;
2407 
2408     switch(nBands)
2409     {
2410       case 1:
2411         poDS->m_nColorType = PNG_COLOR_TYPE_GRAY;
2412         break;  // If a non-gray palette is set, we'll change this.
2413 
2414       case 2:
2415         poDS->m_nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
2416         break;
2417 
2418       case 3:
2419         poDS->m_nColorType = PNG_COLOR_TYPE_RGB;
2420         break;
2421 
2422       case 4:
2423         poDS->m_nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
2424         break;
2425     }
2426 
2427     poDS->m_nBitDepth = (eType == GDT_Byte ? 8 : 16);
2428 
2429     poDS->m_pabyBuffer = reinterpret_cast<GByte *>(
2430         CPLMalloc( nBands * nXSize * poDS->m_nBitDepth / 8 ) );
2431 
2432     // Create band information objects.
2433     for( int iBand = 1; iBand <= poDS->nBands; iBand++ )
2434         poDS->SetBand( iBand, new PNGRasterBand( poDS, iBand ) );
2435 
2436     // Do we need a world file?
2437     if( CPLFetchBool( papszOptions, "WORLDFILE", false ) )
2438         poDS->m_bGeoTransformValid = TRUE;
2439 
2440     // Create the file.
2441 
2442     poDS->m_fpImage = VSIFOpenL( pszFilename, "wb" );
2443     if( poDS->m_fpImage == NULL )
2444     {
2445         CPLError( CE_Failure, CPLE_OpenFailed,
2446                   "Unable to create PNG file %s.\n",
2447                   pszFilename );
2448         delete poDS;
2449         return NULL;
2450     }
2451 
2452     poDS->m_pszFilename = CPLStrdup(pszFilename);
2453 
2454     return poDS;
2455 }
2456 
2457 #endif
2458