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