From b2e91006f4ba715c7d7fe97dee4181cb56127090 Mon Sep 17 00:00:00 2001 From: Likai Ding Date: Tue, 13 Aug 2013 14:07:48 +0800 Subject: Gallery2: Support GIF animation This change implements a Java GIF decoder. Change-Id: I227cef76cbacd66b7e87bc59b4f07d518b70a859 Signed-off-by: Xiaojing Zhang --- AndroidManifest.xml | 10 ++ res/layout/view_gif_image.xml | 17 ++ src/com/android/gallery3d/app/PhotoPage.java | 10 ++ src/com/android/gallery3d/ui/PhotoView.java | 4 +- src/com/android/gallery3d/util/GIFView.java | 207 +++++++++++++++++++++++ src/com/android/gallery3d/util/GifDecoder.java | 6 +- src/com/android/gallery3d/util/ViewGifImage.java | 67 ++++++++ 7 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 res/layout/view_gif_image.xml create mode 100755 src/com/android/gallery3d/util/GIFView.java create mode 100755 src/com/android/gallery3d/util/ViewGifImage.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 60211c7ac..faa1e0f59 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -307,6 +307,16 @@ android:theme="@style/Theme.Gallery" android:configChanges="orientation|keyboardHidden|screenSize" /> + + + + + + + + + + + + + + + + diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 90e47ab82..793b1d75a 100755 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -81,6 +81,7 @@ import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GDepth; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.UsageStatistics; +import com.android.gallery3d.util.ViewGifImage; import java.util.List; import java.util.Locale; @@ -1343,6 +1344,10 @@ public abstract class PhotoPage extends ActivityState implements // item is not ready or it is camera preview, ignore return; } + if (item.getMimeType().equals(MediaItem.MIME_TYPE_GIF)) { + viewAnimateGif((Activity) mActivity, item.getContentUri()); + return; + } int supported = item.getSupportedOperations(); boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0); @@ -1842,4 +1847,9 @@ public abstract class PhotoPage extends ActivityState implements m3DButton.refresh(); } } + + private static void viewAnimateGif(Activity activity, Uri uri) { + Intent intent = new Intent(ViewGifImage.VIEW_GIF_ACTION, uri); + activity.startActivity(intent); + } } diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index e3b72eead..246e647b4 100755 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -734,7 +734,7 @@ public class PhotoView extends GLView { canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f)); int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f); //Full pic locates at index 0 of the array in PhotoDataAdapter - if (mModel.isVideo(0)) { + if (mModel.isVideo(0) || mModel.isGif(0)) { drawVideoPlayIcon(canvas, s); } if (mLoadingState == Model.LOADING_FAIL ) { @@ -864,7 +864,7 @@ public class PhotoView extends GLView { invalidate(); } int s = Math.min(drawW, drawH); - if (mModel.isVideo(mIndex)) { + if (mModel.isVideo(mIndex) || mModel.isGif(mIndex)) { drawVideoPlayIcon(canvas, s); } diff --git a/src/com/android/gallery3d/util/GIFView.java b/src/com/android/gallery3d/util/GIFView.java new file mode 100755 index 000000000..7bcc52d1b --- /dev/null +++ b/src/com/android/gallery3d/util/GIFView.java @@ -0,0 +1,207 @@ +package com.android.gallery3d.util; + +import org.codeaurora.gallery.R; + +import android.content.Context; +import android.content.ContentResolver; +import android.content.res.AssetManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.ImageView; +import android.widget.Toast; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; + +public class GIFView extends ImageView implements GifAction { + + private static final String TAG = "GIFView"; + private static final float SCALE_LIMIT = 4; + private static final long FRAME_DELAY = 200; //milliseconds + + private GifDecoder mGifDecoder = null; + private Bitmap mCurrentImage = null; + private DrawThread mDrawThread = null; + + private Uri mUri; + private Context mContext; + + public GIFView(Context context) { + super(context); + mContext = context; + } + + public boolean setDrawable(Uri uri) { + if (null == uri) { + return false; + } + mUri = uri; + + InputStream is = getInputStream(uri); + if (is == null || (getFileSize (is) == 0)) { + return false; + } + startDecode(is); + return true; + } + + private int getFileSize (InputStream is) { + if(is == null) return 0; + + int size = 0; + try { + if (is instanceof FileInputStream) { + FileInputStream f = (FileInputStream) is; + size = (int) f.getChannel().size(); + } else { + while (-1 != is.read()) { + size++; + } + } + + } catch (IOException e) { + Log.e(TAG, "catch exception:" + e); + } + + return size; + + } + + private InputStream getInputStream (Uri uri) { + ContentResolver cr = mContext.getContentResolver(); + InputStream input = null; + try { + input = cr.openInputStream(uri); + } catch (IOException e) { + Log.e(TAG, "catch exception:" + e); + } + return input; + } + + private void startDecode(InputStream is) { + freeGifDecoder(); + mGifDecoder = new GifDecoder(is, this); + mGifDecoder.start(); + } + + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mGifDecoder == null) { + return; + } + + if (mCurrentImage == null) { + mCurrentImage = mGifDecoder.getImage(); + } + if (mCurrentImage == null) { + // if this gif can not be displayed, just try to show it as jpg by parsing mUri + setImageURI(mUri); + return; + } + setImageURI(null); + int saveCount = canvas.getSaveCount(); + canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + Rect sRect = null; + Rect dRect = null; + + int imageHeight = mCurrentImage.getHeight(); + int imageWidth = mCurrentImage.getWidth(); + + int displayHeight = ViewGifImage.mDM.heightPixels; + int displayWidth = ViewGifImage.mDM.widthPixels; + + int width, height; + if (imageWidth >= displayWidth || imageHeight >= displayHeight) { + // scale-down the image + if (imageWidth * displayHeight > displayWidth * imageHeight) { + width = displayWidth; + height = (imageHeight * width) / imageWidth; + } else { + height = displayHeight; + width = (imageWidth * height) / imageHeight; + } + } else { + // scale-up the image + float scale = Math.min(SCALE_LIMIT, Math.min(displayWidth / (float) imageWidth, + displayHeight / (float) imageHeight)); + width = (int) (imageWidth * scale); + height = (int) (imageHeight * scale); + } + dRect = new Rect((displayWidth - width) / 2, (displayHeight - height) / 2, + (displayWidth + width) / 2, (displayHeight + height) / 2); + canvas.drawBitmap(mCurrentImage, sRect, dRect, null); + canvas.restoreToCount(saveCount); + } + + public void parseOk(boolean parseStatus, int frameIndex) { + if (parseStatus) { + //indicates the start of a new GIF + if (mGifDecoder != null && frameIndex == -1 + && mGifDecoder.getFrameCount() > 1) { + if (mDrawThread != null) { + mDrawThread = null; + } + mDrawThread = new DrawThread(); + mDrawThread.start(); + } + } else { + Log.e(TAG, "parse error"); + } + } + + private Handler mRedrawHandler = new Handler() { + public void handleMessage(Message msg) { + invalidate(); + } + }; + + private class DrawThread extends Thread { + public void run() { + while (true) { + if (mGifDecoder == null || !isShown() || mRedrawHandler == null) { + break; + } + GifFrame frame = mGifDecoder.next(); + mCurrentImage = frame.mImage; + + Message msg = mRedrawHandler.obtainMessage(); + mRedrawHandler.sendMessage(msg); + try { + Thread.sleep(getDelay(frame)); + } catch (InterruptedException e) { + Log.e(TAG, "catch exception:" + e); + } + } + } + + } + + private long getDelay (GifFrame frame) { + //in milliseconds + return frame.mDelayInMs == 0 ? FRAME_DELAY : frame.mDelayInMs; + } + + private void freeGifDecoder () { + if (mGifDecoder != null) { + mGifDecoder.free(); + mGifDecoder = null; + } + + } + + public void freeMemory() { + if (mDrawThread != null) { + mDrawThread = null; + } + freeGifDecoder(); + } +} diff --git a/src/com/android/gallery3d/util/GifDecoder.java b/src/com/android/gallery3d/util/GifDecoder.java index 1342355e6..5b076238d 100755 --- a/src/com/android/gallery3d/util/GifDecoder.java +++ b/src/com/android/gallery3d/util/GifDecoder.java @@ -7,7 +7,7 @@ import android.util.Log; import java.io.ByteArrayInputStream; import java.io.InputStream; -public class GifDecoder { +public class GifDecoder extends Thread { public static final int STATUS_PARSING = 0; public static final int STATUS_FORMAT_ERROR = 1; @@ -71,16 +71,14 @@ public class GifDecoder { public GifDecoder(byte[] data, GifAction act) { mGifData = data; mGifAction = act; - startDecoder(); } public GifDecoder(InputStream is, GifAction act) { mIS = is; mGifAction = act; - startDecoder(); } - public void startDecoder() { + public void run() { if (mIS != null) { readStream(); } else if (mGifData != null) { diff --git a/src/com/android/gallery3d/util/ViewGifImage.java b/src/com/android/gallery3d/util/ViewGifImage.java new file mode 100755 index 000000000..a85af5638 --- /dev/null +++ b/src/com/android/gallery3d/util/ViewGifImage.java @@ -0,0 +1,67 @@ +package com.android.gallery3d.util; + +import org.codeaurora.gallery.R; + +import android.app.Activity; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; + +public class ViewGifImage extends Activity { + private static final String TAG = "ViewGifImage"; + public static final String VIEW_GIF_ACTION = "com.android.gallery3d.VIEW_GIF"; + + public static DisplayMetrics mDM; + + private ImageView mGifView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.view_gif_image); + mDM = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(mDM); + if (getIntent().getAction() != null + && getIntent().getAction().equals(VIEW_GIF_ACTION)) { + Uri gifUri = getIntent().getData(); + showGifPicture(gifUri); + } + } + + @Override + protected void onStop() { + super.onStop(); + finish(); + } + + @Override + protected void onDestroy() { + if (mGifView != null && mGifView instanceof GIFView) { + ((GIFView) mGifView).freeMemory(); + mGifView = null; + } + super.onDestroy(); + } + + private void showGifPicture(Uri uri) { + mGifView = new GIFView(this); + ((LinearLayout) findViewById(R.id.image_absoluteLayout)).addView(mGifView, + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + if (((GIFView) mGifView).setDrawable(uri)) return; + + finish(); + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + getWindowManager().getDefaultDisplay().getMetrics(mDM); + super.onConfigurationChanged(newConfig); + } +} -- cgit v1.2.3