You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
16 KiB
406 lines
16 KiB
/*
|
|
* 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)
|
|
: mDemux(NULL)
|
|
, mIsKeyFrame(NULL)
|
|
, mRawByteBuffer(NULL) {
|
|
if (stream->getRawBuffer() != NULL) {
|
|
mData.size = stream->getRawBufferSize();
|
|
mData.bytes = stream->getRawBufferAddr();
|
|
mRawByteBuffer = stream->getRawBuffer();
|
|
} else {
|
|
// 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;
|
|
}
|
|
uint32_t readSize = GetLE32(riff_header + TAG_SIZE);
|
|
if (readSize > MAX_CHUNK_PAYLOAD) {
|
|
ALOGE("WebP got header size too large");
|
|
return;
|
|
}
|
|
mData.size = CHUNK_HEADER_SIZE + readSize;
|
|
if(mData.size < RIFF_HEADER_SIZE) {
|
|
ALOGE("WebP file malformed");
|
|
return;
|
|
}
|
|
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[] mIsKeyFrame;
|
|
if (mRawByteBuffer == NULL) {
|
|
delete[] mData.bytes;
|
|
}
|
|
}
|
|
|
|
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 + 1, &currIter);
|
|
ALOG_ASSERT(ok, "Could not retrieve frame# %d", lastFrame);
|
|
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 bool acceptsWebPBuffer() {
|
|
return true;
|
|
}
|
|
|
|
static FrameSequence* createFramesequence(Stream* stream) {
|
|
return new FrameSequence_webp(stream);
|
|
}
|
|
|
|
static RegistryEntry gEntry = {
|
|
RIFF_HEADER_SIZE,
|
|
isWebP,
|
|
createFramesequence,
|
|
NULL,
|
|
acceptsWebPBuffer,
|
|
};
|
|
static Registry gRegister(gEntry);
|
|
|