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 rendered. 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 background 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. The 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 BaseGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig)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(), sDecoderThread); 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_FRAME_REQ, this)); 182 } catch (OutOfMemoryError e) { 183 mError = true; 184 } 185 } 186 } 187 188 /** 189 * Sets the loop count for multi-frame animation. 190 */ setLoopCount(int loopCount)191 public void setLoopCount(int loopCount) { 192 mLoopCount = loopCount; 193 } 194 195 /** 196 * Returns the loop count for multi-frame animation. 197 */ getLoopCount()198 public int getLoopCount() { 199 return mLoopCount; 200 } 201 202 /** 203 * Sets whether to start animation on load or not. 204 */ setAnimateOnLoad(boolean animateOnLoad)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 */ isValid()212 public boolean isValid() { 213 return !mError && mFirstFrameReady; 214 } 215 onRecycle()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 */ setAnimationEnabled(boolean animationEnabled)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 onBoundsChange(Rect bounds)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_DECODER, this)); 254 } 255 } 256 257 @Override setVisible(boolean visible, boolean restart)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 draw(Canvas canvas)271 public void draw(Canvas canvas) { 272 if (mError || mWidth == 0 || mHeight == 0 || mRecycled || !mFirstFrameReady) { 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 getIntrinsicWidth()302 public int getIntrinsicWidth() { 303 return mIntrinsicWidth; 304 } 305 306 @Override getIntrinsicHeight()307 public int getIntrinsicHeight() { 308 return mIntrinsicHeight; 309 } 310 311 @Override getOpacity()312 public int getOpacity() { 313 return PixelFormat.UNKNOWN; 314 } 315 316 @Override setAlpha(int alpha)317 public void setAlpha(int alpha) { 318 } 319 320 @Override setColorFilter(ColorFilter cf)321 public void setColorFilter(ColorFilter cf) { 322 } 323 324 @Override isRunning()325 public boolean isRunning() { 326 return mRunning; 327 } 328 329 @Override start()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 stop()342 public void stop() { 343 if (isRunning()) { 344 unscheduleSelf(this); 345 } 346 } 347 348 @Override scheduleSelf(Runnable what, long when)349 public void scheduleSelf(Runnable what, long when) { 350 if (mAnimationEnabled) { 351 super.scheduleSelf(what, when); 352 mScheduled = true; 353 } 354 } 355 356 @Override unscheduleSelf(Runnable what)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 run()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 background thread. 379 */ reset()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 previously done. 390 */ restartAnimation()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 */ readColorTable(int[] colorTable, int ncolors)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 */ readNextFrame()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 image. 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 */ disposeOfLastFrame()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 thread. 528 */ readGraphicControlExt()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 background thread. 553 */ readNetscapeExtension()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 */ readBitmap()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 flag 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 restored 641 * before the next frame is rendered. Called from the background thread. 642 */ backupFrame()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 thread. 665 */ decodeBitmapData()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_STACK_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 */ populateImageData()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 destination 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 */ readBlock()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 */ readShort()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 */ skip()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 handleMessage(Message msg)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 */ postProcessFrame(Bitmap bitmap)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 DecoderThread()907 public DecoderThread() { 908 super(DECODER_THREAD_NAME); 909 } 910 911 @Override handleMessage(Message msg)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 < gif.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_FRAME_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 } 960