SkMovie_gif.cpp revision 75c03a1ae3a4291d1151495c9b2324b0dce9b3a1
1
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkMovie.h"
11#include "SkColor.h"
12#include "SkColorPriv.h"
13#include "SkStream.h"
14#include "SkTemplates.h"
15#include "SkUtils.h"
16
17#include "gif_lib.h"
18
19class SkGIFMovie : public SkMovie {
20public:
21    SkGIFMovie(SkStream* stream);
22    virtual ~SkGIFMovie();
23
24protected:
25    virtual bool onGetInfo(Info*);
26    virtual bool onSetTime(SkMSec);
27    virtual bool onGetBitmap(SkBitmap*);
28
29private:
30    GifFileType* fGIF;
31    int fCurrIndex;
32    int fLastDrawIndex;
33    SkBitmap fBackup;
34};
35
36static int Decode(GifFileType* fileType, GifByteType* out, int size) {
37    SkStream* stream = (SkStream*) fileType->UserData;
38    return (int) stream->read(out, size);
39}
40
41SkGIFMovie::SkGIFMovie(SkStream* stream)
42{
43#if GIFLIB_MAJOR < 5
44    fGIF = DGifOpen( stream, Decode );
45#else
46    fGIF = DGifOpen( stream, Decode, NULL );
47#endif
48    if (NULL == fGIF)
49        return;
50
51    if (DGifSlurp(fGIF) != GIF_OK)
52    {
53        DGifCloseFile(fGIF);
54        fGIF = NULL;
55    }
56    fCurrIndex = -1;
57    fLastDrawIndex = -1;
58}
59
60SkGIFMovie::~SkGIFMovie()
61{
62    if (fGIF)
63        DGifCloseFile(fGIF);
64}
65
66static SkMSec savedimage_duration(const SavedImage* image)
67{
68    for (int j = 0; j < image->ExtensionBlockCount; j++)
69    {
70        if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
71        {
72            int size = image->ExtensionBlocks[j].ByteCount;
73            SkASSERT(size >= 4);
74            const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
75            return ((b[2] << 8) | b[1]) * 10;
76        }
77    }
78    return 0;
79}
80
81bool SkGIFMovie::onGetInfo(Info* info)
82{
83    if (NULL == fGIF)
84        return false;
85
86    SkMSec dur = 0;
87    for (int i = 0; i < fGIF->ImageCount; i++)
88        dur += savedimage_duration(&fGIF->SavedImages[i]);
89
90    info->fDuration = dur;
91    info->fWidth = fGIF->SWidth;
92    info->fHeight = fGIF->SHeight;
93    info->fIsOpaque = false;    // how to compute?
94    return true;
95}
96
97bool SkGIFMovie::onSetTime(SkMSec time)
98{
99    if (NULL == fGIF)
100        return false;
101
102    SkMSec dur = 0;
103    for (int i = 0; i < fGIF->ImageCount; i++)
104    {
105        dur += savedimage_duration(&fGIF->SavedImages[i]);
106        if (dur >= time)
107        {
108            fCurrIndex = i;
109            return fLastDrawIndex != fCurrIndex;
110        }
111    }
112    fCurrIndex = fGIF->ImageCount - 1;
113    return true;
114}
115
116static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap,
117                     int transparent, int width)
118{
119    for (; width > 0; width--, src++, dst++) {
120        if (*src != transparent) {
121            const GifColorType& col = cmap->Colors[*src];
122            *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
123        }
124    }
125}
126
127#if GIFLIB_MAJOR < 5
128static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src,
129                               const ColorMapObject* cmap, int transparent, int copyWidth,
130                               int copyHeight, const GifImageDesc& imageDesc, int rowStep,
131                               int startRow)
132{
133    int row;
134    // every 'rowStep'th row, starting with row 'startRow'
135    for (row = startRow; row < copyHeight; row += rowStep) {
136        uint32_t* dst = bm->getAddr32(imageDesc.Left, imageDesc.Top + row);
137        copyLine(dst, src, cmap, transparent, copyWidth);
138        src += imageDesc.Width;
139    }
140
141    // pad for rest height
142    src += imageDesc.Width * ((imageDesc.Height - row + rowStep - 1) / rowStep);
143}
144
145static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
146                          int transparent)
147{
148    int width = bm->width();
149    int height = bm->height();
150    GifWord copyWidth = frame->ImageDesc.Width;
151    if (frame->ImageDesc.Left + copyWidth > width) {
152        copyWidth = width - frame->ImageDesc.Left;
153    }
154
155    GifWord copyHeight = frame->ImageDesc.Height;
156    if (frame->ImageDesc.Top + copyHeight > height) {
157        copyHeight = height - frame->ImageDesc.Top;
158    }
159
160    // deinterlace
161    const unsigned char* src = (unsigned char*)frame->RasterBits;
162
163    // group 1 - every 8th row, starting with row 0
164    copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 0);
165
166    // group 2 - every 8th row, starting with row 4
167    copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 4);
168
169    // group 3 - every 4th row, starting with row 2
170    copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 4, 2);
171
172    copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 2, 1);
173}
174#endif
175
176static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
177                       int transparent)
178{
179    int width = bm->width();
180    int height = bm->height();
181    const unsigned char* src = (unsigned char*)frame->RasterBits;
182    uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top);
183    GifWord copyWidth = frame->ImageDesc.Width;
184    if (frame->ImageDesc.Left + copyWidth > width) {
185        copyWidth = width - frame->ImageDesc.Left;
186    }
187
188    GifWord copyHeight = frame->ImageDesc.Height;
189    if (frame->ImageDesc.Top + copyHeight > height) {
190        copyHeight = height - frame->ImageDesc.Top;
191    }
192
193    int srcPad, dstPad;
194    dstPad = width - copyWidth;
195    srcPad = frame->ImageDesc.Width - copyWidth;
196    for (; copyHeight > 0; copyHeight--) {
197        copyLine(dst, src, cmap, transparent, copyWidth);
198        src += frame->ImageDesc.Width;
199        dst += width;
200    }
201}
202
203static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height,
204                     uint32_t col)
205{
206    int bmWidth = bm->width();
207    int bmHeight = bm->height();
208    uint32_t* dst = bm->getAddr32(left, top);
209    GifWord copyWidth = width;
210    if (left + copyWidth > bmWidth) {
211        copyWidth = bmWidth - left;
212    }
213
214    GifWord copyHeight = height;
215    if (top + copyHeight > bmHeight) {
216        copyHeight = bmHeight - top;
217    }
218
219    for (; copyHeight > 0; copyHeight--) {
220        sk_memset32(dst, col, copyWidth);
221        dst += bmWidth;
222    }
223}
224
225static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap)
226{
227    int transparent = -1;
228
229    for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
230        ExtensionBlock* eb = frame->ExtensionBlocks + i;
231        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
232            eb->ByteCount == 4) {
233            bool has_transparency = ((eb->Bytes[0] & 1) == 1);
234            if (has_transparency) {
235                transparent = (unsigned char)eb->Bytes[3];
236            }
237        }
238    }
239
240    if (frame->ImageDesc.ColorMap != NULL) {
241        // use local color table
242        cmap = frame->ImageDesc.ColorMap;
243    }
244
245    if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
246        SkDEBUGFAIL("bad colortable setup");
247        return;
248    }
249
250#if GIFLIB_MAJOR < 5
251    // before GIFLIB 5, de-interlacing wasn't done by library at load time
252    if (frame->ImageDesc.Interlace) {
253        blitInterlace(bm, frame, cmap, transparent);
254        return;
255    }
256#endif
257
258    blitNormal(bm, frame, cmap, transparent);
259}
260
261static bool checkIfWillBeCleared(const SavedImage* frame)
262{
263    for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
264        ExtensionBlock* eb = frame->ExtensionBlocks + i;
265        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
266            eb->ByteCount == 4) {
267            // check disposal method
268            int disposal = ((eb->Bytes[0] >> 2) & 7);
269            if (disposal == 2 || disposal == 3) {
270                return true;
271            }
272        }
273    }
274    return false;
275}
276
277static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal)
278{
279    *trans = false;
280    *disposal = 0;
281    for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
282        ExtensionBlock* eb = frame->ExtensionBlocks + i;
283        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
284            eb->ByteCount == 4) {
285            *trans = ((eb->Bytes[0] & 1) == 1);
286            *disposal = ((eb->Bytes[0] >> 2) & 7);
287        }
288    }
289}
290
291// return true if area of 'target' is completely covers area of 'covered'
292static bool checkIfCover(const SavedImage* target, const SavedImage* covered)
293{
294    if (target->ImageDesc.Left <= covered->ImageDesc.Left
295        && covered->ImageDesc.Left + covered->ImageDesc.Width <=
296               target->ImageDesc.Left + target->ImageDesc.Width
297        && target->ImageDesc.Top <= covered->ImageDesc.Top
298        && covered->ImageDesc.Top + covered->ImageDesc.Height <=
299               target->ImageDesc.Top + target->ImageDesc.Height) {
300        return true;
301    }
302    return false;
303}
304
305static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next,
306                                 SkBitmap* backup, SkColor color)
307{
308    // We can skip disposal process if next frame is not transparent
309    // and completely covers current area
310    bool curTrans;
311    int curDisposal;
312    getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal);
313    bool nextTrans;
314    int nextDisposal;
315    getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal);
316    if ((curDisposal == 2 || curDisposal == 3)
317        && (nextTrans || !checkIfCover(next, cur))) {
318        switch (curDisposal) {
319        // restore to background color
320        // -> 'background' means background under this image.
321        case 2:
322            fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top,
323                     cur->ImageDesc.Width, cur->ImageDesc.Height,
324                     color);
325            break;
326
327        // restore to previous
328        case 3:
329            bm->swap(*backup);
330            break;
331        }
332    }
333
334    // Save current image if next frame's disposal method == 3
335    if (nextDisposal == 3) {
336        const uint32_t* src = bm->getAddr32(0, 0);
337        uint32_t* dst = backup->getAddr32(0, 0);
338        int cnt = bm->width() * bm->height();
339        memcpy(dst, src, cnt*sizeof(uint32_t));
340    }
341}
342
343bool SkGIFMovie::onGetBitmap(SkBitmap* bm)
344{
345    const GifFileType* gif = fGIF;
346    if (NULL == gif)
347        return false;
348
349    if (gif->ImageCount < 1) {
350        return false;
351    }
352
353    const int width = gif->SWidth;
354    const int height = gif->SHeight;
355    if (width <= 0 || height <= 0) {
356        return false;
357    }
358
359    // no need to draw
360    if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) {
361        return true;
362    }
363
364    int startIndex = fLastDrawIndex + 1;
365    if (fLastDrawIndex < 0 || !bm->readyToDraw()) {
366        // first time
367
368        startIndex = 0;
369
370        // create bitmap
371        bm->setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
372        if (!bm->allocPixels(NULL)) {
373            return false;
374        }
375        // create bitmap for backup
376        fBackup.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
377        if (!fBackup.allocPixels(NULL)) {
378            return false;
379        }
380    } else if (startIndex > fCurrIndex) {
381        // rewind to 1st frame for repeat
382        startIndex = 0;
383    }
384
385    int lastIndex = fCurrIndex;
386    if (lastIndex < 0) {
387        // first time
388        lastIndex = 0;
389    } else if (lastIndex > fGIF->ImageCount - 1) {
390        // this block must not be reached.
391        lastIndex = fGIF->ImageCount - 1;
392    }
393
394    SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
395    if (gif->SColorMap != NULL) {
396        const GifColorType& col = gif->SColorMap->Colors[fGIF->SBackGroundColor];
397        bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
398    }
399
400    static SkColor paintingColor = SkPackARGB32(0, 0, 0, 0);
401    // draw each frames - not intelligent way
402    for (int i = startIndex; i <= lastIndex; i++) {
403        const SavedImage* cur = &fGIF->SavedImages[i];
404        if (i == 0) {
405            bool trans;
406            int disposal;
407            getTransparencyAndDisposalMethod(cur, &trans, &disposal);
408            if (!trans && gif->SColorMap != NULL) {
409                paintingColor = bgColor;
410            } else {
411                paintingColor = SkColorSetARGB(0, 0, 0, 0);
412            }
413
414            bm->eraseColor(paintingColor);
415            fBackup.eraseColor(paintingColor);
416        } else {
417            // Dispose previous frame before move to next frame.
418            const SavedImage* prev = &fGIF->SavedImages[i-1];
419            disposeFrameIfNeeded(bm, prev, cur, &fBackup, paintingColor);
420        }
421
422        // Draw frame
423        // We can skip this process if this index is not last and disposal
424        // method == 2 or method == 3
425        if (i == lastIndex || !checkIfWillBeCleared(cur)) {
426            drawFrame(bm, cur, gif->SColorMap);
427        }
428    }
429
430    // save index
431    fLastDrawIndex = lastIndex;
432    return true;
433}
434
435///////////////////////////////////////////////////////////////////////////////
436
437#include "SkTRegistry.h"
438
439SkMovie* Factory(SkStream* stream) {
440    char buf[GIF_STAMP_LEN];
441    if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
442        if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
443                memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
444                memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
445            // must rewind here, since our construct wants to re-read the data
446            stream->rewind();
447            return SkNEW_ARGS(SkGIFMovie, (stream));
448        }
449    }
450    return NULL;
451}
452
453static SkTRegistry<SkMovie*, SkStream*> gReg(Factory);
454