Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: BIGGIF Driver
4 : * Purpose: Implement GDAL support for reading large GIF files in a
5 : * streaming fashion rather than the slurp-into-memory approach
6 : * of the normal GIF driver.
7 : * Author: Frank Warmerdam, warmerdam@pobox.com
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2001-2008, Frank Warmerdam <warmerdam@pobox.com>
11 : * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "cpl_string.h"
17 : #include "gdal_frmts.h"
18 : #include "gdal_pam.h"
19 : #include "gifabstractdataset.h"
20 : #include "gifdrivercore.h"
21 :
22 : /************************************************************************/
23 : /* ==================================================================== */
24 : /* BIGGIFDataset */
25 : /* ==================================================================== */
26 : /************************************************************************/
27 :
28 : class BIGGifRasterBand;
29 :
30 : class BIGGIFDataset final : public GIFAbstractDataset
31 : {
32 : friend class BIGGifRasterBand;
33 :
34 : int nLastLineRead;
35 :
36 : GDALDataset *poWorkDS;
37 :
38 : CPLErr ReOpen();
39 :
40 : protected:
41 : int CloseDependentDatasets() override;
42 :
43 : public:
44 : BIGGIFDataset();
45 : ~BIGGIFDataset() override;
46 :
47 : static GDALDataset *Open(GDALOpenInfo *);
48 : };
49 :
50 : /************************************************************************/
51 : /* ==================================================================== */
52 : /* BIGGifRasterBand */
53 : /* ==================================================================== */
54 : /************************************************************************/
55 :
56 : class BIGGifRasterBand final : public GIFAbstractRasterBand
57 : {
58 : friend class BIGGIFDataset;
59 :
60 : public:
61 : BIGGifRasterBand(BIGGIFDataset *, int);
62 :
63 : CPLErr IReadBlock(int, int, void *) override;
64 : };
65 :
66 : /************************************************************************/
67 : /* BIGGifRasterBand() */
68 : /************************************************************************/
69 :
70 6 : BIGGifRasterBand::BIGGifRasterBand(BIGGIFDataset *poDSIn, int nBackground)
71 6 : : GIFAbstractRasterBand(poDSIn, 1, poDSIn->hGifFile->SavedImages,
72 6 : nBackground, TRUE)
73 :
74 : {
75 6 : }
76 :
77 : /************************************************************************/
78 : /* IReadBlock() */
79 : /************************************************************************/
80 :
81 1200 : CPLErr BIGGifRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
82 : void *pImage)
83 : {
84 1200 : BIGGIFDataset *poGDS = (BIGGIFDataset *)poDS;
85 :
86 1200 : CPLAssert(nBlockXOff == 0);
87 :
88 1200 : if (panInterlaceMap != nullptr)
89 1200 : nBlockYOff = panInterlaceMap[nBlockYOff];
90 :
91 : /* -------------------------------------------------------------------- */
92 : /* Do we already have this line in the work dataset? */
93 : /* -------------------------------------------------------------------- */
94 1200 : if (poGDS->poWorkDS != nullptr && nBlockYOff <= poGDS->nLastLineRead)
95 : {
96 796 : return poGDS->poWorkDS->RasterIO(GF_Read, 0, nBlockYOff, nBlockXSize, 1,
97 : pImage, nBlockXSize, 1, GDT_Byte, 1,
98 796 : nullptr, 0, 0, 0, nullptr);
99 : }
100 :
101 : /* -------------------------------------------------------------------- */
102 : /* Do we need to restart from the start of the image? */
103 : /* -------------------------------------------------------------------- */
104 404 : if (nBlockYOff <= poGDS->nLastLineRead)
105 : {
106 2 : if (poGDS->ReOpen() == CE_Failure)
107 0 : return CE_Failure;
108 : }
109 :
110 : /* -------------------------------------------------------------------- */
111 : /* Read till we get our target line. */
112 : /* -------------------------------------------------------------------- */
113 404 : CPLErr eErr = CE_None;
114 1606 : while (poGDS->nLastLineRead < nBlockYOff && eErr == CE_None)
115 : {
116 1202 : if (DGifGetLine(poGDS->hGifFile, (GifPixelType *)pImage, nBlockXSize) ==
117 : GIF_ERROR)
118 : {
119 0 : CPLError(CE_Failure, CPLE_AppDefined,
120 : "Failure decoding scanline of GIF file.");
121 0 : return CE_Failure;
122 : }
123 :
124 1202 : poGDS->nLastLineRead++;
125 :
126 1202 : if (poGDS->poWorkDS != nullptr)
127 : {
128 800 : eErr = poGDS->poWorkDS->RasterIO(
129 : GF_Write, 0, poGDS->nLastLineRead, nBlockXSize, 1, pImage,
130 : nBlockXSize, 1, GDT_Byte, 1, nullptr, 0, 0, 0, nullptr);
131 : }
132 : }
133 :
134 404 : return eErr;
135 : }
136 :
137 : /************************************************************************/
138 : /* ==================================================================== */
139 : /* BIGGIFDataset */
140 : /* ==================================================================== */
141 : /************************************************************************/
142 :
143 : /************************************************************************/
144 : /* BIGGIFDataset() */
145 : /************************************************************************/
146 :
147 6 : BIGGIFDataset::BIGGIFDataset() : nLastLineRead(-1), poWorkDS(nullptr)
148 : {
149 6 : }
150 :
151 : /************************************************************************/
152 : /* ~BIGGIFDataset() */
153 : /************************************************************************/
154 :
155 12 : BIGGIFDataset::~BIGGIFDataset()
156 :
157 : {
158 6 : BIGGIFDataset::FlushCache(true);
159 :
160 6 : BIGGIFDataset::CloseDependentDatasets();
161 12 : }
162 :
163 : /************************************************************************/
164 : /* CloseDependentDatasets() */
165 : /************************************************************************/
166 :
167 16 : int BIGGIFDataset::CloseDependentDatasets()
168 : {
169 16 : int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
170 :
171 16 : if (poWorkDS != nullptr)
172 : {
173 2 : bHasDroppedRef = TRUE;
174 :
175 2 : CPLString osTempFilename = poWorkDS->GetDescription();
176 2 : GDALDriver *poDrv = poWorkDS->GetDriver();
177 :
178 2 : GDALClose((GDALDatasetH)poWorkDS);
179 2 : poWorkDS = nullptr;
180 :
181 2 : if (poDrv != nullptr)
182 2 : poDrv->Delete(osTempFilename);
183 :
184 2 : poWorkDS = nullptr;
185 : }
186 :
187 16 : return bHasDroppedRef;
188 : }
189 :
190 : /************************************************************************/
191 : /* ReOpen() */
192 : /* */
193 : /* (Re)Open the gif file and process past the first image */
194 : /* descriptor. */
195 : /************************************************************************/
196 :
197 8 : CPLErr BIGGIFDataset::ReOpen()
198 :
199 : {
200 : /* -------------------------------------------------------------------- */
201 : /* If the file is already open, close it so we can restart. */
202 : /* -------------------------------------------------------------------- */
203 8 : if (hGifFile != nullptr)
204 2 : GIFAbstractDataset::myDGifCloseFile(hGifFile);
205 :
206 : /* -------------------------------------------------------------------- */
207 : /* If we are actually reopening, then we assume that access to */
208 : /* the image data is not strictly once through sequential, and */
209 : /* we will try to create a working database in a temporary */
210 : /* directory to hold the image as we read through it the second */
211 : /* time. */
212 : /* -------------------------------------------------------------------- */
213 8 : if (hGifFile != nullptr)
214 : {
215 2 : GDALDriver *poGTiffDriver = (GDALDriver *)GDALGetDriverByName("GTiff");
216 :
217 2 : if (poGTiffDriver != nullptr)
218 : {
219 : /* Create as a sparse file to avoid filling up the whole file */
220 : /* while closing and then destroying this temporary dataset */
221 2 : const char *apszOptions[] = {"COMPRESS=LZW", "SPARSE_OK=YES",
222 : nullptr};
223 2 : CPLString osTempFilename = CPLGenerateTempFilename("biggif");
224 :
225 2 : osTempFilename += ".tif";
226 :
227 2 : poWorkDS = poGTiffDriver->Create(osTempFilename, nRasterXSize,
228 : nRasterYSize, 1, GDT_Byte,
229 : const_cast<char **>(apszOptions));
230 : }
231 : }
232 :
233 : /* -------------------------------------------------------------------- */
234 : /* Open */
235 : /* -------------------------------------------------------------------- */
236 8 : VSIFSeekL(fp, 0, SEEK_SET);
237 :
238 8 : nLastLineRead = -1;
239 8 : hGifFile = GIFAbstractDataset::myDGifOpen(fp, GIFAbstractDataset::ReadFunc);
240 8 : if (hGifFile == nullptr)
241 : {
242 0 : CPLError(CE_Failure, CPLE_OpenFailed,
243 : "DGifOpen() failed. Perhaps the gif file is corrupt?\n");
244 :
245 0 : return CE_Failure;
246 : }
247 :
248 : /* -------------------------------------------------------------------- */
249 : /* Find the first image record. */
250 : /* -------------------------------------------------------------------- */
251 8 : GifRecordType RecordType = FindFirstImage(hGifFile);
252 8 : if (RecordType != IMAGE_DESC_RECORD_TYPE)
253 : {
254 0 : GIFAbstractDataset::myDGifCloseFile(hGifFile);
255 0 : hGifFile = nullptr;
256 :
257 0 : CPLError(CE_Failure, CPLE_OpenFailed,
258 : "Failed to find image description record in GIF file.");
259 0 : return CE_Failure;
260 : }
261 :
262 8 : if (DGifGetImageDesc(hGifFile) == GIF_ERROR)
263 : {
264 0 : GIFAbstractDataset::myDGifCloseFile(hGifFile);
265 0 : hGifFile = nullptr;
266 :
267 0 : CPLError(CE_Failure, CPLE_OpenFailed,
268 : "Image description reading failed in GIF file.");
269 0 : return CE_Failure;
270 : }
271 :
272 8 : return CE_None;
273 : }
274 :
275 : /************************************************************************/
276 : /* Open() */
277 : /************************************************************************/
278 :
279 6 : GDALDataset *BIGGIFDataset::Open(GDALOpenInfo *poOpenInfo)
280 :
281 : {
282 6 : if (!GIFDriverIdentify(poOpenInfo))
283 0 : return nullptr;
284 :
285 6 : if (poOpenInfo->eAccess == GA_Update)
286 : {
287 0 : CPLError(CE_Failure, CPLE_NotSupported,
288 : "The GIF driver does not support update access to existing"
289 : " files.\n");
290 0 : return nullptr;
291 : }
292 :
293 : /* -------------------------------------------------------------------- */
294 : /* Create a corresponding GDALDataset. */
295 : /* -------------------------------------------------------------------- */
296 6 : BIGGIFDataset *poDS = new BIGGIFDataset();
297 :
298 6 : poDS->fp = poOpenInfo->fpL;
299 6 : poOpenInfo->fpL = nullptr;
300 6 : poDS->eAccess = GA_ReadOnly;
301 6 : if (poDS->ReOpen() == CE_Failure)
302 : {
303 0 : delete poDS;
304 0 : return nullptr;
305 : }
306 :
307 : /* -------------------------------------------------------------------- */
308 : /* Capture some information from the file that is of interest. */
309 : /* -------------------------------------------------------------------- */
310 :
311 6 : poDS->nRasterXSize = poDS->hGifFile->SavedImages[0].ImageDesc.Width;
312 6 : poDS->nRasterYSize = poDS->hGifFile->SavedImages[0].ImageDesc.Height;
313 6 : if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
314 : {
315 0 : delete poDS;
316 0 : return nullptr;
317 : }
318 :
319 6 : if (poDS->hGifFile->SavedImages[0].ImageDesc.ColorMap == nullptr &&
320 6 : poDS->hGifFile->SColorMap == nullptr)
321 : {
322 0 : CPLDebug("GIF", "Skipping image without color table");
323 0 : delete poDS;
324 0 : return nullptr;
325 : }
326 :
327 : /* -------------------------------------------------------------------- */
328 : /* Create band information objects. */
329 : /* -------------------------------------------------------------------- */
330 6 : poDS->SetBand(1,
331 6 : new BIGGifRasterBand(poDS, poDS->hGifFile->SBackGroundColor));
332 :
333 : /* -------------------------------------------------------------------- */
334 : /* Check for georeferencing. */
335 : /* -------------------------------------------------------------------- */
336 6 : poDS->DetectGeoreferencing(poOpenInfo);
337 :
338 : /* -------------------------------------------------------------------- */
339 : /* Initialize any PAM information. */
340 : /* -------------------------------------------------------------------- */
341 6 : poDS->SetDescription(poOpenInfo->pszFilename);
342 6 : poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
343 :
344 : /* -------------------------------------------------------------------- */
345 : /* Support overviews. */
346 : /* -------------------------------------------------------------------- */
347 12 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
348 6 : poOpenInfo->GetSiblingFiles());
349 :
350 6 : return poDS;
351 : }
352 :
353 : /************************************************************************/
354 : /* GDALRegister_BIGGIF() */
355 : /************************************************************************/
356 :
357 19 : void GDALRegister_BIGGIF()
358 :
359 : {
360 19 : if (GDALGetDriverByName(BIGGIF_DRIVER_NAME) != nullptr)
361 0 : return;
362 :
363 19 : GDALDriver *poDriver = new GDALDriver();
364 19 : BIGGIFDriverSetCommonMetadata(poDriver);
365 :
366 19 : poDriver->pfnOpen = BIGGIFDataset::Open;
367 :
368 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
369 : }
|