• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
22 class GIFMovie : public Movie {
23 public:
24     explicit GIFMovie(SkStream* stream);
25     virtual ~GIFMovie();
26 
27 protected:
28     virtual bool onGetInfo(Info*);
29     virtual bool onSetTime(SkMSec);
30     virtual bool onGetBitmap(SkBitmap*);
31 
32 private:
33     GifFileType* fGIF;
34     int fCurrIndex;
35     int fLastDrawIndex;
36     SkBitmap fBackup;
37     SkColor fPaintingColor;
38 };
39 
Decode(GifFileType * fileType,GifByteType * out,int size)40 static int Decode(GifFileType* fileType, GifByteType* out, int size) {
41     SkStream* stream = (SkStream*) fileType->UserData;
42     return (int) stream->read(out, size);
43 }
44 
GIFMovie(SkStream * stream)45 GIFMovie::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 
~GIFMovie()65 GIFMovie::~GIFMovie()
66 {
67     if (fGIF)
68         DGifCloseFile(fGIF, nullptr);
69 }
70 
savedimage_duration(const SavedImage * image)71 static 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 
onGetInfo(Info * info)85 bool 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 
onSetTime(SkMSec time)101 bool 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 
copyLine(uint32_t * dst,const unsigned char * src,const ColorMapObject * cmap,int transparent,int width)120 static 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
copyInterlaceGroup(SkBitmap * bm,const unsigned char * & src,const ColorMapObject * cmap,int transparent,int copyWidth,int copyHeight,const GifImageDesc & imageDesc,int rowStep,int startRow)132 static 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 
blitInterlace(SkBitmap * bm,const SavedImage * frame,const ColorMapObject * cmap,int transparent)149 static 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 
blitNormal(SkBitmap * bm,const SavedImage * frame,const ColorMapObject * cmap,int transparent)180 static 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 
fillRect(SkBitmap * bm,GifWord left,GifWord top,GifWord width,GifWord height,uint32_t col)204 static 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 
drawFrame(SkBitmap * bm,const SavedImage * frame,const ColorMapObject * cmap)226 static 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 
checkIfWillBeCleared(const SavedImage * frame)262 static 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 
getTransparencyAndDisposalMethod(const SavedImage * frame,bool * trans,int * disposal)278 static 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'
checkIfCover(const SavedImage * target,const SavedImage * covered)293 static 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 
disposeFrameIfNeeded(SkBitmap * bm,const SavedImage * cur,const SavedImage * next,SkBitmap * backup,SkColor color)306 static 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 
onGetBitmap(SkBitmap * bm)344 bool 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 && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
395         const GifColorType& col = gif->SColorMap->Colors[gif->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 
DecodeStream(SkStreamRewindable * stream)435 Movie* 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