OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved. |
| 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 package jp.tomorrowkey.android.gifplayer; |
| 18 |
| 19 import android.graphics.Bitmap; |
| 20 import android.graphics.Canvas; |
| 21 import android.graphics.ColorFilter; |
| 22 import android.graphics.Paint; |
| 23 import android.graphics.PixelFormat; |
| 24 import android.graphics.Rect; |
| 25 import android.graphics.drawable.Animatable; |
| 26 import android.graphics.drawable.Drawable; |
| 27 import android.os.Handler; |
| 28 import android.os.HandlerThread; |
| 29 import android.os.Looper; |
| 30 import android.os.Message; |
| 31 import android.os.SystemClock; |
| 32 import android.util.Log; |
| 33 |
| 34 /** |
| 35 * A base GIF Drawable with support for animations. |
| 36 * |
| 37 * Inspired by http://code.google.com/p/android-gifview/ |
| 38 */ |
| 39 public class BaseGifDrawable extends Drawable implements Runnable, Animatable, |
| 40 android.os.Handler.Callback { |
| 41 |
| 42 private static final String TAG = "GifDrawable"; |
| 43 |
| 44 // Max decoder pixel stack size |
| 45 private static final int MAX_STACK_SIZE = 4096; |
| 46 private static final int MAX_BITS = 4097; |
| 47 |
| 48 // Frame disposal methods |
| 49 private static final int DISPOSAL_METHOD_UNKNOWN = 0; |
| 50 private static final int DISPOSAL_METHOD_LEAVE = 1; |
| 51 private static final int DISPOSAL_METHOD_BACKGROUND = 2; |
| 52 private static final int DISPOSAL_METHOD_RESTORE = 3; |
| 53 |
| 54 // Message types |
| 55 private static final int READ_FRAME_REQ = 10; |
| 56 private static final int READ_FRAME_RESP = 11; |
| 57 private static final int RESET_DECODER = 12; |
| 58 |
| 59 // Specifies the minimum amount of time before a subsequent frame will be re
ndered. |
| 60 private static final int MIN_FRAME_SCHEDULE_DELAY_MS = 5; |
| 61 |
| 62 private static final byte[] NETSCAPE2_0 = "NETSCAPE2.0".getBytes(); |
| 63 |
| 64 private static Paint sPaint; |
| 65 private static Paint sScalePaint; |
| 66 |
| 67 protected final BaseGifImage mGifImage; |
| 68 private final byte[] mData; |
| 69 |
| 70 private int mPosition; |
| 71 protected int mIntrinsicWidth; |
| 72 protected int mIntrinsicHeight; |
| 73 |
| 74 private int mWidth; |
| 75 private int mHeight; |
| 76 |
| 77 protected Bitmap mBitmap; |
| 78 protected int[] mColors; |
| 79 private boolean mScale; |
| 80 private float mScaleFactor; |
| 81 |
| 82 // The following are marked volatile because they are read/written in the ba
ckground decoder |
| 83 // thread and read from the UI thread. No further synchronization is needed
because their |
| 84 // values will only ever change from at most once, and it is safe to lazily
detect the change |
| 85 // in the UI thread. |
| 86 private volatile boolean mError; |
| 87 private volatile boolean mDone; |
| 88 private volatile boolean mAnimateOnLoad = true; |
| 89 |
| 90 private int mBackgroundColor; |
| 91 private boolean mLocalColorTableUsed; |
| 92 private int mLocalColorTableSize; |
| 93 private int[] mLocalColorTable; |
| 94 private int[] mActiveColorTable; |
| 95 private boolean mInterlace; |
| 96 |
| 97 // Each frame specifies a sub-region of the image that should be updated. T
he values are |
| 98 // clamped to the GIF dimensions if they exceed the intrinsic dimensions. |
| 99 private int mFrameX, mFrameY, mFrameWidth, mFrameHeight; |
| 100 |
| 101 // This specifies the width of the actual data within a GIF frame. It will
be equal to |
| 102 // mFrameWidth unless the frame sub-region was clamped to prevent exceeding
the intrinsic |
| 103 // dimensions. |
| 104 private int mFrameStep; |
| 105 |
| 106 private byte[] mBlock = new byte[256]; |
| 107 private int mDisposalMethod = DISPOSAL_METHOD_BACKGROUND; |
| 108 private boolean mTransparency; |
| 109 private int mTransparentColorIndex; |
| 110 |
| 111 // LZW decoder working arrays |
| 112 private short[] mPrefix = new short[MAX_STACK_SIZE]; |
| 113 private byte[] mSuffix = new byte[MAX_STACK_SIZE]; |
| 114 private byte[] mPixelStack = new byte[MAX_STACK_SIZE + 1]; |
| 115 private byte[] mPixels; |
| 116 |
| 117 private boolean mBackupSaved; |
| 118 private int[] mBackup; |
| 119 |
| 120 private int mFrameCount; |
| 121 |
| 122 private long mLastFrameTime; |
| 123 |
| 124 private boolean mRunning; |
| 125 protected int mFrameDelay; |
| 126 private int mNextFrameDelay; |
| 127 protected boolean mScheduled; |
| 128 private boolean mAnimationEnabled = true; |
| 129 private final Handler mHandler = new Handler(Looper.getMainLooper(), this); |
| 130 private static DecoderThread sDecoderThread; |
| 131 private static Handler sDecoderHandler; |
| 132 |
| 133 private boolean mRecycled; |
| 134 protected boolean mFirstFrameReady; |
| 135 private boolean mEndOfFile; |
| 136 private int mLoopCount = 0; // 0 to repeat endlessly. |
| 137 private int mLoopIndex = 0; |
| 138 |
| 139 private final Bitmap.Config mBitmapConfig; |
| 140 private boolean mFirstFrame = true; |
| 141 |
| 142 public BaseGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig) { |
| 143 this.mBitmapConfig = bitmapConfig; |
| 144 |
| 145 // Create the background decoder thread, if necessary. |
| 146 if (sDecoderThread == null) { |
| 147 sDecoderThread = new DecoderThread(); |
| 148 sDecoderThread.start(); |
| 149 sDecoderHandler = new Handler(sDecoderThread.getLooper(), sDecoderTh
read); |
| 150 } |
| 151 |
| 152 if (sPaint == null) { |
| 153 sPaint = new Paint(Paint.FILTER_BITMAP_FLAG); |
| 154 sScalePaint = new Paint(Paint.FILTER_BITMAP_FLAG); |
| 155 sScalePaint.setFilterBitmap(true); |
| 156 } |
| 157 |
| 158 mGifImage = gifImage; |
| 159 mData = gifImage.getData(); |
| 160 mPosition = mGifImage.mHeaderSize; |
| 161 mFrameWidth = mFrameStep = mIntrinsicWidth = gifImage.getWidth(); |
| 162 mFrameHeight = mIntrinsicHeight = gifImage.getHeight(); |
| 163 mBackgroundColor = mGifImage.mBackgroundColor; |
| 164 mError = mGifImage.mError; |
| 165 |
| 166 if (!mError) { |
| 167 try { |
| 168 mBitmap = Bitmap.createBitmap(mIntrinsicWidth, mIntrinsicHeight,
mBitmapConfig); |
| 169 if (mBitmap == null) { |
| 170 throw new OutOfMemoryError("Cannot allocate bitmap"); |
| 171 } |
| 172 |
| 173 int pixelCount = mIntrinsicWidth * mIntrinsicHeight; |
| 174 mColors = new int[pixelCount]; |
| 175 mPixels = new byte[pixelCount]; |
| 176 |
| 177 mWidth = mIntrinsicHeight; |
| 178 mHeight = mIntrinsicHeight; |
| 179 |
| 180 // Read the first frame |
| 181 sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_F
RAME_REQ, this)); |
| 182 } catch (OutOfMemoryError e) { |
| 183 mError = true; |
| 184 } |
| 185 } |
| 186 } |
| 187 |
| 188 /** |
| 189 * Sets the loop count for multi-frame animation. |
| 190 */ |
| 191 public void setLoopCount(int loopCount) { |
| 192 mLoopCount = loopCount; |
| 193 } |
| 194 |
| 195 /** |
| 196 * Returns the loop count for multi-frame animation. |
| 197 */ |
| 198 public int getLoopCount() { |
| 199 return mLoopCount; |
| 200 } |
| 201 |
| 202 /** |
| 203 * Sets whether to start animation on load or not. |
| 204 */ |
| 205 public void setAnimateOnLoad(boolean animateOnLoad) { |
| 206 mAnimateOnLoad = animateOnLoad; |
| 207 } |
| 208 |
| 209 /** |
| 210 * Returns {@code true} if the GIF is valid and {@code false} otherwise. |
| 211 */ |
| 212 public boolean isValid() { |
| 213 return !mError && mFirstFrameReady; |
| 214 } |
| 215 |
| 216 public void onRecycle() { |
| 217 if (mBitmap != null) { |
| 218 mBitmap.recycle(); |
| 219 } |
| 220 mBitmap = null; |
| 221 mRecycled = true; |
| 222 } |
| 223 |
| 224 /** |
| 225 * Enables or disables the GIF from animating. GIF animations are enabled by
default. |
| 226 */ |
| 227 public void setAnimationEnabled(boolean animationEnabled) { |
| 228 if (mAnimationEnabled == animationEnabled) { |
| 229 return; |
| 230 } |
| 231 |
| 232 mAnimationEnabled = animationEnabled; |
| 233 if (mAnimationEnabled) { |
| 234 start(); |
| 235 } else { |
| 236 stop(); |
| 237 } |
| 238 } |
| 239 |
| 240 @Override |
| 241 protected void onBoundsChange(Rect bounds) { |
| 242 super.onBoundsChange(bounds); |
| 243 mWidth = bounds.width(); |
| 244 mHeight = bounds.height(); |
| 245 mScale = mWidth != mIntrinsicWidth && mHeight != mIntrinsicHeight; |
| 246 if (mScale) { |
| 247 mScaleFactor = Math.max((float) mWidth / mIntrinsicWidth, |
| 248 (float) mHeight / mIntrinsicHeight); |
| 249 } |
| 250 |
| 251 if (!mError && !mRecycled) { |
| 252 // Request that the decoder reset itself |
| 253 sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(RESET_DECO
DER, this)); |
| 254 } |
| 255 } |
| 256 |
| 257 @Override |
| 258 public boolean setVisible(boolean visible, boolean restart) { |
| 259 boolean changed = super.setVisible(visible, restart); |
| 260 if (visible) { |
| 261 if (changed || restart) { |
| 262 start(); |
| 263 } |
| 264 } else { |
| 265 stop(); |
| 266 } |
| 267 return changed; |
| 268 } |
| 269 |
| 270 @Override |
| 271 public void draw(Canvas canvas) { |
| 272 if (mError || mWidth == 0 || mHeight == 0 || mRecycled || !mFirstFrameRe
ady) { |
| 273 return; |
| 274 } |
| 275 |
| 276 if (mScale) { |
| 277 canvas.save(); |
| 278 canvas.scale(mScaleFactor, mScaleFactor, 0, 0); |
| 279 canvas.drawBitmap(mBitmap, 0, 0, sScalePaint); |
| 280 canvas.restore(); |
| 281 } else { |
| 282 canvas.drawBitmap(mBitmap, 0, 0, sPaint); |
| 283 } |
| 284 |
| 285 if (mRunning) { |
| 286 if (!mScheduled) { |
| 287 // Schedule the next frame at mFrameDelay milliseconds from the
previous frame or |
| 288 // the minimum sceduling delay from now, whichever is later. |
| 289 mLastFrameTime = Math.max( |
| 290 mLastFrameTime + mFrameDelay, |
| 291 SystemClock.uptimeMillis() + MIN_FRAME_SCHEDULE_DELAY_MS); |
| 292 scheduleSelf(this, mLastFrameTime); |
| 293 } |
| 294 } else if (!mDone) { |
| 295 start(); |
| 296 } else { |
| 297 unscheduleSelf(this); |
| 298 } |
| 299 } |
| 300 |
| 301 @Override |
| 302 public int getIntrinsicWidth() { |
| 303 return mIntrinsicWidth; |
| 304 } |
| 305 |
| 306 @Override |
| 307 public int getIntrinsicHeight() { |
| 308 return mIntrinsicHeight; |
| 309 } |
| 310 |
| 311 @Override |
| 312 public int getOpacity() { |
| 313 return PixelFormat.UNKNOWN; |
| 314 } |
| 315 |
| 316 @Override |
| 317 public void setAlpha(int alpha) { |
| 318 } |
| 319 |
| 320 @Override |
| 321 public void setColorFilter(ColorFilter cf) { |
| 322 } |
| 323 |
| 324 @Override |
| 325 public boolean isRunning() { |
| 326 return mRunning; |
| 327 } |
| 328 |
| 329 @Override |
| 330 public void start() { |
| 331 if (!isRunning()) { |
| 332 mRunning = true; |
| 333 if (!mAnimateOnLoad) { |
| 334 mDone = true; |
| 335 } |
| 336 mLastFrameTime = SystemClock.uptimeMillis(); |
| 337 run(); |
| 338 } |
| 339 } |
| 340 |
| 341 @Override |
| 342 public void stop() { |
| 343 if (isRunning()) { |
| 344 unscheduleSelf(this); |
| 345 } |
| 346 } |
| 347 |
| 348 @Override |
| 349 public void scheduleSelf(Runnable what, long when) { |
| 350 if (mAnimationEnabled) { |
| 351 super.scheduleSelf(what, when); |
| 352 mScheduled = true; |
| 353 } |
| 354 } |
| 355 |
| 356 @Override |
| 357 public void unscheduleSelf(Runnable what) { |
| 358 super.unscheduleSelf(what); |
| 359 mRunning = false; |
| 360 } |
| 361 |
| 362 /** |
| 363 * Moves to the next frame. |
| 364 */ |
| 365 @Override |
| 366 public void run() { |
| 367 if (mRecycled) { |
| 368 return; |
| 369 } |
| 370 |
| 371 // Send request to decoder to read the next frame |
| 372 if (!mDone) { |
| 373 sDecoderHandler.sendMessage(sDecoderHandler.obtainMessage(READ_FRAME
_REQ, this)); |
| 374 } |
| 375 } |
| 376 |
| 377 /** |
| 378 * Restarts decoding the image from the beginning. Called from the backgrou
nd thread. |
| 379 */ |
| 380 private void reset() { |
| 381 // Return to the position of the first image frame in the stream. |
| 382 mPosition = mGifImage.mHeaderSize; |
| 383 mBackupSaved = false; |
| 384 mFrameCount = 0; |
| 385 mDisposalMethod = DISPOSAL_METHOD_UNKNOWN; |
| 386 } |
| 387 |
| 388 /** |
| 389 * Restarts animation if a limited number of loops of animation have been pr
eviously done. |
| 390 */ |
| 391 public void restartAnimation() { |
| 392 if (mDone && mLoopCount > 0) { |
| 393 reset(); |
| 394 mDone = false; |
| 395 mLoopIndex = 0; |
| 396 run(); |
| 397 } |
| 398 } |
| 399 |
| 400 /** |
| 401 * Reads color table as 256 RGB integer values. Called from the background
thread. |
| 402 * |
| 403 * @param ncolors int number of colors to read |
| 404 */ |
| 405 private void readColorTable(int[] colorTable, int ncolors) { |
| 406 for (int i = 0; i < ncolors; i++) { |
| 407 int r = mData[mPosition++] & 0xff; |
| 408 int g = mData[mPosition++] & 0xff; |
| 409 int b = mData[mPosition++] & 0xff; |
| 410 colorTable[i] = 0xff000000 | (r << 16) | (g << 8) | b; |
| 411 } |
| 412 } |
| 413 |
| 414 /** |
| 415 * Reads GIF content blocks. Called from the background thread. |
| 416 * |
| 417 * @return true if the next frame has been parsed successfully, false if EOF |
| 418 * has been reached |
| 419 */ |
| 420 private void readNextFrame() { |
| 421 // Don't clear the image if it is a terminator. |
| 422 if ((mData[mPosition] & 0xff) == 0x3b) { |
| 423 mEndOfFile = true; |
| 424 return; |
| 425 } |
| 426 disposeOfLastFrame(); |
| 427 |
| 428 mDisposalMethod = DISPOSAL_METHOD_UNKNOWN; |
| 429 mTransparency = false; |
| 430 |
| 431 mEndOfFile = false; |
| 432 mNextFrameDelay = 100; |
| 433 mLocalColorTable = null; |
| 434 |
| 435 while (true) { |
| 436 int code = mData[mPosition++] & 0xff; |
| 437 switch (code) { |
| 438 case 0: // Empty block, ignore |
| 439 break; |
| 440 case 0x21: // Extension. Extensions precede the corresponding i
mage. |
| 441 code = mData[mPosition++] & 0xff; |
| 442 switch (code) { |
| 443 case 0xf9: // graphics control extension |
| 444 readGraphicControlExt(); |
| 445 break; |
| 446 case 0xff: // application extension |
| 447 readBlock(); |
| 448 boolean netscape = true; |
| 449 for (int i = 0; i < NETSCAPE2_0.length; i++) { |
| 450 if (mBlock[i] != NETSCAPE2_0[i]) { |
| 451 netscape = false; |
| 452 break; |
| 453 } |
| 454 } |
| 455 if (netscape) { |
| 456 readNetscapeExtension(); |
| 457 } else { |
| 458 skip(); // don't care |
| 459 } |
| 460 break; |
| 461 case 0xfe:// comment extension |
| 462 skip(); |
| 463 break; |
| 464 case 0x01:// plain text extension |
| 465 skip(); |
| 466 break; |
| 467 default: // uninteresting extension |
| 468 skip(); |
| 469 } |
| 470 break; |
| 471 |
| 472 case 0x2C: // Image separator |
| 473 readBitmap(); |
| 474 return; |
| 475 |
| 476 case 0x3b: // Terminator |
| 477 mEndOfFile = true; |
| 478 return; |
| 479 |
| 480 default: // We don't know what this is. Just skip it. |
| 481 break; |
| 482 } |
| 483 } |
| 484 } |
| 485 |
| 486 /** |
| 487 * Disposes of the previous frame. Called from the background thread. |
| 488 */ |
| 489 private void disposeOfLastFrame() { |
| 490 if (mFirstFrame) { |
| 491 mFirstFrame = false; |
| 492 return; |
| 493 } |
| 494 switch (mDisposalMethod) { |
| 495 case DISPOSAL_METHOD_UNKNOWN: |
| 496 case DISPOSAL_METHOD_LEAVE: { |
| 497 mBackupSaved = false; |
| 498 break; |
| 499 } |
| 500 case DISPOSAL_METHOD_RESTORE: { |
| 501 if (mBackupSaved) { |
| 502 System.arraycopy(mBackup, 0, mColors, 0, mBackup.length); |
| 503 } |
| 504 break; |
| 505 } |
| 506 case DISPOSAL_METHOD_BACKGROUND: { |
| 507 mBackupSaved = false; |
| 508 |
| 509 // Fill last image rect area with background color |
| 510 int color = 0; |
| 511 if (!mTransparency) { |
| 512 color = mBackgroundColor; |
| 513 } |
| 514 for (int i = 0; i < mFrameHeight; i++) { |
| 515 int n1 = (mFrameY + i) * mIntrinsicWidth + mFrameX; |
| 516 int n2 = n1 + mFrameWidth; |
| 517 for (int k = n1; k < n2; k++) { |
| 518 mColors[k] = color; |
| 519 } |
| 520 } |
| 521 break; |
| 522 } |
| 523 } |
| 524 } |
| 525 |
| 526 /** |
| 527 * Reads Graphics Control Extension values. Called from the background thre
ad. |
| 528 */ |
| 529 private void readGraphicControlExt() { |
| 530 mPosition++; // Block size, fixed |
| 531 |
| 532 int packed = mData[mPosition++] & 0xff; // Packed fields |
| 533 |
| 534 mDisposalMethod = (packed & 0x1c) >> 2; // Disposal method |
| 535 mTransparency = (packed & 1) != 0; |
| 536 mNextFrameDelay = readShort() * 10; // Delay in milliseconds |
| 537 |
| 538 // It seems that there are broken tools out there that set a 0ms or 10ms |
| 539 // timeout when they really want a "default" one. |
| 540 // Following WebKit's lead (http://trac.webkit.org/changeset/73295) |
| 541 // we use 10 frames per second as the default frame rate. |
| 542 if (mNextFrameDelay <= 10) { |
| 543 mNextFrameDelay = 100; |
| 544 } |
| 545 |
| 546 mTransparentColorIndex = mData[mPosition++] & 0xff; |
| 547 |
| 548 mPosition++; // Block terminator - ignore |
| 549 } |
| 550 |
| 551 /** |
| 552 * Reads Netscape extension to obtain iteration count. Called from the back
ground thread. |
| 553 */ |
| 554 private void readNetscapeExtension() { |
| 555 int count; |
| 556 do { |
| 557 count = readBlock(); |
| 558 } while ((count > 0) && !mError); |
| 559 } |
| 560 |
| 561 /** |
| 562 * Reads next frame image. Called from the background thread. |
| 563 */ |
| 564 private void readBitmap() { |
| 565 mFrameX = readShort(); // (sub)image position & size |
| 566 mFrameY = readShort(); |
| 567 |
| 568 int width = readShort(); |
| 569 int height = readShort(); |
| 570 |
| 571 // Clamp the frame dimensions to the intrinsic dimensions. |
| 572 mFrameWidth = Math.min(width, mIntrinsicWidth - mFrameX); |
| 573 mFrameHeight = Math.min(height, mIntrinsicHeight - mFrameY); |
| 574 |
| 575 // The frame step is set to the specfied frame width before clamping. |
| 576 mFrameStep = width; |
| 577 |
| 578 // Increase the size of the decoding buffer if necessary. |
| 579 int framePixelCount = width * height; |
| 580 if (framePixelCount > mPixels.length) { |
| 581 mPixels = new byte[framePixelCount]; |
| 582 } |
| 583 |
| 584 int packed = mData[mPosition++] & 0xff; |
| 585 // 3 - sort flag |
| 586 // 4-5 - reserved lctSize = 2 << (packed & 7); |
| 587 // 6-8 - local color table size |
| 588 mInterlace = (packed & 0x40) != 0; |
| 589 mLocalColorTableUsed = (packed & 0x80) != 0; // 1 - local color table fl
ag interlace |
| 590 mLocalColorTableSize = (int) Math.pow(2, (packed & 0x07) + 1); |
| 591 |
| 592 if (mLocalColorTableUsed) { |
| 593 if (mLocalColorTable == null) { |
| 594 mLocalColorTable = new int[256]; |
| 595 } |
| 596 readColorTable(mLocalColorTable, mLocalColorTableSize); |
| 597 mActiveColorTable = mLocalColorTable; |
| 598 } else { |
| 599 mActiveColorTable = mGifImage.mGlobalColorTable; |
| 600 if (mGifImage.mBackgroundIndex == mTransparentColorIndex) { |
| 601 mBackgroundColor = 0; |
| 602 } |
| 603 } |
| 604 int savedColor = 0; |
| 605 if (mTransparency) { |
| 606 savedColor = mActiveColorTable[mTransparentColorIndex]; |
| 607 mActiveColorTable[mTransparentColorIndex] = 0; |
| 608 } |
| 609 |
| 610 if (mActiveColorTable == null) { |
| 611 mError = true; |
| 612 } |
| 613 |
| 614 if (mError) { |
| 615 return; |
| 616 } |
| 617 |
| 618 decodeBitmapData(); |
| 619 |
| 620 skip(); |
| 621 |
| 622 if (mError) { |
| 623 return; |
| 624 } |
| 625 |
| 626 if (mDisposalMethod == DISPOSAL_METHOD_RESTORE) { |
| 627 backupFrame(); |
| 628 } |
| 629 |
| 630 populateImageData(); |
| 631 |
| 632 if (mTransparency) { |
| 633 mActiveColorTable[mTransparentColorIndex] = savedColor; |
| 634 } |
| 635 |
| 636 mFrameCount++; |
| 637 } |
| 638 |
| 639 /** |
| 640 * Stores the relevant portion of the current frame so that it can be restor
ed |
| 641 * before the next frame is rendered. Called from the background thread. |
| 642 */ |
| 643 private void backupFrame() { |
| 644 if (mBackupSaved) { |
| 645 return; |
| 646 } |
| 647 |
| 648 if (mBackup == null) { |
| 649 mBackup = null; |
| 650 try { |
| 651 mBackup = new int[mColors.length]; |
| 652 } catch (OutOfMemoryError e) { |
| 653 Log.e(TAG, "GifDrawable.backupFrame threw an OOME", e); |
| 654 } |
| 655 } |
| 656 |
| 657 if (mBackup != null) { |
| 658 System.arraycopy(mColors, 0, mBackup, 0, mColors.length); |
| 659 mBackupSaved = true; |
| 660 } |
| 661 } |
| 662 |
| 663 /** |
| 664 * Decodes LZW image data into pixel array. Called from the background thre
ad. |
| 665 */ |
| 666 private void decodeBitmapData() { |
| 667 int npix = mFrameWidth * mFrameHeight; |
| 668 |
| 669 // Initialize GIF data stream decoder. |
| 670 int dataSize = mData[mPosition++] & 0xff; |
| 671 int clear = 1 << dataSize; |
| 672 int endOfInformation = clear + 1; |
| 673 int available = clear + 2; |
| 674 int oldCode = -1; |
| 675 int codeSize = dataSize + 1; |
| 676 int codeMask = (1 << codeSize) - 1; |
| 677 for (int code = 0; code < clear; code++) { |
| 678 mPrefix[code] = 0; // XXX ArrayIndexOutOfBoundsException |
| 679 mSuffix[code] = (byte) code; |
| 680 } |
| 681 |
| 682 // Decode GIF pixel stream. |
| 683 int datum = 0; |
| 684 int bits = 0; |
| 685 int first = 0; |
| 686 int top = 0; |
| 687 int pi = 0; |
| 688 while (pi < npix) { |
| 689 int blockSize = mData[mPosition++] & 0xff; |
| 690 if (blockSize == 0) { |
| 691 break; |
| 692 } |
| 693 |
| 694 int blockEnd = mPosition + blockSize; |
| 695 while (mPosition < blockEnd) { |
| 696 datum += (mData[mPosition++] & 0xff) << bits; |
| 697 bits += 8; |
| 698 |
| 699 while (bits >= codeSize) { |
| 700 // Get the next code. |
| 701 int code = datum & codeMask; |
| 702 datum >>= codeSize; |
| 703 bits -= codeSize; |
| 704 |
| 705 // Interpret the code |
| 706 if (code == clear) { |
| 707 // Reset decoder. |
| 708 codeSize = dataSize + 1; |
| 709 codeMask = (1 << codeSize) - 1; |
| 710 available = clear + 2; |
| 711 oldCode = -1; |
| 712 continue; |
| 713 } |
| 714 |
| 715 // Check for explicit end-of-stream |
| 716 if (code == endOfInformation) { |
| 717 mPosition = blockEnd; |
| 718 return; |
| 719 } |
| 720 |
| 721 if (oldCode == -1) { |
| 722 mPixels[pi++] = mSuffix[code]; |
| 723 oldCode = code; |
| 724 first = code; |
| 725 continue; |
| 726 } |
| 727 |
| 728 int inCode = code; |
| 729 if (code >= available) { |
| 730 mPixelStack[top++] = (byte) first; |
| 731 code = oldCode; |
| 732 if (top == MAX_BITS) { |
| 733 mError = true; |
| 734 return; |
| 735 } |
| 736 } |
| 737 |
| 738 while (code >= clear) { |
| 739 if (code >= MAX_BITS || code == mPrefix[code]) { |
| 740 mError = true; |
| 741 return; |
| 742 } |
| 743 |
| 744 mPixelStack[top++] = mSuffix[code]; |
| 745 code = mPrefix[code]; |
| 746 |
| 747 if (top == MAX_BITS) { |
| 748 mError = true; |
| 749 return; |
| 750 } |
| 751 } |
| 752 |
| 753 first = mSuffix[code]; |
| 754 mPixelStack[top++] = (byte) first; |
| 755 |
| 756 // Add new code to the dictionary |
| 757 if (available < MAX_STACK_SIZE) { |
| 758 mPrefix[available] = (short) oldCode; |
| 759 mSuffix[available] = (byte) first; |
| 760 available++; |
| 761 |
| 762 if (((available & codeMask) == 0) && (available < MAX_ST
ACK_SIZE)) { |
| 763 codeSize++; |
| 764 codeMask += available; |
| 765 } |
| 766 } |
| 767 |
| 768 oldCode = inCode; |
| 769 |
| 770 // Drain the pixel stack. |
| 771 do { |
| 772 mPixels[pi++] = mPixelStack[--top]; |
| 773 } while (top > 0); |
| 774 } |
| 775 } |
| 776 } |
| 777 |
| 778 while (pi < npix) { |
| 779 mPixels[pi++] = 0; // clear missing pixels |
| 780 } |
| 781 } |
| 782 |
| 783 /** |
| 784 * Populates the color array with pixels for the next frame. |
| 785 */ |
| 786 private void populateImageData() { |
| 787 |
| 788 // Copy each source line to the appropriate place in the destination |
| 789 int pass = 1; |
| 790 int inc = 8; |
| 791 int iline = 0; |
| 792 for (int i = 0; i < mFrameHeight; i++) { |
| 793 int line = i; |
| 794 if (mInterlace) { |
| 795 if (iline >= mFrameHeight) { |
| 796 pass++; |
| 797 switch (pass) { |
| 798 case 2: |
| 799 iline = 4; |
| 800 break; |
| 801 case 3: |
| 802 iline = 2; |
| 803 inc = 4; |
| 804 break; |
| 805 case 4: |
| 806 iline = 1; |
| 807 inc = 2; |
| 808 break; |
| 809 default: |
| 810 break; |
| 811 } |
| 812 } |
| 813 line = iline; |
| 814 iline += inc; |
| 815 } |
| 816 line += mFrameY; |
| 817 if (line < mIntrinsicHeight) { |
| 818 int k = line * mIntrinsicWidth; |
| 819 int dx = k + mFrameX; // start of line in dest |
| 820 int dlim = dx + mFrameWidth; // end of dest line |
| 821 |
| 822 // It is unnecesary to test if dlim is beyond the edge of the de
stination line, |
| 823 // since mFrameWidth is clamped to a maximum of mIntrinsicWidth
- mFrameX. |
| 824 |
| 825 int sx = i * mFrameStep; // start of line in source |
| 826 while (dx < dlim) { |
| 827 // map color and insert in destination |
| 828 int index = mPixels[sx++] & 0xff; |
| 829 int c = mActiveColorTable[index]; |
| 830 if (c != 0) { |
| 831 mColors[dx] = c; |
| 832 } |
| 833 dx++; |
| 834 } |
| 835 } |
| 836 } |
| 837 } |
| 838 |
| 839 /** |
| 840 * Reads next variable length block from input. Called from the background
thread. |
| 841 * |
| 842 * @return number of bytes stored in "buffer" |
| 843 */ |
| 844 private int readBlock() { |
| 845 int blockSize = mData[mPosition++] & 0xff; |
| 846 if (blockSize > 0) { |
| 847 System.arraycopy(mData, mPosition, mBlock, 0, blockSize); |
| 848 mPosition += blockSize; |
| 849 } |
| 850 return blockSize; |
| 851 } |
| 852 |
| 853 /** |
| 854 * Reads next 16-bit value, LSB first. Called from the background thread. |
| 855 */ |
| 856 private int readShort() { |
| 857 // read 16-bit value, LSB first |
| 858 int byte1 = mData[mPosition++] & 0xff; |
| 859 int byte2 = mData[mPosition++] & 0xff; |
| 860 return byte1 | (byte2 << 8); |
| 861 } |
| 862 |
| 863 /** |
| 864 * Skips variable length blocks up to and including next zero length block. |
| 865 * Called from the background thread. |
| 866 */ |
| 867 private void skip() { |
| 868 int blockSize; |
| 869 do { |
| 870 blockSize = mData[mPosition++] & 0xff; |
| 871 mPosition += blockSize; |
| 872 } while (blockSize > 0); |
| 873 } |
| 874 |
| 875 @Override |
| 876 public boolean handleMessage(Message msg) { |
| 877 if (msg.what == BaseGifDrawable.READ_FRAME_RESP) { |
| 878 mFrameDelay = msg.arg1; |
| 879 if (mBitmap != null) { |
| 880 mBitmap.setPixels(mColors, 0, mIntrinsicWidth, |
| 881 0, 0, mIntrinsicWidth, mIntrinsicHeight); |
| 882 postProcessFrame(mBitmap); |
| 883 mFirstFrameReady = true; |
| 884 mScheduled = false; |
| 885 invalidateSelf(); |
| 886 } |
| 887 return true; |
| 888 } |
| 889 |
| 890 return false; |
| 891 } |
| 892 |
| 893 /** |
| 894 * Gives a subclass a chance to apply changes to the mutable bitmap |
| 895 * before showing the frame. |
| 896 */ |
| 897 protected void postProcessFrame(Bitmap bitmap) { |
| 898 } |
| 899 |
| 900 /** |
| 901 * Background thread that handles reading and decoding frames of GIF images. |
| 902 */ |
| 903 private static class DecoderThread extends HandlerThread |
| 904 implements android.os.Handler.Callback { |
| 905 private static final String DECODER_THREAD_NAME = "GifDecoder"; |
| 906 |
| 907 public DecoderThread() { |
| 908 super(DECODER_THREAD_NAME); |
| 909 } |
| 910 |
| 911 @Override |
| 912 public boolean handleMessage(Message msg) { |
| 913 BaseGifDrawable gif = (BaseGifDrawable) msg.obj; |
| 914 if (gif == null || gif.mBitmap == null || gif.mRecycled) { |
| 915 return true; |
| 916 } |
| 917 |
| 918 switch (msg.what) { |
| 919 |
| 920 case READ_FRAME_REQ: |
| 921 // Processed on background thread |
| 922 do { |
| 923 try { |
| 924 gif.readNextFrame(); |
| 925 } catch (ArrayIndexOutOfBoundsException e) { |
| 926 gif.mEndOfFile = true; |
| 927 } |
| 928 |
| 929 // Check for EOF |
| 930 if (gif.mEndOfFile) { |
| 931 if (gif.mFrameCount == 0) { |
| 932 // could not read first frame |
| 933 gif.mError = true; |
| 934 } else if (gif.mFrameCount > 1) { |
| 935 if (gif.mLoopCount == 0 || ++gif.mLoopIndex < gi
f.mLoopCount) { |
| 936 // Repeat the animation |
| 937 gif.reset(); |
| 938 } else { |
| 939 gif.mDone = true; |
| 940 } |
| 941 } else { |
| 942 // Only one frame. Mark as done. |
| 943 gif.mDone = true; |
| 944 } |
| 945 } |
| 946 } while (gif.mEndOfFile && !gif.mError && !gif.mDone); |
| 947 gif.mHandler.sendMessage(gif.mHandler.obtainMessage(READ_FRA
ME_RESP, |
| 948 gif.mNextFrameDelay, 0)); |
| 949 return true; |
| 950 |
| 951 case RESET_DECODER: |
| 952 gif.reset(); |
| 953 return true; |
| 954 } |
| 955 |
| 956 return false; |
| 957 } |
| 958 } |
| 959 } |
OLD | NEW |