| /* |
| * 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); |
| |