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