/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "JNIHelpers.h" #include "utils/log.h" #include "utils/math.h" #include "FrameSequence_gif.h" #define GIF_DEBUG 0 // These constants are chosen to imitate common browser behavior // Note that 0 delay is undefined behavior in the gif standard static const long MIN_DELAY_MS = 20; static const long DEFAULT_DELAY_MS = 100; static int streamReader(GifFileType* fileType, GifByteType* out, int size) { Stream* stream = (Stream*) fileType->UserData; return (int) stream->read(out, size); } static Color8888 gifColorToColor8888(const GifColorType& color) { return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue); } static long getDelayMs(GraphicsControlBlock& gcb) { long delayMs = gcb.DelayTime * 10; if (delayMs < MIN_DELAY_MS) { return DEFAULT_DELAY_MS; } return delayMs; } static bool willBeCleared(const GraphicsControlBlock& gcb) { return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS; } //////////////////////////////////////////////////////////////////////////////// // Frame sequence //////////////////////////////////////////////////////////////////////////////// FrameSequence_gif::FrameSequence_gif(Stream* stream) : mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) { mGif = DGifOpen(stream, streamReader, NULL); if (!mGif) { ALOGW("Gif load failed"); return; } if (DGifSlurp(mGif) != GIF_OK) { ALOGW("Gif slurp failed"); DGifCloseFile(mGif); mGif = NULL; return; } long durationMs = 0; int lastUnclearedFrame = -1; mPreservedFrames = new bool[mGif->ImageCount]; mRestoringFrames = new int[mGif->ImageCount]; GraphicsControlBlock gcb; for (int i = 0; i < mGif->ImageCount; i++) { const SavedImage& image = mGif->SavedImages[i]; // find the loop extension pair for (int j = 0; (j + 1) < image.ExtensionBlockCount; j++) { ExtensionBlock* eb1 = image.ExtensionBlocks + j; ExtensionBlock* eb2 = image.ExtensionBlocks + j + 1; if (eb1->Function == APPLICATION_EXT_FUNC_CODE // look for "NETSCAPE2.0" app extension && eb1->ByteCount == 11 && !memcmp((const char*)(eb1->Bytes), "NETSCAPE2.0", 11) // verify extension contents and get loop count && eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount == 3 && eb2->Bytes[0] == 1) { mLoopCount = (int)(eb2->Bytes[2] << 8) + (int)(eb2->Bytes[1]); } } DGifSavedExtensionToGCB(mGif, i, &gcb); // timing durationMs += getDelayMs(gcb); // preserve logic mPreservedFrames[i] = false; mRestoringFrames[i] = -1; if (gcb.DisposalMode == DISPOSE_PREVIOUS && lastUnclearedFrame >= 0) { mPreservedFrames[lastUnclearedFrame] = true; mRestoringFrames[i] = lastUnclearedFrame; } if (!willBeCleared(gcb)) { lastUnclearedFrame = i; } } #if GIF_DEBUG ALOGD("FrameSequence_gif created with size %d %d, frames %d dur %ld", mGif->SWidth, mGif->SHeight, mGif->ImageCount, durationMs); for (int i = 0; i < mGif->ImageCount; i++) { DGifSavedExtensionToGCB(mGif, i, &gcb); ALOGD(" Frame %d - must preserve %d, restore point %d, trans color %d", i, mPreservedFrames[i], mRestoringFrames[i], gcb.TransparentColor); } #endif if (mGif->SColorMap) { // calculate bg color GraphicsControlBlock gcb; DGifSavedExtensionToGCB(mGif, 0, &gcb); if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) { mBgColor = gifColorToColor8888(mGif->SColorMap->Colors[mGif->SBackGroundColor]); } } } FrameSequence_gif::~FrameSequence_gif() { if (mGif) { DGifCloseFile(mGif); } delete[] mPreservedFrames; delete[] mRestoringFrames; } FrameSequenceState* FrameSequence_gif::createState() const { return new FrameSequenceState_gif(*this); } //////////////////////////////////////////////////////////////////////////////// // draw helpers //////////////////////////////////////////////////////////////////////////////// // return true if area of 'target' is completely covers area of 'covered' static bool checkIfCover(const GifImageDesc& target, const GifImageDesc& covered) { return target.Left <= covered.Left && covered.Left + covered.Width <= target.Left + target.Width && target.Top <= covered.Top && covered.Top + covered.Height <= target.Top + target.Height; } static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap, int transparent, int width) { for (; width > 0; width--, src++, dst++) { if (*src != transparent) { *dst = gifColorToColor8888(cmap->Colors[*src]); } } } static void setLineColor(Color8888* dst, Color8888 color, int width) { for (; width > 0; width--, dst++) { *dst = color; } } static void getCopySize(const GifImageDesc& imageDesc, int maxWidth, int maxHeight, GifWord& copyWidth, GifWord& copyHeight) { copyWidth = imageDesc.Width; if (imageDesc.Left + copyWidth > maxWidth) { copyWidth = maxWidth - imageDesc.Left; } copyHeight = imageDesc.Height; if (imageDesc.Top + copyHeight > maxHeight) { copyHeight = maxHeight - imageDesc.Top; } } //////////////////////////////////////////////////////////////////////////////// // Frame sequence state //////////////////////////////////////////////////////////////////////////////// FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) : mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) { } FrameSequenceState_gif::~FrameSequenceState_gif() { delete[] mPreserveBuffer; } void FrameSequenceState_gif::savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr) { if (frameNr == mPreserveBufferFrame) return; mPreserveBufferFrame = frameNr; const int width = mFrameSequence.getWidth(); const int height = mFrameSequence.getHeight(); if (!mPreserveBuffer) { mPreserveBuffer = new Color8888[width * height]; } for (int y = 0; y < height; y++) { memcpy(mPreserveBuffer + width * y, outputPtr + outputPixelStride * y, width * 4); } } void FrameSequenceState_gif::restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride) { const int width = mFrameSequence.getWidth(); const int height = mFrameSequence.getHeight(); if (!mPreserveBuffer) { ALOGD("preserve buffer not allocated! ah!"); return; } for (int y = 0; y < height; y++) { memcpy(outputPtr + outputPixelStride * y, mPreserveBuffer + width * y, width * 4); } } long FrameSequenceState_gif::drawFrame(int frameNr, Color8888* outputPtr, int outputPixelStride, int previousFrameNr) { GifFileType* gif = mFrameSequence.getGif(); if (!gif) { ALOGD("Cannot drawFrame, mGif is NULL"); return -1; } #if GIF_DEBUG ALOGD(" drawFrame on %p nr %d on addr %p, previous frame nr %d", this, frameNr, outputPtr, previousFrameNr); #endif const int height = mFrameSequence.getHeight(); const int width = mFrameSequence.getWidth(); GraphicsControlBlock gcb; int start = max(previousFrameNr + 1, 0); for (int i = max(start - 1, 0); i < frameNr; i++) { int neededPreservedFrame = mFrameSequence.getRestoringFrame(i); if (neededPreservedFrame >= 0 && (mPreserveBufferFrame != neededPreservedFrame)) { #if GIF_DEBUG ALOGD("frame %d needs frame %d preserved, but %d is currently, so drawing from scratch", i, neededPreservedFrame, mPreserveBufferFrame); #endif start = 0; } } for (int i = start; i <= frameNr; i++) { DGifSavedExtensionToGCB(gif, i, &gcb); const SavedImage& frame = gif->SavedImages[i]; #if GIF_DEBUG bool frameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR; ALOGD("producing frame %d, drawing frame %d (opaque %d, disp %d, del %d)", frameNr, i, frameOpaque, gcb.DisposalMode, gcb.DelayTime); #endif if (i == 0) { //clear bitmap Color8888 bgColor = mFrameSequence.getBackgroundColor(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { outputPtr[y * outputPixelStride + x] = bgColor; } } } else { GraphicsControlBlock prevGcb; DGifSavedExtensionToGCB(gif, i - 1, &prevGcb); const SavedImage& prevFrame = gif->SavedImages[i - 1]; bool prevFrameDisposed = willBeCleared(prevGcb); bool newFrameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR; bool prevFrameCompletelyCovered = newFrameOpaque && checkIfCover(frame.ImageDesc, prevFrame.ImageDesc); if (prevFrameDisposed && !prevFrameCompletelyCovered) { switch (prevGcb.DisposalMode) { case DISPOSE_BACKGROUND: { Color8888* dst = outputPtr + prevFrame.ImageDesc.Left + prevFrame.ImageDesc.Top * outputPixelStride; GifWord copyWidth, copyHeight; getCopySize(prevFrame.ImageDesc, width, height, copyWidth, copyHeight); for (; copyHeight > 0; copyHeight--) { setLineColor(dst, TRANSPARENT, copyWidth); dst += outputPixelStride; } } break; case DISPOSE_PREVIOUS: { restorePreserveBuffer(outputPtr, outputPixelStride); } break; } } if (mFrameSequence.getPreservedFrame(i - 1)) { // currently drawn frame will be restored by a following DISPOSE_PREVIOUS draw, so // we preserve it savePreserveBuffer(outputPtr, outputPixelStride, i - 1); } } bool willBeCleared = gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS; if (i == frameNr || !willBeCleared) { const ColorMapObject* cmap = gif->SColorMap; if (frame.ImageDesc.ColorMap) { cmap = frame.ImageDesc.ColorMap; } if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) { ALOGW("Warning: potentially corrupt color map"); } const unsigned char* src = (unsigned char*)frame.RasterBits; Color8888* dst = outputPtr + frame.ImageDesc.Left + frame.ImageDesc.Top * outputPixelStride; GifWord copyWidth, copyHeight; getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight); for (; copyHeight > 0; copyHeight--) { copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth); src += frame.ImageDesc.Width; dst += outputPixelStride; } } } // return last frame's delay const int maxFrame = gif->ImageCount; const int lastFrame = (frameNr + maxFrame - 1) % maxFrame; DGifSavedExtensionToGCB(gif, lastFrame, &gcb); return getDelayMs(gcb); } //////////////////////////////////////////////////////////////////////////////// // Registry //////////////////////////////////////////////////////////////////////////////// #include "Registry.h" static bool isGif(void* header, int header_size) { return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN) || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN) || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN); } static FrameSequence* createFramesequence(Stream* stream) { return new FrameSequence_gif(stream); } static RegistryEntry gEntry = { GIF_STAMP_LEN, isGif, createFramesequence, NULL, }; static Registry gRegister(gEntry);