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