gdal/frmts/gif/gifdataset.cpp

711 строки
27 KiB
C++

/******************************************************************************
*
* 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 spatialys.com>
*
* 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_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
constexpr int InterlacedOffset[] = {0, 4, 2, 1};
constexpr 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 final : 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 final : public GIFAbstractRasterBand
{
public:
GIFRasterBand(GIFDataset *, int, SavedImage *, int);
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 == nullptr)
{
memset(pImage, 0, nBlockXSize);
return CE_None;
}
if (panInterlaceMap != nullptr)
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 == nullptr)
return nullptr;
if (poOpenInfo->eAccess == GA_Update)
{
CPLError(CE_Failure, CPLE_NotSupported,
"The GIF driver does not support update access to existing"
" files.");
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Ingest. */
/* -------------------------------------------------------------------- */
VSILFILE *fp = poOpenInfo->fpL;
poOpenInfo->fpL = nullptr;
GifFileType *hGifFile =
GIFAbstractDataset::myDGifOpen(fp, GIFAbstractDataset::ReadFunc);
if (hGifFile == nullptr)
{
VSIFCloseL(fp);
CPLError(CE_Failure, CPLE_OpenFailed,
"DGifOpen() failed for %s. "
"Perhaps the gif file is corrupt?",
poOpenInfo->pszFilename);
return nullptr;
}
// 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 nullptr;
}
}
GIFAbstractDataset::myDGifCloseFile(hGifFile);
VSIFSeekL(fp, 0, SEEK_SET);
hGifFile = GIFAbstractDataset::myDGifOpen(fp, GIFAbstractDataset::ReadFunc);
if (hGifFile == nullptr)
{
VSIFCloseL(fp);
CPLError(CE_Failure, CPLE_OpenFailed,
"DGifOpen() failed for %s. "
"Perhaps the gif file is corrupt?",
poOpenInfo->pszFilename);
return nullptr;
}
const int nGifErr = DGifSlurp(hGifFile);
if (nGifErr != GIF_OK || hGifFile->SavedImages == nullptr)
{
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 nullptr;
}
CPLError(CE_Failure, CPLE_OpenFailed,
"DGifSlurp() failed for %s. "
"Perhaps the gif file is corrupt?",
poOpenInfo->pszFilename);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* 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;
if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
{
delete poDS;
return nullptr;
}
/* -------------------------------------------------------------------- */
/* 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 == nullptr &&
poDS->hGifFile->SColorMap == nullptr)
{
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 = false;
#endif
poDS->SetBand(poDS->nBands + 1,
new GIFRasterBand(poDS, poDS->nBands + 1, psImage,
hGifFile->SBackGroundColor));
}
if (poDS->nBands == 0)
{
delete poDS;
return nullptr;
}
/* -------------------------------------------------------------------- */
/* 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 == nullptr)
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 nullptr;
}
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 nullptr;
}
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 nullptr;
}
/* -------------------------------------------------------------------- */
/* Open the output file. */
/* -------------------------------------------------------------------- */
VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
if (fp == nullptr)
{
CPLError(CE_Failure, CPLE_OpenFailed, "Failed to create %s:\n%s",
pszFilename, VSIStrerror(errno));
return nullptr;
}
#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 == nullptr)
{
VSIFCloseL(fp);
CPLError(CE_Failure, CPLE_OpenFailed,
"EGifOpenFilename(%s) failed. Does file already exist?",
pszFilename);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Prepare colortable. */
/* -------------------------------------------------------------------- */
GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
ColorMapObject *psGifCT = nullptr;
if (poBand->GetColorTable() == nullptr)
{
psGifCT = GifMakeMapObject(256, nullptr);
if (psGifCT == nullptr)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot allocate color table");
GIFAbstractDataset::myEGifCloseFile(hGifFile);
VSIFCloseL(fp);
return nullptr;
}
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, nullptr);
if (psGifCT == nullptr)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot allocate color table");
GIFAbstractDataset::myEGifCloseFile(hGifFile);
VSIFCloseL(fp);
return nullptr;
}
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 nullptr;
}
GifFreeMapObject(psGifCT);
psGifCT = nullptr;
// 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, nullptr) ==
GIF_ERROR)
{
GDALPrintGifError(hGifFile, "Error writing gif file.");
GIFAbstractDataset::myEGifCloseFile(hGifFile);
VSIFCloseL(fp);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Loop over image, copying image data. */
/* -------------------------------------------------------------------- */
GDALPamDataset *poDS = nullptr;
GByte *pabyScanline = static_cast<GByte *>(CPLMalloc(nXSize));
if (!pfnProgress(0.0, nullptr, 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, nullptr);
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, nullptr,
pProgressData))
{
goto error;
}
}
}
else
{
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, nullptr);
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, nullptr,
pProgressData))
{
goto error;
}
}
}
}
CPLFree(pabyScanline);
pabyScanline = nullptr;
/* -------------------------------------------------------------------- */
/* cleanup */
/* -------------------------------------------------------------------- */
if (GIFAbstractDataset::myEGifCloseFile(hGifFile) == GIF_ERROR)
{
CPLError(CE_Failure, CPLE_AppDefined, "EGifCloseFile() failed.");
hGifFile = nullptr;
goto error;
}
hGifFile = nullptr;
VSIFCloseL(fp);
fp = nullptr;
/* -------------------------------------------------------------------- */
/* 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, nullptr, 0));
return poGIF_DS;
}
error:
if (hGifFile)
GIFAbstractDataset::myEGifCloseFile(hGifFile);
if (fp)
VSIFCloseL(fp);
if (pabyScanline)
CPLFree(pabyScanline);
return nullptr;
}
/************************************************************************/
/* GDALRegister_GIF() */
/************************************************************************/
void GDALRegister_GIF()
{
if (GDALGetDriverByName("GIF") != nullptr)
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, "drivers/raster/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);
}