diff options
author | Chris Craik <ccraik@google.com> | 2014-03-19 20:02:28 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-03-19 20:02:29 +0000 |
commit | 2a2ff6ed0db11be779f55462ec30e60154db8e3d (patch) | |
tree | de7dca2a18dc3f4fcf806bfc8d44e885ba789edc /framesequence | |
parent | bbeed6726cd11a0285a59225605c4ae33ee25257 (diff) | |
parent | b34f1da83570613bb349f8026d4325552ac495ed (diff) | |
download | android_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.mk | 6 | ||||
-rw-r--r-- | framesequence/jni/FrameSequence_webp.cpp | 371 | ||||
-rw-r--r-- | framesequence/jni/FrameSequence_webp.h | 92 |
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 |