Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}