summaryrefslogtreecommitdiffstats
path: root/framesequence
diff options
context:
space:
mode:
authorChris Craik <ccraik@google.com>2014-03-19 20:02:28 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-03-19 20:02:29 +0000
commit2a2ff6ed0db11be779f55462ec30e60154db8e3d (patch)
treede7dca2a18dc3f4fcf806bfc8d44e885ba789edc /framesequence
parentbbeed6726cd11a0285a59225605c4ae33ee25257 (diff)
parentb34f1da83570613bb349f8026d4325552ac495ed (diff)
downloadandroid_frameworks_ex-2a2ff6ed0db11be779f55462ec30e60154db8e3d.tar.gz
android_frameworks_ex-2a2ff6ed0db11be779f55462ec30e60154db8e3d.tar.bz2
android_frameworks_ex-2a2ff6ed0db11be779f55462ec30e60154db8e3d.zip
Merge "Add Animated WebP support in RasterMill"
Diffstat (limited to 'framesequence')
-rw-r--r--framesequence/jni/Android.mk6
-rw-r--r--framesequence/jni/FrameSequence_webp.cpp371
-rw-r--r--framesequence/jni/FrameSequence_webp.h92
3 files changed, 467 insertions, 2 deletions
diff --git a/framesequence/jni/Android.mk b/framesequence/jni/Android.mk
index ee86fc1..e9d0ec5 100644
--- a/framesequence/jni/Android.mk
+++ b/framesequence/jni/Android.mk
@@ -19,12 +19,13 @@ include $(CLEAR_VARS)
## Main library
-LOCAL_STATIC_LIBRARIES += libgif
+LOCAL_STATIC_LIBRARIES += libgif libwebp-decode
LOCAL_LDFLAGS := -llog -ljnigraphics
LOCAL_C_INCLUDES := \
- external/giflib
+ external/giflib \
+ external/webp/include
LOCAL_MODULE := libframesequence
LOCAL_SRC_FILES := \
@@ -32,6 +33,7 @@ LOCAL_SRC_FILES := \
FrameSequence.cpp \
FrameSequenceJNI.cpp \
FrameSequence_gif.cpp \
+ FrameSequence_webp.cpp \
JNIHelpers.cpp \
Registry.cpp \
Stream.cpp
diff --git a/framesequence/jni/FrameSequence_webp.cpp b/framesequence/jni/FrameSequence_webp.cpp
new file mode 100644
index 0000000..bced919
--- /dev/null
+++ b/framesequence/jni/FrameSequence_webp.cpp
@@ -0,0 +1,371 @@
+/*
+ * 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 "webp/format_constants.h"
+
+#include "FrameSequence_webp.h"
+
+#define WEBP_DEBUG 0
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence
+////////////////////////////////////////////////////////////////////////////////
+
+static uint32_t GetLE32(const uint8_t* const data) {
+ return MKFOURCC(data[0], data[1], data[2], data[3]);
+}
+
+// Returns true if the frame covers full canvas.
+static bool isFullFrame(const WebPIterator& frame, int canvasWidth, int canvasHeight) {
+ return (frame.width == canvasWidth && frame.height == canvasHeight);
+}
+
+// Construct mIsKeyFrame array.
+void FrameSequence_webp::constructDependencyChain() {
+ const size_t frameCount = getFrameCount();
+ mIsKeyFrame = new bool[frameCount];
+ const int canvasWidth = getWidth();
+ const int canvasHeight = getHeight();
+
+ WebPIterator prev;
+ WebPIterator curr;
+
+ // Note: WebPDemuxGetFrame() uses base-1 counting.
+ int ok = WebPDemuxGetFrame(mDemux, 1, &curr);
+ ALOG_ASSERT(ok, "Could not retrieve frame# 0");
+ mIsKeyFrame[0] = true; // 0th frame is always a key frame.
+ for (size_t i = 1; i < frameCount; i++) {
+ prev = curr;
+ ok = WebPDemuxGetFrame(mDemux, i + 1, &curr); // Get ith frame.
+ ALOG_ASSERT(ok, "Could not retrieve frame# %d", i);
+
+ if ((!curr.has_alpha || curr.blend_method == WEBP_MUX_NO_BLEND) &&
+ isFullFrame(curr, canvasWidth, canvasHeight)) {
+ mIsKeyFrame[i] = true;
+ } else {
+ mIsKeyFrame[i] = (prev.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) &&
+ (isFullFrame(prev, canvasWidth, canvasHeight) || mIsKeyFrame[i - 1]);
+ }
+ }
+ WebPDemuxReleaseIterator(&prev);
+ WebPDemuxReleaseIterator(&curr);
+
+#if WEBP_DEBUG
+ ALOGD("Dependency chain:");
+ for (size_t i = 0; i < frameCount; i++) {
+ ALOGD("Frame# %zu: %s", i, mIsKeyFrame[i] ? "Key frame" : "NOT a key frame");
+ }
+#endif
+}
+
+FrameSequence_webp::FrameSequence_webp(Stream* stream) {
+ // Read RIFF header to get file size.
+ uint8_t riff_header[RIFF_HEADER_SIZE];
+ if (stream->read(riff_header, RIFF_HEADER_SIZE) != RIFF_HEADER_SIZE) {
+ ALOGE("WebP header load failed");
+ return;
+ }
+ mData.size = CHUNK_HEADER_SIZE + GetLE32(riff_header + TAG_SIZE);
+ mData.bytes = new uint8_t[mData.size];
+ memcpy((void*)mData.bytes, riff_header, RIFF_HEADER_SIZE);
+
+ // Read rest of the bytes.
+ void* remaining_bytes = (void*)(mData.bytes + RIFF_HEADER_SIZE);
+ size_t remaining_size = mData.size - RIFF_HEADER_SIZE;
+ if (stream->read(remaining_bytes, remaining_size) != remaining_size) {
+ ALOGE("WebP full load failed");
+ return;
+ }
+
+ // Construct demux.
+ mDemux = WebPDemux(&mData);
+ if (!mDemux) {
+ ALOGE("Parsing of WebP container file failed");
+ return;
+ }
+ mLoopCount = WebPDemuxGetI(mDemux, WEBP_FF_LOOP_COUNT);
+ mFormatFlags = WebPDemuxGetI(mDemux, WEBP_FF_FORMAT_FLAGS);
+#if WEBP_DEBUG
+ ALOGD("FrameSequence_webp created with size = %d x %d, number of frames = %d, flags = 0x%X",
+ getWidth(), getHeight(), getFrameCount(), mFormatFlags);
+#endif
+ constructDependencyChain();
+}
+
+FrameSequence_webp::~FrameSequence_webp() {
+ WebPDemuxDelete(mDemux);
+ delete[] mData.bytes;
+ delete[] mIsKeyFrame;
+}
+
+FrameSequenceState* FrameSequence_webp::createState() const {
+ return new FrameSequenceState_webp(*this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// draw helpers
+////////////////////////////////////////////////////////////////////////////////
+
+static bool willBeCleared(const WebPIterator& iter) {
+ return iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND;
+}
+
+// return true if area of 'target' completely covers area of 'covered'
+static bool checkIfCover(const WebPIterator& target, const WebPIterator& covered) {
+ const int covered_x_max = covered.x_offset + covered.width;
+ const int target_x_max = target.x_offset + target.width;
+ const int covered_y_max = covered.y_offset + covered.height;
+ const int target_y_max = target.y_offset + target.height;
+ return target.x_offset <= covered.x_offset
+ && covered_x_max <= target_x_max
+ && target.y_offset <= covered.y_offset
+ && covered_y_max <= target_y_max;
+}
+
+// Clear all pixels in a line to transparent.
+static void clearLine(Color8888* dst, int width) {
+ memset(dst, 0, width * sizeof(*dst)); // Note: Assumes TRANSPARENT == 0x0.
+}
+
+// Copy all pixels from 'src' to 'dst'.
+static void copyFrame(const Color8888* src, int srcStride, Color8888* dst, int dstStride,
+ int width, int height) {
+ for (int y = 0; y < height; y++) {
+ memcpy(dst, src, width * sizeof(*dst));
+ src += srcStride;
+ dst += dstStride;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence state
+////////////////////////////////////////////////////////////////////////////////
+
+FrameSequenceState_webp::FrameSequenceState_webp(const FrameSequence_webp& frameSequence) :
+ mFrameSequence(frameSequence) {
+ WebPInitDecoderConfig(&mDecoderConfig);
+ mDecoderConfig.output.is_external_memory = 1;
+ mDecoderConfig.output.colorspace = MODE_rgbA; // Pre-multiplied alpha mode.
+ const int canvasWidth = mFrameSequence.getWidth();
+ const int canvasHeight = mFrameSequence.getHeight();
+ mPreservedBuffer = new Color8888[canvasWidth * canvasHeight];
+}
+
+FrameSequenceState_webp::~FrameSequenceState_webp() {
+ delete[] mPreservedBuffer;
+}
+
+void FrameSequenceState_webp::initializeFrame(const WebPIterator& currIter, Color8888* currBuffer,
+ int currStride, const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride) {
+ const int canvasWidth = mFrameSequence.getWidth();
+ const int canvasHeight = mFrameSequence.getHeight();
+ const bool currFrameIsKeyFrame = mFrameSequence.isKeyFrame(currIter.frame_num - 1);
+
+ if (currFrameIsKeyFrame) { // Clear canvas.
+ for (int y = 0; y < canvasHeight; y++) {
+ Color8888* dst = currBuffer + y * currStride;
+ clearLine(dst, canvasWidth);
+ }
+ } else {
+ // Preserve previous frame as starting state of current frame.
+ copyFrame(prevBuffer, prevStride, currBuffer, currStride, canvasWidth, canvasHeight);
+
+ // Dispose previous frame rectangle to Background if needed.
+ bool prevFrameCompletelyCovered =
+ (!currIter.has_alpha || currIter.blend_method == WEBP_MUX_NO_BLEND) &&
+ checkIfCover(currIter, prevIter);
+ if ((prevIter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) &&
+ !prevFrameCompletelyCovered) {
+ Color8888* dst = currBuffer + prevIter.x_offset + prevIter.y_offset * currStride;
+ for (int j = 0; j < prevIter.height; j++) {
+ clearLine(dst, prevIter.width);
+ dst += currStride;
+ }
+ }
+ }
+}
+
+bool FrameSequenceState_webp::decodeFrame(const WebPIterator& currIter, Color8888* currBuffer,
+ int currStride, const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride) {
+ Color8888* dst = currBuffer + currIter.x_offset + currIter.y_offset * currStride;
+ mDecoderConfig.output.u.RGBA.rgba = (uint8_t*)dst;
+ mDecoderConfig.output.u.RGBA.stride = currStride * 4;
+ mDecoderConfig.output.u.RGBA.size = mDecoderConfig.output.u.RGBA.stride * currIter.height;
+
+ const WebPData& currFrame = currIter.fragment;
+ if (WebPDecode(currFrame.bytes, currFrame.size, &mDecoderConfig) != VP8_STATUS_OK) {
+ return false;
+ }
+
+ const int canvasWidth = mFrameSequence.getWidth();
+ const int canvasHeight = mFrameSequence.getHeight();
+ const bool currFrameIsKeyFrame = mFrameSequence.isKeyFrame(currIter.frame_num - 1);
+ // During the decoding of current frame, we may have set some pixels to be transparent
+ // (i.e. alpha < 255). However, the value of each of these pixels should have been determined
+ // by blending it against the value of that pixel in the previous frame if WEBP_MUX_BLEND was
+ // specified. So, we correct these pixels based on disposal method of the previous frame and
+ // the previous frame buffer.
+ if (currIter.blend_method == WEBP_MUX_BLEND && !currFrameIsKeyFrame) {
+ if (prevIter.dispose_method == WEBP_MUX_DISPOSE_NONE) {
+ for (int y = 0; y < currIter.height; y++) {
+ const int canvasY = currIter.y_offset + y;
+ for (int x = 0; x < currIter.width; x++) {
+ const int canvasX = currIter.x_offset + x;
+ Color8888& currPixel = currBuffer[canvasY * currStride + canvasX];
+ // FIXME: Use alpha-blending when alpha is between 0 and 255.
+ if (!(currPixel & COLOR_8888_ALPHA_MASK)) {
+ const Color8888 prevPixel = prevBuffer[canvasY * prevStride + canvasX];
+ currPixel = prevPixel;
+ }
+ }
+ }
+ } else { // prevIter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND
+ // Need to restore transparent pixels to as they were just after frame initialization.
+ // That is:
+ // * Transparent if it belongs to previous frame rectangle <-- This is a no-op.
+ // * Pixel in the previous canvas otherwise <-- Need to restore.
+ for (int y = 0; y < currIter.height; y++) {
+ const int canvasY = currIter.y_offset + y;
+ for (int x = 0; x < currIter.width; x++) {
+ const int canvasX = currIter.x_offset + x;
+ Color8888& currPixel = currBuffer[canvasY * currStride + canvasX];
+ // FIXME: Use alpha-blending when alpha is between 0 and 255.
+ if (!(currPixel & COLOR_8888_ALPHA_MASK) &&
+ isFullFrame(prevIter, canvasWidth, canvasHeight)) {
+ const Color8888 prevPixel = prevBuffer[canvasY * prevStride + canvasX];
+ currPixel = prevPixel;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+long FrameSequenceState_webp::drawFrame(int frameNr,
+ Color8888* outputPtr, int outputPixelStride, int previousFrameNr) {
+ WebPDemuxer* demux = mFrameSequence.getDemuxer();
+ ALOG_ASSERT(demux, "Cannot drawFrame, mDemux is NULL");
+
+#if WEBP_DEBUG
+ ALOGD(" drawFrame called for frame# %d, previous frame# %d", frameNr, previousFrameNr);
+#endif
+
+ const int canvasWidth = mFrameSequence.getWidth();
+ const int canvasHeight = mFrameSequence.getHeight();
+
+ // Find the first frame to be decoded.
+ int start = max(previousFrameNr + 1, 0);
+ int earliestRequired = frameNr;
+ while (earliestRequired > start) {
+ if (mFrameSequence.isKeyFrame(earliestRequired)) {
+ start = earliestRequired;
+ break;
+ }
+ earliestRequired--;
+ }
+
+ WebPIterator currIter;
+ WebPIterator prevIter;
+ int ok = WebPDemuxGetFrame(demux, start, &currIter); // Get frame number 'start - 1'.
+ ALOG_ASSERT(ok, "Could not retrieve frame# %d", start - 1);
+
+ // Use preserve buffer only if needed.
+ Color8888* prevBuffer = (frameNr == 0) ? outputPtr : mPreservedBuffer;
+ int prevStride = (frameNr == 0) ? outputPixelStride : canvasWidth;
+ Color8888* currBuffer = outputPtr;
+ int currStride = outputPixelStride;
+
+ for (int i = start; i <= frameNr; i++) {
+ prevIter = currIter;
+ ok = WebPDemuxGetFrame(demux, i + 1, &currIter); // Get ith frame.
+ ALOG_ASSERT(ok, "Could not retrieve frame# %d", i);
+#if WEBP_DEBUG
+ ALOGD(" producing frame %d (has_alpha = %d, dispose = %s, blend = %s, duration = %d)",
+ i, currIter.has_alpha,
+ (currIter.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none" : "background",
+ (currIter.blend_method == WEBP_MUX_BLEND) ? "yes" : "no", currIter.duration);
+#endif
+ // We swap the prev/curr buffers as we go.
+ Color8888* tmpBuffer = prevBuffer;
+ prevBuffer = currBuffer;
+ currBuffer = tmpBuffer;
+
+ int tmpStride = prevStride;
+ prevStride = currStride;
+ currStride = tmpStride;
+
+#if WEBP_DEBUG
+ ALOGD(" prev = %p, curr = %p, out = %p, tmp = %p",
+ prevBuffer, currBuffer, outputPtr, mPreservedBuffer);
+#endif
+ // Process this frame.
+ initializeFrame(currIter, currBuffer, currStride, prevIter, prevBuffer, prevStride);
+
+ if (i == frameNr || !willBeCleared(currIter)) {
+ if (!decodeFrame(currIter, currBuffer, currStride, prevIter, prevBuffer, prevStride)) {
+ ALOGE("Error decoding frame# %d", i);
+ return -1;
+ }
+ }
+ }
+
+ if (outputPtr != currBuffer) {
+ copyFrame(currBuffer, currStride, outputPtr, outputPixelStride, canvasWidth, canvasHeight);
+ }
+
+ // Return last frame's delay.
+ const int frameCount = mFrameSequence.getFrameCount();
+ const int lastFrame = (frameNr + frameCount - 1) % frameCount;
+ ok = WebPDemuxGetFrame(demux, lastFrame, &currIter);
+ ALOG_ASSERT(ok, "Could not retrieve frame# %d", lastFrame - 1);
+ const int lastFrameDelay = currIter.duration;
+
+ WebPDemuxReleaseIterator(&currIter);
+ WebPDemuxReleaseIterator(&prevIter);
+
+ return lastFrameDelay;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Registry
+////////////////////////////////////////////////////////////////////////////////
+
+#include "Registry.h"
+
+static bool isWebP(void* header, int header_size) {
+ const uint8_t* const header_str = (const uint8_t*)header;
+ return (header_size >= RIFF_HEADER_SIZE) &&
+ !memcmp("RIFF", header_str, 4) &&
+ !memcmp("WEBP", header_str + 8, 4);
+}
+
+static FrameSequence* createFramesequence(Stream* stream) {
+ return new FrameSequence_webp(stream);
+}
+
+static RegistryEntry gEntry = {
+ RIFF_HEADER_SIZE,
+ isWebP,
+ createFramesequence,
+ NULL,
+};
+static Registry gRegister(gEntry);
+
diff --git a/framesequence/jni/FrameSequence_webp.h b/framesequence/jni/FrameSequence_webp.h
new file mode 100644
index 0000000..9fcf3d8
--- /dev/null
+++ b/framesequence/jni/FrameSequence_webp.h
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_FRAMESQUENCE_WEBP_H
+#define RASTERMILL_FRAMESQUENCE_WEBP_H
+
+#include "config.h"
+#include "webp/decode.h"
+#include "webp/demux.h"
+
+#include "Stream.h"
+#include "Color.h"
+#include "FrameSequence.h"
+
+// Parser for a possibly-animated WebP bitstream.
+class FrameSequence_webp : public FrameSequence {
+public:
+ FrameSequence_webp(Stream* stream);
+ virtual ~FrameSequence_webp();
+
+ virtual int getWidth() const {
+ return WebPDemuxGetI(mDemux, WEBP_FF_CANVAS_WIDTH);
+ }
+
+ virtual int getHeight() const {
+ return WebPDemuxGetI(mDemux, WEBP_FF_CANVAS_HEIGHT);
+ }
+
+ virtual bool isOpaque() const {
+ return !(mFormatFlags & ALPHA_FLAG);
+ }
+
+ virtual int getFrameCount() const {
+ return WebPDemuxGetI(mDemux, WEBP_FF_FRAME_COUNT);
+ }
+
+ virtual int getDefaultLoopCount() const {
+ return mLoopCount;
+ }
+
+ virtual FrameSequenceState* createState() const;
+
+ WebPDemuxer* getDemuxer() const { return mDemux; }
+
+ bool isKeyFrame(size_t frameNr) const { return mIsKeyFrame[frameNr]; /* TODO: CHECK BOUNDS*/ }
+
+private:
+ void constructDependencyChain();
+
+ WebPData mData;
+ WebPDemuxer* mDemux;
+ int mLoopCount;
+ uint32_t mFormatFlags;
+ // mIsKeyFrame[i] is true if ith canvas can be constructed without decoding any prior frames.
+ bool* mIsKeyFrame;
+};
+
+// Produces frames of a possibly-animated WebP file for display.
+class FrameSequenceState_webp : public FrameSequenceState {
+public:
+ FrameSequenceState_webp(const FrameSequence_webp& frameSequence);
+ virtual ~FrameSequenceState_webp();
+
+ // Returns frame's delay time in milliseconds.
+ virtual long drawFrame(int frameNr,
+ Color8888* outputPtr, int outputPixelStride, int previousFrameNr);
+
+private:
+ void initializeFrame(const WebPIterator& currIter, Color8888* currBuffer, int currStride,
+ const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride);
+ bool decodeFrame(const WebPIterator& iter, Color8888* currBuffer, int currStride,
+ const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride);
+
+ const FrameSequence_webp& mFrameSequence;
+ WebPDecoderConfig mDecoderConfig;
+ Color8888* mPreservedBuffer;
+};
+
+#endif //RASTERMILL_FRAMESQUENCE_WEBP_H