summaryrefslogtreecommitdiffstats
path: root/framesequence/jni/FrameSequence_gif.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framesequence/jni/FrameSequence_gif.cpp')
-rw-r--r--framesequence/jni/FrameSequence_gif.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp
new file mode 100644
index 0000000..e9f3ace
--- /dev/null
+++ b/framesequence/jni/FrameSequence_gif.cpp
@@ -0,0 +1,345 @@
+/*
+ * 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 <string.h>
+#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) :
+ 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];
+ 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 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);
+