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