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 int size = image->ExtensionBlocks[j].ByteCount; 73 SkASSERT(size >= 4); 74 const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes; 75 return ((b[2] << 8) | b[1]) * 10; 76 } 77 } 78 return 0; 79 } 80 81 bool SkGIFMovie::onGetInfo(Info* info) 82 { 83 if (NULL == fGIF) 84 return false; 85 86 SkMSec dur = 0; 87 for (int i = 0; i < fGIF->ImageCount; i++) 88 dur += savedimage_duration(&fGIF->SavedImages[i]); 89 90 info->fDuration = dur; 91 info->fWidth = fGIF->SWidth; 92 info->fHeight = fGIF->SHeight; 93 info->fIsOpaque = false; // how to compute? 94 return true; 95 } 96 97 bool SkGIFMovie::onSetTime(SkMSec time) 98 { 99 if (NULL == fGIF) 100 return false; 101 102 SkMSec dur = 0; 103 for (int i = 0; i < fGIF->ImageCount; i++) 104 { 105 dur += savedimage_duration(&fGIF->SavedImages[i]); 106 if (dur >= time) 107 { 108 fCurrIndex = i; 109 return fLastDrawIndex != fCurrIndex; 110 } 111 } 112 fCurrIndex = fGIF->ImageCount - 1; 113 return true; 114 } 115 116 static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap, 117 int transparent, int width) 118 { 119 for (; width > 0; width--, src++, dst++) { 120 if (*src != transparent) { 121 const GifColorType& col = cmap->Colors[*src]; 122 *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue); 123 } 124 } 125 } 126 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 174 static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap, 175 int transparent) 176 { 177 int width = bm->width(); 178 int height = bm->height(); 179 const unsigned char* src = (unsigned char*)frame->RasterBits; 180 uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top); 181 GifWord copyWidth = frame->ImageDesc.Width; 182 if (frame->ImageDesc.Left + copyWidth > width) { 183 copyWidth = width - frame->ImageDesc.Left; 184 } 185 186 GifWord copyHeight = frame->ImageDesc.Height; 187 if (frame->ImageDesc.Top + copyHeight > height) { 188 copyHeight = height - frame->ImageDesc.Top; 189 } 190 191 int srcPad, dstPad; 192 dstPad = width - copyWidth; 193 srcPad = frame->ImageDesc.Width - copyWidth; 194 for (; copyHeight > 0; copyHeight--) { 195 copyLine(dst, src, cmap, transparent, copyWidth); 196 src += frame->ImageDesc.Width; 197 dst += width; 198 } 199 } 200 201 static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height, 202 uint32_t col) 203 { 204 int bmWidth = bm->width(); 205 int bmHeight = bm->height(); 206 uint32_t* dst = bm->getAddr32(left, top); 207 GifWord copyWidth = width; 208 if (left + copyWidth > bmWidth) { 209 copyWidth = bmWidth - left; 210 } 211 212 GifWord copyHeight = height; 213 if (top + copyHeight > bmHeight) { 214 copyHeight = bmHeight - top; 215 } 216 217 for (; copyHeight > 0; copyHeight--) { 218 sk_memset32(dst, col, copyWidth); 219 dst += bmWidth; 220 } 221 } 222 223 static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap) 224 { 225 int transparent = -1; 226 227 for (int i = 0; i < frame->ExtensionBlockCount; ++i) { 228 ExtensionBlock* eb = frame->ExtensionBlocks + i; 229 if (eb->Function == GRAPHICS_EXT_FUNC_CODE && 230 eb->ByteCount == 4) { 231 bool has_transparency = ((eb->Bytes[0] & 1) == 1); 232 if (has_transparency) { 233 transparent = (unsigned char)eb->Bytes[3]; 234 } 235 } 236 } 237 238 if (frame->ImageDesc.ColorMap != NULL) { 239 // use local color table 240 cmap = frame->ImageDesc.ColorMap; 241 } 242 243 if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { 244 SkDEBUGFAIL("bad colortable setup"); 245 return; 246 } 247 248 if (frame->ImageDesc.Interlace) { 249 blitInterlace(bm, frame, cmap, transparent); 250 } else { 251 blitNormal(bm, frame, cmap, transparent); 252 } 253 } 254 255 static bool checkIfWillBeCleared(const SavedImage* frame) 256 { 257 for (int i = 0; i < frame->ExtensionBlockCount; ++i) { 258 ExtensionBlock* eb = frame->ExtensionBlocks + i; 259 if (eb->Function == GRAPHICS_EXT_FUNC_CODE && 260 eb->ByteCount == 4) { 261 // check disposal method 262 int disposal = ((eb->Bytes[0] >> 2) & 7); 263 if (disposal == 2 || disposal == 3) { 264 return true; 265 } 266 } 267 } 268 return false; 269 } 270 271 static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal) 272 { 273 *trans = false; 274 *disposal = 0; 275 for (int i = 0; i < frame->ExtensionBlockCount; ++i) { 276 ExtensionBlock* eb = frame->ExtensionBlocks + i; 277 if (eb->Function == GRAPHICS_EXT_FUNC_CODE && 278 eb->ByteCount == 4) { 279 *trans = ((eb->Bytes[0] & 1) == 1); 280 *disposal = ((eb->Bytes[0] >> 2) & 7); 281 } 282 } 283 } 284 285 // return true if area of 'target' is completely covers area of 'covered' 286 static bool checkIfCover(const SavedImage* target, const SavedImage* covered) 287 { 288 if (target->ImageDesc.Left <= covered->ImageDesc.Left 289 && covered->ImageDesc.Left + covered->ImageDesc.Width <= 290 target->ImageDesc.Left + target->ImageDesc.Width 291 && target->ImageDesc.Top <= covered->ImageDesc.Top 292 && covered->ImageDesc.Top + covered->ImageDesc.Height <= 293 target->ImageDesc.Top + target->ImageDesc.Height) { 294 return true; 295 } 296 return false; 297 } 298 299 static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next, 300 SkBitmap* backup, SkColor color) 301 { 302 // We can skip disposal process if next frame is not transparent 303 // and completely covers current area 304 bool curTrans; 305 int curDisposal; 306 getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal); 307 bool nextTrans; 308 int nextDisposal; 309 getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal); 310 if ((curDisposal == 2 || curDisposal == 3) 311 && (nextTrans || !checkIfCover(next, cur))) { 312 switch (curDisposal) { 313 // restore to background color 314 // -> 'background' means background under this image. 315 case 2: 316 fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top, 317 cur->ImageDesc.Width, cur->ImageDesc.Height, 318 color); 319 break; 320 321 // restore to previous 322 case 3: 323 bm->swap(*backup); 324 break; 325 } 326 } 327 328 // Save current image if next frame's disposal method == 3 329 if (nextDisposal == 3) { 330 const uint32_t* src = bm->getAddr32(0, 0); 331 uint32_t* dst = backup->getAddr32(0, 0); 332 int cnt = bm->width() * bm->height(); 333 memcpy(dst, src, cnt*sizeof(uint32_t)); 334 } 335 } 336 337 bool SkGIFMovie::onGetBitmap(SkBitmap* bm) 338 { 339 const GifFileType* gif = fGIF; 340 if (NULL == gif) 341 return false; 342 343 if (gif->ImageCount < 1) { 344 return false; 345 } 346 347 const int width = gif->SWidth; 348 const int height = gif->SHeight; 349 if (width <= 0 || height <= 0) { 350 return false; 351 } 352 353 // no need to draw 354 if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) { 355 return true; 356 } 357 358 int startIndex = fLastDrawIndex + 1; 359 if (fLastDrawIndex < 0 || !bm->readyToDraw()) { 360 // first time 361 362 startIndex = 0; 363 364 // create bitmap 365 bm->setConfig(SkBitmap::kARGB_8888_Config, width, height, 0); 366 if (!bm->allocPixels(NULL)) { 367 return false; 368 } 369 // create bitmap for backup 370 fBackup.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0); 371 if (!fBackup.allocPixels(NULL)) { 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(SkStream* 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*, SkStream*> gReg(Factory); 448