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