0,0 → 1,1117 |
/****************************************************************************** |
* $Id: pngdataset.cpp,v 1.31 2005/05/17 19:05:52 fwarmerdam Exp $ |
* |
* Project: PNG Driver |
* Purpose: Implement GDAL PNG Support |
* Author: Frank Warmerdam, warmerda@home.com |
* |
****************************************************************************** |
* Copyright (c) 2000, Frank Warmerdam |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included |
* in all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
****************************************************************************** |
* |
* ISSUES: |
* o CollectMetadata() will only capture TEXT chunks before the image |
* data as the code is currently structured. |
* o Interlaced images are read entirely into memory for use. This is |
* bad for large images. |
* o Image reading is always strictly sequential. Reading backwards will |
* cause the file to be rewound, and access started again from the |
* beginning. |
* o 1, 2 and 4 bit data promoted to 8 bit. |
* o Transparency values not currently read and applied to palette. |
* o 16 bit alpha values are not scaled by to eight bit. |
* o I should install setjmp()/longjmp() based error trapping for PNG calls. |
* Currently a failure in png libraries will result in a complete |
* application termination. |
* |
* $Log: pngdataset.cpp,v $ |
* Revision 1.31 2005/05/17 19:05:52 fwarmerdam |
* Allow flowthrough to pam for geotransform & nodata. |
* |
* Revision 1.30 2005/04/27 16:35:45 fwarmerdam |
* PAM enable |
* |
* Revision 1.29 2004/08/26 21:20:08 warmerda |
* Adjusted so png_access_version_number() is not called for old libpng |
* versions (pre 1.2). |
* |
* Revision 1.28 2004/08/25 13:42:37 warmerda |
* Added png version checking after png_create_read_struct() as per suggestion from |
* Ben Discoe. |
* |
* Revision 1.27 2004/05/28 16:05:54 warmerda |
* fix bug in bGeoTransformValid setting reading worldfiles |
* |
* Revision 1.26 2004/01/29 18:48:01 warmerda |
* Changed to do the swapping ourseleves. The png_set_swap() function didn't |
* seem to be having the desired effect |
* |
* Revision 1.25 2004/01/29 14:55:26 warmerda |
* ensure 16bit files are byte swapped as needed |
* |
* Revision 1.24 2003/09/15 20:45:00 warmerda |
* add pngw and pgw support |
* |
* Revision 1.23 2003/07/08 21:27:34 warmerda |
* avoid warnings |
* |
* Revision 1.22 2003/01/31 18:08:24 warmerda |
* Added test for broken png_get_channels(). I don't know what is *really* |
* going on here. |
* |
* Revision 1.21 2003/01/25 22:28:18 warmerda |
* improved data type error message a bit |
* |
* Revision 1.20 2003/01/25 22:26:29 warmerda |
* fixed a read, and a write bug with 16bit png files |
* |
* Revision 1.19 2002/11/23 18:54:17 warmerda |
* added CREATIONDATATYPES metadata for drivers |
* |
* Revision 1.18 2002/09/04 06:50:37 warmerda |
* avoid static driver pointers |
* |
* Revision 1.17 2002/07/13 04:16:39 warmerda |
* added WORLDFILE support |
* |
* Revision 1.16 2002/06/18 02:49:23 warmerda |
* fixed multiline string constants |
* |
* Revision 1.15 2002/06/12 21:12:25 warmerda |
* update to metadata based driver info |
* |
* Revision 1.14 2002/04/20 10:12:05 dron |
* Added support for GDALWriteWolrldFile() |
* New option WORLDFILE=YES |
* |
* Revision 1.13 2002/04/20 09:51:03 dron |
* *** empty log message *** |
* |
* Revision 1.12 2001/11/11 23:51:00 warmerda |
* added required class keyword to friend declarations |
* |
* Revision 1.11 2001/09/28 14:30:19 warmerda |
* Ensure num_trans is initialized to zero in case png_get_tRNS fails. |
* |
* Revision 1.10 2001/08/23 03:45:15 warmerda |
* If there is only one transparent color in the color table, also consider |
* it to be the nodata value. |
* |
* Revision 1.9 2001/08/23 03:32:37 warmerda |
* implemented read/write support for transparency (colortable/nodata) |
* |
* Revision 1.8 2001/08/22 17:12:07 warmerda |
* added world file support |
* |
* Revision 1.7 2001/07/18 04:51:57 warmerda |
* added CPL_CVSID |
* |
* Revision 1.6 2000/08/15 19:28:26 warmerda |
* added help topic |
* |
* Revision 1.5 2000/06/05 13:04:15 warmerda |
* Fixed case where png_get_text fails due to no text blocks existing. |
* |
* Revision 1.4 2000/05/15 22:26:42 warmerda |
* Avoid warning. |
* |
* Revision 1.3 2000/04/28 20:57:07 warmerda |
* Removed some unused code. |
* |
* Revision 1.2 2000/04/28 20:56:01 warmerda |
* converted to use CreateCopy() instead of Create() |
* |
* Revision 1.1 2000/04/27 19:39:51 warmerda |
* New |
*/ |
|
#include <gdal/gdal_pam.h> |
#include <png.h> |
#include <gdal/cpl_string.h> |
|
CPL_CVSID("$Id: pngdataset.cpp,v 1.31 2005/05/17 19:05:52 fwarmerdam Exp $"); |
|
CPL_C_START |
void GDALRegister_PNG(void); |
CPL_C_END |
|
|
/************************************************************************/ |
/* ==================================================================== */ |
/* PNGDataset */ |
/* ==================================================================== */ |
/************************************************************************/ |
|
class PNGRasterBand; |
|
class PNGDataset : public GDALPamDataset |
{ |
friend class PNGRasterBand; |
|
FILE *fpImage; |
png_structp hPNG; |
png_infop psPNGInfo; |
int nBitDepth; |
int nColorType; /* PNG_COLOR_TYPE_* */ |
int bInterlaced; |
|
int nBufferStartLine; |
int nBufferLines; |
int nLastLineRead; |
GByte *pabyBuffer; |
|
GDALColorTable *poColorTable; |
|
int bGeoTransformValid; |
double adfGeoTransform[6]; |
|
int bHaveNoData; |
double dfNoDataValue; |
|
void CollectMetadata(); |
CPLErr LoadScanline( int ); |
void Restart(); |
|
public: |
PNGDataset(); |
~PNGDataset(); |
|
static GDALDataset *Open( GDALOpenInfo * ); |
|
virtual CPLErr GetGeoTransform( double * ); |
virtual void FlushCache( void ); |
}; |
|
/************************************************************************/ |
/* ==================================================================== */ |
/* PNGRasterBand */ |
/* ==================================================================== */ |
/************************************************************************/ |
|
class PNGRasterBand : public GDALPamRasterBand |
{ |
friend class PNGDataset; |
|
public: |
|
PNGRasterBand( PNGDataset *, int ); |
|
virtual CPLErr IReadBlock( int, int, void * ); |
|
virtual GDALColorInterp GetColorInterpretation(); |
virtual GDALColorTable *GetColorTable(); |
virtual double GetNoDataValue( int *pbSuccess = NULL ); |
}; |
|
|
/************************************************************************/ |
/* PNGRasterBand() */ |
/************************************************************************/ |
|
PNGRasterBand::PNGRasterBand( PNGDataset *poDS, int nBand ) |
|
{ |
this->poDS = poDS; |
this->nBand = nBand; |
|
if( poDS->nBitDepth == 16 ) |
eDataType = GDT_UInt16; |
else |
eDataType = GDT_Byte; |
|
nBlockXSize = poDS->nRasterXSize;; |
nBlockYSize = 1; |
} |
|
/************************************************************************/ |
/* IReadBlock() */ |
/************************************************************************/ |
|
CPLErr PNGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff, |
void * pImage ) |
|
{ |
PNGDataset *poGDS = (PNGDataset *) poDS; |
CPLErr eErr; |
GByte *pabyScanline; |
int i, nPixelSize, nPixelOffset, nXSize = GetXSize(); |
|
CPLAssert( nBlockXOff == 0 ); |
|
if( poGDS->nBitDepth == 16 ) |
nPixelSize = 2; |
else |
nPixelSize = 1; |
nPixelOffset = poGDS->nBands * nPixelSize; |
|
/* -------------------------------------------------------------------- */ |
/* Load the desired scanline into the working buffer. */ |
/* -------------------------------------------------------------------- */ |
eErr = poGDS->LoadScanline( nBlockYOff ); |
if( eErr != CE_None ) |
return eErr; |
|
pabyScanline = poGDS->pabyBuffer |
+ (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize |
+ nPixelSize * (nBand - 1); |
|
/* -------------------------------------------------------------------- */ |
/* Transfer between the working buffer the the callers buffer. */ |
/* -------------------------------------------------------------------- */ |
if( nPixelSize == nPixelOffset ) |
memcpy( pImage, pabyScanline, nPixelSize * nXSize ); |
else if( nPixelSize == 1 ) |
{ |
for( i = 0; i < nXSize; i++ ) |
((GByte *) pImage)[i] = pabyScanline[i*nPixelOffset]; |
} |
else |
{ |
CPLAssert( nPixelSize == 2 ); |
for( i = 0; i < nXSize; i++ ) |
{ |
((GUInt16 *) pImage)[i] = |
*((GUInt16 *) (pabyScanline+i*nPixelOffset)); |
} |
} |
|
return CE_None; |
} |
|
/************************************************************************/ |
/* GetColorInterpretation() */ |
/************************************************************************/ |
|
GDALColorInterp PNGRasterBand::GetColorInterpretation() |
|
{ |
PNGDataset *poGDS = (PNGDataset *) poDS; |
|
if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY ) |
return GCI_GrayIndex; |
|
else if( poGDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA ) |
{ |
if( nBand == 1 ) |
return GCI_GrayIndex; |
else |
return GCI_AlphaBand; |
} |
|
else if( poGDS->nColorType == PNG_COLOR_TYPE_PALETTE ) |
return GCI_PaletteIndex; |
|
else if( poGDS->nColorType == PNG_COLOR_TYPE_RGB |
|| poGDS->nColorType == PNG_COLOR_TYPE_RGB_ALPHA ) |
{ |
if( nBand == 1 ) |
return GCI_RedBand; |
else if( nBand == 2 ) |
return GCI_GreenBand; |
else if( nBand == 3 ) |
return GCI_BlueBand; |
else |
return GCI_AlphaBand; |
} |
else |
return GCI_GrayIndex; |
} |
|
/************************************************************************/ |
/* GetColorTable() */ |
/************************************************************************/ |
|
GDALColorTable *PNGRasterBand::GetColorTable() |
|
{ |
PNGDataset *poGDS = (PNGDataset *) poDS; |
|
if( nBand == 1 ) |
return poGDS->poColorTable; |
else |
return NULL; |
} |
|
/************************************************************************/ |
/* GetNoDataValue() */ |
/************************************************************************/ |
|
double PNGRasterBand::GetNoDataValue( int *pbSuccess ) |
|
{ |
PNGDataset *poPDS = (PNGDataset *) poDS; |
|
if( poPDS->bHaveNoData ) |
{ |
if( pbSuccess != NULL ) |
*pbSuccess = poPDS->bHaveNoData; |
return poPDS->dfNoDataValue; |
} |
else |
{ |
return GDALPamRasterBand::GetNoDataValue( pbSuccess ); |
} |
} |
|
/************************************************************************/ |
/* ==================================================================== */ |
/* PNGDataset */ |
/* ==================================================================== */ |
/************************************************************************/ |
|
|
/************************************************************************/ |
/* PNGDataset() */ |
/************************************************************************/ |
|
PNGDataset::PNGDataset() |
|
{ |
hPNG = NULL; |
psPNGInfo = NULL; |
pabyBuffer = NULL; |
nBufferStartLine = 0; |
nBufferLines = 0; |
nLastLineRead = -1; |
poColorTable = NULL; |
|
bGeoTransformValid = FALSE; |
adfGeoTransform[0] = 0.0; |
adfGeoTransform[1] = 1.0; |
adfGeoTransform[2] = 0.0; |
adfGeoTransform[3] = 0.0; |
adfGeoTransform[4] = 0.0; |
adfGeoTransform[5] = 1.0; |
|
bHaveNoData = FALSE; |
dfNoDataValue = -1; |
} |
|
/************************************************************************/ |
/* ~PNGDataset() */ |
/************************************************************************/ |
|
PNGDataset::~PNGDataset() |
|
{ |
FlushCache(); |
|
png_destroy_read_struct( &hPNG, &psPNGInfo, NULL ); |
|
VSIFClose( fpImage ); |
|
if( poColorTable != NULL ) |
delete poColorTable; |
} |
|
/************************************************************************/ |
/* GetGeoTransform() */ |
/************************************************************************/ |
|
CPLErr PNGDataset::GetGeoTransform( double * padfTransform ) |
|
{ |
|
if( bGeoTransformValid ) |
{ |
memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 ); |
return CE_None; |
} |
else |
return GDALPamDataset::GetGeoTransform( padfTransform ); |
} |
|
/************************************************************************/ |
/* FlushCache() */ |
/* */ |
/* We override this so we can also flush out local tiff strip */ |
/* cache if need be. */ |
/************************************************************************/ |
|
void PNGDataset::FlushCache() |
|
{ |
GDALPamDataset::FlushCache(); |
|
if( pabyBuffer != NULL ) |
{ |
CPLFree( pabyBuffer ); |
pabyBuffer = NULL; |
nBufferStartLine = 0; |
nBufferLines = 0; |
} |
} |
|
/************************************************************************/ |
/* Restart() */ |
/* */ |
/* Restart reading from the beginning of the file. */ |
/************************************************************************/ |
|
void PNGDataset::Restart() |
|
{ |
png_destroy_read_struct( &hPNG, &psPNGInfo, NULL ); |
|
VSIRewind( fpImage ); |
|
hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, this, NULL, NULL ); |
|
psPNGInfo = png_create_info_struct( hPNG ); |
|
png_init_io( hPNG, fpImage ); |
png_read_info( hPNG, psPNGInfo ); |
|
if( nBitDepth < 8 ) |
png_set_packing( hPNG ); |
|
nLastLineRead = -1; |
} |
|
|
/************************************************************************/ |
/* LoadScanline() */ |
/************************************************************************/ |
|
CPLErr PNGDataset::LoadScanline( int nLine ) |
|
{ |
int i; |
int nPixelOffset; |
|
CPLAssert( nLine >= 0 && nLine < GetRasterYSize() ); |
|
if( nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines) |
return CE_None; |
|
if( nBitDepth == 16 ) |
nPixelOffset = 2 * GetRasterCount(); |
else |
nPixelOffset = 1 * GetRasterCount(); |
|
/* -------------------------------------------------------------------- */ |
/* If the file is interlaced, we will load the entire image */ |
/* into memory using the high level API. */ |
/* -------------------------------------------------------------------- */ |
if( bInterlaced ) |
{ |
png_bytep *png_rows; |
|
CPLAssert( pabyBuffer == NULL ); |
|
if( nLastLineRead != -1 ) |
Restart(); |
|
nBufferStartLine = 0; |
nBufferLines = GetRasterYSize(); |
pabyBuffer = (GByte *) |
VSIMalloc(nPixelOffset*GetRasterXSize()*GetRasterYSize()); |
|
if( pabyBuffer == NULL ) |
{ |
CPLError( CE_Failure, CPLE_OutOfMemory, |
"Unable to allocate buffer for whole interlaced PNG" |
"image of size %dx%d.\n", |
GetRasterXSize(), GetRasterYSize() ); |
return CE_Failure; |
} |
|
png_rows = (png_bytep*)CPLMalloc(sizeof(png_bytep) * GetRasterYSize()); |
for( i = 0; i < GetRasterYSize(); i++ ) |
png_rows[i] = pabyBuffer + i * nPixelOffset * GetRasterXSize(); |
|
png_read_image( hPNG, png_rows ); |
|
CPLFree( png_rows ); |
|
nLastLineRead = GetRasterYSize() - 1; |
|
return CE_None; |
} |
|
/* -------------------------------------------------------------------- */ |
/* Ensure we have space allocated for one scanline */ |
/* -------------------------------------------------------------------- */ |
if( pabyBuffer == NULL ) |
pabyBuffer = (GByte *) CPLMalloc(nPixelOffset * GetRasterXSize()); |
|
/* -------------------------------------------------------------------- */ |
/* Otherwise we just try to read the requested row. Do we need */ |
/* to rewind and start over? */ |
/* -------------------------------------------------------------------- */ |
if( nLine <= nLastLineRead ) |
Restart(); |
|
/* -------------------------------------------------------------------- */ |
/* Read till we get the desired row. */ |
/* -------------------------------------------------------------------- */ |
png_bytep row; |
|
row = pabyBuffer; |
while( nLine > nLastLineRead ) |
{ |
png_read_rows( hPNG, &row, NULL, 1 ); |
nLastLineRead++; |
} |
|
nBufferStartLine = nLine; |
nBufferLines = 1; |
|
/* -------------------------------------------------------------------- */ |
/* Do swap on LSB machines. 16bit PNG data is stored in MSB */ |
/* format. */ |
/* -------------------------------------------------------------------- */ |
#ifdef CPL_LSB |
if( nBitDepth == 16 ) |
GDALSwapWords( row, 2, GetRasterXSize(), 2 ); |
#endif |
|
return CE_None; |
} |
|
/************************************************************************/ |
/* CollectMetadata() */ |
/* */ |
/* We normally do this after reading up to the image, but be */ |
/* forwarned ... we can missing text chunks this way. */ |
/* */ |
/* We turn each PNG text chunk into one metadata item. It */ |
/* might be nice to preserve language information though we */ |
/* don't try to now. */ |
/************************************************************************/ |
|
void PNGDataset::CollectMetadata() |
|
{ |
int nTextCount; |
png_textp text_ptr; |
|
if( png_get_text( hPNG, psPNGInfo, &text_ptr, &nTextCount ) == 0 ) |
return; |
|
for( int iText = 0; iText < nTextCount; iText++ ) |
{ |
char *pszTag = CPLStrdup(text_ptr[iText].key); |
|
for( int i = 0; pszTag[i] != '\0'; i++ ) |
{ |
if( pszTag[i] == ' ' || pszTag[i] == '=' || pszTag[i] == ':' ) |
pszTag[i] = '_'; |
} |
|
SetMetadataItem( pszTag, text_ptr[iText].text ); |
CPLFree( pszTag ); |
} |
} |
|
/************************************************************************/ |
/* Open() */ |
/************************************************************************/ |
|
GDALDataset *PNGDataset::Open( GDALOpenInfo * poOpenInfo ) |
|
{ |
/* -------------------------------------------------------------------- */ |
/* First we check to see if the file has the expected header */ |
/* bytes. */ |
/* -------------------------------------------------------------------- */ |
if( poOpenInfo->nHeaderBytes < 4 ) |
return NULL; |
|
if( png_sig_cmp(poOpenInfo->pabyHeader, (png_size_t)0, |
poOpenInfo->nHeaderBytes) != 0 ) |
return NULL; |
|
if( poOpenInfo->eAccess == GA_Update ) |
{ |
CPLError( CE_Failure, CPLE_NotSupported, |
"The PNG driver does not support update access to existing" |
" datasets.\n" ); |
return NULL; |
} |
|
/* -------------------------------------------------------------------- */ |
/* Create a corresponding GDALDataset. */ |
/* -------------------------------------------------------------------- */ |
PNGDataset *poDS; |
|
poDS = new PNGDataset(); |
|
poDS->eAccess = poOpenInfo->eAccess; |
|
poDS->hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, poDS, |
NULL, NULL ); |
if (poDS->hPNG == NULL) |
{ |
#if LIBPNG_VER_MINOR >= 2 || LIBPNG_VER_MAJOR > 1 |
int version = png_access_version_number(); |
CPLError( CE_Failure, CPLE_NotSupported, |
"The PNG driver failed to access libpng with version '%s'," |
" library is actually version '%d'.\n", |
PNG_LIBPNG_VER_STRING, version); |
#else |
CPLError( CE_Failure, CPLE_NotSupported, |
"The PNG driver failed to in png_create_read_struct().\n" |
"This may be due to version compatibility problems." ); |
#endif |
delete poDS; |
return NULL; |
} |
|
poDS->psPNGInfo = png_create_info_struct( poDS->hPNG ); |
|
/* -------------------------------------------------------------------- */ |
/* Read pre-image data after ensuring the file is rewound. */ |
/* -------------------------------------------------------------------- */ |
/* we should likely do a setjmp() here */ |
|
VSIRewind( poOpenInfo->fp ); |
|
png_init_io( poDS->hPNG, poOpenInfo->fp ); |
png_read_info( poDS->hPNG, poDS->psPNGInfo ); |
|
/* -------------------------------------------------------------------- */ |
/* Capture some information from the file that is of interest. */ |
/* -------------------------------------------------------------------- */ |
poDS->nRasterXSize = png_get_image_width( poDS->hPNG, poDS->psPNGInfo ); |
poDS->nRasterYSize = png_get_image_height( poDS->hPNG, poDS->psPNGInfo ); |
|
poDS->nBands = png_get_channels( poDS->hPNG, poDS->psPNGInfo ); |
poDS->nBitDepth = png_get_bit_depth( poDS->hPNG, poDS->psPNGInfo ); |
poDS->bInterlaced = png_get_interlace_type( poDS->hPNG, poDS->psPNGInfo ) |
!= PNG_INTERLACE_NONE; |
|
poDS->nColorType = png_get_color_type( poDS->hPNG, poDS->psPNGInfo ); |
|
if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE |
&& poDS->nBands > 1 ) |
{ |
CPLDebug( "GDAL", "PNG Driver got %d from png_get_channels(),\n" |
"but this kind of image (paletted) can only have one band.\n" |
"Correcting and continuing, but this may indicate a bug!", |
poDS->nBands ); |
poDS->nBands = 1; |
} |
|
/* -------------------------------------------------------------------- */ |
/* We want to treat 1,2,4 bit images as eight bit. This call */ |
/* causes libpng to unpack the image. */ |
/* -------------------------------------------------------------------- */ |
if( poDS->nBitDepth < 8 ) |
png_set_packing( poDS->hPNG ); |
|
/* -------------------------------------------------------------------- */ |
/* Create band information objects. */ |
/* -------------------------------------------------------------------- */ |
for( int iBand = 0; iBand < poDS->nBands; iBand++ ) |
poDS->SetBand( iBand+1, new PNGRasterBand( poDS, iBand+1 ) ); |
|
/* -------------------------------------------------------------------- */ |
/* Adopt the file pointer. */ |
/* -------------------------------------------------------------------- */ |
poDS->fpImage = poOpenInfo->fp; |
poOpenInfo->fp = NULL; |
|
/* -------------------------------------------------------------------- */ |
/* Is there a palette? Note: we should also read back and */ |
/* apply transparency values if available. */ |
/* -------------------------------------------------------------------- */ |
if( poDS->nColorType == PNG_COLOR_TYPE_PALETTE ) |
{ |
png_color *pasPNGPalette; |
int nColorCount; |
GDALColorEntry oEntry; |
unsigned char *trans = NULL; |
png_color_16 *trans_values = NULL; |
int num_trans = 0; |
int nNoDataIndex = -1; |
|
if( png_get_PLTE( poDS->hPNG, poDS->psPNGInfo, |
&pasPNGPalette, &nColorCount ) == 0 ) |
nColorCount = 0; |
|
png_get_tRNS( poDS->hPNG, poDS->psPNGInfo, |
&trans, &num_trans, &trans_values ); |
|
poDS->poColorTable = new GDALColorTable(); |
|
for( int iColor = nColorCount - 1; iColor >= 0; iColor-- ) |
{ |
oEntry.c1 = pasPNGPalette[iColor].red; |
oEntry.c2 = pasPNGPalette[iColor].green; |
oEntry.c3 = pasPNGPalette[iColor].blue; |
|
if( iColor < num_trans ) |
{ |
oEntry.c4 = trans[iColor]; |
if( oEntry.c4 == 0 ) |
{ |
if( nNoDataIndex == -1 ) |
nNoDataIndex = iColor; |
else |
nNoDataIndex = -2; |
} |
} |
else |
oEntry.c4 = 255; |
|
poDS->poColorTable->SetColorEntry( iColor, &oEntry ); |
} |
|
/* |
** Special hack to an index as the no data value, as long as it |
** is the _only_ transparent color in the palette. |
*/ |
if( nNoDataIndex > -1 ) |
{ |
poDS->bHaveNoData = TRUE; |
poDS->dfNoDataValue = nNoDataIndex; |
} |
} |
|
/* -------------------------------------------------------------------- */ |
/* Check for transparency values in greyscale images. */ |
/* -------------------------------------------------------------------- */ |
if( poDS->nColorType == PNG_COLOR_TYPE_GRAY |
|| poDS->nColorType == PNG_COLOR_TYPE_GRAY_ALPHA ) |
{ |
png_color_16 *trans_values = NULL; |
unsigned char *trans; |
int num_trans; |
|
if( png_get_tRNS( poDS->hPNG, poDS->psPNGInfo, |
&trans, &num_trans, &trans_values ) != 0 |
&& trans_values != NULL ) |
{ |
poDS->bHaveNoData = TRUE; |
poDS->dfNoDataValue = trans_values->gray; |
} |
} |
|
/* -------------------------------------------------------------------- */ |
/* Extract any text chunks as "metadata". */ |
/* -------------------------------------------------------------------- */ |
poDS->CollectMetadata(); |
|
/* -------------------------------------------------------------------- */ |
/* Open overviews. */ |
/* -------------------------------------------------------------------- */ |
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename ); |
|
/* -------------------------------------------------------------------- */ |
/* Initialize any PAM information. */ |
/* -------------------------------------------------------------------- */ |
poDS->SetDescription( poOpenInfo->pszFilename ); |
poDS->TryLoadXML(); |
|
/* -------------------------------------------------------------------- */ |
/* Check for world file. */ |
/* -------------------------------------------------------------------- */ |
poDS->bGeoTransformValid = |
GDALReadWorldFile( poOpenInfo->pszFilename, NULL, |
poDS->adfGeoTransform ); |
|
if( !poDS->bGeoTransformValid ) |
poDS->bGeoTransformValid = |
GDALReadWorldFile( poOpenInfo->pszFilename, ".wld", |
poDS->adfGeoTransform ); |
|
if( !poDS->bGeoTransformValid ) |
poDS->bGeoTransformValid = |
GDALReadWorldFile( poOpenInfo->pszFilename, ".tfw", |
poDS->adfGeoTransform ); |
if( !poDS->bGeoTransformValid ) |
poDS->bGeoTransformValid = |
GDALReadWorldFile( poOpenInfo->pszFilename, ".tifw", |
poDS->adfGeoTransform ); |
|
return poDS; |
} |
|
/************************************************************************/ |
/* PNGCreateCopy() */ |
/************************************************************************/ |
|
static GDALDataset * |
PNGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS, |
int bStrict, char ** papszOptions, |
GDALProgressFunc pfnProgress, void * pProgressData ) |
|
{ |
int nBands = poSrcDS->GetRasterCount(); |
int nXSize = poSrcDS->GetRasterXSize(); |
int nYSize = poSrcDS->GetRasterYSize(); |
|
/* -------------------------------------------------------------------- */ |
/* Some some rudimentary checks */ |
/* -------------------------------------------------------------------- */ |
if( nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4 ) |
{ |
CPLError( CE_Failure, CPLE_NotSupported, |
"PNG driver doesn't support %d bands. Must be 1 (grey),\n" |
"2 (grey+alpha), 3 (rgb) or 4 (rgba) bands.\n", |
nBands ); |
|
return NULL; |
} |
|
if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte |
&& poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 |
&& bStrict ) |
{ |
CPLError( CE_Failure, CPLE_NotSupported, |
"PNG driver doesn't support data type %s. " |
"Only eight bit (Byte) and sixteen bit (UInt16) bands supported.\n", |
GDALGetDataTypeName( |
poSrcDS->GetRasterBand(1)->GetRasterDataType()) ); |
|
return NULL; |
} |
|
/* -------------------------------------------------------------------- */ |
/* Setup some parameters. */ |
/* -------------------------------------------------------------------- */ |
int nColorType=0, nBitDepth; |
GDALDataType eType; |
|
if( nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() == NULL ) |
nColorType = PNG_COLOR_TYPE_GRAY; |
else if( nBands == 1 ) |
nColorType = PNG_COLOR_TYPE_PALETTE; |
else if( nBands == 2 ) |
nColorType = PNG_COLOR_TYPE_GRAY_ALPHA; |
else if( nBands == 3 ) |
nColorType = PNG_COLOR_TYPE_RGB; |
else if( nBands == 4 ) |
nColorType = PNG_COLOR_TYPE_RGB_ALPHA; |
|
if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 ) |
{ |
eType = GDT_Byte; |
nBitDepth = 8; |
} |
else |
{ |
eType = GDT_UInt16; |
nBitDepth = 16; |
} |
|
/* -------------------------------------------------------------------- */ |
/* Create the dataset. */ |
/* -------------------------------------------------------------------- */ |
FILE *fpImage; |
|
fpImage = VSIFOpen( pszFilename, "wb" ); |
if( fpImage == NULL ) |
{ |
CPLError( CE_Failure, CPLE_OpenFailed, |
"Unable to create png file %s.\n", |
pszFilename ); |
return NULL; |
} |
|
/* -------------------------------------------------------------------- */ |
/* Initialize PNG access to the file. */ |
/* -------------------------------------------------------------------- */ |
png_structp hPNG; |
png_infop psPNGInfo; |
|
hPNG = png_create_write_struct( PNG_LIBPNG_VER_STRING, |
NULL, NULL, NULL ); |
psPNGInfo = png_create_info_struct( hPNG ); |
|
png_init_io( hPNG, fpImage ); |
|
png_set_IHDR( hPNG, psPNGInfo, nXSize, nYSize, |
nBitDepth, nColorType, PNG_INTERLACE_NONE, |
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE ); |
|
/* -------------------------------------------------------------------- */ |
/* Try to handle nodata values as a tRNS block (note for */ |
/* paletted images, we safe the effect to apply as part of */ |
/* palette). We don't try to address a nodata value for RGB */ |
/* images. */ |
/* -------------------------------------------------------------------- */ |
int bHaveNoData = FALSE; |
double dfNoDataValue = -1; |
png_color_16 sTRNSColor; |
|
dfNoDataValue = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData ); |
|
if( (nColorType == PNG_COLOR_TYPE_GRAY |
|| nColorType == PNG_COLOR_TYPE_GRAY_ALPHA) |
&& dfNoDataValue > 0 && dfNoDataValue < 65536 ) |
{ |
sTRNSColor.gray = (png_uint_16) dfNoDataValue; |
png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor ); |
} |
|
/* -------------------------------------------------------------------- */ |
/* Write palette if there is one. Technically, I think it is */ |
/* possible to write 16bit palettes for PNG, but we will omit */ |
/* this for now. */ |
/* -------------------------------------------------------------------- */ |
png_color *pasPNGColors = NULL; |
unsigned char *pabyAlpha = NULL; |
|
if( nColorType == PNG_COLOR_TYPE_PALETTE ) |
{ |
GDALColorTable *poCT; |
GDALColorEntry sEntry; |
int iColor, bFoundTrans = FALSE; |
|
poCT = poSrcDS->GetRasterBand(1)->GetColorTable(); |
|
pasPNGColors = (png_color *) CPLMalloc(sizeof(png_color) * |
poCT->GetColorEntryCount()); |
|
for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ ) |
{ |
poCT->GetColorEntryAsRGB( iColor, &sEntry ); |
if( sEntry.c4 != 255 ) |
bFoundTrans = TRUE; |
|
pasPNGColors[iColor].red = (png_byte) sEntry.c1; |
pasPNGColors[iColor].green = (png_byte) sEntry.c2; |
pasPNGColors[iColor].blue = (png_byte) sEntry.c3; |
} |
|
png_set_PLTE( hPNG, psPNGInfo, pasPNGColors, |
poCT->GetColorEntryCount() ); |
|
/* -------------------------------------------------------------------- */ |
/* If we have transparent elements in the palette we need to */ |
/* write a transparency block. */ |
/* -------------------------------------------------------------------- */ |
if( bFoundTrans || bHaveNoData ) |
{ |
|
pabyAlpha = (unsigned char *)CPLMalloc(poCT->GetColorEntryCount()); |
|
for( iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ ) |
{ |
poCT->GetColorEntryAsRGB( iColor, &sEntry ); |
pabyAlpha[iColor] = (unsigned char) sEntry.c4; |
|
if( bHaveNoData && iColor == (int) dfNoDataValue ) |
pabyAlpha[iColor] = 0; |
} |
|
png_set_tRNS( hPNG, psPNGInfo, pabyAlpha, |
poCT->GetColorEntryCount(), NULL ); |
} |
} |
|
png_write_info( hPNG, psPNGInfo ); |
|
/* -------------------------------------------------------------------- */ |
/* Loop over image, copying image data. */ |
/* -------------------------------------------------------------------- */ |
GByte *pabyScanline; |
CPLErr eErr; |
int nWordSize = nBitDepth/8; |
|
pabyScanline = (GByte *) CPLMalloc( nBands * nXSize * nWordSize ); |
|
for( int iLine = 0; iLine < nYSize; iLine++ ) |
{ |
png_bytep row = pabyScanline; |
|
for( int iBand = 0; iBand < nBands; iBand++ ) |
{ |
GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 ); |
eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1, |
pabyScanline + iBand*nWordSize, |
nXSize, 1, eType, |
nBands * nWordSize, |
nBands * nXSize * nWordSize ); |
} |
|
png_write_rows( hPNG, &row, 1 ); |
} |
|
CPLFree( pabyScanline ); |
|
png_write_end( hPNG, psPNGInfo ); |
png_destroy_write_struct( &hPNG, &psPNGInfo ); |
|
VSIFClose( fpImage ); |
|
CPLFree( pabyAlpha ); |
CPLFree( pasPNGColors ); |
|
/* -------------------------------------------------------------------- */ |
/* Do we need a world file? */ |
/* -------------------------------------------------------------------- */ |
if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) ) |
{ |
double adfGeoTransform[6]; |
|
poSrcDS->GetGeoTransform( adfGeoTransform ); |
GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform ); |
} |
|
/* -------------------------------------------------------------------- */ |
/* Re-open dataset, and copy any auxilary pam information. */ |
/* -------------------------------------------------------------------- */ |
PNGDataset *poDS = (PNGDataset *) GDALOpen( pszFilename, GA_ReadOnly ); |
|
if( poDS ) |
poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT ); |
|
return poDS; |
} |
|
/************************************************************************/ |
/* GDALRegister_PNG() */ |
/************************************************************************/ |
|
void GDALRegister_PNG() |
|
{ |
GDALDriver *poDriver; |
|
if( GDALGetDriverByName( "PNG" ) == NULL ) |
{ |
poDriver = new GDALDriver(); |
|
poDriver->SetDescription( "PNG" ); |
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, |
"Portable Network Graphics" ); |
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, |
"frmt_various.html#PNG" ); |
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "png" ); |
poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/png" ); |
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, |
"Byte UInt16" ); |
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, |
"<CreationOptionList>\n" |
" <Option name='WORLDFILE' type='boolean' description='Create world file'/>\n" |
"</CreationOptionList>\n" ); |
|
poDriver->pfnOpen = PNGDataset::Open; |
poDriver->pfnCreateCopy = PNGCreateCopy; |
|
GetGDALDriverManager()->RegisterDriver( poDriver ); |
} |
} |
|