michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkMovie.h" michael@0: #include "SkColor.h" michael@0: #include "SkColorPriv.h" michael@0: #include "SkStream.h" michael@0: #include "SkTemplates.h" michael@0: #include "SkUtils.h" michael@0: michael@0: #include "gif_lib.h" michael@0: michael@0: class SkGIFMovie : public SkMovie { michael@0: public: michael@0: SkGIFMovie(SkStream* stream); michael@0: virtual ~SkGIFMovie(); michael@0: michael@0: protected: michael@0: virtual bool onGetInfo(Info*); michael@0: virtual bool onSetTime(SkMSec); michael@0: virtual bool onGetBitmap(SkBitmap*); michael@0: michael@0: private: michael@0: GifFileType* fGIF; michael@0: int fCurrIndex; michael@0: int fLastDrawIndex; michael@0: SkBitmap fBackup; michael@0: }; michael@0: michael@0: static int Decode(GifFileType* fileType, GifByteType* out, int size) { michael@0: SkStream* stream = (SkStream*) fileType->UserData; michael@0: return (int) stream->read(out, size); michael@0: } michael@0: michael@0: SkGIFMovie::SkGIFMovie(SkStream* stream) michael@0: { michael@0: #if GIFLIB_MAJOR < 5 michael@0: fGIF = DGifOpen( stream, Decode ); michael@0: #else michael@0: fGIF = DGifOpen( stream, Decode, NULL ); michael@0: #endif michael@0: if (NULL == fGIF) michael@0: return; michael@0: michael@0: if (DGifSlurp(fGIF) != GIF_OK) michael@0: { michael@0: DGifCloseFile(fGIF); michael@0: fGIF = NULL; michael@0: } michael@0: fCurrIndex = -1; michael@0: fLastDrawIndex = -1; michael@0: } michael@0: michael@0: SkGIFMovie::~SkGIFMovie() michael@0: { michael@0: if (fGIF) michael@0: DGifCloseFile(fGIF); michael@0: } michael@0: michael@0: static SkMSec savedimage_duration(const SavedImage* image) michael@0: { michael@0: for (int j = 0; j < image->ExtensionBlockCount; j++) michael@0: { michael@0: if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) michael@0: { michael@0: SkASSERT(image->ExtensionBlocks[j].ByteCount >= 4); michael@0: const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes; michael@0: return ((b[2] << 8) | b[1]) * 10; michael@0: } michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: bool SkGIFMovie::onGetInfo(Info* info) michael@0: { michael@0: if (NULL == fGIF) michael@0: return false; michael@0: michael@0: SkMSec dur = 0; michael@0: for (int i = 0; i < fGIF->ImageCount; i++) michael@0: dur += savedimage_duration(&fGIF->SavedImages[i]); michael@0: michael@0: info->fDuration = dur; michael@0: info->fWidth = fGIF->SWidth; michael@0: info->fHeight = fGIF->SHeight; michael@0: info->fIsOpaque = false; // how to compute? michael@0: return true; michael@0: } michael@0: michael@0: bool SkGIFMovie::onSetTime(SkMSec time) michael@0: { michael@0: if (NULL == fGIF) michael@0: return false; michael@0: michael@0: SkMSec dur = 0; michael@0: for (int i = 0; i < fGIF->ImageCount; i++) michael@0: { michael@0: dur += savedimage_duration(&fGIF->SavedImages[i]); michael@0: if (dur >= time) michael@0: { michael@0: fCurrIndex = i; michael@0: return fLastDrawIndex != fCurrIndex; michael@0: } michael@0: } michael@0: fCurrIndex = fGIF->ImageCount - 1; michael@0: return true; michael@0: } michael@0: michael@0: static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap, michael@0: int transparent, int width) michael@0: { michael@0: for (; width > 0; width--, src++, dst++) { michael@0: if (*src != transparent) { michael@0: const GifColorType& col = cmap->Colors[*src]; michael@0: *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #if GIFLIB_MAJOR < 5 michael@0: static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src, michael@0: const ColorMapObject* cmap, int transparent, int copyWidth, michael@0: int copyHeight, const GifImageDesc& imageDesc, int rowStep, michael@0: int startRow) michael@0: { michael@0: int row; michael@0: // every 'rowStep'th row, starting with row 'startRow' michael@0: for (row = startRow; row < copyHeight; row += rowStep) { michael@0: uint32_t* dst = bm->getAddr32(imageDesc.Left, imageDesc.Top + row); michael@0: copyLine(dst, src, cmap, transparent, copyWidth); michael@0: src += imageDesc.Width; michael@0: } michael@0: michael@0: // pad for rest height michael@0: src += imageDesc.Width * ((imageDesc.Height - row + rowStep - 1) / rowStep); michael@0: } michael@0: michael@0: static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap, michael@0: int transparent) michael@0: { michael@0: int width = bm->width(); michael@0: int height = bm->height(); michael@0: GifWord copyWidth = frame->ImageDesc.Width; michael@0: if (frame->ImageDesc.Left + copyWidth > width) { michael@0: copyWidth = width - frame->ImageDesc.Left; michael@0: } michael@0: michael@0: GifWord copyHeight = frame->ImageDesc.Height; michael@0: if (frame->ImageDesc.Top + copyHeight > height) { michael@0: copyHeight = height - frame->ImageDesc.Top; michael@0: } michael@0: michael@0: // deinterlace michael@0: const unsigned char* src = (unsigned char*)frame->RasterBits; michael@0: michael@0: // group 1 - every 8th row, starting with row 0 michael@0: copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 0); michael@0: michael@0: // group 2 - every 8th row, starting with row 4 michael@0: copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 4); michael@0: michael@0: // group 3 - every 4th row, starting with row 2 michael@0: copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 4, 2); michael@0: michael@0: copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 2, 1); michael@0: } michael@0: #endif michael@0: michael@0: static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap, michael@0: int transparent) michael@0: { michael@0: int width = bm->width(); michael@0: int height = bm->height(); michael@0: const unsigned char* src = (unsigned char*)frame->RasterBits; michael@0: uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top); michael@0: GifWord copyWidth = frame->ImageDesc.Width; michael@0: if (frame->ImageDesc.Left + copyWidth > width) { michael@0: copyWidth = width - frame->ImageDesc.Left; michael@0: } michael@0: michael@0: GifWord copyHeight = frame->ImageDesc.Height; michael@0: if (frame->ImageDesc.Top + copyHeight > height) { michael@0: copyHeight = height - frame->ImageDesc.Top; michael@0: } michael@0: michael@0: for (; copyHeight > 0; copyHeight--) { michael@0: copyLine(dst, src, cmap, transparent, copyWidth); michael@0: src += frame->ImageDesc.Width; michael@0: dst += width; michael@0: } michael@0: } michael@0: michael@0: static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height, michael@0: uint32_t col) michael@0: { michael@0: int bmWidth = bm->width(); michael@0: int bmHeight = bm->height(); michael@0: uint32_t* dst = bm->getAddr32(left, top); michael@0: GifWord copyWidth = width; michael@0: if (left + copyWidth > bmWidth) { michael@0: copyWidth = bmWidth - left; michael@0: } michael@0: michael@0: GifWord copyHeight = height; michael@0: if (top + copyHeight > bmHeight) { michael@0: copyHeight = bmHeight - top; michael@0: } michael@0: michael@0: for (; copyHeight > 0; copyHeight--) { michael@0: sk_memset32(dst, col, copyWidth); michael@0: dst += bmWidth; michael@0: } michael@0: } michael@0: michael@0: static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap) michael@0: { michael@0: int transparent = -1; michael@0: michael@0: for (int i = 0; i < frame->ExtensionBlockCount; ++i) { michael@0: ExtensionBlock* eb = frame->ExtensionBlocks + i; michael@0: if (eb->Function == GRAPHICS_EXT_FUNC_CODE && michael@0: eb->ByteCount == 4) { michael@0: bool has_transparency = ((eb->Bytes[0] & 1) == 1); michael@0: if (has_transparency) { michael@0: transparent = (unsigned char)eb->Bytes[3]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (frame->ImageDesc.ColorMap != NULL) { michael@0: // use local color table michael@0: cmap = frame->ImageDesc.ColorMap; michael@0: } michael@0: michael@0: if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { michael@0: SkDEBUGFAIL("bad colortable setup"); michael@0: return; michael@0: } michael@0: michael@0: #if GIFLIB_MAJOR < 5 michael@0: // before GIFLIB 5, de-interlacing wasn't done by library at load time michael@0: if (frame->ImageDesc.Interlace) { michael@0: blitInterlace(bm, frame, cmap, transparent); michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: blitNormal(bm, frame, cmap, transparent); michael@0: } michael@0: michael@0: static bool checkIfWillBeCleared(const SavedImage* frame) michael@0: { michael@0: for (int i = 0; i < frame->ExtensionBlockCount; ++i) { michael@0: ExtensionBlock* eb = frame->ExtensionBlocks + i; michael@0: if (eb->Function == GRAPHICS_EXT_FUNC_CODE && michael@0: eb->ByteCount == 4) { michael@0: // check disposal method michael@0: int disposal = ((eb->Bytes[0] >> 2) & 7); michael@0: if (disposal == 2 || disposal == 3) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal) michael@0: { michael@0: *trans = false; michael@0: *disposal = 0; michael@0: for (int i = 0; i < frame->ExtensionBlockCount; ++i) { michael@0: ExtensionBlock* eb = frame->ExtensionBlocks + i; michael@0: if (eb->Function == GRAPHICS_EXT_FUNC_CODE && michael@0: eb->ByteCount == 4) { michael@0: *trans = ((eb->Bytes[0] & 1) == 1); michael@0: *disposal = ((eb->Bytes[0] >> 2) & 7); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // return true if area of 'target' is completely covers area of 'covered' michael@0: static bool checkIfCover(const SavedImage* target, const SavedImage* covered) michael@0: { michael@0: if (target->ImageDesc.Left <= covered->ImageDesc.Left michael@0: && covered->ImageDesc.Left + covered->ImageDesc.Width <= michael@0: target->ImageDesc.Left + target->ImageDesc.Width michael@0: && target->ImageDesc.Top <= covered->ImageDesc.Top michael@0: && covered->ImageDesc.Top + covered->ImageDesc.Height <= michael@0: target->ImageDesc.Top + target->ImageDesc.Height) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next, michael@0: SkBitmap* backup, SkColor color) michael@0: { michael@0: // We can skip disposal process if next frame is not transparent michael@0: // and completely covers current area michael@0: bool curTrans; michael@0: int curDisposal; michael@0: getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal); michael@0: bool nextTrans; michael@0: int nextDisposal; michael@0: getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal); michael@0: if ((curDisposal == 2 || curDisposal == 3) michael@0: && (nextTrans || !checkIfCover(next, cur))) { michael@0: switch (curDisposal) { michael@0: // restore to background color michael@0: // -> 'background' means background under this image. michael@0: case 2: michael@0: fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top, michael@0: cur->ImageDesc.Width, cur->ImageDesc.Height, michael@0: color); michael@0: break; michael@0: michael@0: // restore to previous michael@0: case 3: michael@0: bm->swap(*backup); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Save current image if next frame's disposal method == 3 michael@0: if (nextDisposal == 3) { michael@0: const uint32_t* src = bm->getAddr32(0, 0); michael@0: uint32_t* dst = backup->getAddr32(0, 0); michael@0: int cnt = bm->width() * bm->height(); michael@0: memcpy(dst, src, cnt*sizeof(uint32_t)); michael@0: } michael@0: } michael@0: michael@0: bool SkGIFMovie::onGetBitmap(SkBitmap* bm) michael@0: { michael@0: const GifFileType* gif = fGIF; michael@0: if (NULL == gif) michael@0: return false; michael@0: michael@0: if (gif->ImageCount < 1) { michael@0: return false; michael@0: } michael@0: michael@0: const int width = gif->SWidth; michael@0: const int height = gif->SHeight; michael@0: if (width <= 0 || height <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: // no need to draw michael@0: if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) { michael@0: return true; michael@0: } michael@0: michael@0: int startIndex = fLastDrawIndex + 1; michael@0: if (fLastDrawIndex < 0 || !bm->readyToDraw()) { michael@0: // first time michael@0: michael@0: startIndex = 0; michael@0: michael@0: // create bitmap michael@0: if (!bm->allocPixels(SkImageInfo::MakeN32Premul(width, height))) { michael@0: return false; michael@0: } michael@0: // create bitmap for backup michael@0: if (!fBackup.allocPixels(SkImageInfo::MakeN32Premul(width, height))) { michael@0: return false; michael@0: } michael@0: } else if (startIndex > fCurrIndex) { michael@0: // rewind to 1st frame for repeat michael@0: startIndex = 0; michael@0: } michael@0: michael@0: int lastIndex = fCurrIndex; michael@0: if (lastIndex < 0) { michael@0: // first time michael@0: lastIndex = 0; michael@0: } else if (lastIndex > fGIF->ImageCount - 1) { michael@0: // this block must not be reached. michael@0: lastIndex = fGIF->ImageCount - 1; michael@0: } michael@0: michael@0: SkColor bgColor = SkPackARGB32(0, 0, 0, 0); michael@0: if (gif->SColorMap != NULL) { michael@0: const GifColorType& col = gif->SColorMap->Colors[fGIF->SBackGroundColor]; michael@0: bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue); michael@0: } michael@0: michael@0: static SkColor paintingColor = SkPackARGB32(0, 0, 0, 0); michael@0: // draw each frames - not intelligent way michael@0: for (int i = startIndex; i <= lastIndex; i++) { michael@0: const SavedImage* cur = &fGIF->SavedImages[i]; michael@0: if (i == 0) { michael@0: bool trans; michael@0: int disposal; michael@0: getTransparencyAndDisposalMethod(cur, &trans, &disposal); michael@0: if (!trans && gif->SColorMap != NULL) { michael@0: paintingColor = bgColor; michael@0: } else { michael@0: paintingColor = SkColorSetARGB(0, 0, 0, 0); michael@0: } michael@0: michael@0: bm->eraseColor(paintingColor); michael@0: fBackup.eraseColor(paintingColor); michael@0: } else { michael@0: // Dispose previous frame before move to next frame. michael@0: const SavedImage* prev = &fGIF->SavedImages[i-1]; michael@0: disposeFrameIfNeeded(bm, prev, cur, &fBackup, paintingColor); michael@0: } michael@0: michael@0: // Draw frame michael@0: // We can skip this process if this index is not last and disposal michael@0: // method == 2 or method == 3 michael@0: if (i == lastIndex || !checkIfWillBeCleared(cur)) { michael@0: drawFrame(bm, cur, gif->SColorMap); michael@0: } michael@0: } michael@0: michael@0: // save index michael@0: fLastDrawIndex = lastIndex; michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #include "SkTRegistry.h" michael@0: michael@0: SkMovie* Factory(SkStreamRewindable* stream) { michael@0: char buf[GIF_STAMP_LEN]; michael@0: if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { michael@0: if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || michael@0: memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || michael@0: memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { michael@0: // must rewind here, since our construct wants to re-read the data michael@0: stream->rewind(); michael@0: return SkNEW_ARGS(SkGIFMovie, (stream)); michael@0: } michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: static SkTRegistry gReg(Factory);