/src/gdal/frmts/gif/biggifdataset.cpp
Line | Count | Source |
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 | | BIGGifRasterBand::BIGGifRasterBand(BIGGIFDataset *poDSIn, int nBackground) |
71 | 287 | : GIFAbstractRasterBand(poDSIn, 1, poDSIn->hGifFile->SavedImages, |
72 | 287 | nBackground, TRUE) |
73 | | |
74 | 287 | { |
75 | 287 | } |
76 | | |
77 | | /************************************************************************/ |
78 | | /* IReadBlock() */ |
79 | | /************************************************************************/ |
80 | | |
81 | | CPLErr BIGGifRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, |
82 | | void *pImage) |
83 | 12.6k | { |
84 | 12.6k | BIGGIFDataset *poGDS = cpl::down_cast<BIGGIFDataset *>(poDS); |
85 | | |
86 | 12.6k | CPLAssert(nBlockXOff == 0); |
87 | | |
88 | 12.6k | if (panInterlaceMap != nullptr) |
89 | 11.7k | nBlockYOff = panInterlaceMap[nBlockYOff]; |
90 | | |
91 | | /* -------------------------------------------------------------------- */ |
92 | | /* Do we already have this line in the work dataset? */ |
93 | | /* -------------------------------------------------------------------- */ |
94 | 12.6k | if (poGDS->poWorkDS != nullptr && nBlockYOff <= poGDS->nLastLineRead) |
95 | 4.99k | { |
96 | 4.99k | return poGDS->poWorkDS->RasterIO(GF_Read, 0, nBlockYOff, nBlockXSize, 1, |
97 | 4.99k | pImage, nBlockXSize, 1, GDT_UInt8, 1, |
98 | 4.99k | nullptr, 0, 0, 0, nullptr); |
99 | 4.99k | } |
100 | | |
101 | | /* -------------------------------------------------------------------- */ |
102 | | /* Do we need to restart from the start of the image? */ |
103 | | /* -------------------------------------------------------------------- */ |
104 | 7.64k | if (nBlockYOff <= poGDS->nLastLineRead) |
105 | 119 | { |
106 | 119 | if (poGDS->ReOpen() == CE_Failure) |
107 | 0 | return CE_Failure; |
108 | 119 | } |
109 | | |
110 | | /* -------------------------------------------------------------------- */ |
111 | | /* Read till we get our target line. */ |
112 | | /* -------------------------------------------------------------------- */ |
113 | 7.64k | CPLErr eErr = CE_None; |
114 | 45.6k | while (poGDS->nLastLineRead < nBlockYOff && eErr == CE_None) |
115 | 39.7k | { |
116 | 39.7k | if (DGifGetLine(poGDS->hGifFile, (GifPixelType *)pImage, nBlockXSize) == |
117 | 39.7k | GIF_ERROR) |
118 | 1.74k | { |
119 | 1.74k | CPLError(CE_Failure, CPLE_AppDefined, |
120 | 1.74k | "Failure decoding scanline of GIF file."); |
121 | 1.74k | return CE_Failure; |
122 | 1.74k | } |
123 | | |
124 | 38.0k | poGDS->nLastLineRead++; |
125 | | |
126 | 38.0k | if (poGDS->poWorkDS != nullptr) |
127 | 20.7k | { |
128 | 20.7k | eErr = poGDS->poWorkDS->RasterIO( |
129 | 20.7k | GF_Write, 0, poGDS->nLastLineRead, nBlockXSize, 1, pImage, |
130 | 20.7k | nBlockXSize, 1, GDT_UInt8, 1, nullptr, 0, 0, 0, nullptr); |
131 | 20.7k | } |
132 | 38.0k | } |
133 | | |
134 | 5.90k | return eErr; |
135 | 7.64k | } |
136 | | |
137 | | /************************************************************************/ |
138 | | /* ==================================================================== */ |
139 | | /* BIGGIFDataset */ |
140 | | /* ==================================================================== */ |
141 | | /************************************************************************/ |
142 | | |
143 | | /************************************************************************/ |
144 | | /* BIGGIFDataset() */ |
145 | | /************************************************************************/ |
146 | | |
147 | 458 | BIGGIFDataset::BIGGIFDataset() : nLastLineRead(-1), poWorkDS(nullptr) |
148 | 458 | { |
149 | 458 | } |
150 | | |
151 | | /************************************************************************/ |
152 | | /* ~BIGGIFDataset() */ |
153 | | /************************************************************************/ |
154 | | |
155 | | BIGGIFDataset::~BIGGIFDataset() |
156 | | |
157 | 458 | { |
158 | 458 | BIGGIFDataset::FlushCache(true); |
159 | | |
160 | 458 | BIGGIFDataset::CloseDependentDatasets(); |
161 | 458 | } |
162 | | |
163 | | /************************************************************************/ |
164 | | /* CloseDependentDatasets() */ |
165 | | /************************************************************************/ |
166 | | |
167 | | int BIGGIFDataset::CloseDependentDatasets() |
168 | 458 | { |
169 | 458 | int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets(); |
170 | | |
171 | 458 | if (poWorkDS != nullptr) |
172 | 119 | { |
173 | 119 | bHasDroppedRef = TRUE; |
174 | | |
175 | 119 | CPLString osTempFilename = poWorkDS->GetDescription(); |
176 | 119 | GDALDriver *poDrv = poWorkDS->GetDriver(); |
177 | | |
178 | 119 | GDALClose((GDALDatasetH)poWorkDS); |
179 | 119 | poWorkDS = nullptr; |
180 | | |
181 | 119 | if (poDrv != nullptr) |
182 | 119 | poDrv->Delete(osTempFilename); |
183 | | |
184 | 119 | poWorkDS = nullptr; |
185 | 119 | } |
186 | | |
187 | 458 | return bHasDroppedRef; |
188 | 458 | } |
189 | | |
190 | | /************************************************************************/ |
191 | | /* ReOpen() */ |
192 | | /* */ |
193 | | /* (Re)Open the gif file and process past the first image */ |
194 | | /* descriptor. */ |
195 | | /************************************************************************/ |
196 | | |
197 | | CPLErr BIGGIFDataset::ReOpen() |
198 | | |
199 | 577 | { |
200 | | /* -------------------------------------------------------------------- */ |
201 | | /* If the file is already open, close it so we can restart. */ |
202 | | /* -------------------------------------------------------------------- */ |
203 | 577 | if (hGifFile != nullptr) |
204 | 119 | 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 | 577 | if (hGifFile != nullptr) |
214 | 119 | { |
215 | 119 | GDALDriver *poGTiffDriver = (GDALDriver *)GDALGetDriverByName("GTiff"); |
216 | | |
217 | 119 | if (poGTiffDriver != nullptr) |
218 | 119 | { |
219 | | /* Create as a sparse file to avoid filling up the whole file */ |
220 | | /* while closing and then destroying this temporary dataset */ |
221 | 119 | const char *apszOptions[] = {"COMPRESS=LZW", "SPARSE_OK=YES", |
222 | 119 | nullptr}; |
223 | 119 | CPLString osTempFilename = CPLGenerateTempFilenameSafe("biggif"); |
224 | | |
225 | 119 | osTempFilename += ".tif"; |
226 | | |
227 | 119 | poWorkDS = poGTiffDriver->Create(osTempFilename, nRasterXSize, |
228 | 119 | nRasterYSize, 1, GDT_UInt8, |
229 | 119 | const_cast<char **>(apszOptions)); |
230 | 119 | } |
231 | 119 | } |
232 | | |
233 | | /* -------------------------------------------------------------------- */ |
234 | | /* Open */ |
235 | | /* -------------------------------------------------------------------- */ |
236 | 577 | VSIFSeekL(fp, 0, SEEK_SET); |
237 | | |
238 | 577 | nLastLineRead = -1; |
239 | 577 | hGifFile = GIFAbstractDataset::myDGifOpen(fp, GIFAbstractDataset::ReadFunc); |
240 | 577 | if (hGifFile == nullptr) |
241 | 2 | { |
242 | 2 | CPLError(CE_Failure, CPLE_OpenFailed, |
243 | 2 | "DGifOpen() failed. Perhaps the gif file is corrupt?"); |
244 | | |
245 | 2 | return CE_Failure; |
246 | 2 | } |
247 | | |
248 | | /* -------------------------------------------------------------------- */ |
249 | | /* Find the first image record. */ |
250 | | /* -------------------------------------------------------------------- */ |
251 | 575 | GifRecordType RecordType = FindFirstImage(hGifFile); |
252 | 575 | if (RecordType != IMAGE_DESC_RECORD_TYPE) |
253 | 123 | { |
254 | 123 | GIFAbstractDataset::myDGifCloseFile(hGifFile); |
255 | 123 | hGifFile = nullptr; |
256 | | |
257 | 123 | CPLError(CE_Failure, CPLE_OpenFailed, |
258 | 123 | "Failed to find image description record in GIF file."); |
259 | 123 | return CE_Failure; |
260 | 123 | } |
261 | | |
262 | 452 | if (DGifGetImageDesc(hGifFile) == GIF_ERROR) |
263 | 32 | { |
264 | 32 | GIFAbstractDataset::myDGifCloseFile(hGifFile); |
265 | 32 | hGifFile = nullptr; |
266 | | |
267 | 32 | CPLError(CE_Failure, CPLE_OpenFailed, |
268 | 32 | "Image description reading failed in GIF file."); |
269 | 32 | return CE_Failure; |
270 | 32 | } |
271 | | |
272 | 420 | return CE_None; |
273 | 452 | } |
274 | | |
275 | | /************************************************************************/ |
276 | | /* Open() */ |
277 | | /************************************************************************/ |
278 | | |
279 | | GDALDataset *BIGGIFDataset::Open(GDALOpenInfo *poOpenInfo) |
280 | | |
281 | 458 | { |
282 | 458 | if (!GIFDriverIdentify(poOpenInfo)) |
283 | 0 | return nullptr; |
284 | | |
285 | 458 | if (poOpenInfo->eAccess == GA_Update) |
286 | 0 | { |
287 | 0 | ReportUpdateNotSupportedByDriver("GIF"); |
288 | 0 | return nullptr; |
289 | 0 | } |
290 | | |
291 | | /* -------------------------------------------------------------------- */ |
292 | | /* Create a corresponding GDALDataset. */ |
293 | | /* -------------------------------------------------------------------- */ |
294 | 458 | BIGGIFDataset *poDS = new BIGGIFDataset(); |
295 | | |
296 | 458 | poDS->fp = poOpenInfo->fpL; |
297 | 458 | poOpenInfo->fpL = nullptr; |
298 | 458 | poDS->eAccess = GA_ReadOnly; |
299 | 458 | if (poDS->ReOpen() == CE_Failure) |
300 | 157 | { |
301 | 157 | delete poDS; |
302 | 157 | return nullptr; |
303 | 157 | } |
304 | | |
305 | | /* -------------------------------------------------------------------- */ |
306 | | /* Capture some information from the file that is of interest. */ |
307 | | /* -------------------------------------------------------------------- */ |
308 | | |
309 | 301 | poDS->nRasterXSize = poDS->hGifFile->SavedImages[0].ImageDesc.Width; |
310 | 301 | poDS->nRasterYSize = poDS->hGifFile->SavedImages[0].ImageDesc.Height; |
311 | 301 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize)) |
312 | 7 | { |
313 | 7 | delete poDS; |
314 | 7 | return nullptr; |
315 | 7 | } |
316 | | |
317 | 294 | if (poDS->hGifFile->SavedImages[0].ImageDesc.ColorMap == nullptr && |
318 | 49 | poDS->hGifFile->SColorMap == nullptr) |
319 | 7 | { |
320 | 7 | CPLDebug("GIF", "Skipping image without color table"); |
321 | 7 | delete poDS; |
322 | 7 | return nullptr; |
323 | 7 | } |
324 | | |
325 | | /* -------------------------------------------------------------------- */ |
326 | | /* Create band information objects. */ |
327 | | /* -------------------------------------------------------------------- */ |
328 | 287 | poDS->SetBand(1, |
329 | 287 | new BIGGifRasterBand(poDS, poDS->hGifFile->SBackGroundColor)); |
330 | | |
331 | | /* -------------------------------------------------------------------- */ |
332 | | /* Check for georeferencing. */ |
333 | | /* -------------------------------------------------------------------- */ |
334 | 287 | poDS->DetectGeoreferencing(poOpenInfo); |
335 | | |
336 | | /* -------------------------------------------------------------------- */ |
337 | | /* Initialize any PAM information. */ |
338 | | /* -------------------------------------------------------------------- */ |
339 | 287 | poDS->SetDescription(poOpenInfo->pszFilename); |
340 | 287 | poDS->TryLoadXML(poOpenInfo->GetSiblingFiles()); |
341 | | |
342 | | /* -------------------------------------------------------------------- */ |
343 | | /* Support overviews. */ |
344 | | /* -------------------------------------------------------------------- */ |
345 | 287 | poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename, |
346 | 287 | poOpenInfo->GetSiblingFiles()); |
347 | | |
348 | 287 | return poDS; |
349 | 294 | } |
350 | | |
351 | | /************************************************************************/ |
352 | | /* GDALRegister_BIGGIF() */ |
353 | | /************************************************************************/ |
354 | | |
355 | | void GDALRegister_BIGGIF() |
356 | | |
357 | 22 | { |
358 | 22 | if (GDALGetDriverByName(BIGGIF_DRIVER_NAME) != nullptr) |
359 | 0 | return; |
360 | | |
361 | 22 | GDALDriver *poDriver = new GDALDriver(); |
362 | 22 | BIGGIFDriverSetCommonMetadata(poDriver); |
363 | | |
364 | 22 | poDriver->pfnOpen = BIGGIFDataset::Open; |
365 | | |
366 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
367 | 22 | } |