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