OLD | NEW |
| (Empty) |
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 "SkMovie.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 SkGIFMovie : public SkMovie { | |
23 public: | |
24 SkGIFMovie(SkStream* stream); | |
25 virtual ~SkGIFMovie(); | |
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 | |
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 | |
45 SkGIFMovie::SkGIFMovie(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 | |
65 SkGIFMovie::~SkGIFMovie() | |
66 { | |
67 if (fGIF) | |
68 DGifCloseFile(fGIF, nullptr); | |
69 } | |
70 | |
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 | |
85 bool SkGIFMovie::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 | |
101 bool SkGIFMovie::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 | |
120 static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObje
ct* cmap, | |
121 int transparent, int width) | |
122 { | |
123 for (; width > 0; width--, src++, dst++) { | |
124 if (*src != transparent) { | |
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 | |
132 static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src, | |
133 const ColorMapObject* cmap, int transparent, int
copyWidth, | |
134 int copyHeight, const GifImageDesc& imageDesc, in
t 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 | |
149 static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapO
bject* 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 | |
180 static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObje
ct* 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 | |
204 static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, Gif
Word 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 | |
226 static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObjec
t* 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 | |
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 | |
278 static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* tran
s, 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' | |
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 | |
306 static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const Save
dImage* 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 | |
344 bool SkGIFMovie::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) { | |
395 const GifColorType& col = gif->SColorMap->Colors[fGIF->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 | |
435 #include "SkTRegistry.h" | |
436 | |
437 SkMovie* Factory(SkStreamRewindable* stream) { | |
438 char buf[GIF_STAMP_LEN]; | |
439 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { | |
440 if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || | |
441 memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || | |
442 memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { | |
443 // must rewind here, since our construct wants to re-read the data | |
444 stream->rewind(); | |
445 return new SkGIFMovie(stream); | |
446 } | |
447 } | |
448 return nullptr; | |
449 } | |
450 | |
451 static SkTRegistry<SkMovie*(*)(SkStreamRewindable*)> gReg(Factory); | |
OLD | NEW |