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 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