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