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