EVOLUTION-MANAGER
Edit File: gifdataset.cpp
/****************************************************************************** * * Project: GIF Driver * Purpose: Implement GDAL GIF Support using libungif code. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2001, Frank Warmerdam * Copyright (c) 2007-2012, 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. ****************************************************************************/ #include "gifabstractdataset.h" #include "cpl_string.h" #include "gdal_frmts.h" #include "gdal_pam.h" CPL_CVSID("$Id: gifdataset.cpp 36501 2016-11-25 14:09:24Z rouault $"); CPL_C_START #if !(defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5) // This prototype seems to have been messed up! GifFileType * EGifOpen(void* userData, OutputFunc writeFunc); // Define alias compatible with giflib >= 5.0.0 #define GifMakeMapObject MakeMapObject #define GifFreeMapObject FreeMapObject #endif // defined(GIFLIB_MAJOR) && GIFLIB_MAJOR < 5 CPL_C_END static const int InterlacedOffset[] = { 0, 4, 2, 1 }; static const int InterlacedJumps[] = { 8, 8, 4, 2 }; /************************************************************************/ /* VSIGIFWriteFunc() */ /* */ /* Proxy write function. */ /************************************************************************/ static int VSIGIFWriteFunc( GifFileType *psGFile, const GifByteType *pabyBuffer, int nBytesToWrite ) { VSILFILE* fp = static_cast<VSILFILE *>(psGFile->UserData); if( VSIFTellL(fp) == 0 && nBytesToWrite >= 6 && memcmp(pabyBuffer, "GIF87a", 6) == 0 ) { // This is a hack to write a GIF89a instead of GIF87a (we have to, since // we are using graphical extension block). EGifSpew would write GIF89a // when it detects an extension block if we were using it As we don't, // we could have used EGifSetGifVersion instead, but the version of // libungif in GDAL has a bug: it writes on read-only memory! // This is a well-known problem. Just google for "EGifSetGifVersion // segfault". // Most readers don't even care if it is GIF87a or GIF89a, but it is // better to write the right version. size_t nRet = VSIFWriteL("GIF89a", 1, 6, fp); nRet += VSIFWriteL( reinterpret_cast<const char *>(pabyBuffer) + 6, 1, nBytesToWrite - 6, fp ); return static_cast<int>(nRet); } return static_cast<int>(VSIFWriteL( pabyBuffer, 1, nBytesToWrite, fp )); } /************************************************************************/ /* ==================================================================== */ /* GIFDataset */ /* ==================================================================== */ /************************************************************************/ class GIFRasterBand; class GIFDataset : public GIFAbstractDataset { friend class GIFRasterBand; public: GIFDataset(); static GDALDataset *Open( GDALOpenInfo * ); static GDALDataset* CreateCopy( const char * pszFilename, GDALDataset *poSrcDS, int bStrict, char ** papszOptions, GDALProgressFunc pfnProgress, void * pProgressData ); }; /************************************************************************/ /* ==================================================================== */ /* GIFRasterBand */ /* ==================================================================== */ /************************************************************************/ class GIFRasterBand : public GIFAbstractRasterBand { public: GIFRasterBand( GIFDataset *, int, SavedImage *, int ); virtual CPLErr IReadBlock( int, int, void * ) override; }; /************************************************************************/ /* GIFRasterBand() */ /************************************************************************/ GIFRasterBand::GIFRasterBand( GIFDataset *poDSIn, int nBandIn, SavedImage *psSavedImage, int nBackground ) : GIFAbstractRasterBand(poDSIn, nBandIn, psSavedImage, nBackground, FALSE) {} /************************************************************************/ /* IReadBlock() */ /************************************************************************/ CPLErr GIFRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff, int nBlockYOff, void * pImage ) { CPLAssert( nBlockXOff == 0 ); if( psImage == NULL ) { memset(pImage, 0, nBlockXSize); return CE_None; } if( panInterlaceMap != NULL ) nBlockYOff = panInterlaceMap[nBlockYOff]; memcpy( pImage, psImage->RasterBits + nBlockYOff * nBlockXSize, nBlockXSize ); return CE_None; } /************************************************************************/ /* ==================================================================== */ /* GIFDataset */ /* ==================================================================== */ /************************************************************************/ /************************************************************************/ /* GIFDataset() */ /************************************************************************/ GIFDataset::GIFDataset() {} /************************************************************************/ /* Open() */ /************************************************************************/ GDALDataset *GIFDataset::Open( GDALOpenInfo * poOpenInfo ) { if( !Identify( poOpenInfo ) || poOpenInfo->fpL == NULL ) return NULL; if( poOpenInfo->eAccess == GA_Update ) { CPLError( CE_Failure, CPLE_NotSupported, "The GIF driver does not support update access to existing" " files." ); return NULL; } /* -------------------------------------------------------------------- */ /* Ingest. */ /* -------------------------------------------------------------------- */ VSILFILE *fp = poOpenInfo->fpL; poOpenInfo->fpL = NULL; GifFileType *hGifFile = GIFAbstractDataset::myDGifOpen( fp, GIFAbstractDataset::ReadFunc ); if( hGifFile == NULL ) { VSIFCloseL( fp ); CPLError( CE_Failure, CPLE_OpenFailed, "DGifOpen() failed for %s. " "Perhaps the gif file is corrupt?", poOpenInfo->pszFilename ); return NULL; } // The following code enables us to detect GIF datasets eligible // for BIGGIF driver even with an unpatched giflib. /* -------------------------------------------------------------------- */ /* Find the first image record. */ /* -------------------------------------------------------------------- */ GifRecordType RecordType = FindFirstImage(hGifFile); if( RecordType == IMAGE_DESC_RECORD_TYPE && DGifGetImageDesc(hGifFile) != GIF_ERROR) { const int width = hGifFile->SavedImages[0].ImageDesc.Width; const int height = hGifFile->SavedImages[0].ImageDesc.Height; if( static_cast<double>(width) * height > 100000000.0 ) { CPLDebug( "GIF", "Due to limitations of the GDAL GIF driver we " "deliberately avoid opening large GIF files " "(larger than 100 megapixels)."); GIFAbstractDataset::myDGifCloseFile( hGifFile ); // Reset poOpenInfo->fpL since BIGGIF may need it. poOpenInfo->fpL = fp; VSIFSeekL(fp, 0, SEEK_SET); return NULL; } } GIFAbstractDataset::myDGifCloseFile( hGifFile ); VSIFSeekL( fp, 0, SEEK_SET); hGifFile = GIFAbstractDataset::myDGifOpen( fp, GIFAbstractDataset::ReadFunc ); if( hGifFile == NULL ) { VSIFCloseL( fp ); CPLError( CE_Failure, CPLE_OpenFailed, "DGifOpen() failed for %s. " "Perhaps the gif file is corrupt?", poOpenInfo->pszFilename ); return NULL; } const int nGifErr = DGifSlurp( hGifFile ); if( nGifErr != GIF_OK || hGifFile->SavedImages == NULL ) { VSIFCloseL( fp ); GIFAbstractDataset::myDGifCloseFile(hGifFile); if( nGifErr == D_GIF_ERR_DATA_TOO_BIG ) { CPLDebug( "GIF", "DGifSlurp() failed for %s because it was too large. " "Due to limitations of the GDAL GIF driver we " "deliberately avoid opening large GIF files " "(larger than 100 megapixels).", poOpenInfo->pszFilename ); return NULL; } CPLError( CE_Failure, CPLE_OpenFailed, "DGifSlurp() failed for %s. " "Perhaps the gif file is corrupt?", poOpenInfo->pszFilename ); return NULL; } /* -------------------------------------------------------------------- */ /* Create a corresponding GDALDataset. */ /* -------------------------------------------------------------------- */ GIFDataset *poDS = new GIFDataset(); poDS->fp = fp; poDS->eAccess = GA_ReadOnly; poDS->hGifFile = hGifFile; /* -------------------------------------------------------------------- */ /* Capture some information from the file that is of interest. */ /* -------------------------------------------------------------------- */ poDS->nRasterXSize = hGifFile->SavedImages[0].ImageDesc.Width; poDS->nRasterYSize = hGifFile->SavedImages[0].ImageDesc.Height; /* -------------------------------------------------------------------- */ /* Create band information objects. */ /* -------------------------------------------------------------------- */ for( int iImage = 0; iImage < hGifFile->ImageCount; iImage++ ) { SavedImage *psImage = hGifFile->SavedImages + iImage; if( psImage->ImageDesc.Width != poDS->nRasterXSize || psImage->ImageDesc.Height != poDS->nRasterYSize ) continue; if( psImage->ImageDesc.ColorMap == NULL && poDS->hGifFile->SColorMap == NULL ) { CPLDebug("GIF", "Skipping image without color table"); continue; } #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 // Since giflib 5, de-interlacing is done by DGifSlurp(). psImage->ImageDesc.Interlace = 0; #endif poDS->SetBand( poDS->nBands+1, new GIFRasterBand( poDS, poDS->nBands+1, psImage, hGifFile->SBackGroundColor )); } if( poDS->nBands == 0 ) { delete poDS; return NULL; } /* -------------------------------------------------------------------- */ /* Check for georeferencing. */ /* -------------------------------------------------------------------- */ poDS->DetectGeoreferencing(poOpenInfo); /* -------------------------------------------------------------------- */ /* Initialize any PAM information. */ /* -------------------------------------------------------------------- */ poDS->SetDescription( poOpenInfo->pszFilename ); poDS->TryLoadXML( poOpenInfo->GetSiblingFiles() ); /* -------------------------------------------------------------------- */ /* Support overviews. */ /* -------------------------------------------------------------------- */ poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename, poOpenInfo->GetSiblingFiles() ); return poDS; } /************************************************************************/ /* GDALPrintGifError() */ /************************************************************************/ static void GDALPrintGifError( CPL_UNUSED GifFileType *hGifFile, const char* pszMsg ) { // GIFLIB_MAJOR is only defined in libgif >= 4.2.0. // libgif 4.2.0 has retired PrintGifError() and added GifErrorString(). #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && \ ((GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2) || GIFLIB_MAJOR > 4) // Static string actually, hence the const char* cast. #if GIFLIB_MAJOR >= 5 const char* pszGIFLIBError = GifErrorString(hGifFile->Error); #else // TODO(schwehr): Can we remove the cast for older libgif? const char* pszGIFLIBError = (const char*) GifErrorString(); #endif if( pszGIFLIBError == NULL ) pszGIFLIBError = "Unknown error"; CPLError( CE_Failure, CPLE_AppDefined, "%s. GIFLib Error : %s", pszMsg, pszGIFLIBError ); #else PrintGifError(); CPLError( CE_Failure, CPLE_AppDefined, "%s", pszMsg ); #endif } /************************************************************************/ /* CreateCopy() */ /************************************************************************/ GDALDataset * GIFDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS, int bStrict, char ** papszOptions, GDALProgressFunc pfnProgress, void * pProgressData ) { /* -------------------------------------------------------------------- */ /* Check for interlaced option. */ /* -------------------------------------------------------------------- */ const bool bInterlace = CPLFetchBool(papszOptions, "INTERLACING", false); /* -------------------------------------------------------------------- */ /* Some some rudimentary checks */ /* -------------------------------------------------------------------- */ const int nBands = poSrcDS->GetRasterCount(); if( nBands != 1 ) { CPLError( CE_Failure, CPLE_NotSupported, "GIF driver only supports one band images." ); return NULL; } const int nXSize = poSrcDS->GetRasterXSize(); const int nYSize = poSrcDS->GetRasterYSize(); if( nXSize > 65535 || nYSize > 65535 ) { CPLError(CE_Failure, CPLE_NotSupported, "GIF driver only supports datasets up to 65535x65535 size."); return NULL; } if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict ) { CPLError( CE_Failure, CPLE_NotSupported, "GIF driver doesn't support data type %s. " "Only eight bit bands supported.", GDALGetDataTypeName( poSrcDS->GetRasterBand(1)->GetRasterDataType()) ); return NULL; } /* -------------------------------------------------------------------- */ /* Open the output file. */ /* -------------------------------------------------------------------- */ VSILFILE *fp = VSIFOpenL( pszFilename, "wb" ); if( fp == NULL ) { CPLError( CE_Failure, CPLE_OpenFailed, "Failed to create %s:\n%s", pszFilename, VSIStrerror( errno ) ); return NULL; } #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 int nError = 0; GifFileType *hGifFile = EGifOpen( fp, VSIGIFWriteFunc, &nError ); #else GifFileType *hGifFile = EGifOpen( fp, VSIGIFWriteFunc ); #endif if( hGifFile == NULL ) { VSIFCloseL( fp ); CPLError( CE_Failure, CPLE_OpenFailed, "EGifOpenFilename(%s) failed. Does file already exist?", pszFilename ); return NULL; } /* -------------------------------------------------------------------- */ /* Prepare colortable. */ /* -------------------------------------------------------------------- */ GDALRasterBand *poBand = poSrcDS->GetRasterBand(1); ColorMapObject *psGifCT = NULL; if( poBand->GetColorTable() == NULL ) { psGifCT = GifMakeMapObject( 256, NULL ); if( psGifCT == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot allocate color table"); GIFAbstractDataset::myEGifCloseFile(hGifFile); VSIFCloseL( fp ); return NULL; } for( int iColor = 0; iColor < 256; iColor++ ) { psGifCT->Colors[iColor].Red = static_cast<GifByteType>(iColor); psGifCT->Colors[iColor].Green = static_cast<GifByteType>(iColor); psGifCT->Colors[iColor].Blue = static_cast<GifByteType>(iColor); } } else { GDALColorTable *poCT = poBand->GetColorTable(); int nFullCount = 2; while( nFullCount < poCT->GetColorEntryCount() ) nFullCount = nFullCount * 2; psGifCT = GifMakeMapObject( nFullCount, NULL ); if( psGifCT == NULL ) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot allocate color table"); GIFAbstractDataset::myEGifCloseFile(hGifFile); VSIFCloseL( fp ); return NULL; } int iColor = 0; for( ; iColor < poCT->GetColorEntryCount(); iColor++ ) { GDALColorEntry sEntry; poCT->GetColorEntryAsRGB( iColor, &sEntry ); psGifCT->Colors[iColor].Red = static_cast<GifByteType>(sEntry.c1); psGifCT->Colors[iColor].Green = static_cast<GifByteType>(sEntry.c2); psGifCT->Colors[iColor].Blue = static_cast<GifByteType>(sEntry.c3); } for( ; iColor < nFullCount; iColor++ ) { psGifCT->Colors[iColor].Red = 0; psGifCT->Colors[iColor].Green = 0; psGifCT->Colors[iColor].Blue = 0; } } /* -------------------------------------------------------------------- */ /* Setup parameters. */ /* -------------------------------------------------------------------- */ if( EGifPutScreenDesc(hGifFile, nXSize, nYSize, 8, /* ColorRes */ 255, /* Background */ psGifCT) == GIF_ERROR ) { GifFreeMapObject(psGifCT); GDALPrintGifError(hGifFile, "Error writing gif file."); GIFAbstractDataset::myEGifCloseFile(hGifFile); VSIFCloseL( fp ); return NULL; } GifFreeMapObject(psGifCT); psGifCT = NULL; // Support for transparency. int bNoDataValue = 0; double noDataValue = poBand->GetNoDataValue(&bNoDataValue); if( bNoDataValue && noDataValue >= 0 && noDataValue <= 255 ) { unsigned char extensionData[4] = { 1, // Transparent Color Flag. 0, 0, static_cast<unsigned char>(noDataValue) }; EGifPutExtension(hGifFile, 0xf9, 4, extensionData); } if( EGifPutImageDesc(hGifFile, 0, 0, nXSize, nYSize, bInterlace, NULL) == GIF_ERROR ) { GDALPrintGifError(hGifFile, "Error writing gif file."); GIFAbstractDataset::myEGifCloseFile(hGifFile); VSIFCloseL( fp ); return NULL; } /* -------------------------------------------------------------------- */ /* Loop over image, copying image data. */ /* -------------------------------------------------------------------- */ GDALPamDataset *poDS = NULL; GByte *pabyScanline = static_cast<GByte *>(CPLMalloc( nXSize )); if( !pfnProgress( 0.0, NULL, pProgressData ) ) { CPLError( CE_Failure, CPLE_AppDefined, "Unable to setup progress." ); } if( !bInterlace ) { for( int iLine = 0; iLine < nYSize; iLine++ ) { const CPLErr eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1, pabyScanline, nXSize, 1, GDT_Byte, nBands, nBands * nXSize, NULL ); if( eErr != CE_None || EGifPutLine( hGifFile, pabyScanline, nXSize ) == GIF_ERROR ) { CPLError( CE_Failure, CPLE_AppDefined, "Error writing gif file." ); goto error; } if( !pfnProgress( (iLine + 1) * 1.0 / nYSize, NULL, pProgressData ) ) { goto error; } } } else { int nLinesToRead = 0; for( int i = 0; i < 4; i++) { for( int j = InterlacedOffset[i]; j < nYSize; j += InterlacedJumps[i] ) { nLinesToRead++; } } int nLinesRead = 0; // Need to perform 4 passes on the images: for( int i = 0; i < 4; i++) { for( int j = InterlacedOffset[i]; j < nYSize; j += InterlacedJumps[i] ) { const CPLErr eErr = poBand->RasterIO( GF_Read, 0, j, nXSize, 1, pabyScanline, nXSize, 1, GDT_Byte, 1, nXSize, NULL ); if( eErr != CE_None || EGifPutLine(hGifFile, pabyScanline, nXSize) == GIF_ERROR ) { CPLError( CE_Failure, CPLE_AppDefined, "Error writing gif file." ); goto error; } nLinesRead++; if( !pfnProgress( nLinesRead * 1.0 / nYSize, NULL, pProgressData ) ) { goto error; } } } } CPLFree( pabyScanline ); pabyScanline = NULL; /* -------------------------------------------------------------------- */ /* cleanup */ /* -------------------------------------------------------------------- */ if( GIFAbstractDataset::myEGifCloseFile(hGifFile) == GIF_ERROR ) { CPLError( CE_Failure, CPLE_AppDefined, "EGifCloseFile() failed." ); hGifFile = NULL; goto error; } hGifFile = NULL; VSIFCloseL( fp ); fp = 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. CPLPushErrorHandler(CPLQuietErrorHandler); poDS = static_cast<GDALPamDataset *>(GDALOpen(pszFilename, GA_ReadOnly)); CPLPopErrorHandler(); if( poDS ) { poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT ); return poDS; } else { CPLErrorReset(); GIFDataset* poGIF_DS = new GIFDataset(); poGIF_DS->nRasterXSize = nXSize; poGIF_DS->nRasterYSize = nYSize; for( int i = 0; i < nBands; i++ ) poGIF_DS->SetBand( i+1, new GIFRasterBand( poGIF_DS, i+1, NULL, 0 ) ); return poGIF_DS; } error: if( hGifFile ) GIFAbstractDataset::myEGifCloseFile(hGifFile); if( fp ) VSIFCloseL( fp ); if( pabyScanline ) CPLFree( pabyScanline ); return NULL; } /************************************************************************/ /* GDALRegister_GIF() */ /************************************************************************/ void GDALRegister_GIF() { if( GDALGetDriverByName( "GIF" ) != NULL ) return; GDALDriver *poDriver = new GDALDriver(); poDriver->SetDescription( "GIF" ); poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" ); poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "Graphics Interchange Format (.gif)" ); poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_gif.html" ); poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gif" ); poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/gif" ); poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" ); poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, "<CreationOptionList>\n" " <Option name='INTERLACING' type='boolean'/>\n" " <Option name='WORLDFILE' type='boolean'/>\n" "</CreationOptionList>\n" ); poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" ); poDriver->pfnOpen = GIFDataset::Open; poDriver->pfnCreateCopy = GIFDataset::CreateCopy; poDriver->pfnIdentify = GIFAbstractDataset::Identify; GetGDALDriverManager()->RegisterDriver( poDriver ); }