1/* libs/graphics/images/SkImageDecoder_libgif.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include "SkImageDecoder.h"
19#include "SkColor.h"
20#include "SkColorPriv.h"
21#include "SkStream.h"
22#include "SkTemplates.h"
23#include "SkPackBits.h"
24
25#include "gif_lib.h"
26
27class SkGIFImageDecoder : public SkImageDecoder {
28public:
29    virtual Format getFormat() const {
30        return kGIF_Format;
31    }
32
33protected:
34    virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
35};
36
37static const uint8_t gStartingIterlaceYValue[] = {
38    0, 4, 2, 1
39};
40static const uint8_t gDeltaIterlaceYValue[] = {
41    8, 8, 4, 2
42};
43
44/*  Implement the GIF interlace algorithm in an iterator.
45    1) grab every 8th line beginning at 0
46    2) grab every 8th line beginning at 4
47    3) grab every 4th line beginning at 2
48    4) grab every 2nd line beginning at 1
49*/
50class GifInterlaceIter {
51public:
52    GifInterlaceIter(int height) : fHeight(height) {
53        fStartYPtr = gStartingIterlaceYValue;
54        fDeltaYPtr = gDeltaIterlaceYValue;
55
56        fCurrY = *fStartYPtr++;
57        fDeltaY = *fDeltaYPtr++;
58    }
59
60    int currY() const {
61        SkASSERT(fStartYPtr);
62        SkASSERT(fDeltaYPtr);
63        return fCurrY;
64    }
65
66    void next() {
67        SkASSERT(fStartYPtr);
68        SkASSERT(fDeltaYPtr);
69
70        int y = fCurrY + fDeltaY;
71        // We went from an if statement to a while loop so that we iterate
72        // through fStartYPtr until a valid row is found. This is so that images
73        // that are smaller than 5x5 will not trash memory.
74        while (y >= fHeight) {
75            if (gStartingIterlaceYValue +
76                    SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
77                // we done
78                SkDEBUGCODE(fStartYPtr = NULL;)
79                SkDEBUGCODE(fDeltaYPtr = NULL;)
80                y = 0;
81            } else {
82                y = *fStartYPtr++;
83                fDeltaY = *fDeltaYPtr++;
84            }
85        }
86        fCurrY = y;
87    }
88
89private:
90    const int fHeight;
91    int fCurrY;
92    int fDeltaY;
93    const uint8_t* fStartYPtr;
94    const uint8_t* fDeltaYPtr;
95};
96
97///////////////////////////////////////////////////////////////////////////////
98
99//#define GIF_STAMP       "GIF"    /* First chars in file - GIF stamp. */
100//#define GIF_STAMP_LEN   (sizeof(GIF_STAMP) - 1)
101
102static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
103                              int size) {
104    SkStream* stream = (SkStream*) fileType->UserData;
105    return (int) stream->read(out, size);
106}
107
108void CheckFreeExtension(SavedImage* Image) {
109    if (Image->ExtensionBlocks) {
110        FreeExtension(Image);
111    }
112}
113
114// return NULL on failure
115static const ColorMapObject* find_colormap(const GifFileType* gif) {
116    const ColorMapObject* cmap = gif->Image.ColorMap;
117    if (NULL == cmap) {
118        cmap = gif->SColorMap;
119    }
120    // some sanity checks
121    if (cmap && ((unsigned)cmap->ColorCount > 256 ||
122                 cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
123        cmap = NULL;
124    }
125    return cmap;
126}
127
128// return -1 if not found (i.e. we're completely opaque)
129static int find_transpIndex(const SavedImage& image, int colorCount) {
130    int transpIndex = -1;
131    for (int i = 0; i < image.ExtensionBlockCount; ++i) {
132        const ExtensionBlock* eb = image.ExtensionBlocks + i;
133        if (eb->Function == 0xF9 && eb->ByteCount == 4) {
134            if (eb->Bytes[0] & 1) {
135                transpIndex = (unsigned char)eb->Bytes[3];
136                // check for valid transpIndex
137                if (transpIndex >= colorCount) {
138                    transpIndex = -1;
139                }
140                break;
141            }
142        }
143    }
144    return transpIndex;
145}
146
147static bool error_return(GifFileType* gif, const SkBitmap& bm,
148                         const char msg[]) {
149#if 0
150    SkDebugf("libgif error <%s> bitmap [%d %d] pixels %p colortable %p\n",
151             msg, bm.width(), bm.height(), bm.getPixels(), bm.getColorTable());
152#endif
153    return false;
154}
155
156bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
157    GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
158    if (NULL == gif) {
159        return error_return(gif, *bm, "DGifOpen");
160    }
161
162    SkAutoTCallIProc<GifFileType, DGifCloseFile> acp(gif);
163
164    SavedImage temp_save;
165    temp_save.ExtensionBlocks=NULL;
166    temp_save.ExtensionBlockCount=0;
167    SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
168
169    int width, height;
170    GifRecordType recType;
171    GifByteType *extData;
172    int transpIndex = -1;   // -1 means we don't have it (yet)
173
174    do {
175        if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
176            return error_return(gif, *bm, "DGifGetRecordType");
177        }
178
179        switch (recType) {
180        case IMAGE_DESC_RECORD_TYPE: {
181            if (DGifGetImageDesc(gif) == GIF_ERROR) {
182                return error_return(gif, *bm, "IMAGE_DESC_RECORD_TYPE");
183            }
184
185            if (gif->ImageCount < 1) {    // sanity check
186                return error_return(gif, *bm, "ImageCount < 1");
187            }
188
189            width = gif->SWidth;
190            height = gif->SHeight;
191            if (width <= 0 || height <= 0 ||
192                !this->chooseFromOneChoice(SkBitmap::kIndex8_Config,
193                                           width, height)) {
194                return error_return(gif, *bm, "chooseFromOneChoice");
195            }
196
197            bm->setConfig(SkBitmap::kIndex8_Config, width, height);
198            if (SkImageDecoder::kDecodeBounds_Mode == mode)
199                return true;
200
201            SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
202            const GifImageDesc& desc = image->ImageDesc;
203
204            // check for valid descriptor
205            if (   (desc.Top | desc.Left) < 0 ||
206                    desc.Left + desc.Width > width ||
207                    desc.Top + desc.Height > height) {
208                return error_return(gif, *bm, "TopLeft");
209            }
210
211            // now we decode the colortable
212            int colorCount = 0;
213            {
214                const ColorMapObject* cmap = find_colormap(gif);
215                if (NULL == cmap) {
216                    return error_return(gif, *bm, "null cmap");
217                }
218
219                colorCount = cmap->ColorCount;
220                SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount));
221                SkPMColor* colorPtr = ctable->lockColors();
222                for (int index = 0; index < colorCount; index++)
223                    colorPtr[index] = SkPackARGB32(0xFF,
224                                                   cmap->Colors[index].Red,
225                                                   cmap->Colors[index].Green,
226                                                   cmap->Colors[index].Blue);
227
228                transpIndex = find_transpIndex(temp_save, colorCount);
229                if (transpIndex < 0)
230                    ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
231                else
232                    colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor
233                ctable->unlockColors(true);
234
235                SkAutoUnref aurts(ctable);
236                if (!this->allocPixelRef(bm, ctable)) {
237                    return error_return(gif, *bm, "allocPixelRef");
238                }
239            }
240
241            SkAutoLockPixels alp(*bm);
242
243            // time to decode the scanlines
244            //
245            uint8_t*  scanline = bm->getAddr8(0, 0);
246            const int rowBytes = bm->rowBytes();
247            const int innerWidth = desc.Width;
248            const int innerHeight = desc.Height;
249
250            // abort if either inner dimension is <= 0
251            if (innerWidth <= 0 || innerHeight <= 0) {
252                return error_return(gif, *bm, "non-pos inner width/height");
253            }
254
255            // are we only a subset of the total bounds?
256            if ((desc.Top | desc.Left) > 0 ||
257                 innerWidth < width || innerHeight < height)
258            {
259                int fill;
260                if (transpIndex >= 0) {
261                    fill = transpIndex;
262                } else {
263                    fill = gif->SBackGroundColor;
264                }
265                // check for valid fill index/color
266                if (static_cast<unsigned>(fill) >=
267                        static_cast<unsigned>(colorCount)) {
268                    fill = 0;
269                }
270                memset(scanline, fill, bm->getSize());
271                // bump our starting address
272                scanline += desc.Top * rowBytes + desc.Left;
273            }
274
275            // now decode each scanline
276            if (gif->Image.Interlace)
277            {
278                GifInterlaceIter iter(innerHeight);
279                for (int y = 0; y < innerHeight; y++)
280                {
281                    uint8_t* row = scanline + iter.currY() * rowBytes;
282                    if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) {
283                        return error_return(gif, *bm, "interlace DGifGetLine");
284                    }
285                    iter.next();
286                }
287            }
288            else
289            {
290                // easy, non-interlace case
291                for (int y = 0; y < innerHeight; y++) {
292                    if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
293                        return error_return(gif, *bm, "DGifGetLine");
294                    }
295                    scanline += rowBytes;
296                }
297            }
298            goto DONE;
299            } break;
300
301        case EXTENSION_RECORD_TYPE:
302            if (DGifGetExtension(gif, &temp_save.Function,
303                                 &extData) == GIF_ERROR) {
304                return error_return(gif, *bm, "DGifGetExtension");
305            }
306
307            while (extData != NULL) {
308                /* Create an extension block with our data */
309                if (AddExtensionBlock(&temp_save, extData[0],
310                                      &extData[1]) == GIF_ERROR) {
311                    return error_return(gif, *bm, "AddExtensionBlock");
312                }
313                if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
314                    return error_return(gif, *bm, "DGifGetExtensionNext");
315                }
316                temp_save.Function = 0;
317            }
318            break;
319
320        case TERMINATE_RECORD_TYPE:
321            break;
322
323        default:	/* Should be trapped by DGifGetRecordType */
324            break;
325        }
326    } while (recType != TERMINATE_RECORD_TYPE);
327
328DONE:
329    return true;
330}
331
332///////////////////////////////////////////////////////////////////////////////
333
334#include "SkTRegistry.h"
335
336static SkImageDecoder* Factory(SkStream* stream) {
337    char buf[GIF_STAMP_LEN];
338    if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
339        if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
340                memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
341                memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
342            return SkNEW(SkGIFImageDecoder);
343        }
344    }
345    return NULL;
346}
347
348static SkTRegistry<SkImageDecoder*, SkStream*> gReg(Factory);
349