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 19class SkGIFMovie : public SkMovie { 20public: 21 SkGIFMovie(SkStream* stream); 22 virtual ~SkGIFMovie(); 23 24protected: 25 virtual bool onGetInfo(Info*); 26 virtual bool onSetTime(SkMSec); 27 virtual bool onGetBitmap(SkBitmap*); 28 29private: 30 GifFileType* fGIF; 31 int fCurrIndex; 32 int fLastDrawIndex; 33 SkBitmap fBackup; 34}; 35 36static int Decode(GifFileType* fileType, GifByteType* out, int size) { 37 SkStream* stream = (SkStream*) fileType->UserData; 38 return (int) stream->read(out, size); 39} 40 41SkGIFMovie::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 60SkGIFMovie::~SkGIFMovie() 61{ 62 if (fGIF) 63 DGifCloseFile(fGIF); 64} 65 66static 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 80bool 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 96bool 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 115static 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 127static 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 144static 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 175static 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 199static 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 221static 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 257static 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 273static 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' 288static 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 301static 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 339bool 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 433SkMovie* 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 447static SkTRegistry<SkMovie*(*)(SkStreamRewindable*)> gReg(Factory); 448