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