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