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