/*
* 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);
}
// Returns true if the rectangle defined by 'frame' contains pixel (x, y).
static bool FrameContainsPixel(const WebPIterator& frame, int x, int y) {
const int left = frame.x_offset;
const int right = left + frame.width;
const int top = frame.y_offset;
const int bottom = top + frame.height;
return x >= left && x < right && y >= top && y < bottom;
}
// 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)
&& !FrameContainsPixel(prevIter, canvasX, canvasY)) {
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);