Subversion Repositories public

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
312 andreas 1
/******************************************************************************
2
 * $Id: pngdataset.cpp,v 1.31 2005/05/17 19:05:52 fwarmerdam Exp $
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
 * $Log: pngdataset.cpp,v $
46
 * Revision 1.31  2005/05/17 19:05:52  fwarmerdam
47
 * Allow flowthrough to pam for geotransform & nodata.
48
 *
49
 * Revision 1.30  2005/04/27 16:35:45  fwarmerdam
50
 * PAM enable
51
 *
52
 * Revision 1.29  2004/08/26 21:20:08  warmerda
53
 * Adjusted so png_access_version_number() is not called for old libpng
54
 * versions (pre 1.2).
55
 *
56
 * Revision 1.28  2004/08/25 13:42:37  warmerda
57
 * Added png version checking after png_create_read_struct() as per suggestion from
58
 * Ben Discoe.
59
 *
60
 * Revision 1.27  2004/05/28 16:05:54  warmerda
61
 * fix bug in bGeoTransformValid setting reading worldfiles
62
 *
63
 * Revision 1.26  2004/01/29 18:48:01  warmerda
64
 * Changed to do the swapping ourseleves.  The png_set_swap() function didn't
65
 * seem to be having the desired effect
66
 *
67
 * Revision 1.25  2004/01/29 14:55:26  warmerda
68
 * ensure 16bit files are byte swapped as needed
69
 *
70
 * Revision 1.24  2003/09/15 20:45:00  warmerda
71
 * add pngw and pgw support
72
 *
73
 * Revision 1.23  2003/07/08 21:27:34  warmerda
74
 * avoid warnings
75
 *
76
 * Revision 1.22  2003/01/31 18:08:24  warmerda
77
 * Added test for broken png_get_channels().  I don't know what is *really*
78
 * going on here.
79
 *
80
 * Revision 1.21  2003/01/25 22:28:18  warmerda
81
 * improved data type error message a bit
82
 *
83
 * Revision 1.20  2003/01/25 22:26:29  warmerda
84
 * fixed a read, and a write bug with 16bit png files
85
 *
86
 * Revision 1.19  2002/11/23 18:54:17  warmerda
87
 * added CREATIONDATATYPES metadata for drivers
88
 *
89
 * Revision 1.18  2002/09/04 06:50:37  warmerda
90
 * avoid static driver pointers
91
 *
92
 * Revision 1.17  2002/07/13 04:16:39  warmerda
93
 * added WORLDFILE support
94
 *
95
 * Revision 1.16  2002/06/18 02:49:23  warmerda
96
 * fixed multiline string constants
97
 *
98
 * Revision 1.15  2002/06/12 21:12:25  warmerda
99
 * update to metadata based driver info
100
 *
101
 * Revision 1.14  2002/04/20 10:12:05  dron
102
 * Added support for GDALWriteWolrldFile()
103
 * New option WORLDFILE=YES
104
 *
105
 * Revision 1.13  2002/04/20 09:51:03  dron
106
 * *** empty log message ***
107
 *
108
 * Revision 1.12  2001/11/11 23:51:00  warmerda
109
 * added required class keyword to friend declarations
110
 *
111
 * Revision 1.11  2001/09/28 14:30:19  warmerda
112
 * Ensure num_trans is initialized to zero in case png_get_tRNS fails.
113
 *
114
 * Revision 1.10  2001/08/23 03:45:15  warmerda
115
 * If there is only one transparent color in the color table, also consider
116
 * it to be the nodata value.
117
 *
118
 * Revision 1.9  2001/08/23 03:32:37  warmerda
119
 * implemented read/write support for transparency (colortable/nodata)
120
 *
121
 * Revision 1.8  2001/08/22 17:12:07  warmerda
122
 * added world file support
123
 *
124
 * Revision 1.7  2001/07/18 04:51:57  warmerda
125
 * added CPL_CVSID
126
 *
127
 * Revision 1.6  2000/08/15 19:28:26  warmerda
128
 * added help topic
129
 *
130
 * Revision 1.5  2000/06/05 13:04:15  warmerda
131
 * Fixed case where png_get_text fails due to no text blocks existing.
132
 *
133
 * Revision 1.4  2000/05/15 22:26:42  warmerda
134
 * Avoid warning.
135
 *
136
 * Revision 1.3  2000/04/28 20:57:07  warmerda
137
 * Removed some unused code.
138
 *
139
 * Revision 1.2  2000/04/28 20:56:01  warmerda
140
 * converted to use CreateCopy() instead of Create()
141
 *
142
 * Revision 1.1  2000/04/27 19:39:51  warmerda
143
 * New
144
 */
145
 
146
#include <gdal/gdal_pam.h>
147
#include <png.h>
148
#include <gdal/cpl_string.h>
149
 
150
CPL_CVSID("$Id: pngdataset.cpp,v 1.31 2005/05/17 19:05:52 fwarmerdam Exp $");
151
 
152
CPL_C_START
153
void	GDALRegister_PNG(void);
154
CPL_C_END
155
 
156
 
157
/************************************************************************/
158
/* ==================================================================== */
159
/*				PNGDataset				*/
160
/* ==================================================================== */
161
/************************************************************************/
162
 
163
class PNGRasterBand;
164
 
165
class PNGDataset : public GDALPamDataset
166
{
167
    friend class PNGRasterBand;
168
 
169
    FILE        *fpImage;
170
    png_structp hPNG;
171
    png_infop   psPNGInfo;
172
    int         nBitDepth;
173
    int         nColorType; /* PNG_COLOR_TYPE_* */
174
    int         bInterlaced;
175
 
176
    int         nBufferStartLine;
177
    int         nBufferLines;
178
    int         nLastLineRead;
179
    GByte      *pabyBuffer;
180
 
181
    GDALColorTable *poColorTable;
182
 
183
    int	   bGeoTransformValid;
184
    double adfGeoTransform[6];
185
 
186
    int		bHaveNoData;
187
    double 	dfNoDataValue;
188
 
189
    void        CollectMetadata();
190
    CPLErr      LoadScanline( int );
191
    void        Restart();
192
 
193
  public:
194
                 PNGDataset();
195
                 ~PNGDataset();
196
 
197
    static GDALDataset *Open( GDALOpenInfo * );
198
 
199
    virtual CPLErr GetGeoTransform( double * );
200
    virtual void FlushCache( void );
201
};
202
 
203
/************************************************************************/
204
/* ==================================================================== */
205
/*                            PNGRasterBand                             */
206
/* ==================================================================== */
207
/************************************************************************/
208
 
209
class PNGRasterBand : public GDALPamRasterBand
210
{
211
    friend class PNGDataset;
212
 
213
  public:
214
 
215
                   PNGRasterBand( PNGDataset *, int );
216
 
217
    virtual CPLErr IReadBlock( int, int, void * );
218
 
219
    virtual GDALColorInterp GetColorInterpretation();
220
    virtual GDALColorTable *GetColorTable();
221
    virtual double GetNoDataValue( int *pbSuccess = NULL );
222
};
223
 
224
 
225
/************************************************************************/
226
/*                           PNGRasterBand()                            */
227
/************************************************************************/
228
 
229
PNGRasterBand::PNGRasterBand( PNGDataset *poDS, int nBand )
230
 
231
{
232
    this->poDS = poDS;
233
    this->nBand = nBand;
234
 
235
    if( poDS->nBitDepth == 16 )
236
        eDataType = GDT_UInt16;
237
    else
238
        eDataType = GDT_Byte;
239
 
240
    nBlockXSize = poDS->nRasterXSize;;
241
    nBlockYSize = 1;
242
}
243
 
244
/************************************************************************/
245
/*                             IReadBlock()                             */
246
/************************************************************************/
247
 
248
CPLErr PNGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
249
                                  void * pImage )
250
 
251
{
252
    PNGDataset	*poGDS = (PNGDataset *) poDS;
253
    CPLErr      eErr;
254
    GByte       *pabyScanline;
255
    int         i, nPixelSize, nPixelOffset, nXSize = GetXSize();
256
 
257
    CPLAssert( nBlockXOff == 0 );
258
 
259
    if( poGDS->nBitDepth == 16 )
260
        nPixelSize = 2;
261
    else
262
        nPixelSize = 1;
263
    nPixelOffset = poGDS->nBands * nPixelSize;
264
 
265
/* -------------------------------------------------------------------- */
266
/*      Load the desired scanline into the working buffer.              */
267
/* -------------------------------------------------------------------- */
268
    eErr = poGDS->LoadScanline( nBlockYOff );
269
    if( eErr != CE_None )
270
        return eErr;
271
 
272
    pabyScanline = poGDS->pabyBuffer 
273
        + (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize
274
        + nPixelSize * (nBand - 1);
275
 
276
/* -------------------------------------------------------------------- */
277
/*      Transfer between the working buffer the the callers buffer.     */
278
/* -------------------------------------------------------------------- */
279
    if( nPixelSize == nPixelOffset )
280
        memcpy( pImage, pabyScanline, nPixelSize * nXSize );
281
    else if( nPixelSize == 1 )
282
    {
283
        for( i = 0; i < nXSize; i++ )
284
            ((GByte *) pImage)[i] = pabyScanline[i*nPixelOffset];
285
    }
286
    else 
287
    {
288
        CPLAssert( nPixelSize == 2 );
289
        for( i = 0; i < nXSize; i++ )
290
        {
291
            ((GUInt16 *) pImage)[i] = 
292
                *((GUInt16 *) (pabyScanline+i*nPixelOffset));
293
        }
294
    }
295
 
296
    return CE_None;
297
}
298
 
299
/************************************************************************/
300
/*                       GetColorInterpretation()                       */
301
/************************************************************************/
302
 
303
GDALColorInterp PNGRasterBand::GetColorInterpretation()
304
 
305
{
306
    PNGDataset	*poGDS = (PNGDataset *) poDS;
307
 
308
    if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY )
309
        return GCI_GrayIndex;
310
 
311
    else if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA )
312
    {
313
        if( nBand == 1 )
314
            return GCI_GrayIndex;
315
        else
316
            return GCI_AlphaBand;
317
    }
318
 
319
    else  if( poGDS->nColorType == PNG_COLOR_TYPE_PALETTE )
320
        return GCI_PaletteIndex;
321
 
322
    else  if( poGDS->nColorType == PNG_COLOR_TYPE_RGB
323
              || poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA )
324
    {
325
        if( nBand == 1 )
326
            return GCI_RedBand;
327
        else if( nBand == 2 )
328
            return GCI_GreenBand;
329
        else if( nBand == 3 )
330
            return GCI_BlueBand;
331
        else 
332
            return GCI_AlphaBand;
333
    }
334
    else
335
        return GCI_GrayIndex;
336
}
337
 
338
/************************************************************************/
339
/*                           GetColorTable()                            */
340
/************************************************************************/
341
 
342
GDALColorTable *PNGRasterBand::GetColorTable()
343
 
344
{
345
    PNGDataset	*poGDS = (PNGDataset *) poDS;
346
 
347
    if( nBand == 1 )
348
        return poGDS->poColorTable;
349
    else
350
        return NULL;
351
}
352
 
353
/************************************************************************/
354
/*                           GetNoDataValue()                           */
355
/************************************************************************/
356
 
357
double PNGRasterBand::GetNoDataValue( int *pbSuccess )
358
 
359
{
360
    PNGDataset *poPDS = (PNGDataset *) poDS;
361
 
362
    if( poPDS->bHaveNoData )
363
    {
364
        if( pbSuccess != NULL )
365
            *pbSuccess = poPDS->bHaveNoData;
366
        return poPDS->dfNoDataValue;
367
    }
368
    else
369
    {
370
        return GDALPamRasterBand::GetNoDataValue( pbSuccess );
371
    }
372
}
373
 
374
/************************************************************************/
375
/* ==================================================================== */
376
/*                             PNGDataset                               */
377
/* ==================================================================== */
378
/************************************************************************/
379
 
380
 
381
/************************************************************************/
382
/*                            PNGDataset()                            */
383
/************************************************************************/
384
 
385
PNGDataset::PNGDataset()
386
 
387
{
388
    hPNG = NULL;
389
    psPNGInfo = NULL;
390
    pabyBuffer = NULL;
391
    nBufferStartLine = 0;
392
    nBufferLines = 0;
393
    nLastLineRead = -1;
394
    poColorTable = NULL;
395
 
396
    bGeoTransformValid = FALSE;
397
    adfGeoTransform[0] = 0.0;
398
    adfGeoTransform[1] = 1.0;
399
    adfGeoTransform[2] = 0.0;
400
    adfGeoTransform[3] = 0.0;
401
    adfGeoTransform[4] = 0.0;
402
    adfGeoTransform[5] = 1.0;
403
 
404
    bHaveNoData = FALSE;
405
    dfNoDataValue = -1;
406
}
407
 
408
/************************************************************************/
409
/*                           ~PNGDataset()                            */
410
/************************************************************************/
411
 
412
PNGDataset::~PNGDataset()
413
 
414
{
415
    FlushCache();
416
 
417
    png_destroy_read_struct( &hPNG, &psPNGInfo, NULL );
418
 
419
    VSIFClose( fpImage );
420
 
421
    if( poColorTable != NULL )
422
        delete poColorTable;
423
}
424
 
425
/************************************************************************/
426
/*                          GetGeoTransform()                           */
427
/************************************************************************/
428
 
429
CPLErr PNGDataset::GetGeoTransform( double * padfTransform )
430
 
431
{
432
 
433
    if( bGeoTransformValid )
434
    {
435
        memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
436
        return CE_None;
437
    }
438
    else
439
        return GDALPamDataset::GetGeoTransform( padfTransform );
440
}
441
 
442
/************************************************************************/
443
/*                             FlushCache()                             */
444
/*                                                                      */
445
/*      We override this so we can also flush out local tiff strip      */
446
/*      cache if need be.                                               */
447
/************************************************************************/
448
 
449
void PNGDataset::FlushCache()
450
 
451
{
452
    GDALPamDataset::FlushCache();
453
 
454
    if( pabyBuffer != NULL )
455
    {
456
        CPLFree( pabyBuffer );
457
        pabyBuffer = NULL;
458
        nBufferStartLine = 0;
459
        nBufferLines = 0;
460
    }
461
}
462
 
463
/************************************************************************/
464
/*                              Restart()                               */
465
/*                                                                      */
466
/*      Restart reading from the beginning of the file.                 */
467
/************************************************************************/
468
 
469
void PNGDataset::Restart()
470
 
471
{
472
    png_destroy_read_struct( &hPNG, &psPNGInfo, NULL );
473
 
474
    VSIRewind( fpImage );
475
 
476
    hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, this, NULL, NULL );
477
 
478
    psPNGInfo = png_create_info_struct( hPNG );
479
 
480
    png_init_io( hPNG, fpImage );
481
    png_read_info( hPNG, psPNGInfo );
482
 
483
    if( nBitDepth < 8 )
484
        png_set_packing( hPNG );
485
 
486
    nLastLineRead = -1;
487
}
488
 
489
 
490
/************************************************************************/
491
/*                            LoadScanline()                            */
492
/************************************************************************/
493
 
494
CPLErr PNGDataset::LoadScanline( int nLine )
495
 
496
{
497
    int   i;
498
    int   nPixelOffset;
499
 
500
    CPLAssert( nLine >= 0 && nLine < GetRasterYSize() );
501
 
502
    if( nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines)
503
        return CE_None;
504
 
505
    if( nBitDepth == 16 )
506
        nPixelOffset = 2 * GetRasterCount();
507
    else
508
        nPixelOffset = 1 * GetRasterCount();
509
 
510
/* -------------------------------------------------------------------- */
511
/*      If the file is interlaced, we will load the entire image        */
512
/*      into memory using the high level API.                           */
513
/* -------------------------------------------------------------------- */
514
    if( bInterlaced )
515
    {
516
        png_bytep	*png_rows;
517
 
518
        CPLAssert( pabyBuffer == NULL );
519
 
520
        if( nLastLineRead != -1 )
521
            Restart();
522
 
523
        nBufferStartLine = 0;
524
        nBufferLines = GetRasterYSize();
525
        pabyBuffer = (GByte *) 
526
            VSIMalloc(nPixelOffset*GetRasterXSize()*GetRasterYSize());
527
 
528
        if( pabyBuffer == NULL )
529
        {
530
            CPLError( CE_Failure, CPLE_OutOfMemory, 
531
                      "Unable to allocate buffer for whole interlaced PNG"
532
                      "image of size %dx%d.\n", 
533
                      GetRasterXSize(), GetRasterYSize() );
534
            return CE_Failure;
535
        }
536
 
537
        png_rows = (png_bytep*)CPLMalloc(sizeof(png_bytep) * GetRasterYSize());
538
        for( i = 0; i < GetRasterYSize(); i++ )
539
            png_rows[i] = pabyBuffer + i * nPixelOffset * GetRasterXSize();
540
 
541
        png_read_image( hPNG, png_rows );
542
 
543
        CPLFree( png_rows );
544
 
545
        nLastLineRead = GetRasterYSize() - 1;
546
 
547
        return CE_None;
548
    }
549
 
550
/* -------------------------------------------------------------------- */
551
/*      Ensure we have space allocated for one scanline                 */
552
/* -------------------------------------------------------------------- */
553
    if( pabyBuffer == NULL )
554
        pabyBuffer = (GByte *) CPLMalloc(nPixelOffset * GetRasterXSize());
555
 
556
/* -------------------------------------------------------------------- */
557
/*      Otherwise we just try to read the requested row.  Do we need    */
558
/*      to rewind and start over?                                       */
559
/* -------------------------------------------------------------------- */
560
    if( nLine <= nLastLineRead )
561
        Restart();
562
 
563
/* -------------------------------------------------------------------- */
564
/*      Read till we get the desired row.                               */
565
/* -------------------------------------------------------------------- */
566
    png_bytep      row;
567
 
568
    row = pabyBuffer;
569
    while( nLine > nLastLineRead )
570
    {
571
        png_read_rows( hPNG, &row, NULL, 1 );
572
        nLastLineRead++;
573
    }
574
 
575
    nBufferStartLine = nLine;
576
    nBufferLines = 1;
577
 
578
/* -------------------------------------------------------------------- */
579
/*      Do swap on LSB machines.  16bit PNG data is stored in MSB       */
580
/*      format.                                                         */
581
/* -------------------------------------------------------------------- */
582
#ifdef CPL_LSB
583
    if( nBitDepth == 16 )
584
        GDALSwapWords( row, 2, GetRasterXSize(), 2 );
585
#endif
586
 
587
    return CE_None;
588
}
589
 
590
/************************************************************************/
591
/*                          CollectMetadata()                           */
592
/*                                                                      */
593
/*      We normally do this after reading up to the image, but be       */
594
/*      forwarned ... we can missing text chunks this way.              */
595
/*                                                                      */
596
/*      We turn each PNG text chunk into one metadata item.  It         */
597
/*      might be nice to preserve language information though we        */
598
/*      don't try to now.                                               */
599
/************************************************************************/
600
 
601
void PNGDataset::CollectMetadata()
602
 
603
{
604
    int   nTextCount;
605
    png_textp text_ptr;
606
 
607
    if( png_get_text( hPNG, psPNGInfo, &text_ptr, &nTextCount ) == 0 )
608
        return;
609
 
610
    for( int iText = 0; iText < nTextCount; iText++ )
611
    {
612
        char	*pszTag = CPLStrdup(text_ptr[iText].key);
613
 
614
        for( int i = 0; pszTag[i] != '\0'; i++ )
615
        {
616
            if( pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':' )
617
                pszTag[i] = '_';
618
        }
619
 
620
        SetMetadataItem( pszTag, text_ptr[iText].text );
621
        CPLFree( pszTag );
622
    }
623
}
624
 
625
/************************************************************************/
626
/*                                Open()                                */
627
/************************************************************************/
628
 
629
GDALDataset *PNGDataset::Open( GDALOpenInfo * poOpenInfo )
630
 
631
{
632
/* -------------------------------------------------------------------- */
633
/*	First we check to see if the file has the expected header	*/
634
/*	bytes.								*/    
635
/* -------------------------------------------------------------------- */
636
    if( poOpenInfo->nHeaderBytes < 4 )
637
        return NULL;
638
 
639
    if( png_sig_cmp(poOpenInfo->pabyHeader, (png_size_t)0, 
640
                    poOpenInfo->nHeaderBytes) != 0 )
641
        return NULL;
642
 
643
    if( poOpenInfo->eAccess == GA_Update )
644
    {
645
        CPLError( CE_Failure, CPLE_NotSupported, 
646
                  "The PNG driver does not support update access to existing"
647
                  " datasets.\n" );
648
        return NULL;
649
    }
650
 
651
/* -------------------------------------------------------------------- */
652
/*      Create a corresponding GDALDataset.                             */
653
/* -------------------------------------------------------------------- */
654
    PNGDataset 	*poDS;
655
 
656
    poDS = new PNGDataset();
657
 
658
    poDS->eAccess = poOpenInfo->eAccess;
659
 
660
    poDS->hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, poDS, 
661
                                         NULL, NULL );
662
    if (poDS->hPNG == NULL)
663
    {
664
#if LIBPNG_VER_MINOR >= 2 || LIBPNG_VER_MAJOR > 1
665
        int version = png_access_version_number();
666
        CPLError( CE_Failure, CPLE_NotSupported, 
667
                  "The PNG driver failed to access libpng with version '%s',"
668
                  " library is actually version '%d'.\n",
669
                  PNG_LIBPNG_VER_STRING, version);
670
#else
671
        CPLError( CE_Failure, CPLE_NotSupported, 
672
                  "The PNG driver failed to in png_create_read_struct().\n"
673
                  "This may be due to version compatibility problems." );
674
#endif
675
        delete poDS;
676
        return NULL;
677
    }
678
 
679
    poDS->psPNGInfo = png_create_info_struct( poDS->hPNG );
680
 
681
/* -------------------------------------------------------------------- */
682
/*	Read pre-image data after ensuring the file is rewound.         */
683
/* -------------------------------------------------------------------- */
684
    /* we should likely do a setjmp() here */
685
 
686
    VSIRewind( poOpenInfo->fp );
687
 
688
    png_init_io( poDS->hPNG, poOpenInfo->fp );
689
    png_read_info( poDS->hPNG, poDS->psPNGInfo );
690
 
691
/* -------------------------------------------------------------------- */
692
/*      Capture some information from the file that is of interest.     */
693
/* -------------------------------------------------------------------- */
694
    poDS->nRasterXSize = png_get_image_width( poDS->hPNG, poDS->psPNGInfo );
695
    poDS->nRasterYSize = png_get_image_height( poDS->hPNG, poDS->psPNGInfo );
696
 
697
    poDS->nBands = png_get_channels( poDS->hPNG, poDS->psPNGInfo );
698
    poDS->nBitDepth = png_get_bit_depth( poDS->hPNG, poDS->psPNGInfo );
699
    poDS->bInterlaced = png_get_interlace_type( poDS->hPNG, poDS->psPNGInfo ) 
700
        				!= PNG_INTERLACE_NONE;
701
 
702
    poDS->nColorType = png_get_color_type( poDS->hPNG, poDS->psPNGInfo );
703
 
704
    if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE 
705
        && poDS->nBands > 1 )
706
    {
707
        CPLDebug( "GDAL", "PNG Driver got %d from png_get_channels(),\n"
708
                  "but this kind of image (paletted) can only have one band.\n"
709
                  "Correcting and continuing, but this may indicate a bug!",
710
                  poDS->nBands );
711
        poDS->nBands = 1;
712
    }
713
 
714
/* -------------------------------------------------------------------- */
715
/*      We want to treat 1,2,4 bit images as eight bit.  This call      */
716
/*      causes libpng to unpack the image.                              */
717
/* -------------------------------------------------------------------- */
718
    if( poDS->nBitDepth < 8 )
719
        png_set_packing( poDS->hPNG );
720
 
721
/* -------------------------------------------------------------------- */
722
/*      Create band information objects.                                */
723
/* -------------------------------------------------------------------- */
724
    for( int iBand = 0; iBand < poDS->nBands; iBand++ )
725
        poDS->SetBand( iBand+1, new PNGRasterBand( poDS, iBand+1 ) );
726
 
727
/* -------------------------------------------------------------------- */
728
/*      Adopt the file pointer.                                         */
729
/* -------------------------------------------------------------------- */
730
    poDS->fpImage = poOpenInfo->fp;
731
    poOpenInfo->fp = NULL;
732
 
733
/* -------------------------------------------------------------------- */
734
/*      Is there a palette?  Note: we should also read back and         */
735
/*      apply transparency values if available.                         */
736
/* -------------------------------------------------------------------- */
737
    if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE )
738
    {
739
        png_color *pasPNGPalette;
740
        int	nColorCount;
741
        GDALColorEntry oEntry;
742
        unsigned char *trans = NULL;
743
        png_color_16 *trans_values = NULL;
744
        int	num_trans = 0;
745
        int	nNoDataIndex = -1;
746
 
747
        if( png_get_PLTE( poDS->hPNG, poDS->psPNGInfo, 
748
                          &pasPNGPalette, &nColorCount ) == 0 )
749
            nColorCount = 0;
750
 
751
        png_get_tRNS( poDS->hPNG, poDS->psPNGInfo, 
752
                      &trans, &num_trans, &trans_values );
753
 
754
        poDS->poColorTable = new GDALColorTable();
755
 
756
        for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
757
        {
758
            oEntry.c1 = pasPNGPalette[iColor].red;
759
            oEntry.c2 = pasPNGPalette[iColor].green;
760
            oEntry.c3 = pasPNGPalette[iColor].blue;
761
 
762
            if( iColor < num_trans )
763
            {
764
                oEntry.c4 = trans[iColor];
765
                if( oEntry.c4 == 0 )
766
                {
767
                    if( nNoDataIndex == -1 )
768
                        nNoDataIndex = iColor;
769
                    else
770
                        nNoDataIndex = -2;
771
                }
772
            }
773
            else
774
                oEntry.c4 = 255;
775
 
776
            poDS->poColorTable->SetColorEntry( iColor, &oEntry );
777
        }
778
 
779
        /*
780
        ** Special hack to an index as the no data value, as long as it
781
        ** is the _only_ transparent color in the palette.
782
        */
783
        if( nNoDataIndex > -1 )
784
        {
785
            poDS->bHaveNoData = TRUE;
786
            poDS->dfNoDataValue = nNoDataIndex;
787
        }
788
    }
789
 
790
/* -------------------------------------------------------------------- */
791
/*      Check for transparency values in greyscale images.              */
792
/* -------------------------------------------------------------------- */
793
    if( poDS->nColorType == PNG_COLOR_TYPE_GRAY 
794
        || poDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA )
795
    {
796
        png_color_16 *trans_values = NULL;
797
        unsigned char *trans;
798
        int num_trans;
799
 
800
        if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo, 
801
                          &trans, &num_trans, &trans_values ) != 0 
802
            && trans_values != NULL )
803
        {
804
            poDS->bHaveNoData = TRUE;
805
            poDS->dfNoDataValue = trans_values->gray;
806
        }
807
    }
808
 
809
/* -------------------------------------------------------------------- */
810
/*      Extract any text chunks as "metadata".                          */
811
/* -------------------------------------------------------------------- */
812
    poDS->CollectMetadata();
813
 
814
/* -------------------------------------------------------------------- */
815
/*      Open overviews.                                                 */
816
/* -------------------------------------------------------------------- */
817
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
818
 
819
/* -------------------------------------------------------------------- */
820
/*      Initialize any PAM information.                                 */
821
/* -------------------------------------------------------------------- */
822
    poDS->SetDescription( poOpenInfo->pszFilename );
823
    poDS->TryLoadXML();
824
 
825
/* -------------------------------------------------------------------- */
826
/*      Check for world file.                                           */
827
/* -------------------------------------------------------------------- */
828
    poDS->bGeoTransformValid = 
829
        GDALReadWorldFile( poOpenInfo->pszFilename, NULL, 
830
                           poDS->adfGeoTransform );
831
 
832
    if( !poDS->bGeoTransformValid )
833
        poDS->bGeoTransformValid = 
834
            GDALReadWorldFile( poOpenInfo->pszFilename, ".wld", 
835
                               poDS->adfGeoTransform );
836
 
837
    if( !poDS->bGeoTransformValid )
838
        poDS->bGeoTransformValid = 
839
            GDALReadWorldFile( poOpenInfo->pszFilename, ".tfw", 
840
                               poDS->adfGeoTransform );
841
    if( !poDS->bGeoTransformValid )
842
        poDS->bGeoTransformValid = 
843
            GDALReadWorldFile( poOpenInfo->pszFilename, ".tifw", 
844
                               poDS->adfGeoTransform );
845
 
846
    return poDS;
847
}
848
 
849
/************************************************************************/
850
/*                           PNGCreateCopy()                            */
851
/************************************************************************/
852
 
853
static GDALDataset *
854
PNGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS, 
855
               int bStrict, char ** papszOptions, 
856
               GDALProgressFunc pfnProgress, void * pProgressData )
857
 
858
{
859
    int  nBands = poSrcDS->GetRasterCount();
860
    int  nXSize = poSrcDS->GetRasterXSize();
861
    int  nYSize = poSrcDS->GetRasterYSize();
862
 
863
/* -------------------------------------------------------------------- */
864
/*      Some some rudimentary checks                                    */
865
/* -------------------------------------------------------------------- */
866
    if( nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4 )
867
    {
868
        CPLError( CE_Failure, CPLE_NotSupported, 
869
                  "PNG driver doesn't support %d bands.  Must be 1 (grey),\n"
870
                  "2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n", 
871
                  nBands );
872
 
873
        return NULL;
874
    }
875
 
876
    if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte 
877
        && poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16
878
        && bStrict )
879
    {
880
        CPLError( CE_Failure, CPLE_NotSupported, 
881
                  "PNG driver doesn't support data type %s. "
882
                  "Only eight bit (Byte) and sixteen bit (UInt16) bands supported.\n", 
883
                  GDALGetDataTypeName( 
884
                      poSrcDS->GetRasterBand(1)->GetRasterDataType()) );
885
 
886
        return NULL;
887
    }
888
 
889
/* -------------------------------------------------------------------- */
890
/*      Setup some parameters.                                          */
891
/* -------------------------------------------------------------------- */
892
    int  nColorType=0, nBitDepth;
893
    GDALDataType eType;
894
 
895
    if( nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == NULL )
896
        nColorType = PNG_COLOR_TYPE_GRAY;
897
    else if( nBands == 1 )
898
        nColorType = PNG_COLOR_TYPE_PALETTE;
899
    else if( nBands == 2 )
900
        nColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
901
    else if( nBands == 3 )
902
        nColorType = PNG_COLOR_TYPE_RGB;
903
    else if( nBands == 4 )
904
        nColorType = PNG_COLOR_TYPE_RGB_ALPHA;
905
 
906
    if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 )
907
    {
908
        eType = GDT_Byte;
909
        nBitDepth = 8;
910
    }
911
    else 
912
    {
913
        eType = GDT_UInt16;
914
        nBitDepth = 16;
915
    }
916
 
917
/* -------------------------------------------------------------------- */
918
/*      Create the dataset.                                             */
919
/* -------------------------------------------------------------------- */
920
    FILE	*fpImage;
921
 
922
    fpImage = VSIFOpen( pszFilename, "wb" );
923
    if( fpImage == NULL )
924
    {
925
        CPLError( CE_Failure, CPLE_OpenFailed, 
926
                  "Unable to create png file %s.\n", 
927
                  pszFilename );
928
        return NULL;
929
    }
930
 
931
/* -------------------------------------------------------------------- */
932
/*      Initialize PNG access to the file.                              */
933
/* -------------------------------------------------------------------- */
934
    png_structp hPNG;
935
    png_infop   psPNGInfo;
936
 
937
    hPNG = png_create_write_struct( PNG_LIBPNG_VER_STRING, 
938
                                    NULL, NULL, NULL );
939
    psPNGInfo = png_create_info_struct( hPNG );
940
 
941
    png_init_io( hPNG, fpImage );
942
 
943
    png_set_IHDR( hPNG, psPNGInfo, nXSize, nYSize, 
944
                  nBitDepth, nColorType, PNG_INTERLACE_NONE, 
945
                  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );
946
 
947
/* -------------------------------------------------------------------- */
948
/*      Try to handle nodata values as a tRNS block (note for           */
949
/*      paletted images, we safe the effect to apply as part of         */
950
/*      palette).  We don't try to address a nodata value for RGB       */
951
/*      images.                                                         */
952
/* -------------------------------------------------------------------- */
953
    int		bHaveNoData = FALSE;
954
    double	dfNoDataValue = -1;
955
    png_color_16 sTRNSColor;
956
 
957
    dfNoDataValue = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData );
958
 
959
    if( (nColorType == PNG_COLOR_TYPE_GRAY 
960
         || nColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
961
        && dfNoDataValue > 0 && dfNoDataValue < 65536 )
962
    {
963
        sTRNSColor.gray = (png_uint_16) dfNoDataValue;
964
        png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor );
965
    }
966
 
967
/* -------------------------------------------------------------------- */
968
/*      Write palette if there is one.  Technically, I think it is      */
969
/*      possible to write 16bit palettes for PNG, but we will omit      */
970
/*      this for now.                                                   */
971
/* -------------------------------------------------------------------- */
972
    png_color	*pasPNGColors = NULL;
973
    unsigned char	*pabyAlpha = NULL;
974
 
975
    if( nColorType == PNG_COLOR_TYPE_PALETTE )
976
    {
977
        GDALColorTable	*poCT;
978
        GDALColorEntry  sEntry;
979
        int		iColor, bFoundTrans = FALSE;
980
 
981
        poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
982
 
983
        pasPNGColors = (png_color *) CPLMalloc(sizeof(png_color) *
984
                                               poCT->GetColorEntryCount());
985
 
986
        for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
987
        {
988
            poCT->GetColorEntryAsRGB( iColor, &sEntry );
989
            if( sEntry.c4 != 255 )
990
                bFoundTrans = TRUE;
991
 
992
            pasPNGColors[iColor].red = (png_byte) sEntry.c1;
993
            pasPNGColors[iColor].green = (png_byte) sEntry.c2;
994
            pasPNGColors[iColor].blue = (png_byte) sEntry.c3;
995
        }
996
 
997
        png_set_PLTE( hPNG, psPNGInfo, pasPNGColors, 
998
                      poCT->GetColorEntryCount() );
999
 
1000
/* -------------------------------------------------------------------- */
1001
/*      If we have transparent elements in the palette we need to       */
1002
/*      write a transparency block.                                     */
1003
/* -------------------------------------------------------------------- */
1004
        if( bFoundTrans || bHaveNoData )
1005
        {
1006
 
1007
            pabyAlpha = (unsigned char *)CPLMalloc(poCT->GetColorEntryCount());
1008
 
1009
            for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ )
1010
            {
1011
                poCT->GetColorEntryAsRGB( iColor, &sEntry );
1012
                pabyAlpha[iColor] = (unsigned char) sEntry.c4;
1013
 
1014
                if( bHaveNoData && iColor == (int) dfNoDataValue )
1015
                    pabyAlpha[iColor] = 0;
1016
            }
1017
 
1018
            png_set_tRNS( hPNG, psPNGInfo, pabyAlpha, 
1019
                          poCT->GetColorEntryCount(), NULL );
1020
        }
1021
    }
1022
 
1023
    png_write_info( hPNG, psPNGInfo );
1024
 
1025
/* -------------------------------------------------------------------- */
1026
/*      Loop over image, copying image data.                            */
1027
/* -------------------------------------------------------------------- */
1028
    GByte 	*pabyScanline;
1029
    CPLErr      eErr;
1030
    int         nWordSize = nBitDepth/8;
1031
 
1032
    pabyScanline = (GByte *) CPLMalloc( nBands * nXSize * nWordSize );
1033
 
1034
    for( int iLine = 0; iLine < nYSize; iLine++ )
1035
    {
1036
        png_bytep       row = pabyScanline;
1037
 
1038
        for( int iBand = 0; iBand < nBands; iBand++ )
1039
        {
1040
            GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 );
1041
            eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1, 
1042
                                     pabyScanline + iBand*nWordSize, 
1043
                                     nXSize, 1, eType,
1044
                                     nBands * nWordSize, 
1045
                                     nBands * nXSize * nWordSize );
1046
        }
1047
 
1048
        png_write_rows( hPNG, &row, 1 );
1049
    }
1050
 
1051
    CPLFree( pabyScanline );
1052
 
1053
    png_write_end( hPNG, psPNGInfo );
1054
    png_destroy_write_struct( &hPNG, &psPNGInfo );
1055
 
1056
    VSIFClose( fpImage );
1057
 
1058
    CPLFree( pabyAlpha );
1059
    CPLFree( pasPNGColors );
1060
 
1061
/* -------------------------------------------------------------------- */
1062
/*      Do we need a world file?                                          */
1063
/* -------------------------------------------------------------------- */
1064
    if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
1065
    {
1066
    	double      adfGeoTransform[6];
1067
 
1068
	poSrcDS->GetGeoTransform( adfGeoTransform );
1069
	GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
1070
    }
1071
 
1072
/* -------------------------------------------------------------------- */
1073
/*      Re-open dataset, and copy any auxilary pam information.         */
1074
/* -------------------------------------------------------------------- */
1075
    PNGDataset *poDS = (PNGDataset *) GDALOpen( pszFilename, GA_ReadOnly );
1076
 
1077
    if( poDS )
1078
        poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );
1079
 
1080
    return poDS;
1081
}
1082
 
1083
/************************************************************************/
1084
/*                          GDALRegister_PNG()                        */
1085
/************************************************************************/
1086
 
1087
void GDALRegister_PNG()
1088
 
1089
{
1090
    GDALDriver	*poDriver;
1091
 
1092
    if( GDALGetDriverByName( "PNG" ) == NULL )
1093
    {
1094
        poDriver = new GDALDriver();
1095
 
1096
        poDriver->SetDescription( "PNG" );
1097
        poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 
1098
                                   "Portable Network Graphics" );
1099
        poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 
1100
                                   "frmt_various.html#PNG" );
1101
        poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "png" );
1102
        poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/png" );
1103
 
1104
        poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, 
1105
                                   "Byte UInt16" );
1106
        poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, 
1107
"<CreationOptionList>\n"
1108
"   <Option name='WORLDFILE' type='boolean' description='Create world file'/>\n"
1109
"</CreationOptionList>\n" );
1110
 
1111
        poDriver->pfnOpen = PNGDataset::Open;
1112
        poDriver->pfnCreateCopy = PNGCreateCopy;
1113
 
1114
        GetGDALDriverManager()->RegisterDriver( poDriver );
1115
    }
1116
}
1117