FrameSequence_webp.cpp revision b34f1da83570613bb349f8026d4325552ac495ed
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <string.h>
18#include "JNIHelpers.h"
19#include "utils/log.h"
20#include "utils/math.h"
21#include "webp/format_constants.h"
22
23#include "FrameSequence_webp.h"
24
25#define WEBP_DEBUG 0
26
27////////////////////////////////////////////////////////////////////////////////
28// Frame sequence
29////////////////////////////////////////////////////////////////////////////////
30
31static uint32_t GetLE32(const uint8_t* const data) {
32    return MKFOURCC(data[0], data[1], data[2], data[3]);
33}
34
35// Returns true if the frame covers full canvas.
36static bool isFullFrame(const WebPIterator& frame, int canvasWidth, int canvasHeight) {
37    return (frame.width == canvasWidth && frame.height == canvasHeight);
38}
39
40// Construct mIsKeyFrame array.
41void FrameSequence_webp::constructDependencyChain() {
42    const size_t frameCount = getFrameCount();
43    mIsKeyFrame = new bool[frameCount];
44    const int canvasWidth = getWidth();
45    const int canvasHeight = getHeight();
46
47    WebPIterator prev;
48    WebPIterator curr;
49
50    // Note: WebPDemuxGetFrame() uses base-1 counting.
51    int ok = WebPDemuxGetFrame(mDemux, 1, &curr);
52    ALOG_ASSERT(ok, "Could not retrieve frame# 0");
53    mIsKeyFrame[0] = true;  // 0th frame is always a key frame.
54    for (size_t i = 1; i < frameCount; i++) {
55        prev = curr;
56        ok = WebPDemuxGetFrame(mDemux, i + 1, &curr);  // Get ith frame.
57        ALOG_ASSERT(ok, "Could not retrieve frame# %d", i);
58
59        if ((!curr.has_alpha || curr.blend_method == WEBP_MUX_NO_BLEND) &&
60                isFullFrame(curr, canvasWidth, canvasHeight)) {
61            mIsKeyFrame[i] = true;
62        } else {
63            mIsKeyFrame[i] = (prev.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) &&
64                    (isFullFrame(prev, canvasWidth, canvasHeight) || mIsKeyFrame[i - 1]);
65        }
66    }
67    WebPDemuxReleaseIterator(&prev);
68    WebPDemuxReleaseIterator(&curr);
69
70#if WEBP_DEBUG
71    ALOGD("Dependency chain:");
72    for (size_t i = 0; i < frameCount; i++) {
73        ALOGD("Frame# %zu: %s", i, mIsKeyFrame[i] ? "Key frame" : "NOT a key frame");
74    }
75#endif
76}
77
78FrameSequence_webp::FrameSequence_webp(Stream* stream) {
79    // Read RIFF header to get file size.
80    uint8_t riff_header[RIFF_HEADER_SIZE];
81    if (stream->read(riff_header, RIFF_HEADER_SIZE) != RIFF_HEADER_SIZE) {
82        ALOGE("WebP header load failed");
83        return;
84    }
85    mData.size = CHUNK_HEADER_SIZE + GetLE32(riff_header + TAG_SIZE);
86    mData.bytes = new uint8_t[mData.size];
87    memcpy((void*)mData.bytes, riff_header, RIFF_HEADER_SIZE);
88
89    // Read rest of the bytes.
90    void* remaining_bytes = (void*)(mData.bytes + RIFF_HEADER_SIZE);
91    size_t remaining_size = mData.size - RIFF_HEADER_SIZE;
92    if (stream->read(remaining_bytes, remaining_size) != remaining_size) {
93        ALOGE("WebP full load failed");
94        return;
95    }
96
97    // Construct demux.
98    mDemux = WebPDemux(&mData);
99    if (!mDemux) {
100        ALOGE("Parsing of WebP container file failed");
101        return;
102    }
103    mLoopCount = WebPDemuxGetI(mDemux, WEBP_FF_LOOP_COUNT);
104    mFormatFlags = WebPDemuxGetI(mDemux, WEBP_FF_FORMAT_FLAGS);
105#if WEBP_DEBUG
106    ALOGD("FrameSequence_webp created with size = %d x %d, number of frames = %d, flags = 0x%X",
107          getWidth(), getHeight(), getFrameCount(), mFormatFlags);
108#endif
109    constructDependencyChain();
110}
111
112FrameSequence_webp::~FrameSequence_webp() {
113    WebPDemuxDelete(mDemux);
114    delete[] mData.bytes;
115    delete[] mIsKeyFrame;
116}
117
118FrameSequenceState* FrameSequence_webp::createState() const {
119    return new FrameSequenceState_webp(*this);
120}
121
122////////////////////////////////////////////////////////////////////////////////
123// draw helpers
124////////////////////////////////////////////////////////////////////////////////
125
126static bool willBeCleared(const WebPIterator& iter) {
127    return iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND;
128}
129
130// return true if area of 'target' completely covers area of 'covered'
131static bool checkIfCover(const WebPIterator& target, const WebPIterator& covered) {
132    const int covered_x_max = covered.x_offset + covered.width;
133    const int target_x_max = target.x_offset + target.width;
134    const int covered_y_max = covered.y_offset + covered.height;
135    const int target_y_max = target.y_offset + target.height;
136    return target.x_offset <= covered.x_offset
137           && covered_x_max <= target_x_max
138           && target.y_offset <= covered.y_offset
139           && covered_y_max <= target_y_max;
140}
141
142// Clear all pixels in a line to transparent.
143static void clearLine(Color8888* dst, int width) {
144    memset(dst, 0, width * sizeof(*dst));  // Note: Assumes TRANSPARENT == 0x0.
145}
146
147// Copy all pixels from 'src' to 'dst'.
148static void copyFrame(const Color8888* src, int srcStride, Color8888* dst, int dstStride,
149        int width, int height) {
150    for (int y = 0; y < height; y++) {
151        memcpy(dst, src, width * sizeof(*dst));
152        src += srcStride;
153        dst += dstStride;
154    }
155}
156
157////////////////////////////////////////////////////////////////////////////////
158// Frame sequence state
159////////////////////////////////////////////////////////////////////////////////
160
161FrameSequenceState_webp::FrameSequenceState_webp(const FrameSequence_webp& frameSequence) :
162        mFrameSequence(frameSequence) {
163    WebPInitDecoderConfig(&mDecoderConfig);
164    mDecoderConfig.output.is_external_memory = 1;
165    mDecoderConfig.output.colorspace = MODE_rgbA;  // Pre-multiplied alpha mode.
166    const int canvasWidth = mFrameSequence.getWidth();
167    const int canvasHeight = mFrameSequence.getHeight();
168    mPreservedBuffer = new Color8888[canvasWidth * canvasHeight];
169}
170
171FrameSequenceState_webp::~FrameSequenceState_webp() {
172    delete[] mPreservedBuffer;
173}
174
175void FrameSequenceState_webp::initializeFrame(const WebPIterator& currIter, Color8888* currBuffer,
176        int currStride, const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride) {
177    const int canvasWidth = mFrameSequence.getWidth();
178    const int canvasHeight = mFrameSequence.getHeight();
179    const bool currFrameIsKeyFrame = mFrameSequence.isKeyFrame(currIter.frame_num - 1);
180
181    if (currFrameIsKeyFrame) {  // Clear canvas.
182        for (int y = 0; y < canvasHeight; y++) {
183            Color8888* dst = currBuffer + y * currStride;
184            clearLine(dst, canvasWidth);
185        }
186    } else {
187        // Preserve previous frame as starting state of current frame.
188        copyFrame(prevBuffer, prevStride, currBuffer, currStride, canvasWidth, canvasHeight);
189
190        // Dispose previous frame rectangle to Background if needed.
191        bool prevFrameCompletelyCovered =
192                (!currIter.has_alpha || currIter.blend_method == WEBP_MUX_NO_BLEND) &&
193                checkIfCover(currIter, prevIter);
194        if ((prevIter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) &&
195                !prevFrameCompletelyCovered) {
196            Color8888* dst = currBuffer + prevIter.x_offset + prevIter.y_offset * currStride;
197            for (int j = 0; j < prevIter.height; j++) {
198                clearLine(dst, prevIter.width);
199                dst += currStride;
200            }
201        }
202    }
203}
204
205bool FrameSequenceState_webp::decodeFrame(const WebPIterator& currIter, Color8888* currBuffer,
206        int currStride, const WebPIterator& prevIter, const Color8888* prevBuffer, int prevStride) {
207    Color8888* dst = currBuffer + currIter.x_offset + currIter.y_offset * currStride;
208    mDecoderConfig.output.u.RGBA.rgba = (uint8_t*)dst;
209    mDecoderConfig.output.u.RGBA.stride = currStride * 4;
210    mDecoderConfig.output.u.RGBA.size = mDecoderConfig.output.u.RGBA.stride * currIter.height;
211
212    const WebPData& currFrame = currIter.fragment;
213    if (WebPDecode(currFrame.bytes, currFrame.size, &mDecoderConfig) != VP8_STATUS_OK) {
214        return false;
215    }
216
217    const int canvasWidth = mFrameSequence.getWidth();
218    const int canvasHeight = mFrameSequence.getHeight();
219    const bool currFrameIsKeyFrame = mFrameSequence.isKeyFrame(currIter.frame_num - 1);
220    // During the decoding of current frame, we may have set some pixels to be transparent
221    // (i.e. alpha < 255). However, the value of each of these pixels should have been determined
222    // by blending it against the value of that pixel in the previous frame if WEBP_MUX_BLEND was
223    // specified. So, we correct these pixels based on disposal method of the previous frame and
224    // the previous frame buffer.
225    if (currIter.blend_method == WEBP_MUX_BLEND && !currFrameIsKeyFrame) {
226        if (prevIter.dispose_method == WEBP_MUX_DISPOSE_NONE) {
227            for (int y = 0; y < currIter.height; y++) {
228                const int canvasY = currIter.y_offset + y;
229                for (int x = 0; x < currIter.width; x++) {
230                    const int canvasX = currIter.x_offset + x;
231                    Color8888& currPixel = currBuffer[canvasY * currStride + canvasX];
232                    // FIXME: Use alpha-blending when alpha is between 0 and 255.
233                    if (!(currPixel & COLOR_8888_ALPHA_MASK)) {
234                        const Color8888 prevPixel = prevBuffer[canvasY * prevStride + canvasX];
235                        currPixel = prevPixel;
236                    }
237                }
238            }
239        } else {  // prevIter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND
240            // Need to restore transparent pixels to as they were just after frame initialization.
241            // That is:
242            //   * Transparent if it belongs to previous frame rectangle <-- This is a no-op.
243            //   * Pixel in the previous canvas otherwise <-- Need to restore.
244            for (int y = 0; y < currIter.height; y++) {
245                const int canvasY = currIter.y_offset + y;
246                for (int x = 0; x < currIter.width; x++) {
247                    const int canvasX = currIter.x_offset + x;
248                    Color8888& currPixel = currBuffer[canvasY * currStride + canvasX];
249                    // FIXME: Use alpha-blending when alpha is between 0 and 255.
250                    if (!(currPixel & COLOR_8888_ALPHA_MASK) &&
251                            isFullFrame(prevIter, canvasWidth, canvasHeight)) {
252                        const Color8888 prevPixel = prevBuffer[canvasY * prevStride + canvasX];
253                        currPixel = prevPixel;
254                    }
255                }
256            }
257        }
258    }
259    return true;
260}
261
262long FrameSequenceState_webp::drawFrame(int frameNr,
263        Color8888* outputPtr, int outputPixelStride, int previousFrameNr) {
264    WebPDemuxer* demux = mFrameSequence.getDemuxer();
265    ALOG_ASSERT(demux, "Cannot drawFrame, mDemux is NULL");
266
267#if WEBP_DEBUG
268    ALOGD("  drawFrame called for frame# %d, previous frame# %d", frameNr, previousFrameNr);
269#endif
270
271    const int canvasWidth = mFrameSequence.getWidth();
272    const int canvasHeight = mFrameSequence.getHeight();
273
274    // Find the first frame to be decoded.
275    int start = max(previousFrameNr + 1, 0);
276    int earliestRequired = frameNr;
277    while (earliestRequired > start) {
278        if (mFrameSequence.isKeyFrame(earliestRequired)) {
279            start = earliestRequired;
280            break;
281        }
282        earliestRequired--;
283    }
284
285    WebPIterator currIter;
286    WebPIterator prevIter;
287    int ok = WebPDemuxGetFrame(demux, start, &currIter);  // Get frame number 'start - 1'.
288    ALOG_ASSERT(ok, "Could not retrieve frame# %d", start - 1);
289
290    // Use preserve buffer only if needed.
291    Color8888* prevBuffer = (frameNr == 0) ? outputPtr : mPreservedBuffer;
292    int prevStride = (frameNr == 0) ? outputPixelStride : canvasWidth;
293    Color8888* currBuffer = outputPtr;
294    int currStride = outputPixelStride;
295
296    for (int i = start; i <= frameNr; i++) {
297        prevIter = currIter;
298        ok = WebPDemuxGetFrame(demux, i + 1, &currIter);  // Get ith frame.
299        ALOG_ASSERT(ok, "Could not retrieve frame# %d", i);
300#if WEBP_DEBUG
301        ALOGD("      producing frame %d (has_alpha = %d, dispose = %s, blend = %s, duration = %d)",
302              i, currIter.has_alpha,
303              (currIter.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none" : "background",
304              (currIter.blend_method == WEBP_MUX_BLEND) ? "yes" : "no", currIter.duration);
305#endif
306        // We swap the prev/curr buffers as we go.
307        Color8888* tmpBuffer = prevBuffer;
308        prevBuffer = currBuffer;
309        currBuffer = tmpBuffer;
310
311        int tmpStride = prevStride;
312        prevStride = currStride;
313        currStride = tmpStride;
314
315#if WEBP_DEBUG
316        ALOGD("            prev = %p, curr = %p, out = %p, tmp = %p",
317              prevBuffer, currBuffer, outputPtr, mPreservedBuffer);
318#endif
319        // Process this frame.
320        initializeFrame(currIter, currBuffer, currStride, prevIter, prevBuffer, prevStride);
321
322        if (i == frameNr || !willBeCleared(currIter)) {
323            if (!decodeFrame(currIter, currBuffer, currStride, prevIter, prevBuffer, prevStride)) {
324                ALOGE("Error decoding frame# %d", i);
325                return -1;
326            }
327        }
328    }
329
330    if (outputPtr != currBuffer) {
331        copyFrame(currBuffer, currStride, outputPtr, outputPixelStride, canvasWidth, canvasHeight);
332    }
333
334    // Return last frame's delay.
335    const int frameCount = mFrameSequence.getFrameCount();
336    const int lastFrame = (frameNr + frameCount - 1) % frameCount;
337    ok = WebPDemuxGetFrame(demux, lastFrame, &currIter);
338    ALOG_ASSERT(ok, "Could not retrieve frame# %d", lastFrame - 1);
339    const int lastFrameDelay = currIter.duration;
340
341    WebPDemuxReleaseIterator(&currIter);
342    WebPDemuxReleaseIterator(&prevIter);
343
344    return lastFrameDelay;
345}
346
347////////////////////////////////////////////////////////////////////////////////
348// Registry
349////////////////////////////////////////////////////////////////////////////////
350
351#include "Registry.h"
352
353static bool isWebP(void* header, int header_size) {
354    const uint8_t* const header_str = (const uint8_t*)header;
355    return (header_size >= RIFF_HEADER_SIZE) &&
356            !memcmp("RIFF", header_str, 4) &&
357            !memcmp("WEBP", header_str + 8, 4);
358}
359
360static FrameSequence* createFramesequence(Stream* stream) {
361    return new FrameSequence_webp(stream);
362}
363
364static RegistryEntry gEntry = {
365        RIFF_HEADER_SIZE,
366        isWebP,
367        createFramesequence,
368        NULL,
369};
370static Registry gRegister(gEntry);
371
372