FrameSequence_gif.cpp revision edf9b83e5df6e178e5bce7ffb0fe6ff0c9248a7c
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 mLoopCount(1), 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 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; 84 if (eb1->Function == APPLICATION_EXT_FUNC_CODE 85 // look for "NETSCAPE2.0" app extension 86 && eb1->ByteCount == 11 87 && !memcmp((const char*)(eb1->Bytes), "NETSCAPE2.0", 11) 88 // verify extension contents and get loop count 89 && eb2->Function == CONTINUE_EXT_FUNC_CODE 90 && eb2->ByteCount == 3 91 && eb2->Bytes[0] == 1) { 92 mLoopCount = (int)(eb2->Bytes[2] << 8) + (int)(eb2->Bytes[1]); 93 } 94 } 95 96 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 336 // return last frame's delay 337 const int maxFrame = gif->ImageCount; 338 const int lastFrame = (frameNr + maxFrame - 1) % maxFrame; 339 DGifSavedExtensionToGCB(gif, lastFrame, &gcb); 340 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 367