Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 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 |
| 28 | static const long MIN_DELAY_MS = 20; |
| 29 | static const long DEFAULT_DELAY_MS = 100; |
| 30 | |
| 31 | static int streamReader(GifFileType* fileType, GifByteType* out, int size) { |
| 32 | Stream* stream = (Stream*) fileType->UserData; |
| 33 | return (int) stream->read(out, size); |
| 34 | } |
| 35 | |
| 36 | static Color8888 gifColorToColor8888(const GifColorType& color) { |
| 37 | return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue); |
| 38 | } |
| 39 | |
| 40 | static 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 | |
| 48 | static bool willBeCleared(const GraphicsControlBlock& gcb) { |
| 49 | return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS; |
| 50 | } |
| 51 | |
| 52 | //////////////////////////////////////////////////////////////////////////////// |
| 53 | // Frame sequence |
| 54 | //////////////////////////////////////////////////////////////////////////////// |
| 55 | |
| 56 | FrameSequence_gif::FrameSequence_gif(Stream* stream) : |
Chris Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 57 | mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) { |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 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"); |
Leon Scroggins III | ca3990d | 2017-03-14 10:45:38 -0400 | [diff] [blame] | 66 | DGifCloseFile(mGif, NULL); |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 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 Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 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; |
Chris Craik | 9d34bc3 | 2014-04-09 16:31:18 -0700 | [diff] [blame] | 84 | if (eb1->Function == APPLICATION_EXT_FUNC_CODE |
Chris Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 85 | // look for "NETSCAPE2.0" app extension |
Chris Craik | 9d34bc3 | 2014-04-09 16:31:18 -0700 | [diff] [blame] | 86 | && eb1->ByteCount == 11 |
| 87 | && !memcmp((const char*)(eb1->Bytes), "NETSCAPE2.0", 11) |
Chris Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 88 | // verify extension contents and get loop count |
Chris Craik | 9d34bc3 | 2014-04-09 16:31:18 -0700 | [diff] [blame] | 89 | && eb2->Function == CONTINUE_EXT_FUNC_CODE |
| 90 | && eb2->ByteCount == 3 |
| 91 | && eb2->Bytes[0] == 1) { |
Urvang Joshi | edf9b83 | 2014-04-17 18:59:30 -0700 | [diff] [blame] | 92 | mLoopCount = (int)(eb2->Bytes[2] << 8) + (int)(eb2->Bytes[1]); |
Chris Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 93 | } |
| 94 | } |
| 95 | |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 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 | |
| 133 | FrameSequence_gif::~FrameSequence_gif() { |
| 134 | if (mGif) { |
Leon Scroggins III | ca3990d | 2017-03-14 10:45:38 -0400 | [diff] [blame] | 135 | DGifCloseFile(mGif, NULL); |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 136 | } |
| 137 | delete[] mPreservedFrames; |
| 138 | delete[] mRestoringFrames; |
| 139 | } |
| 140 | |
| 141 | FrameSequenceState* 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' |
| 150 | static 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 | |
| 157 | static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap, |
| 158 | int transparent, int width) { |
| 159 | for (; width > 0; width--, src++, dst++) { |
Chris Craik | 59ab39b | 2016-10-18 10:17:41 -0700 | [diff] [blame] | 160 | if (*src != transparent && *src < cmap->ColorCount) { |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 161 | *dst = gifColorToColor8888(cmap->Colors[*src]); |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | static void setLineColor(Color8888* dst, Color8888 color, int width) { |
| 167 | for (; width > 0; width--, dst++) { |
| 168 | *dst = color; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | static 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 | |
| 188 | FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) : |
| 189 | mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) { |
| 190 | } |
| 191 | |
| 192 | FrameSequenceState_gif::~FrameSequenceState_gif() { |
| 193 | delete[] mPreserveBuffer; |
| 194 | } |
| 195 | |
| 196 | void 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 | |
| 212 | void 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 | |
| 226 | long 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 | |
Chris Craik | 336e4ba | 2017-11-02 13:33:03 -0700 | [diff] [blame] | 319 | // If a cmap is missing, the frame can't be decoded, so we skip it. |
| 320 | if (cmap) { |
| 321 | const unsigned char* src = (unsigned char*)frame.RasterBits; |
| 322 | Color8888* dst = outputPtr + frame.ImageDesc.Left + |
| 323 | frame.ImageDesc.Top * outputPixelStride; |
| 324 | GifWord copyWidth, copyHeight; |
| 325 | getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight); |
| 326 | for (; copyHeight > 0; copyHeight--) { |
| 327 | copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth); |
| 328 | src += frame.ImageDesc.Width; |
| 329 | dst += outputPixelStride; |
| 330 | } |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 331 | } |
| 332 | } |
| 333 | } |
| 334 | |
Chris Craik | e36c5d6 | 2014-01-13 19:37:04 -0800 | [diff] [blame] | 335 | // return last frame's delay |
| 336 | const int maxFrame = gif->ImageCount; |
| 337 | const int lastFrame = (frameNr + maxFrame - 1) % maxFrame; |
| 338 | DGifSavedExtensionToGCB(gif, lastFrame, &gcb); |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 339 | return getDelayMs(gcb); |
| 340 | } |
| 341 | |
| 342 | //////////////////////////////////////////////////////////////////////////////// |
| 343 | // Registry |
| 344 | //////////////////////////////////////////////////////////////////////////////// |
| 345 | |
| 346 | #include "Registry.h" |
| 347 | |
| 348 | static bool isGif(void* header, int header_size) { |
| 349 | return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN) |
| 350 | || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN) |
| 351 | || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN); |
| 352 | } |
| 353 | |
Chris Craik | 6a61141 | 2015-04-01 14:54:12 -0700 | [diff] [blame] | 354 | static bool acceptsBuffers() { |
| 355 | return false; |
| 356 | } |
| 357 | |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 358 | static FrameSequence* createFramesequence(Stream* stream) { |
| 359 | return new FrameSequence_gif(stream); |
| 360 | } |
| 361 | |
| 362 | static RegistryEntry gEntry = { |
| 363 | GIF_STAMP_LEN, |
| 364 | isGif, |
| 365 | createFramesequence, |
| 366 | NULL, |
Chris Craik | 6a61141 | 2015-04-01 14:54:12 -0700 | [diff] [blame] | 367 | acceptsBuffers, |
Chris Craik | a3ac0a2 | 2014-01-06 12:43:42 -0800 | [diff] [blame] | 368 | }; |
| 369 | static Registry gRegister(gEntry); |
| 370 | |