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