FrameSequence_gif.cpp revision 164b6937223c9f9cbb84b213740e3e80f940b193
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
22#include "FrameSequence_gif.h"
23
24#define GIF_DEBUG 0
25
26static int streamReader(GifFileType* fileType, GifByteType* out, int size) {
27    Stream* stream = (Stream*) fileType->UserData;
28    return (int) stream->read(out, size);
29}
30
31static Color8888 gifColorToColor8888(const GifColorType& color) {
32    return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue);
33}
34
35static long getDelayMs(GraphicsControlBlock& gcb) {
36    return gcb.DelayTime * 10;
37}
38
39static bool willBeCleared(const GraphicsControlBlock& gcb) {
40    return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS;
41}
42
43////////////////////////////////////////////////////////////////////////////////
44// Frame sequence
45////////////////////////////////////////////////////////////////////////////////
46
47FrameSequence_gif::FrameSequence_gif(Stream* stream) :
48        mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) {
49    mGif = DGifOpen(stream, streamReader, NULL);
50    if (!mGif) {
51        ALOGW("Gif load failed");
52        return;
53    }
54
55    if (DGifSlurp(mGif) != GIF_OK) {
56        ALOGW("Gif slurp failed");
57        DGifCloseFile(mGif, NULL);
58        mGif = NULL;
59        return;
60    }
61
62    long durationMs = 0;
63    int lastUnclearedFrame = -1;
64    mPreservedFrames = new bool[mGif->ImageCount];
65    mRestoringFrames = new int[mGif->ImageCount];
66
67    GraphicsControlBlock gcb;
68    for (int i = 0; i < mGif->ImageCount; i++) {
69        const SavedImage& image = mGif->SavedImages[i];
70
71        // find the loop extension pair
72        for (int j = 0; (j + 1) < image.ExtensionBlockCount; j++) {
73            ExtensionBlock* eb1 = image.ExtensionBlocks + j;
74            ExtensionBlock* eb2 = image.ExtensionBlocks + j + 1;
75            if (eb1->Function == APPLICATION_EXT_FUNC_CODE
76                    // look for "NETSCAPE2.0" app extension
77                    && eb1->ByteCount == 11
78                    && !memcmp((const char*)(eb1->Bytes), "NETSCAPE2.0", 11)
79                    // verify extension contents and get loop count
80                    && eb2->Function == CONTINUE_EXT_FUNC_CODE
81                    && eb2->ByteCount == 3
82                    && eb2->Bytes[0] == 1) {
83                mLoopCount = (int)(eb2->Bytes[2] << 8) + (int)(eb2->Bytes[1]);
84            }
85        }
86
87        DGifSavedExtensionToGCB(mGif, i, &gcb);
88
89        // timing
90        durationMs += getDelayMs(gcb);
91
92        // preserve logic
93        mPreservedFrames[i] = false;
94        mRestoringFrames[i] = -1;
95        if (gcb.DisposalMode == DISPOSE_PREVIOUS && lastUnclearedFrame >= 0) {
96            mPreservedFrames[lastUnclearedFrame] = true;
97            mRestoringFrames[i] = lastUnclearedFrame;
98        }
99        if (!willBeCleared(gcb)) {
100            lastUnclearedFrame = i;
101        }
102    }
103
104#if GIF_DEBUG
105    ALOGD("FrameSequence_gif created with size %d %d, frames %d dur %ld",
106            mGif->SWidth, mGif->SHeight, mGif->ImageCount, durationMs);
107    for (int i = 0; i < mGif->ImageCount; i++) {
108        DGifSavedExtensionToGCB(mGif, i, &gcb);
109        ALOGD("    Frame %d - must preserve %d, restore point %d, trans color %d",
110                i, mPreservedFrames[i], mRestoringFrames[i], gcb.TransparentColor);
111    }
112#endif
113
114    if (mGif->SColorMap) {
115        // calculate bg color
116        GraphicsControlBlock gcb;
117        DGifSavedExtensionToGCB(mGif, 0, &gcb);
118        if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
119            mBgColor = gifColorToColor8888(mGif->SColorMap->Colors[mGif->SBackGroundColor]);
120        }
121    }
122}
123
124FrameSequence_gif::~FrameSequence_gif() {
125    if (mGif) {
126        DGifCloseFile(mGif, NULL);
127    }
128    delete[] mPreservedFrames;
129    delete[] mRestoringFrames;
130}
131
132FrameSequenceState* FrameSequence_gif::createState() const {
133    return new FrameSequenceState_gif(*this);
134}
135
136////////////////////////////////////////////////////////////////////////////////
137// draw helpers
138////////////////////////////////////////////////////////////////////////////////
139
140// return true if area of 'target' is completely covers area of 'covered'
141static bool checkIfCover(const GifImageDesc& target, const GifImageDesc& covered) {
142    return target.Left <= covered.Left
143            && covered.Left + covered.Width <= target.Left + target.Width
144            && target.Top <= covered.Top
145            && covered.Top + covered.Height <= target.Top + target.Height;
146}
147
148static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap,
149                     int transparent, int width) {
150    for (; width > 0; width--, src++, dst++) {
151        if (*src != transparent) {
152            *dst = gifColorToColor8888(cmap->Colors[*src]);
153        }
154    }
155}
156
157static void setLineColor(Color8888* dst, Color8888 color, int width) {
158    for (; width > 0; width--, dst++) {
159        *dst = color;
160    }
161}
162
163static void getCopySize(const GifImageDesc& imageDesc, int maxWidth, int maxHeight,
164        GifWord& copyWidth, GifWord& copyHeight) {
165    copyWidth = imageDesc.Width;
166    if (imageDesc.Left + copyWidth > maxWidth) {
167        copyWidth = maxWidth - imageDesc.Left;
168    }
169    copyHeight = imageDesc.Height;
170    if (imageDesc.Top + copyHeight > maxHeight) {
171        copyHeight = maxHeight - imageDesc.Top;
172    }
173}
174
175////////////////////////////////////////////////////////////////////////////////
176// Frame sequence state
177////////////////////////////////////////////////////////////////////////////////
178
179FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) :
180    mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) {
181}
182
183FrameSequenceState_gif::~FrameSequenceState_gif() {
184       delete[] mPreserveBuffer;
185}
186
187void FrameSequenceState_gif::savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr) {
188    if (frameNr == mPreserveBufferFrame) return;
189
190    mPreserveBufferFrame = frameNr;
191    const int width = mFrameSequence.getWidth();
192    const int height = mFrameSequence.getHeight();
193    if (!mPreserveBuffer) {
194        mPreserveBuffer = new Color8888[width * height];
195    }
196    for (int y = 0; y < height; y++) {
197        memcpy(mPreserveBuffer + width * y,
198                outputPtr + outputPixelStride * y,
199                width * 4);
200    }
201}
202
203void FrameSequenceState_gif::restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride) {
204    const int width = mFrameSequence.getWidth();
205    const int height = mFrameSequence.getHeight();
206    if (!mPreserveBuffer) {
207        ALOGD("preserve buffer not allocated! ah!");
208        return;
209    }
210    for (int y = 0; y < height; y++) {
211        memcpy(outputPtr + outputPixelStride * y,
212                mPreserveBuffer + width * y,
213                width * 4);
214    }
215}
216
217long FrameSequenceState_gif::drawFrame(int frameNr,
218        Color8888* outputPtr, int outputPixelStride, int previousFrameNr) {
219
220    GifFileType* gif = mFrameSequence.getGif();
221    if (!gif) {
222        ALOGD("Cannot drawFrame, mGif is NULL");
223        return -1;
224    }
225
226#if GIF_DEBUG
227    ALOGD("      drawFrame on %p nr %d on addr %p, previous frame nr %d",
228            this, frameNr, outputPtr, previousFrameNr);
229#endif
230
231    const int height = mFrameSequence.getHeight();
232    const int width = mFrameSequence.getWidth();
233
234    GraphicsControlBlock gcb;
235
236    int start = max(previousFrameNr + 1, 0);
237
238    for (int i = max(start - 1, 0); i < frameNr; i++) {
239        int neededPreservedFrame = mFrameSequence.getRestoringFrame(i);
240        if (neededPreservedFrame >= 0 && (mPreserveBufferFrame != neededPreservedFrame)) {
241#if GIF_DEBUG
242            ALOGD("frame %d needs frame %d preserved, but %d is currently, so drawing from scratch",
243                    i, neededPreservedFrame, mPreserveBufferFrame);
244#endif
245            start = 0;
246        }
247    }
248
249    for (int i = start; i <= frameNr; i++) {
250        DGifSavedExtensionToGCB(gif, i, &gcb);
251        const SavedImage& frame = gif->SavedImages[i];
252
253#if GIF_DEBUG
254        bool frameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
255        ALOGD("producing frame %d, drawing frame %d (opaque %d, disp %d, del %d)",
256                frameNr, i, frameOpaque, gcb.DisposalMode, gcb.DelayTime);
257#endif
258        if (i == 0) {
259            //clear bitmap
260            Color8888 bgColor = mFrameSequence.getBackgroundColor();
261            for (int y = 0; y < height; y++) {
262                for (int x = 0; x < width; x++) {
263                    outputPtr[y * outputPixelStride + x] = bgColor;
264                }
265            }
266        } else {
267            GraphicsControlBlock prevGcb;
268            DGifSavedExtensionToGCB(gif, i - 1, &prevGcb);
269            const SavedImage& prevFrame = gif->SavedImages[i - 1];
270            bool prevFrameDisposed = willBeCleared(prevGcb);
271
272            bool newFrameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
273            bool prevFrameCompletelyCovered = newFrameOpaque
274                    && checkIfCover(frame.ImageDesc, prevFrame.ImageDesc);
275
276            if (prevFrameDisposed && !prevFrameCompletelyCovered) {
277                switch (prevGcb.DisposalMode) {
278                case DISPOSE_BACKGROUND: {
279                    Color8888* dst = outputPtr + prevFrame.ImageDesc.Left +
280                            prevFrame.ImageDesc.Top * outputPixelStride;
281
282                    GifWord copyWidth, copyHeight;
283                    getCopySize(prevFrame.ImageDesc, width, height, copyWidth, copyHeight);
284                    for (; copyHeight > 0; copyHeight--) {
285                        setLineColor(dst, TRANSPARENT, copyWidth);
286                        dst += outputPixelStride;
287                    }
288                } break;
289                case DISPOSE_PREVIOUS: {
290                    restorePreserveBuffer(outputPtr, outputPixelStride);
291                } break;
292                }
293            }
294
295            if (mFrameSequence.getPreservedFrame(i - 1)) {
296                // currently drawn frame will be restored by a following DISPOSE_PREVIOUS draw, so
297                // we preserve it
298                savePreserveBuffer(outputPtr, outputPixelStride, i - 1);
299            }
300        }
301
302        bool willBeCleared = gcb.DisposalMode == DISPOSE_BACKGROUND
303                || gcb.DisposalMode == DISPOSE_PREVIOUS;
304        if (i == frameNr || !willBeCleared) {
305            const ColorMapObject* cmap = gif->SColorMap;
306            if (frame.ImageDesc.ColorMap) {
307                cmap = frame.ImageDesc.ColorMap;
308            }
309
310            if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
311                ALOGW("Warning: potentially corrupt color map");
312            }
313
314            const unsigned char* src = (unsigned char*)frame.RasterBits;
315            Color8888* dst = outputPtr + frame.ImageDesc.Left +
316                    frame.ImageDesc.Top * outputPixelStride;
317            GifWord copyWidth, copyHeight;
318            getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight);
319            for (; copyHeight > 0; copyHeight--) {
320                copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth);
321                src += frame.ImageDesc.Width;
322                dst += outputPixelStride;
323            }
324        }
325    }
326
327    // return last frame's delay
328    const int maxFrame = gif->ImageCount;
329    const int lastFrame = (frameNr + maxFrame - 1) % maxFrame;
330    DGifSavedExtensionToGCB(gif, lastFrame, &gcb);
331    return getDelayMs(gcb);
332}
333
334////////////////////////////////////////////////////////////////////////////////
335// Registry
336////////////////////////////////////////////////////////////////////////////////
337
338#include "Registry.h"
339
340static bool isGif(void* header, int header_size) {
341    return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN)
342            || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN)
343            || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN);
344}
345
346static bool acceptsBuffers() {
347    return false;
348}
349
350static FrameSequence* createFramesequence(Stream* stream) {
351    return new FrameSequence_gif(stream);
352}
353
354static RegistryEntry gEntry = {
355        GIF_STAMP_LEN,
356        isGif,
357        createFramesequence,
358        NULL,
359        acceptsBuffers,
360};
361static Registry gRegister(gEntry);
362
363