1 : /******************************************************************************
2 : * $Id: pngdataset.cpp 18008 2009-11-12 22:16:00Z rouault $
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 : *
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 1, 2 and 4 bit data promoted to 8 bit.
39 : * o Transparency values not currently read and applied to palette.
40 : * o 16 bit alpha values are not scaled by to eight bit.
41 : * o I should install setjmp()/longjmp() based error trapping for PNG calls.
42 : * Currently a failure in png libraries will result in a complete
43 : * application termination.
44 : *
45 : */
46 :
47 : #include "gdal_pam.h"
48 : #include "png.h"
49 : #include "cpl_string.h"
50 : #include <setjmp.h>
51 :
52 : CPL_CVSID("$Id: pngdataset.cpp 18008 2009-11-12 22:16:00Z rouault $");
53 :
54 : CPL_C_START
55 : void GDALRegister_PNG(void);
56 : CPL_C_END
57 :
58 : // Define SUPPORT_CREATE if you want Create() call supported.
59 : // Note: callers must provide blocks in increasing Y order.
60 :
61 : // Disclaimer (E. Rouault) : this code is NOT production ready at all.
62 : // A lot of issues remains : uninitialized variables, unclosed file,
63 : // inability to handle properly multiband case, inability to read&write
64 : // at the same time. Do NOT use it unless you're ready to fix it
65 : //#define SUPPORT_CREATE
66 :
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 : /* PNGDataset */
82 : /* ==================================================================== */
83 : /************************************************************************/
84 :
85 : class PNGRasterBand;
86 :
87 : class PNGDataset : public GDALPamDataset
88 : {
89 : friend class PNGRasterBand;
90 :
91 : FILE *fpImage;
92 : png_structp hPNG;
93 : png_infop psPNGInfo;
94 : int nBitDepth;
95 : int nColorType; /* PNG_COLOR_TYPE_* */
96 : int bInterlaced;
97 :
98 : int nBufferStartLine;
99 : int nBufferLines;
100 : int nLastLineRead;
101 : GByte *pabyBuffer;
102 :
103 : GDALColorTable *poColorTable;
104 :
105 : int bGeoTransformValid;
106 : double adfGeoTransform[6];
107 :
108 :
109 : void CollectMetadata();
110 : CPLErr LoadScanline( int );
111 : CPLErr LoadInterlacedChunk( int );
112 : void Restart();
113 :
114 : public:
115 : PNGDataset();
116 : ~PNGDataset();
117 :
118 : static GDALDataset *Open( GDALOpenInfo * );
119 : static int Identify( GDALOpenInfo * );
120 :
121 : virtual CPLErr GetGeoTransform( double * );
122 : virtual void FlushCache( void );
123 :
124 : // semi-private.
125 : jmp_buf sSetJmpContext;
126 :
127 : #ifdef SUPPORT_CREATE
128 : int m_nBitDepth;
129 : GByte *m_pabyBuffer;
130 : png_byte *m_pabyAlpha;
131 : png_structp m_hPNG;
132 : png_infop m_psPNGInfo;
133 : png_color *m_pasPNGColors;
134 : FILE *m_fpImage;
135 : int m_bGeoTransformValid;
136 : double m_adfGeoTransform[6];
137 : char *m_pszFilename;
138 : int m_nColorType; /* PNG_COLOR_TYPE_* */
139 :
140 : virtual CPLErr SetGeoTransform( double * );
141 : static GDALDataset *Create( const char* pszFilename,
142 : int nXSize, int nYSize, int nBands,
143 : GDALDataType, char** papszParmList );
144 : protected:
145 : CPLErr write_png_header();
146 :
147 : #endif
148 : };
149 :
150 : /************************************************************************/
151 : /* ==================================================================== */
152 : /* PNGRasterBand */
153 : /* ==================================================================== */
154 : /************************************************************************/
155 :
156 : class PNGRasterBand : public GDALPamRasterBand
157 113 : {
158 : friend class PNGDataset;
159 :
160 : public:
161 :
162 : PNGRasterBand( PNGDataset *, int );
163 :
164 : virtual CPLErr IReadBlock( int, int, void * );
165 :
166 : virtual GDALColorInterp GetColorInterpretation();
167 : virtual GDALColorTable *GetColorTable();
168 : CPLErr SetNoDataValue( double dfNewValue );
169 : virtual double GetNoDataValue( int *pbSuccess = NULL );
170 :
171 : int bHaveNoData;
172 : double dfNoDataValue;
173 :
174 :
175 : #ifdef SUPPORT_CREATE
176 : virtual CPLErr SetColorTable(GDALColorTable*);
177 : virtual CPLErr IWriteBlock( int, int, void * );
178 :
179 : protected:
180 : int m_bBandProvided[5];
181 : void reset_band_provision_flags()
182 : {
183 : PNGDataset& ds = *(PNGDataset*)poDS;
184 :
185 : for(size_t i = 0; i < ds.nBands; i++)
186 : m_bBandProvided[i] = FALSE;
187 : }
188 : #endif
189 : };
190 :
191 :
192 : /************************************************************************/
193 : /* PNGRasterBand() */
194 : /************************************************************************/
195 :
196 113 : PNGRasterBand::PNGRasterBand( PNGDataset *poDS, int nBand )
197 :
198 : {
199 113 : this->poDS = poDS;
200 113 : this->nBand = nBand;
201 :
202 113 : if( poDS->nBitDepth == 16 )
203 45 : eDataType = GDT_UInt16;
204 : else
205 68 : eDataType = GDT_Byte;
206 :
207 113 : nBlockXSize = poDS->nRasterXSize;;
208 113 : nBlockYSize = 1;
209 :
210 113 : bHaveNoData = FALSE;
211 113 : dfNoDataValue = -1;
212 :
213 : #ifdef SUPPORT_CREATE
214 : this->reset_band_provision_flags();
215 : #endif
216 113 : }
217 :
218 : /************************************************************************/
219 : /* IReadBlock() */
220 : /************************************************************************/
221 :
222 : CPLErr PNGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
223 6851 : void * pImage )
224 :
225 : {
226 6851 : PNGDataset *poGDS = (PNGDataset *) poDS;
227 : CPLErr eErr;
228 : GByte *pabyScanline;
229 6851 : int i, nPixelSize, nPixelOffset, nXSize = GetXSize();
230 :
231 6851 : CPLAssert( nBlockXOff == 0 );
232 :
233 6851 : if( poGDS->nBitDepth == 16 )
234 481 : nPixelSize = 2;
235 : else
236 6370 : nPixelSize = 1;
237 6851 : nPixelOffset = poGDS->nBands * nPixelSize;
238 :
239 : /* -------------------------------------------------------------------- */
240 : /* Load the desired scanline into the working buffer. */
241 : /* -------------------------------------------------------------------- */
242 6851 : eErr = poGDS->LoadScanline( nBlockYOff );
243 6851 : if( eErr != CE_None )
244 5 : return eErr;
245 :
246 : pabyScanline = poGDS->pabyBuffer
247 : + (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize
248 6846 : + nPixelSize * (nBand - 1);
249 :
250 : /* -------------------------------------------------------------------- */
251 : /* Transfer between the working buffer the the callers buffer. */
252 : /* -------------------------------------------------------------------- */
253 6846 : if( nPixelSize == nPixelOffset )
254 1922 : memcpy( pImage, pabyScanline, nPixelSize * nXSize );
255 4924 : else if( nPixelSize == 1 )
256 : {
257 961626 : for( i = 0; i < nXSize; i++ )
258 957178 : ((GByte *) pImage)[i] = pabyScanline[i*nPixelOffset];
259 : }
260 : else
261 : {
262 476 : CPLAssert( nPixelSize == 2 );
263 11148 : for( i = 0; i < nXSize; i++ )
264 : {
265 : ((GUInt16 *) pImage)[i] =
266 10672 : *((GUInt16 *) (pabyScanline+i*nPixelOffset));
267 : }
268 : }
269 :
270 : /* -------------------------------------------------------------------- */
271 : /* Forceably load the other bands associated with this scanline. */
272 : /* -------------------------------------------------------------------- */
273 : int iBand;
274 17524 : for(iBand = 1; iBand < poGDS->GetRasterCount(); iBand++)
275 : {
276 : GDALRasterBlock *poBlock;
277 :
278 : poBlock =
279 10678 : poGDS->GetRasterBand(iBand+1)->GetLockedBlockRef(nBlockXOff,nBlockYOff);
280 10678 : poBlock->DropLock();
281 : }
282 :
283 6846 : return CE_None;
284 : }
285 :
286 : /************************************************************************/
287 : /* GetColorInterpretation() */
288 : /************************************************************************/
289 :
290 52 : GDALColorInterp PNGRasterBand::GetColorInterpretation()
291 :
292 : {
293 52 : PNGDataset *poGDS = (PNGDataset *) poDS;
294 :
295 52 : if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY )
296 5 : return GCI_GrayIndex;
297 :
298 47 : else if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA )
299 : {
300 1 : if( nBand == 1 )
301 1 : return GCI_GrayIndex;
302 : else
303 0 : return GCI_AlphaBand;
304 : }
305 :
306 46 : else if( poGDS->nColorType == PNG_COLOR_TYPE_PALETTE )
307 3 : return GCI_PaletteIndex;
308 :
309 43 : else if( poGDS->nColorType == PNG_COLOR_TYPE_RGB
310 : || poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA )
311 : {
312 43 : if( nBand == 1 )
313 11 : return GCI_RedBand;
314 32 : else if( nBand == 2 )
315 11 : return GCI_GreenBand;
316 21 : else if( nBand == 3 )
317 11 : return GCI_BlueBand;
318 : else
319 10 : return GCI_AlphaBand;
320 : }
321 : else
322 0 : return GCI_GrayIndex;
323 : }
324 :
325 : /************************************************************************/
326 : /* GetColorTable() */
327 : /************************************************************************/
328 :
329 14 : GDALColorTable *PNGRasterBand::GetColorTable()
330 :
331 : {
332 14 : PNGDataset *poGDS = (PNGDataset *) poDS;
333 :
334 14 : if( nBand == 1 )
335 9 : return poGDS->poColorTable;
336 : else
337 5 : return NULL;
338 : }
339 :
340 : /************************************************************************/
341 : /* SetNoDataValue() */
342 : /************************************************************************/
343 :
344 22 : CPLErr PNGRasterBand::SetNoDataValue( double dfNewValue )
345 :
346 : {
347 22 : bHaveNoData = TRUE;
348 22 : dfNoDataValue = dfNewValue;
349 :
350 22 : return CE_None;
351 : }
352 :
353 : /************************************************************************/
354 : /* GetNoDataValue() */
355 : /************************************************************************/
356 :
357 42 : double PNGRasterBand::GetNoDataValue( int *pbSuccess )
358 :
359 : {
360 42 : if( bHaveNoData )
361 : {
362 17 : if( pbSuccess != NULL )
363 16 : *pbSuccess = bHaveNoData;
364 17 : return dfNoDataValue;
365 : }
366 : else
367 : {
368 25 : return GDALPamRasterBand::GetNoDataValue( pbSuccess );
369 : }
370 : }
371 :
372 : /************************************************************************/
373 : /* ==================================================================== */
374 : /* PNGDataset */
375 : /* ==================================================================== */
376 : /************************************************************************/
377 :
378 :
379 : /************************************************************************/
380 : /* PNGDataset() */
381 : /************************************************************************/
382 :
383 50 : PNGDataset::PNGDataset()
384 :
385 : {
386 50 : hPNG = NULL;
387 50 : psPNGInfo = NULL;
388 50 : pabyBuffer = NULL;
389 50 : nBufferStartLine = 0;
390 50 : nBufferLines = 0;
391 50 : nLastLineRead = -1;
392 50 : poColorTable = NULL;
393 :
394 50 : bGeoTransformValid = FALSE;
395 50 : adfGeoTransform[0] = 0.0;
396 50 : adfGeoTransform[1] = 1.0;
397 50 : adfGeoTransform[2] = 0.0;
398 50 : adfGeoTransform[3] = 0.0;
399 50 : adfGeoTransform[4] = 0.0;
400 50 : adfGeoTransform[5] = 1.0;
401 50 : }
402 :
403 : /************************************************************************/
404 : /* ~PNGDataset() */
405 : /************************************************************************/
406 :
407 50 : PNGDataset::~PNGDataset()
408 :
409 : {
410 50 : FlushCache();
411 :
412 50 : if( hPNG != NULL )
413 50 : png_destroy_read_struct( &hPNG, &psPNGInfo, NULL );
414 :
415 50 : if( fpImage )
416 50 : VSIFCloseL( fpImage );
417 :
418 50 : if( poColorTable != NULL )
419 6 : delete poColorTable;
420 50 : }
421 :
422 : /************************************************************************/
423 : /* GetGeoTransform() */
424 : /************************************************************************/
425 :
426 20 : CPLErr PNGDataset::GetGeoTransform( double * padfTransform )
427 :
428 : {
429 :
430 20 : if( bGeoTransformValid )
431 : {
432 3 : memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
433 3 : return CE_None;
434 : }
435 : else
436 17 : return GDALPamDataset::GetGeoTransform( padfTransform );
437 : }
438 :
439 : /************************************************************************/
440 : /* FlushCache() */
441 : /* */
442 : /* We override this so we can also flush out local tiff strip */
443 : /* cache if need be. */
444 : /************************************************************************/
445 :
446 50 : void PNGDataset::FlushCache()
447 :
448 : {
449 50 : GDALPamDataset::FlushCache();
450 :
451 50 : if( pabyBuffer != NULL )
452 : {
453 27 : CPLFree( pabyBuffer );
454 27 : pabyBuffer = NULL;
455 27 : nBufferStartLine = 0;
456 27 : nBufferLines = 0;
457 : }
458 50 : }
459 :
460 : /************************************************************************/
461 : /* Restart() */
462 : /* */
463 : /* Restart reading from the beginning of the file. */
464 : /************************************************************************/
465 :
466 1 : void PNGDataset::Restart()
467 :
468 : {
469 1 : png_destroy_read_struct( &hPNG, &psPNGInfo, NULL );
470 :
471 1 : hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, this, NULL, NULL );
472 :
473 1 : png_set_error_fn( hPNG, &sSetJmpContext, png_gdal_error, png_gdal_warning );
474 1 : if( setjmp( sSetJmpContext ) != 0 )
475 0 : return;
476 :
477 1 : psPNGInfo = png_create_info_struct( hPNG );
478 :
479 1 : VSIFSeekL( fpImage, 0, SEEK_SET );
480 1 : png_set_read_fn( hPNG, fpImage, png_vsi_read_data );
481 1 : png_read_info( hPNG, psPNGInfo );
482 :
483 1 : if( nBitDepth < 8 )
484 0 : png_set_packing( hPNG );
485 :
486 1 : nLastLineRead = -1;
487 : }
488 :
489 : /************************************************************************/
490 : /* LoadInterlacedChunk() */
491 : /************************************************************************/
492 :
493 5 : CPLErr PNGDataset::LoadInterlacedChunk( int iLine )
494 :
495 : {
496 : int nPixelOffset;
497 :
498 5 : if( nBitDepth == 16 )
499 0 : nPixelOffset = 2 * GetRasterCount();
500 : else
501 5 : nPixelOffset = 1 * GetRasterCount();
502 :
503 : /* -------------------------------------------------------------------- */
504 : /* Was is the biggest chunk we can safely operate on? */
505 : /* -------------------------------------------------------------------- */
506 : #define MAX_PNG_CHUNK_BYTES 100000000
507 :
508 : int nMaxChunkLines =
509 5 : MAX(1,MAX_PNG_CHUNK_BYTES / (nPixelOffset * GetRasterXSize()));
510 : png_bytep *png_rows;
511 :
512 5 : if( nMaxChunkLines > GetRasterYSize() )
513 5 : nMaxChunkLines = GetRasterYSize();
514 :
515 : /* -------------------------------------------------------------------- */
516 : /* Allocate chunk buffer, if we don't already have it from a */
517 : /* previous request. */
518 : /* -------------------------------------------------------------------- */
519 5 : nBufferLines = nMaxChunkLines;
520 5 : if( nMaxChunkLines + iLine > GetRasterYSize() )
521 0 : nBufferStartLine = GetRasterYSize() - nMaxChunkLines;
522 : else
523 5 : nBufferStartLine = iLine;
524 :
525 5 : if( pabyBuffer == NULL )
526 : {
527 : pabyBuffer = (GByte *)
528 5 : VSIMalloc(nPixelOffset*GetRasterXSize()*nMaxChunkLines);
529 :
530 5 : if( pabyBuffer == NULL )
531 : {
532 : CPLError( CE_Failure, CPLE_OutOfMemory,
533 : "Unable to allocate buffer for whole interlaced PNG"
534 : "image of size %dx%d.\n",
535 0 : GetRasterXSize(), GetRasterYSize() );
536 0 : return CE_Failure;
537 : }
538 : #ifdef notdef
539 : if( nMaxChunkLines < GetRasterYSize() )
540 : CPLDebug( "PNG",
541 : "Interlaced file being handled in %d line chunks.\n"
542 : "Performance is likely to be quite poor.",
543 : nMaxChunkLines );
544 : #endif
545 : }
546 :
547 : /* -------------------------------------------------------------------- */
548 : /* Do we need to restart reading? We do this if we aren't on */
549 : /* the first attempt to read the image. */
550 : /* -------------------------------------------------------------------- */
551 5 : if( nLastLineRead != -1 )
552 : {
553 0 : Restart();
554 0 : if( setjmp( sSetJmpContext ) != 0 )
555 0 : return CE_Failure;
556 : }
557 :
558 : /* -------------------------------------------------------------------- */
559 : /* Allocate and populate rows array. We create a row for each */
560 : /* row in the image, but use our dummy line for rows not in the */
561 : /* target window. */
562 : /* -------------------------------------------------------------------- */
563 : int i;
564 5 : png_bytep dummy_row = (png_bytep)CPLMalloc(nPixelOffset*GetRasterXSize());
565 5 : png_rows = (png_bytep*)CPLMalloc(sizeof(png_bytep) * GetRasterYSize());
566 :
567 1179 : for( i = 0; i < GetRasterYSize(); i++ )
568 : {
569 2348 : if( i >= nBufferStartLine && i < nBufferStartLine + nBufferLines )
570 : png_rows[i] = pabyBuffer
571 1174 : + (i-nBufferStartLine) * nPixelOffset * GetRasterXSize();
572 : else
573 0 : png_rows[i] = dummy_row;
574 : }
575 :
576 5 : png_read_image( hPNG, png_rows );
577 :
578 5 : CPLFree( png_rows );
579 5 : CPLFree( dummy_row );
580 :
581 5 : nLastLineRead = nBufferStartLine + nBufferLines - 1;
582 :
583 5 : return CE_None;
584 : }
585 :
586 : /************************************************************************/
587 : /* LoadScanline() */
588 : /************************************************************************/
589 :
590 6851 : CPLErr PNGDataset::LoadScanline( int nLine )
591 :
592 : {
593 : int nPixelOffset;
594 :
595 6851 : CPLAssert( nLine >= 0 && nLine < GetRasterYSize() );
596 :
597 6851 : if( nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines)
598 4405 : return CE_None;
599 :
600 2446 : if( nBitDepth == 16 )
601 177 : nPixelOffset = 2 * GetRasterCount();
602 : else
603 2269 : nPixelOffset = 1 * GetRasterCount();
604 :
605 2446 : if( setjmp( sSetJmpContext ) != 0 )
606 5 : return CE_Failure;
607 :
608 : /* -------------------------------------------------------------------- */
609 : /* If the file is interlaced, we will load the entire image */
610 : /* into memory using the high level API. */
611 : /* -------------------------------------------------------------------- */
612 2446 : if( bInterlaced )
613 5 : return LoadInterlacedChunk( nLine );
614 :
615 : /* -------------------------------------------------------------------- */
616 : /* Ensure we have space allocated for one scanline */
617 : /* -------------------------------------------------------------------- */
618 2441 : if( pabyBuffer == NULL )
619 22 : pabyBuffer = (GByte *) CPLMalloc(nPixelOffset * GetRasterXSize());
620 :
621 : /* -------------------------------------------------------------------- */
622 : /* Otherwise we just try to read the requested row. Do we need */
623 : /* to rewind and start over? */
624 : /* -------------------------------------------------------------------- */
625 2441 : if( nLine <= nLastLineRead )
626 : {
627 1 : Restart();
628 1 : if( setjmp( sSetJmpContext ) != 0 )
629 0 : return CE_Failure;
630 : }
631 :
632 : /* -------------------------------------------------------------------- */
633 : /* Read till we get the desired row. */
634 : /* -------------------------------------------------------------------- */
635 : png_bytep row;
636 :
637 2441 : row = pabyBuffer;
638 7318 : while( nLine > nLastLineRead )
639 : {
640 2441 : png_read_rows( hPNG, &row, NULL, 1 );
641 2436 : nLastLineRead++;
642 : }
643 :
644 2436 : nBufferStartLine = nLine;
645 2436 : nBufferLines = 1;
646 :
647 : /* -------------------------------------------------------------------- */
648 : /* Do swap on LSB machines. 16bit PNG data is stored in MSB */
649 : /* format. */
650 : /* -------------------------------------------------------------------- */
651 : #ifdef CPL_LSB
652 2436 : if( nBitDepth == 16 )
653 172 : GDALSwapWords( row, 2, GetRasterXSize() * GetRasterCount(), 2 );
654 : #endif
655 :
656 2436 : return CE_None;
657 : }
658 :
659 : /************************************************************************/
660 : /* CollectMetadata() */
661 : /* */
662 : /* We normally do this after reading up to the image, but be */
663 : /* forwarned ... we can missing text chunks this way. */
664 : /* */
665 : /* We turn each PNG text chunk into one metadata item. It */
666 : /* might be nice to preserve language information though we */
667 : /* don't try to now. */
668 : /************************************************************************/
669 :
670 50 : void PNGDataset::CollectMetadata()
671 :
672 : {
673 : int nTextCount;
674 : png_textp text_ptr;
675 :
676 50 : if( nBitDepth < 8 )
677 : {
678 0 : for( int iBand = 0; iBand < nBands; iBand++ )
679 : {
680 : GetRasterBand(iBand+1)->SetMetadataItem(
681 : "NBITS", CPLString().Printf( "%d", nBitDepth ),
682 0 : "IMAGE_STRUCTURE" );
683 : }
684 : }
685 :
686 50 : if( png_get_text( hPNG, psPNGInfo, &text_ptr, &nTextCount ) == 0 )
687 50 : return;
688 :
689 0 : for( int iText = 0; iText < nTextCount; iText++ )
690 : {
691 0 : char *pszTag = CPLStrdup(text_ptr[iText].key);
692 :
693 0 : for( int i = 0; pszTag[i] != '\0'; i++ )
694 : {
695 0 : if( pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':' )
696 0 : pszTag[i] = '_';
697 : }
698 :
699 0 : SetMetadataItem( pszTag, text_ptr[iText].text );
700 0 : CPLFree( pszTag );
701 : }
702 : }
703 :
704 : /************************************************************************/
705 : /* Identify() */
706 : /************************************************************************/
707 :
708 11206 : int PNGDataset::Identify( GDALOpenInfo * poOpenInfo )
709 :
710 : {
711 11206 : if( poOpenInfo->nHeaderBytes < 4 )
712 9827 : return FALSE;
713 :
714 1379 : if( png_sig_cmp(poOpenInfo->pabyHeader, (png_size_t)0,
715 : poOpenInfo->nHeaderBytes) != 0 )
716 1329 : return FALSE;
717 :
718 50 : return TRUE;
719 : }
720 :
721 : /************************************************************************/
722 : /* Open() */
723 : /************************************************************************/
724 :
725 2940 : GDALDataset *PNGDataset::Open( GDALOpenInfo * poOpenInfo )
726 :
727 : {
728 2940 : if( !Identify( poOpenInfo ) )
729 2890 : return NULL;
730 :
731 50 : if( poOpenInfo->eAccess == GA_Update )
732 : {
733 : CPLError( CE_Failure, CPLE_NotSupported,
734 : "The PNG driver does not support update access to existing"
735 0 : " datasets.\n" );
736 0 : return NULL;
737 : }
738 :
739 : /* -------------------------------------------------------------------- */
740 : /* Open a file handle using large file API. */
741 : /* -------------------------------------------------------------------- */
742 50 : FILE *fp = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
743 50 : if( fp == NULL )
744 : {
745 : CPLError( CE_Failure, CPLE_OpenFailed,
746 : "Unexpected failure of VSIFOpenL(%s) in PNG Open()",
747 0 : poOpenInfo->pszFilename );
748 0 : return NULL;
749 : }
750 :
751 : /* -------------------------------------------------------------------- */
752 : /* Create a corresponding GDALDataset. */
753 : /* -------------------------------------------------------------------- */
754 : PNGDataset *poDS;
755 :
756 50 : poDS = new PNGDataset();
757 :
758 50 : poDS->fpImage = fp;
759 50 : poDS->eAccess = poOpenInfo->eAccess;
760 :
761 : poDS->hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, poDS,
762 50 : NULL, NULL );
763 50 : if (poDS->hPNG == NULL)
764 : {
765 : #if LIBPNG_VER_MINOR >= 2 || LIBPNG_VER_MAJOR > 1
766 : int version = png_access_version_number();
767 : CPLError( CE_Failure, CPLE_NotSupported,
768 : "The PNG driver failed to access libpng with version '%s',"
769 : " library is actually version '%d'.\n",
770 : PNG_LIBPNG_VER_STRING, version);
771 : #else
772 : CPLError( CE_Failure, CPLE_NotSupported,
773 : "The PNG driver failed to in png_create_read_struct().\n"
774 0 : "This may be due to version compatibility problems." );
775 : #endif
776 0 : delete poDS;
777 0 : return NULL;
778 : }
779 :
780 50 : poDS->psPNGInfo = png_create_info_struct( poDS->hPNG );
781 :
782 : /* -------------------------------------------------------------------- */
783 : /* Setup error handling. */
784 : /* -------------------------------------------------------------------- */
785 50 : png_set_error_fn( poDS->hPNG, &poDS->sSetJmpContext, png_gdal_error, png_gdal_warning );
786 :
787 50 : if( setjmp( poDS->sSetJmpContext ) != 0 )
788 0 : return NULL;
789 :
790 : /* -------------------------------------------------------------------- */
791 : /* Read pre-image data after ensuring the file is rewound. */
792 : /* -------------------------------------------------------------------- */
793 : /* we should likely do a setjmp() here */
794 :
795 50 : png_set_read_fn( poDS->hPNG, poDS->fpImage, png_vsi_read_data );
796 50 : png_read_info( poDS->hPNG, poDS->psPNGInfo );
797 :
798 : /* -------------------------------------------------------------------- */
799 : /* Capture some information from the file that is of interest. */
800 : /* -------------------------------------------------------------------- */
801 50 : poDS->nRasterXSize = png_get_image_width( poDS->hPNG, poDS->psPNGInfo);
802 50 : poDS->nRasterYSize = png_get_image_height( poDS->hPNG,poDS->psPNGInfo);
803 :
804 50 : poDS->nBands = png_get_channels( poDS->hPNG, poDS->psPNGInfo );
805 50 : poDS->nBitDepth = png_get_bit_depth( poDS->hPNG, poDS->psPNGInfo );
806 : poDS->bInterlaced = png_get_interlace_type( poDS->hPNG, poDS->psPNGInfo )
807 50 : != PNG_INTERLACE_NONE;
808 :
809 50 : poDS->nColorType = png_get_color_type( poDS->hPNG, poDS->psPNGInfo );
810 :
811 50 : if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE
812 : && poDS->nBands > 1 )
813 : {
814 : CPLDebug( "GDAL", "PNG Driver got %d from png_get_channels(),\n"
815 : "but this kind of image (paletted) can only have one band.\n"
816 : "Correcting and continuing, but this may indicate a bug!",
817 0 : poDS->nBands );
818 0 : poDS->nBands = 1;
819 : }
820 :
821 : /* -------------------------------------------------------------------- */
822 : /* We want to treat 1,2,4 bit images as eight bit. This call */
823 : /* causes libpng to unpack the image. */
824 : /* -------------------------------------------------------------------- */
825 50 : if( poDS->nBitDepth < 8 )
826 0 : png_set_packing( poDS->hPNG );
827 :
828 : /* -------------------------------------------------------------------- */
829 : /* Create band information objects. */
830 : /* -------------------------------------------------------------------- */
831 326 : for( int iBand = 0; iBand < poDS->nBands; iBand++ )
832 113 : poDS->SetBand( iBand+1, new PNGRasterBand( poDS, iBand+1 ) );
833 :
834 : /* -------------------------------------------------------------------- */
835 : /* Is there a palette? Note: we should also read back and */
836 : /* apply transparency values if available. */
837 : /* -------------------------------------------------------------------- */
838 50 : if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE )
839 : {
840 : png_color *pasPNGPalette;
841 : int nColorCount;
842 : GDALColorEntry oEntry;
843 6 : unsigned char *trans = NULL;
844 6 : png_color_16 *trans_values = NULL;
845 6 : int num_trans = 0;
846 6 : int nNoDataIndex = -1;
847 :
848 6 : if( png_get_PLTE( poDS->hPNG, poDS->psPNGInfo,
849 : &pasPNGPalette, &nColorCount ) == 0 )
850 0 : nColorCount = 0;
851 :
852 : png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
853 6 : &trans, &num_trans, &trans_values );
854 :
855 6 : poDS->poColorTable = new GDALColorTable();
856 :
857 102 : for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
858 : {
859 96 : oEntry.c1 = pasPNGPalette[iColor].red;
860 96 : oEntry.c2 = pasPNGPalette[iColor].green;
861 96 : oEntry.c3 = pasPNGPalette[iColor].blue;
862 :
863 96 : if( iColor < num_trans )
864 : {
865 96 : oEntry.c4 = trans[iColor];
866 96 : if( oEntry.c4 == 0 )
867 : {
868 6 : if( nNoDataIndex == -1 )
869 6 : nNoDataIndex = iColor;
870 : else
871 0 : nNoDataIndex = -2;
872 : }
873 : }
874 : else
875 0 : oEntry.c4 = 255;
876 :
877 96 : poDS->poColorTable->SetColorEntry( iColor, &oEntry );
878 : }
879 :
880 : /*
881 : ** Special hack to an index as the no data value, as long as it
882 : ** is the _only_ transparent color in the palette.
883 : */
884 6 : if( nNoDataIndex > -1 )
885 : {
886 6 : poDS->GetRasterBand(1)->SetNoDataValue(nNoDataIndex);
887 : }
888 : }
889 :
890 : /* -------------------------------------------------------------------- */
891 : /* Check for transparency values in greyscale images. */
892 : /* -------------------------------------------------------------------- */
893 50 : if( poDS->nColorType == PNG_COLOR_TYPE_GRAY )
894 : {
895 17 : png_color_16 *trans_values = NULL;
896 : unsigned char *trans;
897 : int num_trans;
898 :
899 17 : if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
900 : &trans, &num_trans, &trans_values ) != 0
901 : && trans_values != NULL )
902 : {
903 4 : poDS->GetRasterBand(1)->SetNoDataValue(trans_values->gray);
904 : }
905 : }
906 :
907 : /* -------------------------------------------------------------------- */
908 : /* Check for nodata color for RGB images. */
909 : /* -------------------------------------------------------------------- */
910 50 : if( poDS->nColorType == PNG_COLOR_TYPE_RGB )
911 : {
912 16 : png_color_16 *trans_values = NULL;
913 : unsigned char *trans;
914 : int num_trans;
915 :
916 16 : if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo,
917 : &trans, &num_trans, &trans_values ) != 0
918 : && trans_values != NULL )
919 : {
920 4 : CPLString oNDValue;
921 :
922 : oNDValue.Printf( "%d %d %d",
923 : trans_values->red,
924 : trans_values->green,
925 4 : trans_values->blue );
926 4 : poDS->SetMetadataItem( "NODATA_VALUES", oNDValue.c_str() );
927 :
928 4 : poDS->GetRasterBand(1)->SetNoDataValue(trans_values->red);
929 4 : poDS->GetRasterBand(2)->SetNoDataValue(trans_values->green);
930 4 : poDS->GetRasterBand(3)->SetNoDataValue(trans_values->blue);
931 : }
932 : }
933 :
934 : /* -------------------------------------------------------------------- */
935 : /* Extract any text chunks as "metadata". */
936 : /* -------------------------------------------------------------------- */
937 50 : poDS->CollectMetadata();
938 :
939 : /* -------------------------------------------------------------------- */
940 : /* More metadata. */
941 : /* -------------------------------------------------------------------- */
942 50 : if( poDS->nBands > 1 )
943 : {
944 27 : poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
945 : }
946 :
947 : /* -------------------------------------------------------------------- */
948 : /* Initialize any PAM information. */
949 : /* -------------------------------------------------------------------- */
950 50 : poDS->SetDescription( poOpenInfo->pszFilename );
951 50 : poDS->TryLoadXML();
952 :
953 : /* -------------------------------------------------------------------- */
954 : /* Open overviews. */
955 : /* -------------------------------------------------------------------- */
956 : poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename,
957 50 : poOpenInfo->papszSiblingFiles );
958 :
959 : /* -------------------------------------------------------------------- */
960 : /* Check for world file. */
961 : /* -------------------------------------------------------------------- */
962 : poDS->bGeoTransformValid =
963 : GDALReadWorldFile( poOpenInfo->pszFilename, NULL,
964 50 : poDS->adfGeoTransform );
965 :
966 50 : if( !poDS->bGeoTransformValid )
967 : poDS->bGeoTransformValid =
968 : GDALReadWorldFile( poOpenInfo->pszFilename, ".wld",
969 50 : poDS->adfGeoTransform );
970 :
971 50 : return poDS;
972 : }
973 :
974 : /************************************************************************/
975 : /* PNGCreateCopy() */
976 : /************************************************************************/
977 :
978 : static GDALDataset *
979 : PNGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
980 : int bStrict, char ** papszOptions,
981 28 : GDALProgressFunc pfnProgress, void * pProgressData )
982 :
983 : {
984 28 : int nBands = poSrcDS->GetRasterCount();
985 28 : int nXSize = poSrcDS->GetRasterXSize();
986 28 : int nYSize = poSrcDS->GetRasterYSize();
987 :
988 : /* -------------------------------------------------------------------- */
989 : /* Some some rudimentary checks */
990 : /* -------------------------------------------------------------------- */
991 28 : if( nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4 )
992 : {
993 : CPLError( CE_Failure, CPLE_NotSupported,
994 : "PNG driver doesn't support %d bands. Must be 1 (grey),\n"
995 : "2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n",
996 2 : nBands );
997 :
998 2 : return NULL;
999 : }
1000 :
1001 26 : if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte
1002 : && poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 )
1003 : {
1004 : CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
1005 : "PNG driver doesn't support data type %s. "
1006 : "Only eight bit (Byte) and sixteen bit (UInt16) bands supported. %s\n",
1007 : GDALGetDataTypeName(
1008 : poSrcDS->GetRasterBand(1)->GetRasterDataType()),
1009 9 : (bStrict) ? "" : "Defaulting to Byte" );
1010 :
1011 9 : if (bStrict)
1012 9 : return NULL;
1013 : }
1014 :
1015 : /* -------------------------------------------------------------------- */
1016 : /* Setup some parameters. */
1017 : /* -------------------------------------------------------------------- */
1018 17 : int nColorType=0, nBitDepth;
1019 : GDALDataType eType;
1020 :
1021 17 : if( nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == NULL )
1022 7 : nColorType = PNG_COLOR_TYPE_GRAY;
1023 10 : else if( nBands == 1 )
1024 1 : nColorType = PNG_COLOR_TYPE_PALETTE;
1025 9 : else if( nBands == 2 )
1026 1 : nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
1027 8 : else if( nBands == 3 )
1028 5 : nColorType = PNG_COLOR_TYPE_RGB;
1029 3 : else if( nBands == 4 )
1030 3 : nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
1031 :
1032 17 : if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 )
1033 : {
1034 13 : eType = GDT_Byte;
1035 13 : nBitDepth = 8;
1036 : }
1037 : else
1038 : {
1039 4 : eType = GDT_UInt16;
1040 4 : nBitDepth = 16;
1041 : }
1042 :
1043 : /* -------------------------------------------------------------------- */
1044 : /* Create the dataset. */
1045 : /* -------------------------------------------------------------------- */
1046 : FILE *fpImage;
1047 :
1048 17 : fpImage = VSIFOpenL( pszFilename, "wb" );
1049 17 : if( fpImage == NULL )
1050 : {
1051 : CPLError( CE_Failure, CPLE_OpenFailed,
1052 : "Unable to create png file %s.\n",
1053 0 : pszFilename );
1054 0 : return NULL;
1055 : }
1056 :
1057 : /* -------------------------------------------------------------------- */
1058 : /* Initialize PNG access to the file. */
1059 : /* -------------------------------------------------------------------- */
1060 : png_structp hPNG;
1061 : png_infop psPNGInfo;
1062 :
1063 : jmp_buf sSetJmpContext;
1064 : hPNG = png_create_write_struct( PNG_LIBPNG_VER_STRING,
1065 17 : &sSetJmpContext, png_gdal_error, png_gdal_warning );
1066 17 : psPNGInfo = png_create_info_struct( hPNG );
1067 :
1068 17 : if( setjmp( sSetJmpContext ) != 0 )
1069 : {
1070 1 : VSIFCloseL( fpImage );
1071 1 : png_destroy_write_struct( &hPNG, &psPNGInfo );
1072 1 : return NULL;
1073 : }
1074 :
1075 17 : png_set_write_fn( hPNG, fpImage, png_vsi_write_data, png_vsi_flush );
1076 :
1077 : png_set_IHDR( hPNG, psPNGInfo, nXSize, nYSize,
1078 : nBitDepth, nColorType, PNG_INTERLACE_NONE,
1079 17 : PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );
1080 :
1081 : /* -------------------------------------------------------------------- */
1082 : /* Try to handle nodata values as a tRNS block (note for */
1083 : /* paletted images, we save the effect to apply as part of */
1084 : /* palette). */
1085 : /* -------------------------------------------------------------------- */
1086 : png_color_16 sTRNSColor;
1087 :
1088 : // Gray nodata.
1089 17 : if( nColorType == PNG_COLOR_TYPE_GRAY )
1090 : {
1091 7 : int bHaveNoData = FALSE;
1092 7 : double dfNoDataValue = -1;
1093 :
1094 7 : dfNoDataValue = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1095 :
1096 7 : if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 )
1097 : {
1098 2 : sTRNSColor.gray = (png_uint_16) dfNoDataValue;
1099 2 : png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor );
1100 : }
1101 : }
1102 :
1103 : // RGB nodata.
1104 17 : if( nColorType == PNG_COLOR_TYPE_RGB )
1105 : {
1106 : // First try to use the NODATA_VALUES metadata item.
1107 5 : if ( poSrcDS->GetMetadataItem( "NODATA_VALUES" ) != NULL )
1108 : {
1109 : char **papszValues = CSLTokenizeString(
1110 1 : poSrcDS->GetMetadataItem( "NODATA_VALUES" ) );
1111 :
1112 1 : if( CSLCount(papszValues) >= 3 )
1113 : {
1114 1 : sTRNSColor.red = (png_uint_16) atoi(papszValues[0]);
1115 1 : sTRNSColor.green = (png_uint_16) atoi(papszValues[1]);
1116 1 : sTRNSColor.blue = (png_uint_16) atoi(papszValues[2]);
1117 1 : png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor );
1118 : }
1119 :
1120 1 : CSLDestroy( papszValues );
1121 : }
1122 : // Otherwise, get the nodata value from the bands.
1123 : else
1124 : {
1125 4 : int bHaveNoDataRed = FALSE;
1126 4 : int bHaveNoDataGreen = FALSE;
1127 4 : int bHaveNoDataBlue = FALSE;
1128 4 : double dfNoDataValueRed = -1;
1129 4 : double dfNoDataValueGreen = -1;
1130 4 : double dfNoDataValueBlue = -1;
1131 :
1132 4 : dfNoDataValueRed = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed );
1133 4 : dfNoDataValueGreen= poSrcDS->GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen );
1134 4 : dfNoDataValueBlue = poSrcDS->GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue );
1135 :
1136 4 : if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) &&
1137 : ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) &&
1138 : ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) )
1139 : {
1140 0 : sTRNSColor.red = (png_uint_16) dfNoDataValueRed;
1141 0 : sTRNSColor.green = (png_uint_16) dfNoDataValueGreen;
1142 0 : sTRNSColor.blue = (png_uint_16) dfNoDataValueBlue;
1143 0 : png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor );
1144 : }
1145 : }
1146 : }
1147 :
1148 : /* -------------------------------------------------------------------- */
1149 : /* Write palette if there is one. Technically, I think it is */
1150 : /* possible to write 16bit palettes for PNG, but we will omit */
1151 : /* this for now. */
1152 : /* -------------------------------------------------------------------- */
1153 17 : png_color *pasPNGColors = NULL;
1154 17 : unsigned char *pabyAlpha = NULL;
1155 :
1156 17 : if( nColorType == PNG_COLOR_TYPE_PALETTE )
1157 : {
1158 : GDALColorTable *poCT;
1159 : GDALColorEntry sEntry;
1160 1 : int iColor, bFoundTrans = FALSE;
1161 1 : int bHaveNoData = FALSE;
1162 1 : double dfNoDataValue = -1;
1163 :
1164 1 : dfNoDataValue = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1165 :
1166 1 : poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
1167 :
1168 : pasPNGColors = (png_color *) CPLMalloc(sizeof(png_color) *
1169 1 : poCT->GetColorEntryCount());
1170 :
1171 17 : for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
1172 : {
1173 16 : poCT->GetColorEntryAsRGB( iColor, &sEntry );
1174 16 : if( sEntry.c4 != 255 )
1175 1 : bFoundTrans = TRUE;
1176 :
1177 16 : pasPNGColors[iColor].red = (png_byte) sEntry.c1;
1178 16 : pasPNGColors[iColor].green = (png_byte) sEntry.c2;
1179 16 : pasPNGColors[iColor].blue = (png_byte) sEntry.c3;
1180 : }
1181 :
1182 : png_set_PLTE( hPNG, psPNGInfo, pasPNGColors,
1183 1 : poCT->GetColorEntryCount() );
1184 :
1185 : /* -------------------------------------------------------------------- */
1186 : /* If we have transparent elements in the palette we need to */
1187 : /* write a transparency block. */
1188 : /* -------------------------------------------------------------------- */
1189 1 : if( bFoundTrans || bHaveNoData )
1190 : {
1191 1 : pabyAlpha = (unsigned char *)CPLMalloc(poCT->GetColorEntryCount());
1192 :
1193 17 : for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
1194 : {
1195 16 : poCT->GetColorEntryAsRGB( iColor, &sEntry );
1196 16 : pabyAlpha[iColor] = (unsigned char) sEntry.c4;
1197 :
1198 16 : if( bHaveNoData && iColor == (int) dfNoDataValue )
1199 1 : pabyAlpha[iColor] = 0;
1200 : }
1201 :
1202 : png_set_tRNS( hPNG, psPNGInfo, pabyAlpha,
1203 1 : poCT->GetColorEntryCount(), NULL );
1204 : }
1205 : }
1206 :
1207 17 : png_write_info( hPNG, psPNGInfo );
1208 :
1209 : /* -------------------------------------------------------------------- */
1210 : /* Loop over image, copying image data. */
1211 : /* -------------------------------------------------------------------- */
1212 : GByte *pabyScanline;
1213 17 : CPLErr eErr = CE_None;
1214 17 : int nWordSize = nBitDepth/8;
1215 :
1216 17 : pabyScanline = (GByte *) CPLMalloc( nBands * nXSize * nWordSize );
1217 :
1218 1114 : for( int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++ )
1219 : {
1220 1097 : png_bytep row = pabyScanline;
1221 :
1222 2965 : for( int iBand = 0; iBand < nBands; iBand++ )
1223 : {
1224 1868 : GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 );
1225 : eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1,
1226 : pabyScanline + iBand*nWordSize,
1227 : nXSize, 1, eType,
1228 : nBands * nWordSize,
1229 1868 : nBands * nXSize * nWordSize );
1230 : }
1231 :
1232 : #ifdef CPL_LSB
1233 1097 : if( nBitDepth == 16 )
1234 63 : GDALSwapWords( row, 2, nXSize * nBands, 2 );
1235 : #endif
1236 1097 : if( eErr == CE_None )
1237 1096 : png_write_rows( hPNG, &row, 1 );
1238 :
1239 1097 : if( eErr == CE_None
1240 : && !pfnProgress( (iLine+1) / (double) nYSize,
1241 : NULL, pProgressData ) )
1242 : {
1243 0 : eErr = CE_Failure;
1244 : CPLError( CE_Failure, CPLE_UserInterrupt,
1245 0 : "User terminated CreateCopy()" );
1246 : }
1247 : }
1248 :
1249 17 : CPLFree( pabyScanline );
1250 :
1251 17 : png_write_end( hPNG, psPNGInfo );
1252 16 : png_destroy_write_struct( &hPNG, &psPNGInfo );
1253 :
1254 16 : VSIFCloseL( fpImage );
1255 :
1256 16 : CPLFree( pabyAlpha );
1257 16 : CPLFree( pasPNGColors );
1258 :
1259 16 : if( eErr != CE_None )
1260 0 : return NULL;
1261 :
1262 : /* -------------------------------------------------------------------- */
1263 : /* Do we need a world file? */
1264 : /* -------------------------------------------------------------------- */
1265 16 : if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
1266 : {
1267 : double adfGeoTransform[6];
1268 :
1269 0 : if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None )
1270 0 : GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
1271 : }
1272 :
1273 : /* -------------------------------------------------------------------- */
1274 : /* Re-open dataset, and copy any auxilary pam information. */
1275 : /* -------------------------------------------------------------------- */
1276 16 : PNGDataset *poDS = (PNGDataset *) GDALOpen( pszFilename, GA_ReadOnly );
1277 :
1278 16 : if( poDS )
1279 16 : poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );
1280 :
1281 16 : return poDS;
1282 : }
1283 :
1284 : /************************************************************************/
1285 : /* png_vsi_read_data() */
1286 : /* */
1287 : /* Read data callback through VSI. */
1288 : /************************************************************************/
1289 : static void
1290 615 : png_vsi_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
1291 :
1292 : {
1293 : png_size_t check;
1294 :
1295 : /* fread() returns 0 on error, so it is OK to store this in a png_size_t
1296 : * instead of an int, which is what fread() actually returns.
1297 : */
1298 : check = (png_size_t)VSIFReadL(data, (png_size_t)1, length,
1299 615 : (png_FILE_p)png_ptr->io_ptr);
1300 :
1301 615 : if (check != length)
1302 1 : png_error(png_ptr, "Read Error");
1303 614 : }
1304 :
1305 : /************************************************************************/
1306 : /* png_vsi_write_data() */
1307 : /************************************************************************/
1308 :
1309 : static void
1310 248 : png_vsi_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
1311 : {
1312 : png_uint_32 check;
1313 :
1314 248 : check = VSIFWriteL(data, 1, length, (png_FILE_p)(png_ptr->io_ptr));
1315 :
1316 248 : if (check != length)
1317 0 : png_error(png_ptr, "Write Error");
1318 248 : }
1319 :
1320 : /************************************************************************/
1321 : /* png_vsi_flush() */
1322 : /************************************************************************/
1323 0 : static void png_vsi_flush(png_structp png_ptr)
1324 : {
1325 0 : VSIFFlushL( (png_FILE_p)(png_ptr->io_ptr) );
1326 0 : }
1327 :
1328 : /************************************************************************/
1329 : /* png_gdal_error() */
1330 : /************************************************************************/
1331 :
1332 6 : static void png_gdal_error( png_structp png_ptr, const char *error_message )
1333 : {
1334 : CPLError( CE_Failure, CPLE_AppDefined,
1335 6 : "libpng: %s", error_message );
1336 :
1337 : // We have to use longjmp instead of a C++ exception because
1338 : // libpng is generally not built as C++ and so won't honour unwind
1339 : // semantics. Ugg.
1340 :
1341 6 : jmp_buf* psSetJmpContext = (jmp_buf*) png_ptr->error_ptr;
1342 6 : if (psSetJmpContext)
1343 : {
1344 6 : longjmp( *psSetJmpContext, 1 );
1345 : }
1346 0 : }
1347 :
1348 : /************************************************************************/
1349 : /* png_gdal_warning() */
1350 : /************************************************************************/
1351 :
1352 0 : static void png_gdal_warning( png_structp png_ptr, const char *error_message )
1353 : {
1354 : CPLError( CE_Warning, CPLE_AppDefined,
1355 0 : "libpng: %s", error_message );
1356 0 : }
1357 :
1358 : /************************************************************************/
1359 : /* GDALRegister_PNG() */
1360 : /************************************************************************/
1361 :
1362 409 : void GDALRegister_PNG()
1363 :
1364 : {
1365 : GDALDriver *poDriver;
1366 :
1367 409 : if( GDALGetDriverByName( "PNG" ) == NULL )
1368 : {
1369 392 : poDriver = new GDALDriver();
1370 :
1371 392 : poDriver->SetDescription( "PNG" );
1372 : poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
1373 392 : "Portable Network Graphics" );
1374 : poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
1375 392 : "frmt_various.html#PNG" );
1376 392 : poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "png" );
1377 392 : poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/png" );
1378 :
1379 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
1380 392 : "Byte UInt16" );
1381 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
1382 : "<CreationOptionList>\n"
1383 : " <Option name='WORLDFILE' type='boolean' description='Create world file'/>\n"
1384 392 : "</CreationOptionList>\n" );
1385 :
1386 392 : poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
1387 :
1388 392 : poDriver->pfnOpen = PNGDataset::Open;
1389 392 : poDriver->pfnCreateCopy = PNGCreateCopy;
1390 392 : poDriver->pfnIdentify = PNGDataset::Identify;
1391 : #ifdef SUPPORT_CREATE
1392 : poDriver->pfnCreate = PNGDataset::Create;
1393 : #endif
1394 :
1395 392 : GetGDALDriverManager()->RegisterDriver( poDriver );
1396 : }
1397 409 : }
1398 :
1399 : #ifdef SUPPORT_CREATE
1400 : /************************************************************************/
1401 : /* IWriteBlock() */
1402 : /************************************************************************/
1403 :
1404 : CPLErr PNGRasterBand::IWriteBlock(int x, int y, void* pvData)
1405 : {
1406 : // rcg, added to support Create().
1407 :
1408 : PNGDataset& ds = *(PNGDataset*)poDS;
1409 :
1410 :
1411 : // Write the block (or consolidate into multichannel block)
1412 : // and then write.
1413 :
1414 : const GDALDataType dt = this->GetRasterDataType();
1415 : const size_t wordsize = ds.m_nBitDepth / 8;
1416 : GDALCopyWords( pvData, dt, wordsize,
1417 : ds.m_pabyBuffer + (nBand-1) * wordsize,
1418 : dt, ds.nBands * wordsize,
1419 : nBlockXSize );
1420 :
1421 : // See if we got all the bands.
1422 : size_t i;
1423 : m_bBandProvided[nBand - 1] = TRUE;
1424 : for(i = 0; i < ds.nBands; i++)
1425 : {
1426 : if(!m_bBandProvided[i])
1427 : return CE_None;
1428 : }
1429 :
1430 : // We received all the bands, so
1431 : // reset band flags and write pixels out.
1432 : this->reset_band_provision_flags();
1433 :
1434 :
1435 : // If first block, write out file header.
1436 : if(x == 0 && y == 0)
1437 : {
1438 : CPLErr err = ds.write_png_header();
1439 : if(err != CE_None)
1440 : return err;
1441 : }
1442 :
1443 : #ifdef CPL_LSB
1444 : if( ds.m_nBitDepth == 16 )
1445 : GDALSwapWords( ds.m_pabyBuffer, 2, nBlockXSize * ds.nBands, 2 );
1446 : #endif
1447 : png_write_rows( ds.m_hPNG, &ds.m_pabyBuffer, 1 );
1448 :
1449 : return CE_None;
1450 : }
1451 :
1452 :
1453 : /************************************************************************/
1454 : /* SetGeoTransform() */
1455 : /************************************************************************/
1456 :
1457 : CPLErr PNGDataset::SetGeoTransform( double * padfTransform )
1458 : {
1459 : // rcg, added to support Create().
1460 :
1461 : CPLErr eErr = CE_None;
1462 :
1463 : memcpy( m_adfGeoTransform, padfTransform, sizeof(double) * 6 );
1464 :
1465 : if ( m_pszFilename )
1466 : {
1467 : if ( GDALWriteWorldFile( m_pszFilename, "wld", m_adfGeoTransform )
1468 : == FALSE )
1469 : {
1470 : CPLError( CE_Failure, CPLE_FileIO, "Can't write world file." );
1471 : eErr = CE_Failure;
1472 : }
1473 : }
1474 :
1475 : return eErr;
1476 : }
1477 :
1478 :
1479 : /************************************************************************/
1480 : /* SetColorTable() */
1481 : /************************************************************************/
1482 :
1483 : CPLErr PNGRasterBand::SetColorTable(GDALColorTable* poCT)
1484 : {
1485 : if( poCT == NULL )
1486 : return CE_Failure;
1487 :
1488 : // rcg, added to support Create().
1489 : // We get called even for grayscale files, since some
1490 : // formats need a palette even then. PNG doesn't, so
1491 : // if a gray palette is given, just ignore it.
1492 :
1493 : GDALColorEntry sEntry;
1494 : for( size_t i = 0; i < poCT->GetColorEntryCount(); i++ )
1495 : {
1496 : poCT->GetColorEntryAsRGB( i, &sEntry );
1497 : if( sEntry.c1 != sEntry.c2 || sEntry.c1 != sEntry.c3)
1498 : {
1499 : CPLErr err = GDALPamRasterBand::SetColorTable(poCT);
1500 : if(err != CE_None)
1501 : return err;
1502 :
1503 : PNGDataset& ds = *(PNGDataset*)poDS;
1504 : ds.m_nColorType = PNG_COLOR_TYPE_PALETTE;
1505 : break;
1506 : // band::IWriteBlock will emit color table as part of
1507 : // header preceding first block write.
1508 : }
1509 : }
1510 :
1511 : return CE_None;
1512 : }
1513 :
1514 :
1515 : /************************************************************************/
1516 : /* PNGDataset::write_png_header() */
1517 : /************************************************************************/
1518 :
1519 : CPLErr PNGDataset::write_png_header()
1520 : {
1521 : /* -------------------------------------------------------------------- */
1522 : /* Initialize PNG access to the file. */
1523 : /* -------------------------------------------------------------------- */
1524 :
1525 : m_hPNG = png_create_write_struct(
1526 : PNG_LIBPNG_VER_STRING, NULL,
1527 : png_gdal_error, png_gdal_warning );
1528 :
1529 : m_psPNGInfo = png_create_info_struct( m_hPNG );
1530 :
1531 : png_set_write_fn( m_hPNG, m_fpImage, png_vsi_write_data, png_vsi_flush );
1532 :
1533 : png_set_IHDR( m_hPNG, m_psPNGInfo, nRasterXSize, nRasterYSize,
1534 : m_nBitDepth, m_nColorType, PNG_INTERLACE_NONE,
1535 : PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT );
1536 :
1537 : png_set_compression_level(m_hPNG, Z_BEST_COMPRESSION);
1538 :
1539 : //png_set_swap_alpha(m_hPNG); // Use RGBA order, not ARGB.
1540 :
1541 : /* -------------------------------------------------------------------- */
1542 : /* Try to handle nodata values as a tRNS block (note for */
1543 : /* paletted images, we save the effect to apply as part of */
1544 : /* palette). */
1545 : /* -------------------------------------------------------------------- */
1546 : //m_bHaveNoData = FALSE;
1547 : //m_dfNoDataValue = -1;
1548 : png_color_16 sTRNSColor;
1549 :
1550 :
1551 : int bHaveNoData = FALSE;
1552 : double dfNoDataValue = -1;
1553 :
1554 : if( m_nColorType == PNG_COLOR_TYPE_GRAY )
1555 : {
1556 : dfNoDataValue = this->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1557 :
1558 : if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 )
1559 : {
1560 : sTRNSColor.gray = (png_uint_16) dfNoDataValue;
1561 : png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
1562 : }
1563 : }
1564 :
1565 : // RGB nodata.
1566 : if( nColorType == PNG_COLOR_TYPE_RGB )
1567 : {
1568 : // First try to use the NODATA_VALUES metadata item.
1569 : if ( this->GetMetadataItem( "NODATA_VALUES" ) != NULL )
1570 : {
1571 : char **papszValues = CSLTokenizeString(
1572 : this->GetMetadataItem( "NODATA_VALUES" ) );
1573 :
1574 : if( CSLCount(papszValues) >= 3 )
1575 : {
1576 : sTRNSColor.red = (png_uint_16) atoi(papszValues[0]);
1577 : sTRNSColor.green = (png_uint_16) atoi(papszValues[1]);
1578 : sTRNSColor.blue = (png_uint_16) atoi(papszValues[2]);
1579 : png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
1580 : }
1581 :
1582 : CSLDestroy( papszValues );
1583 : }
1584 : // Otherwise, get the nodata value from the bands.
1585 : else
1586 : {
1587 : int bHaveNoDataRed = FALSE;
1588 : int bHaveNoDataGreen = FALSE;
1589 : int bHaveNoDataBlue = FALSE;
1590 : double dfNoDataValueRed = -1;
1591 : double dfNoDataValueGreen = -1;
1592 : double dfNoDataValueBlue = -1;
1593 :
1594 : dfNoDataValueRed = this->GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed );
1595 : dfNoDataValueGreen= this->GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen );
1596 : dfNoDataValueBlue = this->GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue );
1597 :
1598 : if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) &&
1599 : ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) &&
1600 : ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) )
1601 : {
1602 : sTRNSColor.red = (png_uint_16) dfNoDataValueRed;
1603 : sTRNSColor.green = (png_uint_16) dfNoDataValueGreen;
1604 : sTRNSColor.blue = (png_uint_16) dfNoDataValueBlue;
1605 : png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor );
1606 : }
1607 : }
1608 : }
1609 :
1610 : /* -------------------------------------------------------------------- */
1611 : /* Write palette if there is one. Technically, I think it is */
1612 : /* possible to write 16bit palettes for PNG, but we will omit */
1613 : /* this for now. */
1614 : /* -------------------------------------------------------------------- */
1615 :
1616 : if( nColorType == PNG_COLOR_TYPE_PALETTE )
1617 : {
1618 : GDALColorTable *poCT;
1619 : GDALColorEntry sEntry;
1620 : int iColor, bFoundTrans = FALSE;
1621 : int bHaveNoData = FALSE;
1622 : double dfNoDataValue = -1;
1623 :
1624 : dfNoDataValue = this->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
1625 :
1626 : poCT = this->GetRasterBand(1)->GetColorTable();
1627 :
1628 : m_pasPNGColors = (png_color *) CPLMalloc(sizeof(png_color) *
1629 : poCT->GetColorEntryCount());
1630 :
1631 : for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
1632 : {
1633 : poCT->GetColorEntryAsRGB( iColor, &sEntry );
1634 : if( sEntry.c4 != 255 )
1635 : bFoundTrans = TRUE;
1636 :
1637 : m_pasPNGColors[iColor].red = (png_byte) sEntry.c1;
1638 : m_pasPNGColors[iColor].green = (png_byte) sEntry.c2;
1639 : m_pasPNGColors[iColor].blue = (png_byte) sEntry.c3;
1640 : }
1641 :
1642 : png_set_PLTE( m_hPNG, m_psPNGInfo, m_pasPNGColors,
1643 : poCT->GetColorEntryCount() );
1644 :
1645 : /* -------------------------------------------------------------------- */
1646 : /* If we have transparent elements in the palette we need to */
1647 : /* write a transparency block. */
1648 : /* -------------------------------------------------------------------- */
1649 : if( bFoundTrans || bHaveNoData )
1650 : {
1651 : m_pabyAlpha = (unsigned char *)CPLMalloc(poCT->GetColorEntryCount());
1652 :
1653 : for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
1654 : {
1655 : poCT->GetColorEntryAsRGB( iColor, &sEntry );
1656 : m_pabyAlpha[iColor] = (unsigned char) sEntry.c4;
1657 :
1658 : if( bHaveNoData && iColor == (int) dfNoDataValue )
1659 : m_pabyAlpha[iColor] = 0;
1660 : }
1661 :
1662 : png_set_tRNS( m_hPNG, m_psPNGInfo, m_pabyAlpha,
1663 : poCT->GetColorEntryCount(), NULL );
1664 : }
1665 : }
1666 :
1667 : png_write_info( m_hPNG, m_psPNGInfo );
1668 : return CE_None;
1669 : }
1670 :
1671 :
1672 : /************************************************************************/
1673 : /* Create() */
1674 : /************************************************************************/
1675 :
1676 : // static
1677 : GDALDataset *PNGDataset::Create
1678 : (
1679 : const char* pszFilename,
1680 : int nXSize, int nYSize,
1681 : int nBands,
1682 : GDALDataType eType,
1683 : char **papszOptions
1684 : )
1685 : {
1686 : if( eType != GDT_Byte && eType != GDT_UInt16)
1687 : {
1688 : CPLError( CE_Failure, CPLE_AppDefined,
1689 : "Attempt to create PNG dataset with an illegal\n"
1690 : "data type (%s), only Byte and UInt16 supported by the format.\n",
1691 : GDALGetDataTypeName(eType) );
1692 :
1693 : return NULL;
1694 : }
1695 :
1696 : if( nBands < 1 || nBands > 4 )
1697 : {
1698 : CPLError( CE_Failure, CPLE_NotSupported,
1699 : "PNG driver doesn't support %d bands. "
1700 : "Must be 1 (gray/indexed color),\n"
1701 : "2 (gray+alpha), 3 (rgb) or 4 (rgba) bands.\n",
1702 : nBands );
1703 :
1704 : return NULL;
1705 : }
1706 :
1707 :
1708 : // Bands are:
1709 : // 1: grayscale or indexed color
1710 : // 2: gray plus alpha
1711 : // 3: rgb
1712 : // 4: rgb plus alpha
1713 :
1714 : if(nXSize < 1 || nYSize < 1)
1715 : {
1716 : CPLError( CE_Failure, CPLE_NotSupported,
1717 : "Specified pixel dimensions (% d x %d) are bad.\n",
1718 : nXSize, nYSize );
1719 : }
1720 :
1721 : /* -------------------------------------------------------------------- */
1722 : /* Setup some parameters. */
1723 : /* -------------------------------------------------------------------- */
1724 :
1725 : PNGDataset* poDS = new PNGDataset();
1726 :
1727 : poDS->nRasterXSize = nXSize;
1728 : poDS->nRasterYSize = nYSize;
1729 : poDS->eAccess = GA_Update;
1730 : poDS->nBands = nBands;
1731 :
1732 :
1733 : switch(nBands)
1734 : {
1735 : case 1:
1736 : poDS->m_nColorType = PNG_COLOR_TYPE_GRAY;
1737 : break;// if a non-gray palette is set, we'll change this.
1738 :
1739 : case 2:
1740 : poDS->m_nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
1741 : break;
1742 :
1743 : case 3:
1744 : poDS->m_nColorType = PNG_COLOR_TYPE_RGB;
1745 : break;
1746 :
1747 : case 4:
1748 : poDS->m_nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
1749 : break;
1750 : }
1751 :
1752 : poDS->m_nBitDepth = (eType == GDT_Byte ? 8 : 16);
1753 :
1754 : poDS->m_pabyBuffer = (GByte *) CPLMalloc(
1755 : nBands * nXSize * poDS->m_nBitDepth / 8 );
1756 :
1757 : /* -------------------------------------------------------------------- */
1758 : /* Create band information objects. */
1759 : /* -------------------------------------------------------------------- */
1760 : int iBand;
1761 :
1762 : for( iBand = 1; iBand <= poDS->nBands; iBand++ )
1763 : poDS->SetBand( iBand, new PNGRasterBand( poDS, iBand ) );
1764 :
1765 : /* -------------------------------------------------------------------- */
1766 : /* Do we need a world file? */
1767 : /* -------------------------------------------------------------------- */
1768 : if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
1769 : poDS->m_bGeoTransformValid = TRUE;
1770 :
1771 : /* -------------------------------------------------------------------- */
1772 : /* Create the file. */
1773 : /* -------------------------------------------------------------------- */
1774 :
1775 : poDS->m_fpImage = VSIFOpenL( pszFilename, "wb" );
1776 : if( poDS->m_fpImage == NULL )
1777 : {
1778 : CPLError( CE_Failure, CPLE_OpenFailed,
1779 : "Unable to create PNG file %s.\n",
1780 : pszFilename );
1781 : delete poDS;
1782 : return NULL;
1783 : }
1784 :
1785 : poDS->m_pszFilename = CPLStrdup(pszFilename);
1786 :
1787 : return poDS;
1788 : }
1789 :
1790 : #endif
|