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

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: 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 /*
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 }
OLDNEW
« no previous file with comments | « third_party/gif_player/gif_player.gyp ('k') | third_party/gif_player/src/jp/tomorrowkey/android/gifplayer/BaseGifImage.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698