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