Saturday, 17 August 2013

Android and the friendly mister .gif

Android and the friendly mister .gif

I'm trying to develop an app that manages .gif. Even worst, I'm trying to
display these .gif in a GridView. But that isn't the point. For those
folks who has been working with .gif in Android knows that there's three
ways (hacky ones) to show .gif in Android: WebView, Movie or split the
.gif into frames and turn each frame into a bitmap, I'm using the third
choise. I must use that one because I need display all kind of .gif, and
the other two methods leak with certain .gif. My problem is that with a
huge .gif (like one of 1.5MB) my code can't even load it. This is mainly
because the class that I'm using (GifDecoder.java) reads the whole frame.
Is there anyway to instead read the full frame, just skip some blocks and
get a tiny version of the frame? With a smaller frame I will get a smaller
bitmap. I'm pretty sure that to accomplish this I must change something in
the GifDecoder. I leave my code only just in case you want see how I'm
working my Gifs thumbnails.
public class TinyGifDecoderView extends ImageView {
private GifDecoder mGifDecoder;
private Bitmap _template;
private Paint _paint = new Paint();
protected Rect rectDst;
private Bitmap mStartBitmap;
private Bitmap mMiddleBitmap;
private Bitmap mFinalBitmap;
private int who = 1;
private DiskLruImageCache diskCache;
protected Boolean readyToRock = Boolean.FALSE;
@Override
protected void onDraw(Canvas canvas) {
if (readyToRock) {
drawFrame(canvas);
}
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int minimumWidth =
getResources().getDrawable(R.drawable.tv).getMinimumWidth();
final int minimumHeight =
getResources().getDrawable(R.drawable.tv).getMinimumHeight();
setMeasuredDimension(minimumWidth, minimumHeight);
}
private void drawFrame(Canvas canvas) {
switch (who) {
case 1:
canvas.drawBitmap(mStartBitmap, rectDst, rectDst, _paint);
break;
case 2:
canvas.drawBitmap(mMiddleBitmap, rectDst, rectDst, _paint);
break;
case 3:
canvas.drawBitmap(mFinalBitmap, rectDst, rectDst, _paint);
break;
default:
who = 1;
canvas.drawBitmap(mStartBitmap, rectDst, rectDst, _paint);
break;
}
canvas.drawBitmap(_template, 0, 0, _paint);
who++;
}
public TinyGifDecoderView(Context context, String filepath,
DiskLruImageCache diskCache) {
super(context);
setDiskCache(diskCache);
//Set the animation drawable
setBackgroundDrawable(getResources().getDrawable(R.drawable.tv_loading));
new LoadGif().execute(filepath);
}
class LoadGif extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(String... params) {
if (diskCache.containsKey(Utils.makeKey("tv_template1"))) {
_template = diskCache.getBitmap(Utils.makeKey("tv_template1"));
} else {
_template = BitmapFactory.decodeResource(getResources(),
R.drawable.tv);
diskCache.put(Utils.makeKey("tv_template1"), _template);
}
final String filepath = params[0];
final String frame1 = Utils.makeKey(filepath+"_frame_1");
final String frame2 = Utils.makeKey(filepath+"_frame_2");
final String frame3 = Utils.makeKey(filepath+"_frame_3");
if (diskCache.containsKey(frame1) && diskCache.containsKey(frame2)
&& diskCache.containsKey(frame3)) {
mStartBitmap = diskCache.getBitmap(frame1);
mMiddleBitmap = diskCache.getBitmap(frame2);
mFinalBitmap = diskCache.getBitmap(frame3);
scaleBitmaps();
} else {
InputStream stream = getInputStream(getContext(), filepath);
mGifDecoder = new GifDecoder();
mGifDecoder.read(stream);
final int frameCount = mGifDecoder.getFrameCount();
mStartBitmap = mGifDecoder.getFrame(0);
mMiddleBitmap = mGifDecoder.getFrame(frameCount / 2);
mFinalBitmap = mGifDecoder.getFrame(frameCount);
scaleBitmaps();
diskCache.put(frame1, mStartBitmap);
diskCache.put(frame2, mMiddleBitmap);
diskCache.put(frame3, mFinalBitmap);
disposeDecoder();
}
getTemplateRect();
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
//Run the animation
post(new Runnable() {
@Override
public void run() {
((AnimationDrawable)getBackground()).start();
}
});
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
//Stop the animation and play the gif
((AnimationDrawable)getBackground()).stop();
readyToRock = Boolean.TRUE;
playAnimation();
}
}
private InputStream getInputStream(Context context, String filepath) {
InputStream stream = null;
try {
stream = context.getAssets().open(filepath);
} catch (IOException e) {
e.printStackTrace();
}
return stream;
}
private void playAnimation() {
Runnable updater = new Runnable() {
@Override
public void run() {
TinyGifDecoderView.this.invalidate();
TinyGifDecoderView.this.postDelayed(this, 500);
}
};
postDelayed(updater, 500);
}
/**
* Get the template bounds excluding the offsets.
*/
private void getTemplateRect() {
int templateBottomOffset = _template.getHeight() / 15;
if (rectDst == null) {
rectDst = new Rect(2, 0, _template.getWidth(), _template.getHeight()
- templateBottomOffset);
}
}
/**
* Scale the bitmaps to the <code>_template</code> bounds.
*/
private void scaleBitmaps() {
mStartBitmap = Bitmap.createScaledBitmap(mStartBitmap,
_template.getWidth(), _template.getHeight()
- (_template.getHeight() / 15), false);
mMiddleBitmap = Bitmap.createScaledBitmap(mMiddleBitmap,
_template.getWidth(), _template.getHeight()
- (_template.getHeight() / 15), false);
mFinalBitmap = Bitmap.createScaledBitmap(mFinalBitmap,
_template.getWidth(), _template.getHeight()
- (_template.getHeight() / 15), false);
}
/**
* Clean all the bitmap data in {@link GifDecoder}
*/
private void disposeDecoder() {
mGifDecoder.recycle();
mGifDecoder = null;
System.gc();
}
public DiskLruImageCache getDiskCache() {
return diskCache;
}
public void setDiskCache(DiskLruImageCache diskCache) {
this.diskCache = diskCache;
}
There's no big deal with this class, I'm just taking the first, the middle
and the last frame and I'm drawing them over a template. There's an
animation that I show meanwhile I do this stuff. The bitmaps that I'm
saving in the disk cache bitmap are of the template size, that is 150x100,
pretty smaller. But when the app crashes I can't even reach this step. I
know that I can improve some things in this class, but these aren't the
core problem. If someone can tell me where I need modify the
GifDecoder.java class to decode smaller frames I will apreciate.

No comments:

Post a Comment