Revision: 70273 http://sourceforge.net/p/brlcad/code/70273 Author: starseeker Date: 2017-09-13 15:31:23 +0000 (Wed, 13 Sep 2017) Log Message: ----------- We can safely add png to the formats list since we guarantee libpng
Modified Paths: -------------- brlcad/trunk/src/other/gdal/CMakeLists.txt Added Paths: ----------- brlcad/trunk/src/other/gdal/frmts/png/ brlcad/trunk/src/other/gdal/frmts/png/CMakeLists.txt brlcad/trunk/src/other/gdal/frmts/png/pngdataset.cpp Modified: brlcad/trunk/src/other/gdal/CMakeLists.txt =================================================================== --- brlcad/trunk/src/other/gdal/CMakeLists.txt 2017-09-13 14:14:46 UTC (rev 70272) +++ brlcad/trunk/src/other/gdal/CMakeLists.txt 2017-09-13 15:31:23 UTC (rev 70273) @@ -106,6 +106,7 @@ find_package(PNG) if(PNG_LIBRARY) include_directories(${PNG_INCLUDE_DIR}) + add_definitions(-DUSE_PNG) endif(PNG_LIBRARY) @@ -210,7 +211,7 @@ map ngsgeoid nitf northwood ozi - pds prf + pds png prf r rik rmf rs2 safe saga sentinel2 sgi srtmhgt Added: brlcad/trunk/src/other/gdal/frmts/png/CMakeLists.txt =================================================================== --- brlcad/trunk/src/other/gdal/frmts/png/CMakeLists.txt (rev 0) +++ brlcad/trunk/src/other/gdal/frmts/png/CMakeLists.txt 2017-09-13 15:31:23 UTC (rev 70273) @@ -0,0 +1,2 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_library(frmt_png OBJECT pngdataset.cpp) Property changes on: brlcad/trunk/src/other/gdal/frmts/png/CMakeLists.txt ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: brlcad/trunk/src/other/gdal/frmts/png/pngdataset.cpp =================================================================== --- brlcad/trunk/src/other/gdal/frmts/png/pngdataset.cpp (rev 0) +++ brlcad/trunk/src/other/gdal/frmts/png/pngdataset.cpp 2017-09-13 15:31:23 UTC (rev 70273) @@ -0,0 +1,2377 @@ +/****************************************************************************** + * + * Project: PNG Driver + * Purpose: Implement GDAL PNG Support + * Author: Frank Warmerdam, warme...@home.com + * + ****************************************************************************** + * Copyright (c) 2000, Frank Warmerdam + * Copyright (c) 2007-2014, Even Rouault <even dot rouault at mines-paris dot org> + * + * 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 16 bit alpha values are not scaled by to eight bit. + * + */ + +#include "cpl_string.h" +#include "gdal_frmts.h" +#include "gdal_pam.h" +#include "png.h" + +#include <csetjmp> + +#include <algorithm> + +CPL_CVSID("$Id: pngdataset.cpp 37784 2017-03-18 23:29:45Z rouault $"); + +// Note: Callers must provide blocks in increasing Y order. +// Disclaimer (E. Rouault): this code is not production ready at all. A lot of +// issues remain: uninitialized variables, unclosed files, lack of proper +// multiband handling, and an inability to read and write at the same time. Do +// not use it unless you're ready to fix it. + +// Define SUPPORT_CREATE to enable use of the Create() call. +// #define SUPPORT_CREATE + +#ifdef _MSC_VER +# pragma warning(disable:4611) +#endif + +static void +png_vsi_read_data(png_structp png_ptr, png_bytep data, png_size_t length); + +static void +png_vsi_write_data(png_structp png_ptr, png_bytep data, png_size_t length); + +static void png_vsi_flush(png_structp png_ptr); + +static void png_gdal_error( png_structp png_ptr, const char *error_message ); +static void png_gdal_warning( png_structp png_ptr, const char *error_message ); + +/************************************************************************/ +/* ==================================================================== */ +/* PNGDataset */ +/* ==================================================================== */ +/************************************************************************/ + +class PNGRasterBand; + +#ifdef _MSC_VER +#pragma warning( push ) +// 'PNGDataset': structure was padded due to __declspec(align()) at line where +// we use `jmp_buf`. +#pragma warning( disable : 4324 ) +#endif + +class PNGDataset : public GDALPamDataset +{ + friend class PNGRasterBand; + + VSILFILE *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]; + + void CollectMetadata(); + + int bHasReadXMPMetadata; + void CollectXMPMetadata(); + + CPLErr LoadScanline( int ); + CPLErr LoadInterlacedChunk( int ); + void Restart(); + + int bHasTriedLoadWorldFile; + void LoadWorldFile(); + CPLString osWldFilename; + + int bHasReadICCMetadata; + void LoadICCProfile(); + + static void WriteMetadataAsText(png_structp hPNG, png_infop psPNGInfo, + const char* pszKey, const char* pszValue); + static GDALDataset *OpenStage2( GDALOpenInfo *, PNGDataset*& ); + + public: + PNGDataset(); + virtual ~PNGDataset(); + + static GDALDataset *Open( GDALOpenInfo * ); + static int Identify( GDALOpenInfo * ); + static GDALDataset* CreateCopy( const char * pszFilename, + GDALDataset *poSrcDS, + int bStrict, char ** papszOptions, + GDALProgressFunc pfnProgress, + void * pProgressData ); + + virtual char **GetFileList(void) override; + + virtual CPLErr GetGeoTransform( double * ) override; + virtual void FlushCache( void ) override; + + virtual char **GetMetadataDomainList() override; + + virtual char **GetMetadata( const char * pszDomain = "" ) override; + virtual const char *GetMetadataItem( const char * pszName, + const char * pszDomain = NULL ) override; + + virtual CPLErr IRasterIO( GDALRWFlag, int, int, int, int, + void *, int, int, GDALDataType, + int, int *, + GSpacing, GSpacing, + GSpacing, + GDALRasterIOExtraArg* psExtraArg ) override; + + jmp_buf sSetJmpContext; // Semi-private. + +#ifdef SUPPORT_CREATE + int m_nBitDepth; + GByte *m_pabyBuffer; + png_byte *m_pabyAlpha; + png_structp m_hPNG; + png_infop m_psPNGInfo; + png_color *m_pasPNGColors; + VSILFILE *m_fpImage; + int m_bGeoTransformValid; + double m_adfGeoTransform[6]; + char *m_pszFilename; + int m_nColorType; // PNG_COLOR_TYPE_* + + virtual CPLErr SetGeoTransform( double * ); + static GDALDataset *Create( const char* pszFilename, + int nXSize, int nYSize, int nBands, + GDALDataType, char** papszParmList ); + protected: + CPLErr write_png_header(); + +#endif +}; + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +/************************************************************************/ +/* ==================================================================== */ +/* PNGRasterBand */ +/* ==================================================================== */ +/************************************************************************/ + +class PNGRasterBand : public GDALPamRasterBand +{ + friend class PNGDataset; + + public: + + PNGRasterBand( PNGDataset *, int ); + virtual ~PNGRasterBand() {} + + virtual CPLErr IReadBlock( int, int, void * ) override; + + virtual GDALColorInterp GetColorInterpretation() override; + virtual GDALColorTable *GetColorTable() override; + CPLErr SetNoDataValue( double dfNewValue ) override; + virtual double GetNoDataValue( int *pbSuccess = NULL ) override; + + int bHaveNoData; + double dfNoDataValue; + +#ifdef SUPPORT_CREATE + virtual CPLErr SetColorTable(GDALColorTable*); + virtual CPLErr IWriteBlock( int, int, void * ) override; + + protected: + int m_bBandProvided[5]; + void reset_band_provision_flags() + { + PNGDataset& ds = *reinterpret_cast<PNGDataset *>( poDS ); + + for(size_t i = 0; i < static_cast<size_t>( ds.nBands ); i++) + m_bBandProvided[i] = FALSE; + } +#endif +}; + +/************************************************************************/ +/* PNGRasterBand() */ +/************************************************************************/ + +PNGRasterBand::PNGRasterBand( PNGDataset *poDSIn, int nBandIn ) : + bHaveNoData(FALSE), + dfNoDataValue(-1) +{ + poDS = poDSIn; + nBand = nBandIn; + + if( poDSIn->nBitDepth == 16 ) + eDataType = GDT_UInt16; + else + eDataType = GDT_Byte; + + nBlockXSize = poDSIn->nRasterXSize; + nBlockYSize = 1; + +#ifdef SUPPORT_CREATE + reset_band_provision_flags(); +#endif +} + +/************************************************************************/ +/* IReadBlock() */ +/************************************************************************/ + +CPLErr PNGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff, + void * pImage ) + +{ + PNGDataset *poGDS = reinterpret_cast<PNGDataset *>( poDS ); + int nPixelSize; + + CPLAssert( nBlockXOff == 0 ); + + if( poGDS->nBitDepth == 16 ) + nPixelSize = 2; + else + nPixelSize = 1; + + const int nXSize = GetXSize(); + if (poGDS->fpImage == NULL) + { + memset( pImage, 0, nPixelSize * nXSize ); + return CE_None; + } + + // Load the desired scanline into the working buffer. + CPLErr eErr = poGDS->LoadScanline( nBlockYOff ); + if( eErr != CE_None ) + return eErr; + + const int nPixelOffset = poGDS->nBands * nPixelSize; + + GByte *pabyScanline = poGDS->pabyBuffer + + (nBlockYOff - poGDS->nBufferStartLine) * nPixelOffset * nXSize + + nPixelSize * (nBand - 1); + + // Transfer between the working buffer and the caller's buffer. + if( nPixelSize == nPixelOffset ) + memcpy( pImage, pabyScanline, nPixelSize * nXSize ); + else if( nPixelSize == 1 ) + { + for( int i = 0; i < nXSize; i++ ) + reinterpret_cast<GByte *>( pImage )[i] = pabyScanline[i*nPixelOffset]; + } + else + { + CPLAssert( nPixelSize == 2 ); + for( int i = 0; i < nXSize; i++ ) + { + reinterpret_cast<GUInt16 *>( pImage )[i] = + *reinterpret_cast<GUInt16 *>( pabyScanline+i*nPixelOffset ); + } + } + + // Forcibly load the other bands associated with this scanline. + for(int iBand = 1; iBand < poGDS->GetRasterCount(); iBand++) + { + GDALRasterBlock *poBlock = + poGDS->GetRasterBand(iBand+1)->GetLockedBlockRef(nBlockXOff,nBlockYOff); + if( poBlock != NULL ) + poBlock->DropLock(); + } + + return CE_None; +} + +/************************************************************************/ +/* GetColorInterpretation() */ +/************************************************************************/ + +GDALColorInterp PNGRasterBand::GetColorInterpretation() + +{ + PNGDataset *poGDS = reinterpret_cast<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 = reinterpret_cast<PNGDataset *>( poDS ); + + if( nBand == 1 ) + return poGDS->poColorTable; + + return NULL; +} + +/************************************************************************/ +/* SetNoDataValue() */ +/************************************************************************/ + +CPLErr PNGRasterBand::SetNoDataValue( double dfNewValue ) + +{ + bHaveNoData = TRUE; + dfNoDataValue = dfNewValue; + + return CE_None; +} + +/************************************************************************/ +/* GetNoDataValue() */ +/************************************************************************/ + +double PNGRasterBand::GetNoDataValue( int *pbSuccess ) + +{ + if( bHaveNoData ) + { + if( pbSuccess != NULL ) + *pbSuccess = bHaveNoData; + return dfNoDataValue; + } + + return GDALPamRasterBand::GetNoDataValue( pbSuccess ); +} + +/************************************************************************/ +/* ==================================================================== */ +/* PNGDataset */ +/* ==================================================================== */ +/************************************************************************/ + +/************************************************************************/ +/* PNGDataset() */ +/************************************************************************/ + +PNGDataset::PNGDataset() : + fpImage(NULL), + hPNG(NULL), + psPNGInfo(NULL), + nBitDepth(8), + nColorType(0), + bInterlaced(FALSE), + nBufferStartLine(0), + nBufferLines(0), + nLastLineRead(-1), + pabyBuffer(NULL), + poColorTable(NULL), + bGeoTransformValid(FALSE), + bHasReadXMPMetadata(FALSE), + bHasTriedLoadWorldFile(FALSE), + bHasReadICCMetadata(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; + + memset(&sSetJmpContext, 0, sizeof(sSetJmpContext)); +} + +/************************************************************************/ +/* ~PNGDataset() */ +/************************************************************************/ + +PNGDataset::~PNGDataset() + +{ + FlushCache(); + + if( hPNG != NULL ) + png_destroy_read_struct( &hPNG, &psPNGInfo, NULL ); + + if( fpImage ) + VSIFCloseL( fpImage ); + + if( poColorTable != NULL ) + delete poColorTable; +} + +/************************************************************************/ +/* IsFullBandMap() */ +/************************************************************************/ + +static int IsFullBandMap(int *panBandMap, int nBands) +{ + for(int i=0;i<nBands;i++) + { + if( panBandMap[i] != i + 1 ) + return FALSE; + } + return TRUE; +} + +/************************************************************************/ +/* IRasterIO() */ +/************************************************************************/ + +CPLErr PNGDataset::IRasterIO( GDALRWFlag eRWFlag, + int nXOff, int nYOff, int nXSize, int nYSize, + void *pData, int nBufXSize, int nBufYSize, + GDALDataType eBufType, + int nBandCount, int *panBandMap, + GSpacing nPixelSpace, GSpacing nLineSpace, + GSpacing nBandSpace, + GDALRasterIOExtraArg* psExtraArg ) + +{ + // Coverity says that we cannot pass a nullptr to IRasterIO. + if (panBandMap == NULL) + { + return CE_Failure; + } + + if((eRWFlag == GF_Read) && + (nBandCount == nBands) && + (nXOff == 0) && (nYOff == 0) && + (nXSize == nBufXSize) && (nXSize == nRasterXSize) && + (nYSize == nBufYSize) && (nYSize == nRasterYSize) && + (eBufType == GDT_Byte) && + (eBufType == GetRasterBand(1)->GetRasterDataType()) && + (pData != NULL) && + (panBandMap != NULL) && IsFullBandMap(panBandMap, nBands)) + { + // Pixel interleaved case. + if( nBandSpace == 1 ) + { + for(int y = 0; y < nYSize; ++y) + { + CPLErr tmpError = LoadScanline(y); + if(tmpError != CE_None) return tmpError; + const GByte* pabyScanline = pabyBuffer + + (y - nBufferStartLine) * nBands * nXSize; + if( nPixelSpace == nBandSpace * nBandCount ) + { + memcpy(&(reinterpret_cast<GByte*>( pData )[(y*nLineSpace)]), + pabyScanline, nBandCount * nXSize); + } + else + { + for(int x = 0; x < nXSize; ++x) + { + memcpy(&(reinterpret_cast<GByte*>(pData)[(y*nLineSpace) + (x*nPixelSpace)]), + (const GByte*)&(pabyScanline[x* nBandCount]), nBandCount); + } + } + } + } + else + { + for(int y = 0; y < nYSize; ++y) + { + CPLErr tmpError = LoadScanline(y); + if(tmpError != CE_None) return tmpError; + const GByte* pabyScanline = pabyBuffer + + (y - nBufferStartLine) * nBands * nXSize; + GByte* pabyDest = reinterpret_cast<GByte *>( pData ) + + y*nLineSpace; + if( nPixelSpace <= nBands && nBandSpace > nBands ) + { + // Cache friendly way for typical band interleaved case. + for(int iBand=0;iBand<nBands;iBand++) + { + GByte* pabyDest2 = pabyDest + iBand * nBandSpace; + const GByte* pabyScanline2 = pabyScanline + iBand; + GDALCopyWords( pabyScanline2, GDT_Byte, nBands, + pabyDest2, GDT_Byte, + static_cast<int>(nPixelSpace), + nXSize ); + } + } + else + { + // Generic method + for(int x = 0; x < nXSize; ++x) + { + for(int iBand=0;iBand<nBands;iBand++) + { + pabyDest[(x*nPixelSpace) + iBand * nBandSpace] = + pabyScanline[x*nBands+iBand]; + } + } + } + } + } + + return CE_None; + } + + return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nBandCount, panBandMap, + nPixelSpace, nLineSpace, nBandSpace, + psExtraArg); +} + +/************************************************************************/ +/* GetGeoTransform() */ +/************************************************************************/ + +CPLErr PNGDataset::GetGeoTransform( double * padfTransform ) + +{ + LoadWorldFile(); + + if( bGeoTransformValid ) + { + memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 ); + return CE_None; + } + + 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 ); + + hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, this, NULL, NULL ); + + png_set_error_fn( hPNG, &sSetJmpContext, png_gdal_error, png_gdal_warning ); + if( setjmp( sSetJmpContext ) != 0 ) + return; + + psPNGInfo = png_create_info_struct( hPNG ); + + VSIFSeekL( fpImage, 0, SEEK_SET ); + png_set_read_fn( hPNG, fpImage, png_vsi_read_data ); + png_read_info( hPNG, psPNGInfo ); + + if( nBitDepth < 8 ) + png_set_packing( hPNG ); + + nLastLineRead = -1; +} + +/************************************************************************/ +/* safe_png_read_image() */ +/************************************************************************/ + +static bool safe_png_read_image(png_structp hPNG, + png_bytep *png_rows, + jmp_buf sSetJmpContext) +{ + if( setjmp( sSetJmpContext ) != 0 ) + return false; + png_read_image( hPNG, png_rows ); + return true; +} + +/************************************************************************/ +/* LoadInterlacedChunk() */ +/************************************************************************/ + +CPLErr PNGDataset::LoadInterlacedChunk( int iLine ) + +{ + int nPixelOffset; + + if( nBitDepth == 16 ) + nPixelOffset = 2 * GetRasterCount(); + else + nPixelOffset = 1 * GetRasterCount(); + + // What is the biggest chunk we can safely operate on? + static const int MAX_PNG_CHUNK_BYTES = 100000000; + + int nMaxChunkLines = + std::max(1, MAX_PNG_CHUNK_BYTES / (nPixelOffset * GetRasterXSize())); + + if( nMaxChunkLines > GetRasterYSize() ) + nMaxChunkLines = GetRasterYSize(); + + // Allocate chunk buffer if we don't already have it from a previous + // request. + nBufferLines = nMaxChunkLines; + if( nMaxChunkLines + iLine > GetRasterYSize() ) + nBufferStartLine = GetRasterYSize() - nMaxChunkLines; + else + nBufferStartLine = iLine; + + if( pabyBuffer == NULL ) + { + pabyBuffer = reinterpret_cast<GByte *>( + VSI_MALLOC_VERBOSE(nPixelOffset*GetRasterXSize()*nMaxChunkLines) ); + + if( pabyBuffer == NULL ) + { + return CE_Failure; + } +#ifdef notdef + if( nMaxChunkLines < GetRasterYSize() ) + CPLDebug( "PNG", + "Interlaced file being handled in %d line chunks.\n" + "Performance is likely to be quite poor.", + nMaxChunkLines ); +#endif + } + + // Do we need to restart reading? We do this if we aren't on the first + // attempt to read the image. + if( nLastLineRead != -1 ) + { + Restart(); + } + + // Allocate and populate rows array. We create a row for each row in the + // image but use our dummy line for rows not in the target window. + png_bytep dummy_row = reinterpret_cast<png_bytep>( + CPLMalloc(nPixelOffset*GetRasterXSize()) ); + png_bytep *png_rows + = reinterpret_cast<png_bytep *>( + CPLMalloc(sizeof(png_bytep) * GetRasterYSize()) ); + + for( int i = 0; i < GetRasterYSize(); i++ ) + { + if( i >= nBufferStartLine && i < nBufferStartLine + nBufferLines ) + png_rows[i] = pabyBuffer + + (i-nBufferStartLine) * nPixelOffset * GetRasterXSize(); + else + png_rows[i] = dummy_row; + } + + bool bRet = safe_png_read_image( hPNG, png_rows, sSetJmpContext ); + + CPLFree( png_rows ); + CPLFree( dummy_row ); + if( !bRet ) + return CE_Failure; + + nLastLineRead = nBufferStartLine + nBufferLines - 1; + + return CE_None; +} + +/************************************************************************/ +/* safe_png_read_rows() */ +/************************************************************************/ + +static bool safe_png_read_rows(png_structp hPNG, + png_bytep row, + jmp_buf sSetJmpContext) +{ + if( setjmp( sSetJmpContext ) != 0 ) + return false; + png_read_rows( hPNG, &row, NULL, 1 ); + return true; +} + +/************************************************************************/ +/* LoadScanline() */ +/************************************************************************/ + +CPLErr PNGDataset::LoadScanline( int nLine ) + +{ + CPLAssert( nLine >= 0 && nLine < GetRasterYSize() ); + + if( nLine >= nBufferStartLine && nLine < nBufferStartLine + nBufferLines) + return CE_None; + + int nPixelOffset; + if( nBitDepth == 16 ) + nPixelOffset = 2 * GetRasterCount(); + else + nPixelOffset = 1 * GetRasterCount(); + + // If the file is interlaced, we load the entire image into memory using the + // high-level API. + if( bInterlaced ) + return LoadInterlacedChunk( nLine ); + + // Ensure we have space allocated for one scanline. + if( pabyBuffer == NULL ) + pabyBuffer = reinterpret_cast<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 = pabyBuffer; + while( nLine > nLastLineRead ) + { + if( !safe_png_read_rows( hPNG, row, sSetJmpContext ) ) + return CE_Failure; + nLastLineRead++; + } + + nBufferStartLine = nLine; + nBufferLines = 1; + + // Do swap on LSB machines. 16-bit PNG data is stored in MSB format. +#ifdef CPL_LSB + if( nBitDepth == 16 ) + GDALSwapWords( row, 2, GetRasterXSize() * GetRasterCount(), 2 ); +#endif + + return CE_None; +} + +/************************************************************************/ +/* CollectMetadata() */ +/* */ +/* We normally do this after reading up to the image, but be */ +/* forewarned: we can miss 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() + +{ + if( nBitDepth < 8 ) + { + for( int iBand = 0; iBand < nBands; iBand++ ) + { + GetRasterBand(iBand+1)->SetMetadataItem( + "NBITS", CPLString().Printf( "%d", nBitDepth ), + "IMAGE_STRUCTURE" ); + } + } + + 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] = '_'; + } + + GDALDataset::SetMetadataItem( pszTag, text_ptr[iText].text ); + CPLFree( pszTag ); + } +} + +/************************************************************************/ +/* CollectXMPMetadata() */ +/************************************************************************/ + +// See ยง2.1.5 of +// http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf. + +void PNGDataset::CollectXMPMetadata() + +{ + if (fpImage == NULL || bHasReadXMPMetadata) + return; + + // Save current position to avoid disturbing PNG stream decoding. + const vsi_l_offset nCurOffset = VSIFTellL(fpImage); + + vsi_l_offset nOffset = 8; + VSIFSeekL( fpImage, nOffset, SEEK_SET ); + + // Loop over chunks. + while( true ) + { + int nLength; + + if (VSIFReadL( &nLength, 4, 1, fpImage ) != 1) + break; + nOffset += 4; + CPL_MSBPTR32(&nLength); + if (nLength <= 0) + break; + + char pszChunkType[5]; + if (VSIFReadL( pszChunkType, 4, 1, fpImage ) != 1) + break; + nOffset += 4; + pszChunkType[4] = 0; + + if (strcmp(pszChunkType, "iTXt") == 0 && nLength > 22) + { + char* pszContent = reinterpret_cast<char *>( + VSIMalloc(nLength + 1) ); + if (pszContent == NULL) + break; + if (VSIFReadL( pszContent, nLength, 1, fpImage) != 1) + { + VSIFree(pszContent); + break; + } + nOffset += nLength; + pszContent[nLength] = '\0'; + if (memcmp(pszContent, "XML:com.adobe.xmp\0\0\0\0\0", 22) == 0) + { + // Avoid setting the PAM dirty bit just for that. + int nOldPamFlags = nPamFlags; + + char *apszMDList[2] = { pszContent + 22, NULL }; + SetMetadata(apszMDList, "xml:XMP"); + + nPamFlags = nOldPamFlags; + + VSIFree(pszContent); + + break; + } + else + { + VSIFree(pszContent); + } + } + else + { + nOffset += nLength; + VSIFSeekL( fpImage, nOffset, SEEK_SET ); + } + + nOffset += 4; + int nCRC; + if (VSIFReadL( &nCRC, 4, 1, fpImage ) != 1) + break; + } + + VSIFSeekL( fpImage, nCurOffset, SEEK_SET ); + + bHasReadXMPMetadata = TRUE; +} + +/************************************************************************/ +/* LoadICCProfile() */ +/************************************************************************/ + +void PNGDataset::LoadICCProfile() +{ + if (hPNG == NULL || bHasReadICCMetadata) + return; + bHasReadICCMetadata = TRUE; + + png_charp pszProfileName; + png_uint_32 nProfileLength; +#if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1 + png_bytep pProfileData; +#else + png_charp pProfileData; +#endif + int nCompressionType; + + // Avoid setting the PAM dirty bit just for that. + int nOldPamFlags = nPamFlags; + + if (png_get_iCCP(hPNG, psPNGInfo, &pszProfileName, + &nCompressionType, &pProfileData, &nProfileLength) != 0) + { + // Escape the profile. + char *pszBase64Profile = CPLBase64Encode( + static_cast<int>(nProfileLength), reinterpret_cast<const GByte *>( pProfileData ) ); + + // Set ICC profile metadata. + SetMetadataItem( "SOURCE_ICC_PROFILE", pszBase64Profile, "COLOR_PROFILE" ); + SetMetadataItem( "SOURCE_ICC_PROFILE_NAME", pszProfileName, "COLOR_PROFILE" ); + + nPamFlags = nOldPamFlags; + + CPLFree(pszBase64Profile); + + return; + } + + int nsRGBIntent; + if (png_get_sRGB(hPNG, psPNGInfo, &nsRGBIntent) != 0) + { + SetMetadataItem( "SOURCE_ICC_PROFILE_NAME", "sRGB", "COLOR_PROFILE" ); + + nPamFlags = nOldPamFlags; + + return; + } + + double dfGamma; + bool bGammaAvailable = false; + if (png_get_valid(hPNG, psPNGInfo, PNG_INFO_gAMA)) + { + bGammaAvailable = true; + + png_get_gAMA(hPNG,psPNGInfo, &dfGamma); + + SetMetadataItem( "PNG_GAMMA", + CPLString().Printf( "%.9f", dfGamma ) , "COLOR_PROFILE" ); + } + + // Check that both cHRM and gAMA are available. + if (bGammaAvailable && png_get_valid(hPNG, psPNGInfo, PNG_INFO_cHRM)) + { + double dfaWhitepoint[2]; + double dfaCHR[6]; + + png_get_cHRM(hPNG, psPNGInfo, + &dfaWhitepoint[0], &dfaWhitepoint[1], + &dfaCHR[0], &dfaCHR[1], + &dfaCHR[2], &dfaCHR[3], + &dfaCHR[4], &dfaCHR[5]); + + // Set all the colorimetric metadata. + SetMetadataItem( "SOURCE_PRIMARIES_RED", + CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[0], dfaCHR[1] ) , "COLOR_PROFILE" ); + SetMetadataItem( "SOURCE_PRIMARIES_GREEN", + CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[2], dfaCHR[3] ) , "COLOR_PROFILE" ); + SetMetadataItem( "SOURCE_PRIMARIES_BLUE", + CPLString().Printf( "%.9f, %.9f, 1.0", dfaCHR[4], dfaCHR[5] ) , "COLOR_PROFILE" ); + + SetMetadataItem( "SOURCE_WHITEPOINT", + CPLString().Printf( "%.9f, %.9f, 1.0", dfaWhitepoint[0], dfaWhitepoint[1] ) , "COLOR_PROFILE" ); + } + + nPamFlags = nOldPamFlags; +} + +/************************************************************************/ +/* GetMetadataDomainList() */ +/************************************************************************/ + +char **PNGDataset::GetMetadataDomainList() +{ + return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(), + TRUE, + "xml:XMP", "COLOR_PROFILE", NULL); +} + +/************************************************************************/ +/* GetMetadata() */ +/************************************************************************/ + +char **PNGDataset::GetMetadata( const char * pszDomain ) +{ + if (fpImage == NULL) + return NULL; + if (eAccess == GA_ReadOnly && !bHasReadXMPMetadata && + pszDomain != NULL && EQUAL(pszDomain, "xml:XMP")) + CollectXMPMetadata(); + if (eAccess == GA_ReadOnly && !bHasReadICCMetadata && + pszDomain != NULL && EQUAL(pszDomain, "COLOR_PROFILE")) + LoadICCProfile(); + return GDALPamDataset::GetMetadata(pszDomain); +} + +/************************************************************************/ +/* GetMetadataItem() */ +/************************************************************************/ +const char *PNGDataset::GetMetadataItem( const char * pszName, + const char * pszDomain ) +{ + if (eAccess == GA_ReadOnly && !bHasReadICCMetadata && + pszDomain != NULL && EQUAL(pszDomain, "COLOR_PROFILE")) + LoadICCProfile(); + return GDALPamDataset::GetMetadataItem(pszName, pszDomain); +} + +/************************************************************************/ +/* Identify() */ +/************************************************************************/ + +int PNGDataset::Identify( GDALOpenInfo * poOpenInfo ) + +{ + if( poOpenInfo->nHeaderBytes < 4 ) + return FALSE; + + if( png_sig_cmp(poOpenInfo->pabyHeader, static_cast<png_size_t>( 0 ), + poOpenInfo->nHeaderBytes) != 0 ) + return FALSE; + + return TRUE; +} + +/************************************************************************/ +/* Open() */ +/************************************************************************/ + +GDALDataset *PNGDataset::Open( GDALOpenInfo * poOpenInfo ) + +{ + if( !Identify( poOpenInfo ) ) + 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 = new PNGDataset(); + return OpenStage2( poOpenInfo, poDS ); +} + +GDALDataset *PNGDataset::OpenStage2( GDALOpenInfo * poOpenInfo, PNGDataset*& poDS ) + +{ + poDS->fpImage = poOpenInfo->fpL; + poOpenInfo->fpL = NULL; + poDS->eAccess = poOpenInfo->eAccess; + + poDS->hPNG = png_create_read_struct( PNG_LIBPNG_VER_STRING, poDS, + NULL, NULL ); + if (poDS->hPNG == NULL) + { +#if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 2) || PNG_LIBPNG_VER_MAJOR > 1 + int version = static_cast<int>(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 ); + + // Set up error handling. + png_set_error_fn( poDS->hPNG, &poDS->sSetJmpContext, png_gdal_error, png_gdal_warning ); + + if( setjmp( poDS->sSetJmpContext ) != 0 ) + { + delete poDS; + return NULL; + } + + // Read pre-image data after ensuring the file is rewound. + // We should likely do a setjmp() here. + + png_set_read_fn( poDS->hPNG, poDS->fpImage, png_vsi_read_data ); + png_read_info( poDS->hPNG, poDS->psPNGInfo ); + + // Capture some information from the file that is of interest. + poDS->nRasterXSize = static_cast<int>(png_get_image_width( poDS->hPNG, poDS->psPNGInfo)); + poDS->nRasterYSize = static_cast<int>(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-, and 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 ) ); + + // 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 = NULL; + int nColorCount = 0; + + if( png_get_PLTE( poDS->hPNG, poDS->psPNGInfo, + &pasPNGPalette, &nColorCount ) == 0 ) + nColorCount = 0; + + unsigned char *trans = NULL; + png_color_16 *trans_values = NULL; + int num_trans = 0; + png_get_tRNS( poDS->hPNG, poDS->psPNGInfo, + &trans, &num_trans, &trans_values ); + + poDS->poColorTable = new GDALColorTable(); + + GDALColorEntry oEntry; + int nNoDataIndex = -1; + 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 use an index as the no data value, as long as it is + // the only transparent color in the palette. + if( nNoDataIndex > -1 ) + { + poDS->GetRasterBand(1)->SetNoDataValue(nNoDataIndex); + } + } + + // Check for transparency values in greyscale images. + if( poDS->nColorType == PNG_COLOR_TYPE_GRAY ) + { + 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->GetRasterBand(1)->SetNoDataValue(trans_values->gray); + } + } + + // Check for nodata color for RGB images. + if( poDS->nColorType == PNG_COLOR_TYPE_RGB ) + { + 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 ) + { + CPLString oNDValue; + + oNDValue.Printf( "%d %d %d", + trans_values->red, + trans_values->green, + trans_values->blue ); + poDS->SetMetadataItem( "NODATA_VALUES", oNDValue.c_str() ); + + poDS->GetRasterBand(1)->SetNoDataValue(trans_values->red); + poDS->GetRasterBand(2)->SetNoDataValue(trans_values->green); + poDS->GetRasterBand(3)->SetNoDataValue(trans_values->blue); + } + } + + // Extract any text chunks as "metadata." + poDS->CollectMetadata(); + + // More metadata. + if( poDS->nBands > 1 ) + { + poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" ); + } + + // Initialize any PAM information. + poDS->SetDescription( poOpenInfo->pszFilename ); + poDS->TryLoadXML( poOpenInfo->GetSiblingFiles() ); + + // Open overviews. + poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename, + poOpenInfo->GetSiblingFiles() ); + + return poDS; +} + +/************************************************************************/ +/* LoadWorldFile() */ +/************************************************************************/ + +void PNGDataset::LoadWorldFile() +{ + if (bHasTriedLoadWorldFile) + return; + bHasTriedLoadWorldFile = TRUE; + + char* pszWldFilename = NULL; + bGeoTransformValid = + GDALReadWorldFile2( GetDescription(), NULL, + adfGeoTransform, oOvManager.GetSiblingFiles(), + &pszWldFilename); + + if( !bGeoTransformValid ) + bGeoTransformValid = + GDALReadWorldFile2( GetDescription(), ".wld", + adfGeoTransform, oOvManager.GetSiblingFiles(), + &pszWldFilename); + + if (pszWldFilename) + { + osWldFilename = pszWldFilename; + CPLFree(pszWldFilename); + } +} + +/************************************************************************/ +/* GetFileList() */ +/************************************************************************/ + +char **PNGDataset::GetFileList() + +{ + char **papszFileList = GDALPamDataset::GetFileList(); + + LoadWorldFile(); + + if (!osWldFilename.empty() && + CSLFindString(papszFileList, osWldFilename) == -1) + { + papszFileList = CSLAddString( papszFileList, osWldFilename ); + } + + return papszFileList; +} + +/************************************************************************/ +/* WriteMetadataAsText() */ +/************************************************************************/ + +#if defined(PNG_iTXt_SUPPORTED) || ((PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4) || PNG_LIBPNG_VER_MAJOR > 1) +#define HAVE_ITXT_SUPPORT +#endif + +#ifdef HAVE_ITXT_SUPPORT +static bool IsASCII(const char* pszStr) +{ + for(int i=0;pszStr[i]!='\0';i++) + { + if( reinterpret_cast<GByte *>( + const_cast<char *>( pszStr ) )[i] >= 128 ) + return false; + } + return true; +} +#endif + +void PNGDataset::WriteMetadataAsText(png_structp hPNG, png_infop psPNGInfo, + const char* pszKey, const char* pszValue) +{ + png_text sText; + memset(&sText, 0, sizeof(png_text)); + sText.compression = PNG_TEXT_COMPRESSION_NONE; + sText.key = (png_charp) pszKey; + sText.text = (png_charp) pszValue; +#ifdef HAVE_ITXT_SUPPORT + // UTF-8 values should be written in iTXt, whereas TEXT should be LATIN-1. + if( !IsASCII(pszValue) && CPLIsUTF8(pszValue, -1) ) + sText.compression = PNG_ITXT_COMPRESSION_NONE; +#endif + png_set_text(hPNG, psPNGInfo, &sText, 1); +} + +/************************************************************************/ +/* CreateCopy() */ +/************************************************************************/ + +GDALDataset * +PNGDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS, + int bStrict, char ** papszOptions, + GDALProgressFunc pfnProgress, void * pProgressData ) + +{ + // Perform some rudimentary checks. + const int nBands = poSrcDS->GetRasterCount(); + 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 ) + { + CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported, + "PNG driver doesn't support data type %s. " + "Only eight bit (Byte) and sixteen bit (UInt16) bands supported. %s\n", + GDALGetDataTypeName( + poSrcDS->GetRasterBand(1)->GetRasterDataType()), + (bStrict) ? "" : "Defaulting to Byte" ); + + if (bStrict) + return NULL; + } + + // Create the dataset. + VSILFILE *fpImage = VSIFOpenL( 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. + jmp_buf sSetJmpContext; + + png_structp hPNG = png_create_write_struct( + PNG_LIBPNG_VER_STRING, &sSetJmpContext, png_gdal_error, png_gdal_warning ); + png_infop psPNGInfo = png_create_info_struct( hPNG ); + + if( setjmp( sSetJmpContext ) != 0 ) + { + VSIFCloseL( fpImage ); + png_destroy_write_struct( &hPNG, &psPNGInfo ); + return NULL; + } + + // Set up some parameters. + int nColorType=0; + + 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; + + int nBitDepth; + GDALDataType eType; + if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt16 ) + { + eType = GDT_Byte; + nBitDepth = 8; + if( nBands == 1 ) + { + const char* pszNbits = poSrcDS->GetRasterBand(1)->GetMetadataItem( + "NBITS", "IMAGE_STRUCTURE"); + if( pszNbits != NULL ) + { + nBitDepth = atoi(pszNbits); + if( !(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4) ) + nBitDepth = 8; + } + } + } + else + { + eType = GDT_UInt16; + nBitDepth = 16; + } + + const char* pszNbits = CSLFetchNameValue(papszOptions, "NBITS"); + if( eType == GDT_Byte && pszNbits != NULL ) + { + nBitDepth = atoi(pszNbits); + if( !(nBitDepth == 1 || nBitDepth == 2 || nBitDepth == 4 || nBitDepth == 8) ) + { + CPLError(CE_Warning, CPLE_NotSupported, "Invalid bit depth. Using 8"); + nBitDepth = 8; + } + } + + png_set_write_fn( hPNG, fpImage, png_vsi_write_data, png_vsi_flush ); + + const int nXSize = poSrcDS->GetRasterXSize(); + const int nYSize = poSrcDS->GetRasterYSize(); + + png_set_IHDR( hPNG, psPNGInfo, nXSize, nYSize, + nBitDepth, nColorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE ); + + // Do we want to control the compression level? + const char *pszLevel = CSLFetchNameValue( papszOptions, "ZLEVEL" ); + + if( pszLevel ) + { + const int nLevel = atoi(pszLevel); + if( nLevel < 1 || nLevel > 9 ) + { + CPLError( CE_Failure, CPLE_AppDefined, + "Illegal ZLEVEL value '%s', should be 1-9.", + pszLevel ); + return NULL; + } + + png_set_compression_level( hPNG, nLevel ); + } + + // Try to handle nodata values as a tRNS block (note that for paletted + // images, we save the effect to apply as part of palette). + png_color_16 sTRNSColor; + + // Gray nodata. + if( nColorType == PNG_COLOR_TYPE_GRAY ) + { + int bHaveNoData = FALSE; + const double dfNoDataValue + = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData ); + + if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 ) + { + sTRNSColor.gray = (png_uint_16) dfNoDataValue; + png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor ); + } + } + + // RGB nodata. + if( nColorType == PNG_COLOR_TYPE_RGB ) + { + // First try to use the NODATA_VALUES metadata item. + if ( poSrcDS->GetMetadataItem( "NODATA_VALUES" ) != NULL ) + { + char **papszValues = CSLTokenizeString( + poSrcDS->GetMetadataItem( "NODATA_VALUES" ) ); + + if( CSLCount(papszValues) >= 3 ) + { + sTRNSColor.red = (png_uint_16) atoi(papszValues[0]); + sTRNSColor.green = (png_uint_16) atoi(papszValues[1]); + sTRNSColor.blue = (png_uint_16) atoi(papszValues[2]); + png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor ); + } + + CSLDestroy( papszValues ); + } + // Otherwise, get the nodata value from the bands. + else + { + int bHaveNoDataRed = FALSE; + const double dfNoDataValueRed + = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed ); + + int bHaveNoDataGreen = FALSE; + const double dfNoDataValueGreen + = poSrcDS->GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen ); + + int bHaveNoDataBlue = FALSE; + const double dfNoDataValueBlue + = poSrcDS->GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue ); + + if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) && + ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) && + ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) ) + { + sTRNSColor.red = static_cast<png_uint_16>( dfNoDataValueRed ); + sTRNSColor.green = static_cast<png_uint_16>( dfNoDataValueGreen ); + sTRNSColor.blue = static_cast<png_uint_16>( dfNoDataValueBlue ); + png_set_tRNS( hPNG, psPNGInfo, NULL, 0, &sTRNSColor ); + } + } + } + + // Copy color profile data. + const char *pszICCProfile = CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE"); + const char *pszICCProfileName = CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE_NAME"); + if (pszICCProfileName == NULL) + pszICCProfileName = poSrcDS->GetMetadataItem( "SOURCE_ICC_PROFILE_NAME", "COLOR_PROFILE" ); + + if (pszICCProfile == NULL) + pszICCProfile = poSrcDS->GetMetadataItem( "SOURCE_ICC_PROFILE", "COLOR_PROFILE" ); + + if ((pszICCProfileName != NULL) && EQUAL(pszICCProfileName, "sRGB")) + { + pszICCProfile = NULL; + + png_set_sRGB(hPNG, psPNGInfo, PNG_sRGB_INTENT_PERCEPTUAL); + } + + if (pszICCProfile != NULL) + { + char *pEmbedBuffer = CPLStrdup(pszICCProfile); + png_uint_32 nEmbedLen + = CPLBase64DecodeInPlace(reinterpret_cast<GByte *>( pEmbedBuffer ) ); + const char* pszLocalICCProfileName = (pszICCProfileName!=NULL)?pszICCProfileName:"ICC Profile"; + + png_set_iCCP(hPNG, psPNGInfo, +#if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1 + pszLocalICCProfileName, +#else + (png_charp)pszLocalICCProfileName, +#endif + 0, +#if (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR > 4) || PNG_LIBPNG_VER_MAJOR > 1 + (png_const_bytep)pEmbedBuffer, +#else + (png_charp)pEmbedBuffer, +#endif + nEmbedLen); + + CPLFree(pEmbedBuffer); + } + else if ((pszICCProfileName == NULL) || !EQUAL(pszICCProfileName, "sRGB")) + { + // Output gamma, primaries and whitepoint. + const char *pszGamma = CSLFetchNameValue(papszOptions, "PNG_GAMMA"); + if (pszGamma == NULL) + pszGamma = poSrcDS->GetMetadataItem( "PNG_GAMMA", "COLOR_PROFILE" ); + + if (pszGamma != NULL) + { + double dfGamma = CPLAtof(pszGamma); + png_set_gAMA(hPNG, psPNGInfo, dfGamma); + } + + const char *pszPrimariesRed = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_RED"); + if (pszPrimariesRed == NULL) + pszPrimariesRed = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_RED", "COLOR_PROFILE" ); + const char *pszPrimariesGreen = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_GREEN"); + if (pszPrimariesGreen == NULL) + pszPrimariesGreen = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_GREEN", "COLOR_PROFILE" ); + const char *pszPrimariesBlue = CSLFetchNameValue(papszOptions, "SOURCE_PRIMARIES_BLUE"); + if (pszPrimariesBlue == NULL) + pszPrimariesBlue = poSrcDS->GetMetadataItem( "SOURCE_PRIMARIES_BLUE", "COLOR_PROFILE" ); + const char *pszWhitepoint = CSLFetchNameValue(papszOptions, "SOURCE_WHITEPOINT"); + if (pszWhitepoint == NULL) + pszWhitepoint = poSrcDS->GetMetadataItem( "SOURCE_WHITEPOINT", "COLOR_PROFILE" ); + + if ((pszPrimariesRed != NULL) && (pszPrimariesGreen != NULL) && (pszPrimariesBlue != NULL) && + (pszWhitepoint != NULL)) + { + bool bOk = true; + double faColour[8] = { 0.0 }; + char** apapszTokenList[4] = { NULL }; + + apapszTokenList[0] = CSLTokenizeString2( pszWhitepoint, ",", + CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES ); + apapszTokenList[1] = CSLTokenizeString2( pszPrimariesRed, ",", + CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES ); + apapszTokenList[2] = CSLTokenizeString2( pszPrimariesGreen, ",", + CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES ); + apapszTokenList[3] = CSLTokenizeString2( pszPrimariesBlue, ",", + CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES ); + + if ((CSLCount( apapszTokenList[0] ) == 3) && + (CSLCount( apapszTokenList[1] ) == 3) && + (CSLCount( apapszTokenList[2] ) == 3) && + (CSLCount( apapszTokenList[3] ) == 3)) + { + for( int i = 0; i < 4; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + const double v = CPLAtof(apapszTokenList[i][j]); + + if (j == 2) + { + /* Last term of xyY colour must be 1.0 */ + if (v != 1.0) + { + bOk = false; + break; + } + } + else + { + faColour[i*2 + j] = v; + } + } + if (!bOk) + break; + } + + if (bOk) + { + png_set_cHRM(hPNG, psPNGInfo, + faColour[0], faColour[1], + faColour[2], faColour[3], + faColour[4], faColour[5], + faColour[6], faColour[7]); + } + } + + CSLDestroy( apapszTokenList[0] ); + CSLDestroy( apapszTokenList[1] ); + CSLDestroy( apapszTokenList[2] ); + CSLDestroy( apapszTokenList[3] ); + } + } + + // Write the palette if there is one. Technically, it may be possible to + // write 16-bit palettes for PNG, but for now, this is omitted. + if( nColorType == PNG_COLOR_TYPE_PALETTE ) + { + int bHaveNoData = FALSE; + double dfNoDataValue + = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bHaveNoData ); + + GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable(); + + int nEntryCount = poCT->GetColorEntryCount(); + int nMaxEntryCount = 1 << nBitDepth; + if( nEntryCount > nMaxEntryCount ) + nEntryCount = nMaxEntryCount; + + png_color *pasPNGColors = reinterpret_cast<png_color *>( + CPLMalloc( sizeof(png_color) * nEntryCount ) ); + + GDALColorEntry sEntry; + bool bFoundTrans = false; + for( int iColor = 0; iColor < nEntryCount; iColor++ ) + { + poCT->GetColorEntryAsRGB( iColor, &sEntry ); + if( sEntry.c4 != 255 ) + bFoundTrans = true; + + pasPNGColors[iColor].red = static_cast<png_byte>( sEntry.c1 ); + pasPNGColors[iColor].green = static_cast<png_byte>( sEntry.c2 ); + pasPNGColors[iColor].blue = static_cast<png_byte>( sEntry.c3 ); + } + + png_set_PLTE( hPNG, psPNGInfo, pasPNGColors, + nEntryCount ); + + CPLFree( pasPNGColors ); + + // If we have transparent elements in the palette, we need to write a + // transparency block. + if( bFoundTrans || bHaveNoData ) + { + unsigned char *pabyAlpha + = reinterpret_cast<unsigned char *>( + CPLMalloc(nEntryCount) ); + + for( int iColor = 0; iColor < nEntryCount; iColor++ ) + { + poCT->GetColorEntryAsRGB( iColor, &sEntry ); + pabyAlpha[iColor] = static_cast<unsigned char>( sEntry.c4 ); + + if( bHaveNoData && iColor == static_cast<int>( dfNoDataValue ) ) + pabyAlpha[iColor] = 0; + } + + png_set_tRNS( hPNG, psPNGInfo, pabyAlpha, + nEntryCount, NULL ); + + CPLFree( pabyAlpha ); + } + } + + // Add text info. + // These are predefined keywords. See "4.2.7 tEXt Textual data" of + // http://www.w3.org/TR/PNG-Chunks.html for more information. + const char* apszKeywords[] = { "Title", "Author", "Description", "Copyright", + "Creation Time", "Software", "Disclaimer", + "Warning", "Source", "Comment", NULL }; + const bool bWriteMetadataAsText = CPLTestBool( + CSLFetchNameValueDef(papszOptions, "WRITE_METADATA_AS_TEXT", "FALSE")); + for(int i=0;apszKeywords[i]!=NULL;i++) + { + const char* pszKey = apszKeywords[i]; + const char* pszValue = CSLFetchNameValue(papszOptions, pszKey); + if( pszValue == NULL && bWriteMetadataAsText ) + pszValue = poSrcDS->GetMetadataItem(pszKey); + if( pszValue != NULL ) + { + WriteMetadataAsText(hPNG, psPNGInfo, pszKey, pszValue); + } + } + if( bWriteMetadataAsText ) + { + char** papszSrcMD = poSrcDS->GetMetadata(); + for( ; papszSrcMD && *papszSrcMD; papszSrcMD++ ) + { + char* pszKey = NULL; + const char* pszValue = CPLParseNameValue(*papszSrcMD, &pszKey ); + if( pszKey && pszValue ) + { + if( CSLFindString(const_cast<char**>( apszKeywords ), pszKey) < 0 && + !EQUAL(pszKey, "AREA_OR_POINT") && !EQUAL(pszKey, "NODATA_VALUES") ) + { + WriteMetadataAsText(hPNG, psPNGInfo, pszKey, pszValue); + } + CPLFree(pszKey); + } + } + } + + // Write the PNG info. + png_write_info( hPNG, psPNGInfo ); + + if( nBitDepth < 8 ) + png_set_packing( hPNG ); + + // Loop over the image, copying image data. + CPLErr eErr = CE_None; + const int nWordSize = GDALGetDataTypeSize(eType) / 8; + + GByte *pabyScanline = reinterpret_cast<GByte *>( + CPLMalloc( nBands * nXSize * nWordSize ) ); + + for( int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++ ) + { + png_bytep row = pabyScanline; + + eErr = poSrcDS->RasterIO( GF_Read, 0, iLine, nXSize, 1, + pabyScanline, + nXSize, 1, eType, + nBands, NULL, + nBands * nWordSize, + nBands * nXSize * nWordSize, + nWordSize, + NULL ); + +#ifdef CPL_LSB + if( nBitDepth == 16 ) + GDALSwapWords( row, 2, nXSize * nBands, 2 ); +#endif + if( eErr == CE_None ) + png_write_rows( hPNG, &row, 1 ); + + if( eErr == CE_None + && !pfnProgress( (iLine+1) / static_cast<double>( nYSize ), + NULL, pProgressData ) ) + { + eErr = CE_Failure; + CPLError( CE_Failure, CPLE_UserInterrupt, + "User terminated CreateCopy()" ); + } + } + + CPLFree( pabyScanline ); + + png_write_end( hPNG, psPNGInfo ); + png_destroy_write_struct( &hPNG, &psPNGInfo ); + + VSIFCloseL( fpImage ); + + if( eErr != CE_None ) + return NULL; + + // Do we need a world file? + if( CPLFetchBool( papszOptions, "WORLDFILE", false ) ) + { + double adfGeoTransform[6]; + + if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None ) + GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform ); + } + + // Re-open dataset and copy any auxiliary PAM information. + + /* If writing to stdout, we can't reopen it, so return */ + /* a fake dataset to make the caller happy */ + if( CPLTestBool(CPLGetConfigOption("GDAL_OPEN_AFTER_COPY", "YES")) ) + { + CPLPushErrorHandler(CPLQuietErrorHandler); + GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly); + PNGDataset *poDS = reinterpret_cast<PNGDataset *>( + PNGDataset::Open( &oOpenInfo ) ); + CPLPopErrorHandler(); + if( poDS ) + { + int nFlags = GCIF_PAM_DEFAULT; + if( bWriteMetadataAsText ) + nFlags &= ~GCIF_METADATA; + poDS->CloneInfo( poSrcDS, nFlags ); + return poDS; + } + CPLErrorReset(); + } + + PNGDataset* poPNG_DS = new PNGDataset(); + poPNG_DS->nRasterXSize = nXSize; + poPNG_DS->nRasterYSize = nYSize; + poPNG_DS->nBitDepth = nBitDepth; + for(int i=0;i<nBands;i++) + poPNG_DS->SetBand( i+1, new PNGRasterBand( poPNG_DS, i+1) ); + return poPNG_DS; +} + +/************************************************************************/ +/* png_vsi_read_data() */ +/* */ +/* Read data callback through VSI. */ +/************************************************************************/ +static void +png_vsi_read_data(png_structp png_ptr, png_bytep data, png_size_t length) + +{ + // fread() returns 0 on error, so it is OK to store this in a png_size_t + // instead of an int, which is what fread() actually returns. + const png_size_t check + = static_cast<png_size_t>( + VSIFReadL(data, (png_size_t)1, length, + reinterpret_cast<VSILFILE *>( png_get_io_ptr(png_ptr) ) ) ); + + if (check != length) + png_error(png_ptr, "Read Error"); +} + +/************************************************************************/ +/* png_vsi_write_data() */ +/************************************************************************/ + +static void +png_vsi_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + const size_t check + = VSIFWriteL(data, 1, length, reinterpret_cast<VSILFILE *>( + png_get_io_ptr(png_ptr) ) ); + + if (check != length) + png_error(png_ptr, "Write Error"); +} + +/************************************************************************/ +/* png_vsi_flush() */ +/************************************************************************/ +static void png_vsi_flush(png_structp png_ptr) +{ + VSIFFlushL( reinterpret_cast<VSILFILE *>( png_get_io_ptr(png_ptr) ) ); +} + +/************************************************************************/ +/* png_gdal_error() */ +/************************************************************************/ + +static void png_gdal_error( png_structp png_ptr, const char *error_message ) +{ + CPLError( CE_Failure, CPLE_AppDefined, + "libpng: %s", error_message ); + + // Use longjmp instead of a C++ exception, because libpng is generally not + // built as C++ and so will not honor unwind semantics. + + jmp_buf* psSetJmpContext = reinterpret_cast<jmp_buf *>( + png_get_error_ptr( png_ptr ) ); + if (psSetJmpContext) + { + longjmp( *psSetJmpContext, 1 ); + } +} + +/************************************************************************/ +/* png_gdal_warning() */ +/************************************************************************/ + +static void png_gdal_warning( CPL_UNUSED png_structp png_ptr, + const char *error_message ) +{ + CPLError( CE_Warning, CPLE_AppDefined, + "libpng: %s", error_message ); +} + +/************************************************************************/ +/* GDALRegister_PNG() */ +/************************************************************************/ + +void GDALRegister_PNG() + +{ + if( GDALGetDriverByName( "PNG" ) != NULL ) + return; + + GDALDriver *poDriver = new GDALDriver(); + + poDriver->SetDescription( "PNG" ); + poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" ); + 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' default='FALSE'/>\n" +" <Option name='ZLEVEL' type='int' description='DEFLATE compression level 1-9' default='6'/>\n" +" <Option name='SOURCE_ICC_PROFILE' type='string' description='ICC Profile'/>\n" +" <Option name='SOURCE_ICC_PROFILE_NAME' type='string' description='ICC Profile name'/>\n" +" <Option name='SOURCE_PRIMARIES_RED' type='string' description='x,y,1.0 (xyY) red chromaticity'/>\n" +" <Option name='SOURCE_PRIMARIES_GREEN' type='string' description='x,y,1.0 (xyY) green chromaticity'/>\n" +" <Option name='SOURCE_PRIMARIES_BLUE' type='string' description='x,y,1.0 (xyY) blue chromaticity'/>\n" +" <Option name='SOURCE_WHITEPOINT' type='string' description='x,y,1.0 (xyY) whitepoint'/>\n" +" <Option name='PNG_GAMMA' type='string' description='Gamma'/>\n" +" <Option name='TITLE' type='string' description='Title'/>\n" +" <Option name='DESCRIPTION' type='string' description='Description'/>\n" +" <Option name='COPYRIGHT' type='string' description='Copyright'/>\n" +" <Option name='COMMENT' type='string' description='Comment'/>\n" +" <Option name='WRITE_METADATA_AS_TEXT' type='boolean' description='Whether to write source dataset metadata in TEXT chunks' default='FALSE'/>\n" +" <Option name='NBITS' type='int' description='Force output bit depth: 1, 2 or 4'/>\n" +"</CreationOptionList>\n" ); + + poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" ); + + poDriver->pfnOpen = PNGDataset::Open; + poDriver->pfnCreateCopy = PNGDataset::CreateCopy; + poDriver->pfnIdentify = PNGDataset::Identify; +#ifdef SUPPORT_CREATE + poDriver->pfnCreate = PNGDataset::Create; +#endif + + GetGDALDriverManager()->RegisterDriver( poDriver ); +} + +#ifdef SUPPORT_CREATE +/************************************************************************/ +/* IWriteBlock() */ +/************************************************************************/ + +CPLErr PNGRasterBand::IWriteBlock(int x, int y, void* pvData) +{ + PNGDataset& ds = *reinterpret_cast<PNGDataset*>( poDS ); + + // Write the block (or consolidate into multichannel block) and then write. + + const GDALDataType dt = GetRasterDataType(); + const size_t wordsize = ds.m_nBitDepth / 8; + GDALCopyWords( pvData, dt, wordsize, + ds.m_pabyBuffer + (nBand-1) * wordsize, + dt, ds.nBands * wordsize, + nBlockXSize ); + + // See if we have all the bands. + m_bBandProvided[nBand - 1] = TRUE; + for( size_t i = 0; i < static_cast<size_t>( ds.nBands ); i++ ) + { + if(!m_bBandProvided[i]) + return CE_None; + } + + // We received all the bands, so reset band flags and write pixels out. + this->reset_band_provision_flags(); + + // If it's the first block, write out the file header. + if(x == 0 && y == 0) + { + CPLErr err = ds.write_png_header(); + if(err != CE_None) + return err; + } + +#ifdef CPL_LSB + if( ds.m_nBitDepth == 16 ) + GDALSwapWords( ds.m_pabyBuffer, 2, nBlockXSize * ds.nBands, 2 ); +#endif + png_write_rows( ds.m_hPNG, &ds.m_pabyBuffer, 1 ); + + return CE_None; +} + +/************************************************************************/ +/* SetGeoTransform() */ +/************************************************************************/ + +CPLErr PNGDataset::SetGeoTransform( double * padfTransform ) +{ + memcpy( m_adfGeoTransform, padfTransform, sizeof(double) * 6 ); + + if ( m_pszFilename ) + { + if ( GDALWriteWorldFile( m_pszFilename, "wld", m_adfGeoTransform ) + == FALSE ) + { + CPLError( CE_Failure, CPLE_FileIO, "Can't write world file." ); + return CE_Failure; + } + } + + return CE_None; +} + +/************************************************************************/ +/* SetColorTable() */ +/************************************************************************/ + +CPLErr PNGRasterBand::SetColorTable(GDALColorTable* poCT) +{ + if( poCT == NULL ) + return CE_Failure; + + // We get called even for grayscale files, since some formats need a palette + // even then. PNG doesn't, so if a gray palette is given, just ignore it. + + GDALColorEntry sEntry; + for( size_t i = 0; i < static_cast<size_t>( poCT->GetColorEntryCount() ); i++ ) + { + poCT->GetColorEntryAsRGB( i, &sEntry ); + if( sEntry.c1 != sEntry.c2 || sEntry.c1 != sEntry.c3) + { + CPLErr err = GDALPamRasterBand::SetColorTable(poCT); + if(err != CE_None) + return err; + + PNGDataset& ds = *reinterpret_cast<PNGDataset *>( poDS ); + ds.m_nColorType = PNG_COLOR_TYPE_PALETTE; + break; + // band::IWriteBlock will emit color table as part of the header + // preceding the first block write. + } + } + + return CE_None; +} + +/************************************************************************/ +/* PNGDataset::write_png_header() */ +/************************************************************************/ + +CPLErr PNGDataset::write_png_header() +{ + // Initialize PNG access to the file. + m_hPNG = png_create_write_struct( + PNG_LIBPNG_VER_STRING, NULL, + png_gdal_error, png_gdal_warning ); + + m_psPNGInfo = png_create_info_struct( m_hPNG ); + + png_set_write_fn( m_hPNG, m_fpImage, png_vsi_write_data, png_vsi_flush ); + + png_set_IHDR( m_hPNG, m_psPNGInfo, nRasterXSize, nRasterYSize, + m_nBitDepth, m_nColorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); + + png_set_compression_level(m_hPNG, Z_BEST_COMPRESSION); + + // png_set_swap_alpha(m_hPNG); // Use RGBA order, not ARGB. + + // Try to handle nodata values as a tRNS block (note that for paletted + // images, we save the effect to apply as part of the palette). + //m_bHaveNoData = FALSE; + //m_dfNoDataValue = -1; + png_color_16 sTRNSColor; + + int bHaveNoData = FALSE; + double dfNoDataValue = -1; + + if( m_nColorType == PNG_COLOR_TYPE_GRAY ) + { + dfNoDataValue = GetRasterBand(1)->GetNoDataValue( &bHaveNoData ); + + if ( bHaveNoData && dfNoDataValue >= 0 && dfNoDataValue < 65536 ) + { + sTRNSColor.gray = static_cast<png_uint_16>( dfNoDataValue ); + png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor ); + } + } + + // RGB nodata. + if( nColorType == PNG_COLOR_TYPE_RGB ) + { + // First, try to use the NODATA_VALUES metadata item. + if ( GetMetadataItem( "NODATA_VALUES" ) != NULL ) + { + char **papszValues = CSLTokenizeString( + GetMetadataItem( "NODATA_VALUES" ) ); + + if( CSLCount(papszValues) >= 3 ) + { + sTRNSColor.red = static_cast<png_uint_16>( atoi(papszValues[0] ) ); + sTRNSColor.green = static_cast<png_uint_16>( atoi(papszValues[1] ) ); + sTRNSColor.blue = static_cast<png_uint_16>( atoi(papszValues[2] ) ); + png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor ); + } + + CSLDestroy( papszValues ); + } + // Otherwise, get the nodata value from the bands. + else + { + int bHaveNoDataRed = FALSE; + const double dfNoDataValueRed + = GetRasterBand(1)->GetNoDataValue( &bHaveNoDataRed ); + + int bHaveNoDataGreen = FALSE; + const double dfNoDataValueGreen + = GetRasterBand(2)->GetNoDataValue( &bHaveNoDataGreen ); + + int bHaveNoDataBlue = FALSE; + const double dfNoDataValueBlue + = GetRasterBand(3)->GetNoDataValue( &bHaveNoDataBlue ); + + if ( ( bHaveNoDataRed && dfNoDataValueRed >= 0 && dfNoDataValueRed < 65536 ) && + ( bHaveNoDataGreen && dfNoDataValueGreen >= 0 && dfNoDataValueGreen < 65536 ) && + ( bHaveNoDataBlue && dfNoDataValueBlue >= 0 && dfNoDataValueBlue < 65536 ) ) + { + sTRNSColor.red = static_cast<png_uint_16>( dfNoDataValueRed ); + sTRNSColor.green = static_cast<png_uint_16>( dfNoDataValueGreen ); + sTRNSColor.blue = static_cast<png_uint_16>( dfNoDataValueBlue ); + png_set_tRNS( m_hPNG, m_psPNGInfo, NULL, 0, &sTRNSColor ); + } + } + } + + // Write the palette if there is one. Technically, it may be possible + // to write 16-bit palettes for PNG, but for now, doing so is omitted. + if( nColorType == PNG_COLOR_TYPE_PALETTE ) + { + GDALColorTable *poCT = GetRasterBand(1)->GetColorTable(); + + int bHaveNoData = FALSE; + double dfNoDataValue = GetRasterBand(1)->GetNoDataValue( &bHaveNoData ); + + m_pasPNGColors = reinterpret_cast<png_color *>( + CPLMalloc( sizeof(png_color) * poCT->GetColorEntryCount() ) ); + + GDALColorEntry sEntry; + bool bFoundTrans = false; + for( int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ ) + { + poCT->GetColorEntryAsRGB( iColor, &sEntry ); + if( sEntry.c4 != 255 ) + bFoundTrans = true; + + m_pasPNGColors[iColor].red = static_cast<png_byte>( sEntry.c1 ); + m_pasPNGColors[iColor].green = static_cast<png_byte>( sEntry.c2 ); + m_pasPNGColors[iColor].blue = static_cast<png_byte>( sEntry.c3 ); + } + + png_set_PLTE( m_hPNG, m_psPNGInfo, m_pasPNGColors, + poCT->GetColorEntryCount() ); + + // If we have transparent elements in the palette, we need to write a + // transparency block. + if( bFoundTrans || bHaveNoData ) + { + m_pabyAlpha = reinterpret_cast<unsigned char *>( + CPLMalloc(poCT->GetColorEntryCount() ) ); + + for( int iColor = 0; iColor < poCT->GetColorEntryCount(); iColor++ ) + { + poCT->GetColorEntryAsRGB( iColor, &sEntry ); + m_pabyAlpha[iColor] = static_cast<unsigned char>( sEntry.c4 ); + + if( bHaveNoData && iColor == static_cast<int>( dfNoDataValue ) ) + m_pabyAlpha[iColor] = 0; + } + + png_set_tRNS( m_hPNG, m_psPNGInfo, m_pabyAlpha, + poCT->GetColorEntryCount(), NULL ); + } + } + + png_write_info( m_hPNG, m_psPNGInfo ); + return CE_None; +} + +/************************************************************************/ +/* Create() */ +/************************************************************************/ + +GDALDataset *PNGDataset::Create +( + const char* pszFilename, + int nXSize, int nYSize, + int nBands, + GDALDataType eType, + char **papszOptions +) +{ + if( eType != GDT_Byte && eType != GDT_UInt16) + { + CPLError( CE_Failure, CPLE_AppDefined, + "Attempt to create PNG dataset with an illegal\n" + "data type (%s), only Byte and UInt16 supported by the format.\n", + GDALGetDataTypeName(eType) ); + + return NULL; + } + + if( nBands < 1 || nBands > 4 ) + { + CPLError( CE_Failure, CPLE_NotSupported, + "PNG driver doesn't support %d bands. " + "Must be 1 (gray/indexed color),\n" + "2 (gray+alpha), 3 (rgb) or 4 (rgba) bands.\n", + nBands ); + + return NULL; + } + + // Bands are: + // 1: Grayscale or indexed color. + // 2: Gray plus alpha. + // 3: RGB. + // 4: RGB plus alpha. + + if(nXSize < 1 || nYSize < 1) + { + CPLError( CE_Failure, CPLE_NotSupported, + "Specified pixel dimensions (% d x %d) are bad.\n", + nXSize, nYSize ); + } + + // Set up some parameters. + PNGDataset* poDS = new PNGDataset(); + + poDS->nRasterXSize = nXSize; + poDS->nRasterYSize = nYSize; + poDS->eAccess = GA_Update; + poDS->nBands = nBands; + + switch(nBands) + { + case 1: + poDS->m_nColorType = PNG_COLOR_TYPE_GRAY; + break; // If a non-gray palette is set, we'll change this. + + case 2: + poDS->m_nColorType = PNG_COLOR_TYPE_GRAY_ALPHA; + break; + + case 3: + poDS->m_nColorType = PNG_COLOR_TYPE_RGB; + break; + + case 4: + poDS->m_nColorType = PNG_COLOR_TYPE_RGB_ALPHA; + break; + } + + poDS->m_nBitDepth = (eType == GDT_Byte ? 8 : 16); + + poDS->m_pabyBuffer = reinterpret_cast<GByte *>( + CPLMalloc( nBands * nXSize * poDS->m_nBitDepth / 8 ) ); + + // Create band information objects. + for( int iBand = 1; iBand <= poDS->nBands; iBand++ ) + poDS->SetBand( iBand, new PNGRasterBand( poDS, iBand ) ); + + // Do we need a world file? + if( CPLFetchBool( papszOptions, "WORLDFILE", false ) ) + poDS->m_bGeoTransformValid = TRUE; + + // Create the file. + + poDS->m_fpImage = VSIFOpenL( pszFilename, "wb" ); + if( poDS->m_fpImage == NULL ) + { + CPLError( CE_Failure, CPLE_OpenFailed, + "Unable to create PNG file %s.\n", + pszFilename ); + delete poDS; + return NULL; + } + + poDS->m_pszFilename = CPLStrdup(pszFilename); + + return poDS; +} + +#endif Property changes on: brlcad/trunk/src/other/gdal/frmts/png/pngdataset.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ BRL-CAD Source Commits mailing list brlcad-commits@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/brlcad-commits