Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifDrawable.java

Issue 1343913002: Introduce Animated Logo to Chrome on Android (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: lots of renamings Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698