зеркало из https://github.com/nextcloud-deps/fresco.git
GifAnimationBackend
Reviewed By: oprisnik Differential Revision: D13089206 fbshipit-source-id: 1e055af255449c6d55b5ba515c4504a78a485eae
This commit is contained in:
Родитель
1d965eeb84
Коммит
027f95d683
|
@ -12,6 +12,7 @@ import com.facebook.animated.giflite.decoder.GifMetadataDecoder;
|
|||
import com.facebook.animated.giflite.draw.MovieAnimatedImage;
|
||||
import com.facebook.animated.giflite.draw.MovieDrawer;
|
||||
import com.facebook.animated.giflite.draw.MovieFrame;
|
||||
import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo;
|
||||
import com.facebook.imagepipeline.animated.base.AnimatedImageResult;
|
||||
import com.facebook.imagepipeline.common.ImageDecodeOptions;
|
||||
import com.facebook.imagepipeline.decoder.ImageDecoder;
|
||||
|
@ -38,7 +39,7 @@ public class GifDecoder implements ImageDecoder {
|
|||
|
||||
is.reset();
|
||||
|
||||
GifMetadataDecoder decoder = GifMetadataDecoder.Factory.create(is);
|
||||
GifMetadataDecoder decoder = GifMetadataDecoder.create(is);
|
||||
|
||||
MovieFrame[] frames = new MovieFrame[decoder.getFrameCount()];
|
||||
int currTime = 0;
|
||||
|
@ -52,7 +53,7 @@ public class GifDecoder implements ImageDecoder {
|
|||
frameDuration,
|
||||
movie.width(),
|
||||
movie.height(),
|
||||
decoder.getFrameDisposal(frameNumber));
|
||||
translateFrameDisposal(decoder.getFrameDisposal(frameNumber)));
|
||||
}
|
||||
|
||||
return new CloseableAnimatedImage(
|
||||
|
@ -68,4 +69,17 @@ public class GifDecoder implements ImageDecoder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static AnimatedDrawableFrameInfo.DisposalMethod translateFrameDisposal(int raw) {
|
||||
switch (raw) {
|
||||
case 2: // restore to background
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_BACKGROUND;
|
||||
case 3: // restore to previous
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_PREVIOUS;
|
||||
case 1: // do not dispose
|
||||
// fallthrough
|
||||
default: // unspecified
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,30 +4,242 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.animated.giflite.decoder;
|
||||
|
||||
import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public interface GifMetadataDecoder {
|
||||
public class GifMetadataDecoder {
|
||||
|
||||
void decode() throws IOException;
|
||||
private static final int MAX_BLOCK_SIZE = 256; // blocks sizes are defined by a single byte
|
||||
private static final char[] NETSCAPE =
|
||||
new char[] {'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0'};
|
||||
private static final int CONTROL_INDEX_DISPOSE = 0;
|
||||
private static final int CONTROL_INDEX_DELAY = 1;
|
||||
|
||||
int getFrameCount();
|
||||
private final byte[] block = new byte[MAX_BLOCK_SIZE];
|
||||
private final InputStream mInputStream;
|
||||
private final List<int[]> mFrameControls = new ArrayList<>();
|
||||
private int mLoopCount = 1; // default loop count is 1
|
||||
private boolean mDecoded = false;
|
||||
|
||||
int getLoopCount();
|
||||
public static GifMetadataDecoder create(InputStream is) throws IOException {
|
||||
GifMetadataDecoder decoder = new GifMetadataDecoder(is);
|
||||
decoder.decode();
|
||||
return decoder;
|
||||
}
|
||||
|
||||
AnimatedDrawableFrameInfo.DisposalMethod getFrameDisposal(int frameNumber);
|
||||
private GifMetadataDecoder(InputStream is) {
|
||||
mInputStream = is;
|
||||
}
|
||||
|
||||
int getFrameDurationMs(int frameNumber);
|
||||
public void decode() throws IOException {
|
||||
if (mDecoded) {
|
||||
throw new IllegalStateException("decode called multiple times");
|
||||
}
|
||||
mDecoded = true;
|
||||
readGifInfo();
|
||||
}
|
||||
|
||||
class Factory {
|
||||
public static GifMetadataDecoder create(InputStream is)
|
||||
throws IOException {
|
||||
GifMetadataDecoder decoder = new GifMetadataStreamDecoder(is);
|
||||
decoder.decode();
|
||||
return decoder;
|
||||
public int getFrameCount() {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameCount called before decode");
|
||||
}
|
||||
return mFrameControls.size();
|
||||
}
|
||||
|
||||
public int getLoopCount() {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getLoopCount called before decode");
|
||||
}
|
||||
return mLoopCount;
|
||||
}
|
||||
|
||||
public int getFrameDisposal(int frameNumber) {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameDisposal called before decode");
|
||||
}
|
||||
return mFrameControls.get(frameNumber)[CONTROL_INDEX_DISPOSE];
|
||||
}
|
||||
|
||||
public int getFrameDurationMs(int frameNumber) {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameDurationMs called before decode");
|
||||
}
|
||||
return mFrameControls.get(frameNumber)[CONTROL_INDEX_DELAY];
|
||||
}
|
||||
|
||||
private void readGifInfo() throws IOException {
|
||||
validateAndIgnoreHeader();
|
||||
|
||||
final int[] control = new int[] {0, 0};
|
||||
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
int code = readNextByte();
|
||||
switch (code) {
|
||||
case 0x21: // extension
|
||||
int extCode = readNextByte();
|
||||
switch (extCode) {
|
||||
case 0xff: // application extension
|
||||
readBlock();
|
||||
if (isNetscape()) {
|
||||
readNetscapeExtension();
|
||||
} else {
|
||||
skipExtension();
|
||||
}
|
||||
break;
|
||||
case 0xf9: // graphics control extension
|
||||
readGraphicsControlExtension(control);
|
||||
break;
|
||||
case 0x01: // plain text extension, counts as a frame
|
||||
addFrame(control);
|
||||
skipExtension();
|
||||
break;
|
||||
default:
|
||||
skipExtension();
|
||||
}
|
||||
break;
|
||||
case 0x2C: // image
|
||||
addFrame(control);
|
||||
skipImage();
|
||||
// count as a frame
|
||||
break;
|
||||
case 0x3b: // terminator
|
||||
done = true;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unknown block header [" + Integer.toHexString(code) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFrame(int[] control) {
|
||||
mFrameControls.add(Arrays.copyOf(control, control.length));
|
||||
}
|
||||
|
||||
private void validateAndIgnoreHeader() throws IOException {
|
||||
readIntoBlock(0 /* offset */, 6 /* length */);
|
||||
boolean valid =
|
||||
'G' == (char) block[0]
|
||||
&& 'I' == (char) block[1]
|
||||
&& 'F' == (char) block[2]
|
||||
&& '8' == (char) block[3]
|
||||
&& ('7' == (char) block[4] || '9' == (char) block[4])
|
||||
&& 'a' == (char) block[5];
|
||||
if (!valid) {
|
||||
throw new IOException("Illegal header for gif");
|
||||
}
|
||||
|
||||
readTwoByteInt(); // width
|
||||
readTwoByteInt(); // height
|
||||
|
||||
int fields = readNextByte();
|
||||
boolean hasGlobalColorTable = (fields & 0x80) != 0;
|
||||
int globalColorTableSize = 2 << (fields & 7);
|
||||
|
||||
readNextByte(); // bgc index
|
||||
readNextByte(); // aspect ratio
|
||||
|
||||
if (hasGlobalColorTable) {
|
||||
ignoreColorTable(globalColorTableSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void ignoreColorTable(int numColors) throws IOException {
|
||||
for (int i = 0, N = 3 * numColors; i < N; i++) {
|
||||
readNextByte();
|
||||
}
|
||||
}
|
||||
|
||||
private int readBlock() throws IOException {
|
||||
int blockSize = readNextByte();
|
||||
int n = 0;
|
||||
if (blockSize > 0) {
|
||||
while (n < blockSize) {
|
||||
n += readIntoBlock(n, blockSize - n);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private void skipExtension() throws IOException {
|
||||
int size;
|
||||
do {
|
||||
size = readBlock();
|
||||
} while (size > 0);
|
||||
}
|
||||
|
||||
private void skipImage() throws IOException {
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
|
||||
int flags = readNextByte();
|
||||
boolean hasLct = (flags & 0x80) != 0;
|
||||
if (hasLct) {
|
||||
int lctSize = 2 << (flags & 7);
|
||||
ignoreColorTable(lctSize);
|
||||
}
|
||||
readNextByte();
|
||||
skipExtension();
|
||||
}
|
||||
|
||||
private boolean isNetscape() {
|
||||
if (block.length < NETSCAPE.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0, N = NETSCAPE.length; i < N; i++) {
|
||||
if (NETSCAPE[i] != (char) block[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readNetscapeExtension() throws IOException {
|
||||
int size;
|
||||
do {
|
||||
size = readBlock();
|
||||
if (block[0] == 1) {
|
||||
mLoopCount = ((((int) block[2]) & 0xff) << 8) | (((int) block[1]) & 0xff);
|
||||
}
|
||||
} while (size > 0);
|
||||
}
|
||||
|
||||
private void readGraphicsControlExtension(int[] control) throws IOException {
|
||||
readNextByte();
|
||||
int flags = readNextByte();
|
||||
control[CONTROL_INDEX_DISPOSE] = (flags & 0x1c) >> 2; // dispose
|
||||
control[CONTROL_INDEX_DELAY] = readTwoByteInt() * 10; // delay
|
||||
readNextByte();
|
||||
readNextByte();
|
||||
}
|
||||
|
||||
private int readNextByte() throws IOException {
|
||||
int read = mInputStream.read();
|
||||
if (read == -1) {
|
||||
throw new EOFException("Unexpected end of gif file");
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
private int readTwoByteInt() throws IOException {
|
||||
return readNextByte() | (readNextByte() << 8);
|
||||
}
|
||||
|
||||
private int readIntoBlock(int offset, int length) throws IOException {
|
||||
int count = mInputStream.read(block, offset, length);
|
||||
if (count == -1) {
|
||||
throw new EOFException("Unexpected end of gif file");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,255 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.animated.giflite.decoder;
|
||||
|
||||
import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo;
|
||||
import com.facebook.imagepipeline.animated.base.AnimatedImage;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/* package-private */ class GifMetadataStreamDecoder implements GifMetadataDecoder {
|
||||
|
||||
private static final int MAX_BLOCK_SIZE = 256; // blocks sizes are defined by a single byte
|
||||
private static final String NETSCAPE = "NETSCAPE2.0";
|
||||
private static final int CONTROL_INDEX_DISPOSE = 0;
|
||||
private static final int CONTROL_INDEX_DELAY = 1;
|
||||
|
||||
private final byte[] block = new byte[MAX_BLOCK_SIZE];
|
||||
|
||||
private final InputStream mInputStream;
|
||||
private final List<int[]> mFrameControls = new ArrayList<>();
|
||||
private int mLoopCount = AnimatedImage.LOOP_COUNT_INFINITE;
|
||||
private boolean mDecoded = false;
|
||||
|
||||
public GifMetadataStreamDecoder(InputStream is) {
|
||||
mInputStream = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode() throws IOException {
|
||||
if (mDecoded) {
|
||||
throw new IllegalStateException("decode called multiple times");
|
||||
}
|
||||
mDecoded = true;
|
||||
readGifInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameCount() {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameCount called before decode");
|
||||
}
|
||||
return mFrameControls.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoopCount() {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getLoopCount called before decode");
|
||||
}
|
||||
return mLoopCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatedDrawableFrameInfo.DisposalMethod getFrameDisposal(int frameNumber) {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameDisposal called before decode");
|
||||
}
|
||||
switch (mFrameControls.get(frameNumber)[CONTROL_INDEX_DISPOSE]) {
|
||||
case 2: // restore to background
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_BACKGROUND;
|
||||
case 3: // restore to previous
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_PREVIOUS;
|
||||
case 1: // do not dispose
|
||||
// fallthrough
|
||||
default: // unspecified
|
||||
return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameDurationMs(int frameNumber) {
|
||||
if (!mDecoded) {
|
||||
throw new IllegalStateException("getFrameDurationMS called before decode");
|
||||
}
|
||||
return mFrameControls.get(frameNumber)[CONTROL_INDEX_DELAY];
|
||||
}
|
||||
|
||||
private void readGifInfo() throws IOException {
|
||||
validateAndIgnoreHeader();
|
||||
|
||||
final int[] control = new int[] {0, 0};
|
||||
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
int code = readNextByte();
|
||||
switch (code) {
|
||||
case 0x21: // extension
|
||||
int extCode = readNextByte();
|
||||
switch (extCode) {
|
||||
case 0xff: // application extension
|
||||
readBlock();
|
||||
if (isNetscape()) {
|
||||
readNetscapeExtension();
|
||||
} else {
|
||||
skipExtension();
|
||||
}
|
||||
break;
|
||||
case 0xf9: // graphics control extension
|
||||
readGraphicsControlExtension(control);
|
||||
break;
|
||||
case 0x01: // plain text extension, counts as a frame
|
||||
addFrame(control);
|
||||
skipExtension();
|
||||
break;
|
||||
default:
|
||||
skipExtension();
|
||||
}
|
||||
break;
|
||||
case 0x2C: // image
|
||||
addFrame(control);
|
||||
skipImage();
|
||||
// count as a frame
|
||||
break;
|
||||
case 0x3b: // terminator
|
||||
done = true;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unknown block header [" + Integer.toHexString(code) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFrame(int[] control) {
|
||||
mFrameControls.add(Arrays.copyOf(control, control.length));
|
||||
}
|
||||
|
||||
private void validateAndIgnoreHeader() throws IOException {
|
||||
readIntoBlock(0 /* offset */, 6 /* length */);
|
||||
boolean valid =
|
||||
'G' == (char) block[0]
|
||||
&& 'I' == (char) block[1]
|
||||
&& 'F' == (char) block[2]
|
||||
&& '8' == (char) block[3]
|
||||
&& ('7' == (char) block[4] || '9' == (char) block[4])
|
||||
&& 'a' == (char) block[5];
|
||||
if (!valid) {
|
||||
throw new IOException("Illegal header for gif");
|
||||
}
|
||||
|
||||
readTwoByteInt(); // width
|
||||
readTwoByteInt(); // height
|
||||
|
||||
int fields = readNextByte();
|
||||
boolean hasGlobalColorTable = (fields & 0x80) != 0;
|
||||
int globalColorTableSize = 2 << (fields & 7);
|
||||
|
||||
readNextByte(); // bgc index
|
||||
readNextByte(); // aspect ratio
|
||||
|
||||
if (hasGlobalColorTable) {
|
||||
ignoreColorTable(globalColorTableSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void ignoreColorTable(int numColors) throws IOException {
|
||||
for (int i = 0, N = 3 * numColors; i < N; i++) {
|
||||
readNextByte();
|
||||
}
|
||||
}
|
||||
|
||||
private int readBlock() throws IOException {
|
||||
int blockSize = readNextByte();
|
||||
int n = 0;
|
||||
if (blockSize > 0) {
|
||||
while (n < blockSize) {
|
||||
n += readIntoBlock(n, blockSize - n);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private void skipExtension() throws IOException {
|
||||
int size;
|
||||
do {
|
||||
size = readBlock();
|
||||
} while (size > 0);
|
||||
}
|
||||
|
||||
private void skipImage() throws IOException {
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
readTwoByteInt();
|
||||
|
||||
int flags = readNextByte();
|
||||
boolean hasLct = (flags & 0x80) != 0;
|
||||
if (hasLct) {
|
||||
int lctSize = 2 << (flags & 7);
|
||||
ignoreColorTable(lctSize);
|
||||
}
|
||||
readNextByte();
|
||||
skipExtension();
|
||||
}
|
||||
|
||||
private boolean isNetscape() {
|
||||
if (block.length > NETSCAPE.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0, N = NETSCAPE.length(); i < N; i++) {
|
||||
if (NETSCAPE.charAt(i) != (char) block[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readNetscapeExtension() throws IOException {
|
||||
int size;
|
||||
do {
|
||||
size = readBlock();
|
||||
if (block[0] == 1) {
|
||||
mLoopCount = ((((int) block[2]) & 0xff) << 8) | (((int) block[1]) & 0xff);
|
||||
}
|
||||
} while (size > 0);
|
||||
}
|
||||
|
||||
private void readGraphicsControlExtension(int[] control) throws IOException {
|
||||
readNextByte();
|
||||
int flags = readNextByte();
|
||||
control[CONTROL_INDEX_DISPOSE] = (flags & 0x1c) >> 2; // dispose
|
||||
control[CONTROL_INDEX_DELAY] = readTwoByteInt() * 10; // delay
|
||||
readNextByte();
|
||||
readNextByte();
|
||||
}
|
||||
|
||||
private int readNextByte() throws IOException {
|
||||
int read = mInputStream.read();
|
||||
if (read == -1) {
|
||||
throw new EOFException("Unexpected end of gif file");
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
private int readTwoByteInt() throws IOException {
|
||||
return readNextByte() | (readNextByte() << 8);
|
||||
}
|
||||
|
||||
private int readIntoBlock(int offset, int length) throws IOException {
|
||||
int count = mInputStream.read(block, offset, length);
|
||||
if (count == -1) {
|
||||
throw new EOFException("Unexpected end of gif file");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
package com.facebook.animated.giflite.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Movie;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.facebook.animated.giflite.decoder.GifMetadataDecoder;
|
||||
import com.facebook.fresco.animation.backend.AnimationBackend;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class GifAnimationBackend implements AnimationBackend {
|
||||
|
||||
private final GifMetadataDecoder mGifDecoder;
|
||||
private final Movie mMovie;
|
||||
private final int[] mFrameStartTimes;
|
||||
|
||||
private float mMidX;
|
||||
private float mMidY;
|
||||
|
||||
public static GifAnimationBackend create(String filePath) throws IOException {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(new FileInputStream(filePath));
|
||||
is.mark(Integer.MAX_VALUE);
|
||||
|
||||
GifMetadataDecoder decoder = GifMetadataDecoder.create(is);
|
||||
is.reset();
|
||||
|
||||
Movie movie = Movie.decodeStream(is);
|
||||
return new GifAnimationBackend(decoder, movie);
|
||||
} finally {
|
||||
closeSilently(is);
|
||||
}
|
||||
}
|
||||
|
||||
private GifAnimationBackend(GifMetadataDecoder decoder, Movie movie) {
|
||||
mGifDecoder = decoder;
|
||||
mMovie = movie;
|
||||
mFrameStartTimes = new int[decoder.getFrameCount()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drawFrame(Drawable parent, Canvas canvas, int frameNumber) {
|
||||
mMovie.setTime(getFrameStartTime(frameNumber));
|
||||
mMovie.draw(canvas, mMidX, mMidY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// unimplemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
// unimplemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(Rect bounds) {
|
||||
scale(
|
||||
bounds.right - bounds.left /* viewPortWidth */,
|
||||
bounds.bottom - bounds.top /* viewPortHeight */,
|
||||
mMovie.width() /* sourceWidth */,
|
||||
mMovie.height() /* sourceHeight */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mMovie.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mMovie.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSizeInBytes() {
|
||||
return 0; // no cached data
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// unimplemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameCount() {
|
||||
return mGifDecoder.getFrameCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameDurationMs(int frameNumber) {
|
||||
return mGifDecoder.getFrameDurationMs(frameNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoopCount() {
|
||||
return mGifDecoder.getLoopCount();
|
||||
}
|
||||
|
||||
private int getFrameStartTime(int frameNumber) {
|
||||
if (frameNumber == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (mFrameStartTimes[frameNumber] != 0) {
|
||||
return mFrameStartTimes[frameNumber];
|
||||
}
|
||||
for (int i = 0; i < frameNumber; i++) {
|
||||
mFrameStartTimes[frameNumber] += mGifDecoder.getFrameDurationMs(i);
|
||||
}
|
||||
return mFrameStartTimes[frameNumber];
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the source, and sets the size based on them. Maintains aspect ratio of source, and
|
||||
* ensures that screen is filled in at least one dimension.
|
||||
*
|
||||
* <p>Adapted from com.facebook.cameracore.common.RenderUtil#calculateFitRect
|
||||
*
|
||||
* @param viewPortWidth the width of the display
|
||||
* @param viewPortHeight the height of the display
|
||||
* @param sourceWidth the width of the video
|
||||
* @param sourceHeight the height of the video
|
||||
*/
|
||||
private void scale(int viewPortWidth, int viewPortHeight, int sourceWidth, int sourceHeight) {
|
||||
|
||||
float inputRatio = ((float) sourceWidth) / sourceHeight;
|
||||
float outputRatio = ((float) viewPortWidth) / viewPortHeight;
|
||||
|
||||
int scaledWidth = viewPortWidth;
|
||||
int scaledHeight = viewPortHeight;
|
||||
if (outputRatio > inputRatio) {
|
||||
// Not enough width to fill the output. (Black bars on left and right.)
|
||||
scaledWidth = (int) (viewPortHeight * inputRatio);
|
||||
scaledHeight = viewPortHeight;
|
||||
} else if (outputRatio < inputRatio) {
|
||||
// Not enough height to fill the output. (Black bars on top and bottom.)
|
||||
scaledHeight = (int) (viewPortWidth / inputRatio);
|
||||
scaledWidth = viewPortWidth;
|
||||
}
|
||||
float scale = scaledWidth / (float) sourceWidth;
|
||||
|
||||
mMidX = ((viewPortWidth - scaledWidth) / 2f) / scale;
|
||||
mMidY = ((viewPortHeight - scaledHeight) / 2f) / scale;
|
||||
}
|
||||
|
||||
private static void closeSilently(@Nullable Closeable closeable) {
|
||||
if (closeable == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче