summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/CameraManager.java9
-rw-r--r--src/com/android/camera/NewPhotoModule.java2
-rw-r--r--src/com/android/camera/NewVideoModule.java3
-rw-r--r--src/com/android/camera/PhotoModule.java3
-rw-r--r--src/com/android/camera/PhotoUI.java3
-rw-r--r--src/com/android/camera/VideoModule.java3
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java397
-rw-r--r--src/com/android/camera/ui/FilmStripGestureRecognizer.java107
-rw-r--r--src/com/android/camera/ui/FilmStripView.java544
-rw-r--r--src/com/android/gallery3d/app/AlbumPage.java2
-rw-r--r--src/com/android/gallery3d/app/TrimVideo.java2
-rw-r--r--src/com/android/gallery3d/app/VideoUtils.java208
-rw-r--r--src/com/android/gallery3d/app/Wallpaper.java2
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java23
-rw-r--r--src/com/android/gallery3d/filtershow/PanelController.java11
-rw-r--r--src/com/android/gallery3d/filtershow/cache/CachingPipeline.java5
-rw-r--r--src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java22
-rw-r--r--src/com/android/gallery3d/filtershow/cache/RenderingRequest.java5
-rw-r--r--src/com/android/gallery3d/filtershow/controller/StyleChooser.java36
-rw-r--r--src/com/android/gallery3d/filtershow/crop/BoundedRect.java (renamed from src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java)40
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropActivity.java422
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java106
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropExtras.java (renamed from src/com/android/gallery3d/filtershow/CropExtras.java)2
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropLoader.java202
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropMath.java (renamed from src/com/android/gallery3d/filtershow/imageshow/CropMath.java)31
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropObject.java331
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropView.java195
-rw-r--r--src/com/android/gallery3d/filtershow/editors/Editor.java18
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorCrop.java2
-rw-r--r--src/com/android/gallery3d/filtershow/editors/SwapButton.java119
-rw-r--r--src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java7
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java6
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java2
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java2
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java4
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePreset.java2
-rw-r--r--src/com/android/gallery3d/gadget/WidgetConfigure.java2
-rw-r--r--src/com/android/gallery3d/ingest/ui/MtpImageView.java61
38 files changed, 2563 insertions, 378 deletions
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
index b354654b6..af4b13f29 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/CameraManager.java
@@ -72,6 +72,7 @@ public class CameraManager {
private static final int SET_PREVIEW_DISPLAY_ASYNC = 22;
private static final int SET_PREVIEW_CALLBACK = 23;
private static final int ENABLE_SHUTTER_SOUND = 24;
+ private static final int REFRESH_PARAMETERS = 25;
private Handler mCameraHandler;
private android.hardware.Camera mCamera;
@@ -247,6 +248,10 @@ public class CameraManager {
enableShutterSound((msg.arg1 == 1) ? true : false);
return;
+ case REFRESH_PARAMETERS:
+ mParametersIsDirty = true;
+ return;
+
default:
throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
}
@@ -446,6 +451,10 @@ public class CameraManager {
return mParameters;
}
+ public void refreshParameters() {
+ mCameraHandler.sendEmptyMessage(REFRESH_PARAMETERS);
+ }
+
public void enableShutterSound(boolean enable) {
mCameraHandler.obtainMessage(
ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0).sendToTarget();
diff --git a/src/com/android/camera/NewPhotoModule.java b/src/com/android/camera/NewPhotoModule.java
index dfa1e0cc4..e9e60d8a9 100644
--- a/src/com/android/camera/NewPhotoModule.java
+++ b/src/com/android/camera/NewPhotoModule.java
@@ -64,7 +64,7 @@ import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
import com.android.gallery3d.exif.Rational;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.util.UsageStatistics;
diff --git a/src/com/android/camera/NewVideoModule.java b/src/com/android/camera/NewVideoModule.java
index 3fc748593..f8c36c526 100644
--- a/src/com/android/camera/NewVideoModule.java
+++ b/src/com/android/camera/NewVideoModule.java
@@ -1507,6 +1507,9 @@ public class NewVideoModule implements NewCameraModule,
AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
mActivity.getString(R.string.video_recording_started));
+ // The parameters might have been altered by MediaRecorder already.
+ // We need to force mCameraDevice to refresh before getting it.
+ mCameraDevice.refreshParameters();
// The parameters may have been changed by MediaRecorder upon starting
// recording. We need to alter the parameters if we support camcorder
// zoom. To reduce latency when setting the parameters during zoom, we
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 22bd6503f..8759c19d9 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -64,8 +64,8 @@ import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
import com.android.gallery3d.exif.Rational;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.util.UsageStatistics;
import java.io.File;
@@ -590,6 +590,7 @@ public class PhotoModule
if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(
previewWidth, previewHeight);
+ mActivity.notifyScreenNailChanged();
}
private void resetExposureCompensation() {
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 29ebd6a70..53fdc969e 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -144,7 +144,6 @@ public class PhotoUI implements PieListener,
}
}
- mRootView.addOnLayoutChangeListener(mLayoutListener);
}
public View getRootView() {
@@ -259,6 +258,7 @@ public class PhotoUI implements PieListener,
mShutterButton.setImageResource(R.drawable.btn_new_shutter);
mShutterButton.setOnShutterButtonListener(mController);
mShutterButton.setVisibility(View.VISIBLE);
+ mRootView.addOnLayoutChangeListener(mLayoutListener);
}
// called from onResume every other time
@@ -270,6 +270,7 @@ public class PhotoUI implements PieListener,
if (mMenu != null) {
mMenu.reloadPreferences();
}
+ mRootView.addOnLayoutChangeListener(mLayoutListener);
}
public void initializeZoom(Camera.Parameters params) {
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 09a406ce5..2b30ba48f 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -1532,6 +1532,9 @@ public class VideoModule implements CameraModule,
AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(),
mActivity.getString(R.string.video_recording_started));
+ // The parameters might have been altered by MediaRecorder already.
+ // We need to force mCameraDevice to refresh before getting it.
+ mActivity.mCameraDevice.refreshParameters();
// The parameters may have been changed by MediaRecorder upon starting
// recording. We need to alter the parameters if we support camcorder
// zoom. To reduce latency when setting the parameters during zoom, we
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 5d10953f8..a55cc9d52 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -23,6 +23,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
@@ -33,6 +34,7 @@ import android.widget.ImageView;
import com.android.camera.Storage;
import com.android.camera.ui.FilmStripView;
+import com.android.camera.ui.FilmStripView.ImageData;
import java.util.ArrayList;
import java.util.List;
@@ -47,39 +49,11 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
private static final String TAG = "CamreaFilmStripDataProvider";
private static final int DEFAULT_DECODE_SIZE = 3000;
- private static final String ORDER_CLAUSE = ImageColumns.DATE_TAKEN + " DESC, "
- + ImageColumns._ID + " DESC";
private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
- private static final int COL_ID = 0;
- private static final int COL_TITLE = 1;
- private static final int COL_MIME_TYPE = 2;
- private static final int COL_DATE_TAKEN = 3;
- private static final int COL_DATE_MODIFIED = 4;
- private static final int COL_DATA = 5;
- private static final int COL_ORIENTATION = 6;
- private static final int COL_WIDTH = 7;
- private static final int COL_HEIGHT = 8;
- private static final int COL_SIZE = 9;
-
- private static final String[] PROJECTION = {
- ImageColumns._ID, // 0, int
- ImageColumns.TITLE, // 1, string
- ImageColumns.MIME_TYPE, // 2, tring
- ImageColumns.DATE_TAKEN, // 3, int
- ImageColumns.DATE_MODIFIED, // 4, int
- ImageColumns.DATA, // 5, string
- ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
- ImageColumns.WIDTH, // 7, int
- ImageColumns.HEIGHT, // 8, int
- ImageColumns.SIZE // 9, int
- };
-
- // 32K buffer.
- private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
-
- private List<LocalImageData> mImages;
-
- private FilmStripView mFilmStripView;
+
+ private List<LocalData> mImages;
+
+ private Listener mListener;
private View mCameraPreviewView;
private ColorDrawable mPlaceHolder;
@@ -91,8 +65,8 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
mPlaceHolder = new ColorDrawable(placeHolderColor);
}
- public void setCameraPreviewInfo(int width, int height, int orientation) {
- addOrReplaceCameraData(buildCameraImageData(width, height, orientation));
+ public void setCameraPreviewInfo(int width, int height) {
+ addOrReplaceCameraData(buildCameraImageData(width, height));
}
@Override
@@ -101,7 +75,7 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
@Override
- public FilmStripView.ImageData getImageData(int id) {
+ public ImageData getImageData(int id) {
if (id >= mImages.size()) return null;
return mImages.get(id);
}
@@ -116,7 +90,6 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
}
- @Override
public void requestLoad(ContentResolver resolver) {
QueryTask qtask = new QueryTask();
qtask.execute(resolver);
@@ -128,107 +101,87 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
return null;
}
- LocalImageData data = mImages.get(dataID);
-
- if (data.isCameraData) return mCameraPreviewView;
-
- ImageView v = new ImageView(c);
- v.setImageDrawable(mPlaceHolder);
-
- v.setScaleType(ImageView.ScaleType.FIT_XY);
- LoadBitmapTask task = new LoadBitmapTask(data, v);
- task.execute();
- return v;
+ return mImages.get(dataID).getView(
+ c, mSuggestedWidth, mSuggestedHeight, mPlaceHolder);
}
@Override
- public void setDataListener(FilmStripView v) {
- mFilmStripView = v;
+ public void setListener(Listener listener) {
+ mListener = listener;
}
- private LocalImageData buildCameraImageData(int width, int height, int orientation) {
- LocalImageData d = new LocalImageData();
- d.width = width;
- d.height = height;
- d.orientation = orientation;
- d.isCameraData = true;
+ private LocalData buildCameraImageData(int width, int height) {
+ LocalData d = new CameraPreviewData(width, height);
return d;
}
- private void addOrReplaceCameraData(LocalImageData data) {
- if (mImages == null) mImages = new ArrayList<LocalImageData>();
+ private void addOrReplaceCameraData(LocalData data) {
+ if (mImages == null) mImages = new ArrayList<LocalData>();
if (mImages.size() == 0) {
mImages.add(0, data);
return;
}
- LocalImageData first = mImages.get(0);
- if (first.isCameraData) {
+ LocalData first = mImages.get(0);
+ if (first.getType() == ImageData.TYPE_CAMERA_PREVIEW) {
mImages.set(0, data);
} else {
mImages.add(0, data);
}
}
- private LocalImageData buildCursorImageData(Cursor c) {
- LocalImageData d = new LocalImageData();
- d.id = c.getInt(COL_ID);
- d.title = c.getString(COL_TITLE);
- d.mimeType = c.getString(COL_MIME_TYPE);
- d.path = c.getString(COL_DATA);
- d.orientation = c.getInt(COL_ORIENTATION);
- d.width = c.getInt(COL_WIDTH);
- d.height = c.getInt(COL_HEIGHT);
- if (d.width <= 0 || d.height <= 0) {
- Log.v(TAG, "warning! zero dimension for "
- + d.path + ":" + d.width + "x" + d.height);
- Dimension dim = decodeDimension(d.path);
- if (dim != null) {
- d.width = dim.width;
- d.height = dim.height;
- } else {
- Log.v(TAG, "warning! dimension decode failed for " + d.path);
- Bitmap b = BitmapFactory.decodeFile(d.path);
- if (b == null) return null;
- d.width = b.getWidth();
- d.height = b.getHeight();
+ private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
+ @Override
+ protected List<LocalData> doInBackground(ContentResolver... resolver) {
+ List<LocalData> l = null;
+ Cursor c = resolver[0].query(
+ Images.Media.EXTERNAL_CONTENT_URI,
+ LocalPhotoData.QUERY_PROJECTION,
+ MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
+ LocalPhotoData.QUERY_ORDER);
+ if (c == null) return null;
+
+ // build up the list.
+ l = new ArrayList<LocalData>();
+ c.moveToFirst();
+ while (!c.isLast()) {
+ LocalData data = LocalPhotoData.buildFromCursor(c);
+ if (data != null) {
+ l.add(data);
+ } else {
+ Log.e(TAG, "Error decoding file:"
+ + c.getString(LocalPhotoData.COL_DATA));
+ }
+ c.moveToNext();
}
+ c.close();
+ return l;
}
- if (d.orientation == 90 || d.orientation == 270) {
- int b = d.width;
- d.width = d.height;
- d.height = b;
- }
- return d;
- }
- private Dimension decodeDimension(String path) {
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inJustDecodeBounds = true;
- Bitmap b = BitmapFactory.decodeFile(path, opts);
- if (b == null) return null;
- Dimension d = new Dimension();
- d.width = opts.outWidth;
- d.height = opts.outHeight;
- return d;
- }
-
- private class Dimension {
- public int width;
- public int height;
+ @Override
+ protected void onPostExecute(List<LocalData> l) {
+ boolean changed = (l != mImages);
+ LocalData first = null;
+ if (mImages != null && mImages.size() > 0) {
+ first = mImages.get(0);
+ if (first.getType() != ImageData.TYPE_CAMERA_PREVIEW) first = null;
+ }
+ mImages = l;
+ if (first != null) addOrReplaceCameraData(first);
+ // both might be null.
+ if (changed) mListener.onDataLoaded();
+ }
}
- private class LocalImageData implements FilmStripView.ImageData {
- public boolean isCameraData;
+ private abstract static class LocalData implements FilmStripView.ImageData {
public int id;
public String title;
public String mimeType;
public String path;
- // from MediaStore, can only be 0, 90, 180, 270;
- public int orientation;
// width and height should be adjusted according to orientation.
public int width;
public int height;
+ public int supportedAction;
@Override
public int getWidth() {
@@ -241,90 +194,194 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter {
}
@Override
- public String toString() {
- return "LocalImageData:" + ",data=" + path + ",mimeType=" + mimeType
- + "," + width + "x" + height + ",orientation=" + orientation;
+ public boolean isActionSupported(int action) {
+ return false;
}
+
+ @Override
+ abstract public int getType();
+
+ abstract View getView(Context c, int width, int height, Drawable placeHolder);
}
- private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalImageData>> {
- private ContentResolver mResolver;
- private LocalImageData mCameraImageData;
+ private class CameraPreviewData extends LocalData {
+ private int mWidth;
+ private int mHeight;
+
+ CameraPreviewData(int w, int h) {
+ mWidth = w;
+ mHeight = h;
+ }
@Override
- protected List<LocalImageData> doInBackground(ContentResolver... resolver) {
- List<LocalImageData> l = null;
- Cursor c = resolver[0].query(Images.Media.EXTERNAL_CONTENT_URI, PROJECTION,
- MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
- ORDER_CLAUSE);
- if (c == null) return null;
- l = new ArrayList<LocalImageData>();
- c.moveToFirst();
- while (!c.isLast()) {
- LocalImageData data = buildCursorImageData(c);
- if (data != null) l.add(data);
- else Log.e(TAG, "Error decoding file:" + c.getString(COL_DATA));
- c.moveToNext();
- }
- c.close();
- return l;
+ public int getWidth() {
+ return mWidth;
}
@Override
- protected void onPostExecute(List<LocalImageData> l) {
- boolean changed = (l != mImages);
- LocalImageData first = null;
- if (mImages != null && mImages.size() > 0) {
- first = mImages.get(0);
- if (!first.isCameraData) first = null;
- }
- mImages = l;
- if (first != null) addOrReplaceCameraData(first);
- // both might be null.
- if (changed) mFilmStripView.onDataChanged();
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getType() {
+ return ImageData.TYPE_CAMERA_PREVIEW;
+ }
+
+ @Override
+ View getView(Context c, int width, int height, Drawable placeHolder) {
+ return mCameraPreviewView;
}
}
- private class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> {
- private LocalImageData mData;
- private ImageView mView;
+ private static class LocalPhotoData extends LocalData {
+ static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, "
+ + ImageColumns._ID + " DESC";
+ static final String[] QUERY_PROJECTION = {
+ ImageColumns._ID, // 0, int
+ ImageColumns.TITLE, // 1, string
+ ImageColumns.MIME_TYPE, // 2, tring
+ ImageColumns.DATE_TAKEN, // 3, int
+ ImageColumns.DATE_MODIFIED, // 4, int
+ ImageColumns.DATA, // 5, string
+ ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
+ ImageColumns.WIDTH, // 7, int
+ ImageColumns.HEIGHT, // 8, int
+ ImageColumns.SIZE // 9, int
+ };
+
+ private static final int COL_ID = 0;
+ private static final int COL_TITLE = 1;
+ private static final int COL_MIME_TYPE = 2;
+ private static final int COL_DATE_TAKEN = 3;
+ private static final int COL_DATE_MODIFIED = 4;
+ private static final int COL_DATA = 5;
+ private static final int COL_ORIENTATION = 6;
+ private static final int COL_WIDTH = 7;
+ private static final int COL_HEIGHT = 8;
+ private static final int COL_SIZE = 9;
+
+
+ // 32K buffer.
+ private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
+
+ // from MediaStore, can only be 0, 90, 180, 270;
+ public int orientation;
+
+ static LocalPhotoData buildFromCursor(Cursor c) {
+ LocalPhotoData d = new LocalPhotoData();
+ d.id = c.getInt(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.path = c.getString(COL_DATA);
+ d.orientation = c.getInt(COL_ORIENTATION);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ if (d.width <= 0 || d.height <= 0) {
+ Log.v(TAG, "warning! zero dimension for "
+ + d.path + ":" + d.width + "x" + d.height);
+ Dimension dim = decodeDimension(d.path);
+ if (dim != null) {
+ d.width = dim.width;
+ d.height = dim.height;
+ } else {
+ Log.v(TAG, "warning! dimension decode failed for " + d.path);
+ Bitmap b = BitmapFactory.decodeFile(d.path);
+ if (b == null) return null;
+ d.width = b.getWidth();
+ d.height = b.getHeight();
+ }
+ }
+ if (d.orientation == 90 || d.orientation == 270) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
- public LoadBitmapTask(
- LocalImageData d, ImageView v) {
- mData = d;
- mView = v;
+ @Override
+ View getView(Context c,
+ int decodeWidth, int decodeHeight, Drawable placeHolder) {
+ ImageView v = new ImageView(c);
+ v.setImageDrawable(placeHolder);
+
+ v.setScaleType(ImageView.ScaleType.FIT_XY);
+ LoadBitmapTask task = new LoadBitmapTask(v, decodeWidth, decodeHeight);
+ task.execute();
+ return v;
+ }
+
+
+ @Override
+ public String toString() {
+ return "LocalPhotoData:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",orientation=" + orientation;
}
@Override
- protected Bitmap doInBackground(Void... v) {
- BitmapFactory.Options opts = null;
- Bitmap b;
- int sample = 1;
- while (mSuggestedWidth * sample < mData.width
- || mSuggestedHeight * sample < mData.height) {
- sample *= 2;
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+
+ private static Dimension decodeDimension(String path) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ Bitmap b = BitmapFactory.decodeFile(path, opts);
+ if (b == null) return null;
+ Dimension d = new Dimension();
+ d.width = opts.outWidth;
+ d.height = opts.outHeight;
+ return d;
+ }
+
+ private static class Dimension {
+ public int width;
+ public int height;
+ }
+
+ private class LoadBitmapTask extends AsyncTask<Void, Void, Bitmap> {
+ private ImageView mView;
+ private int mDecodeWidth;
+ private int mDecodeHeight;
+
+ public LoadBitmapTask(ImageView v, int decodeWidth, int decodeHeight) {
+ mView = v;
+ mDecodeWidth = decodeWidth;
+ mDecodeHeight = decodeHeight;
}
- opts = new BitmapFactory.Options();
- opts.inSampleSize = sample;
- opts.inTempStorage = DECODE_TEMP_STORAGE;
- if (isCancelled()) return null;
- b = BitmapFactory.decodeFile(mData.path, opts);
- if (mData.orientation != 0) {
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ BitmapFactory.Options opts = null;
+ Bitmap b;
+ int sample = 1;
+ while (mDecodeWidth * sample < width
+ || mDecodeHeight * sample < height) {
+ sample *= 2;
+ }
+ opts = new BitmapFactory.Options();
+ opts.inSampleSize = sample;
+ opts.inTempStorage = DECODE_TEMP_STORAGE;
if (isCancelled()) return null;
- Matrix m = new Matrix();
- m.setRotate((float) mData.orientation);
- b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+ b = BitmapFactory.decodeFile(path, opts);
+ if (orientation != 0) {
+ if (isCancelled()) return null;
+ Matrix m = new Matrix();
+ m.setRotate((float) orientation);
+ b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+ }
+ return b;
}
- return b;
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Cannot decode bitmap file:" + mData.path);
- return;
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap == null) {
+ Log.e(TAG, "Cannot decode bitmap file:" + path);
+ return;
+ }
+ mView.setImageBitmap(bitmap);
}
- mView.setImageBitmap(bitmap);
}
}
}
diff --git a/src/com/android/camera/ui/FilmStripGestureRecognizer.java b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
new file mode 100644
index 000000000..f0e2534d3
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+// This class aggregates three gesture detectors: GestureDetector,
+// ScaleGestureDetector.
+public class FilmStripGestureRecognizer {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FilmStripGestureRecognizer";
+
+ public interface Listener {
+ boolean onSingleTapUp(float x, float y);
+ boolean onDoubleTap(float x, float y);
+ boolean onScroll(float x, float y, float dx, float dy);
+ boolean onFling(float velocityX, float velocityY);
+ boolean onScaleBegin(float focusX, float focusY);
+ boolean onScale(float focusX, float focusY, float scale);
+ boolean onDown(float x, float y);
+ void onScaleEnd();
+ }
+
+ private final GestureDetector mGestureDetector;
+ private final ScaleGestureDetector mScaleDetector;
+ private final Listener mListener;
+
+ public FilmStripGestureRecognizer(Context context, Listener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new MyGestureListener(),
+ null, true /* ignoreMultitouch */);
+ mScaleDetector = new ScaleGestureDetector(
+ context, new MyScaleListener());
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ return mGestureDetector.onTouchEvent(event) || mScaleDetector.onTouchEvent(event);
+ }
+
+ private class MyGestureListener
+ extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mListener.onSingleTapUp(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return mListener.onDoubleTap(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onScroll(
+ MotionEvent e1, MotionEvent e2, float dx, float dy) {
+ return mListener.onScroll(e2.getX(), e2.getY(), dx, dy);
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return mListener.onFling(velocityX, velocityY);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ mListener.onDown(e.getX(), e.getY());
+ return super.onDown(e);
+ }
+ }
+
+ private class MyScaleListener
+ extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return mListener.onScaleBegin(
+ detector.getFocusX(), detector.getFocusY());
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ return mListener.onScale(detector.getFocusX(),
+ detector.getFocusY(), detector.getScaleFactor());
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mListener.onScaleEnd();
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 326e969b2..eea9f0155 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -16,69 +16,99 @@
package com.android.camera.ui;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
public class FilmStripView extends ViewGroup {
-
private static final String TAG = "FilmStripView";
private static final int BUFFER_SIZE = 5;
// Horizontal padding of children.
private static final int H_PADDING = 50;
// Duration to go back to the first.
- private static final int BACK_SCROLL_DURATION = 500;
- private static final float MIN_SCALE = 0.7f;
+ private static final int DURATION_BACK_ANIM = 500;
+ private static final int DURATION_SCROLL_TO_FILMSTRIP = 350;
+ private static final int DURATION_GEOMETRY_ADJUST = 200;
+ private static final float FILM_STRIP_SCALE = 0.6f;
+ private static final float MAX_SCALE = 1f;
private Context mContext;
- private GestureDetector mGestureDetector;
+ private FilmStripGestureRecognizer mGestureRecognizer;
private DataAdapter mDataAdapter;
private final Rect mDrawArea = new Rect();
private int mCurrentInfo;
- private Scroller mScroller;
- private boolean mIsScrolling;
+ private float mScale;
+ private GeometryAnimator mGeometryAnimator;
private int mCenterPosition = -1;
private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE];
public interface ImageData {
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_CAMERA_PREVIEW = 1;
+ public static final int TYPE_PHOTO = 2;
+ public static final int TYPE_VIDEO = 3;
+ public static final int TYPE_PHOTOSPHERE = 4;
+
+ // The actions are defined bit-wise so we can use bit operations like
+ // | and &.
+ public static final int ACTION_NONE = 0;
+ public static final int ACTION_PROMOTE = 1;
+ public static final int ACTION_DEMOTE = 2;
+
+ // SIZE_FULL means disgard the width or height when deciding the view size
+ // of this ImageData, just use full screen size.
+ public static final int SIZE_FULL = -2;
+
// The values returned by getWidth() and getHeight() will be used for layout.
public int getWidth();
public int getHeight();
+ public int getType();
+ public boolean isActionSupported(int action);
}
public interface DataAdapter {
+ public interface Listener {
+ public void onDataLoaded();
+ public void onDataInserted(int dataID);
+ public void onDataRemoved(int dataID);
+ }
public int getTotalNumber();
public View getView(Context context, int id);
public ImageData getImageData(int id);
public void suggestSize(int w, int h);
- public void requestLoad(ContentResolver r);
- public void setDataListener(FilmStripView v);
+ public void setListener(Listener listener);
}
+ // A helper class to tract and calculate the view coordination.
private static class ViewInfo {
private int mDataID;
// the position of the left of the view in the whole filmstrip.
private int mLeftPosition;
- private View mView;
+ private View mView;
+ private float mOffsetY;
public ViewInfo(int id, View v) {
+ v.setPivotX(0f);
+ v.setPivotY(0f);
mDataID = id;
mView = v;
mLeftPosition = -1;
+ mOffsetY = 0;
}
- public int getId() {
+ public int getID() {
return mDataID;
}
@@ -90,7 +120,15 @@ public class FilmStripView extends ViewGroup {
return mLeftPosition;
}
- public int getCenterPosition() {
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
+ public void setOffsetY(float offset) {
+ mOffsetY = offset;
+ }
+
+ public int getCenterX() {
return mLeftPosition + mView.getWidth() / 2;
}
@@ -98,15 +136,20 @@ public class FilmStripView extends ViewGroup {
return mView;
}
- private void layoutAt(int l, int t) {
- mView.layout(l, t, l + mView.getMeasuredWidth(), t + mView.getMeasuredHeight());
+ private void layoutAt(int left, int top) {
+ mView.layout(left, top, left + mView.getMeasuredWidth(),
+ top + mView.getMeasuredHeight());
}
- public void layoutIn(Rect drawArea, int refCenter) {
+ public void layoutIn(Rect drawArea, int refCenter, float scale) {
// drawArea is where to layout in.
// refCenter is the absolute horizontal position of the center of drawArea.
- layoutAt(drawArea.centerX() + mLeftPosition - refCenter,
- drawArea.centerY() - mView.getMeasuredHeight() / 2);
+ int left = (int) (drawArea.centerX() + (mLeftPosition - refCenter) * scale);
+ int top = (int) (drawArea.centerY() - (mView.getMeasuredHeight() / 2) * scale
+ + mOffsetY);
+ layoutAt(left, top);
+ mView.setScaleX(scale);
+ mView.setScaleY(scale);
}
}
@@ -129,15 +172,33 @@ public class FilmStripView extends ViewGroup {
mCurrentInfo = (BUFFER_SIZE - 1) / 2;
setWillNotDraw(false);
mContext = context;
- mScroller = new Scroller(context);
- mGestureDetector =
- new GestureDetector(context, new MyGestureListener(),
- null, true /* ignoreMultitouch */);
+ mScale = 1.0f;
+ mGeometryAnimator = new GeometryAnimator(context);
+ mGestureRecognizer =
+ new FilmStripGestureRecognizer(context, new MyGestureReceiver());
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public boolean isAnchoredTo(int id) {
+ if (mViewInfo[mCurrentInfo].getID() == id
+ && mViewInfo[mCurrentInfo].getCenterX() == mCenterPosition) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getCurrentType() {
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ if (curr == null) return ImageData.TYPE_NONE;
+ return mDataAdapter.getImageData(curr.getID()).getType();
}
@Override
public void onDraw(Canvas c) {
- if (mIsScrolling) {
+ if (mGeometryAnimator.hasNewGeometry()) {
layoutChildren();
}
}
@@ -146,13 +207,11 @@ public class FilmStripView extends ViewGroup {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int w = MeasureSpec.getSize(widthMeasureSpec);
- int h = MeasureSpec.getSize(heightMeasureSpec);
- float scale = MIN_SCALE;
- if (mDataAdapter != null) mDataAdapter.suggestSize(w / 2, h / 2);
-
- int boundWidth = (int) (w * scale);
- int boundHeight = (int) (h * scale);
+ int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (mDataAdapter != null) {
+ mDataAdapter.suggestSize(boundWidth / 2, boundHeight / 2);
+ }
int wMode = View.MeasureSpec.EXACTLY;
int hMode = View.MeasureSpec.EXACTLY;
@@ -161,22 +220,25 @@ public class FilmStripView extends ViewGroup {
ViewInfo info = mViewInfo[i];
if (mViewInfo[i] == null) continue;
- int imageWidth = mDataAdapter.getImageData(info.getId()).getWidth();
- int imageHeight = mDataAdapter.getImageData(info.getId()).getHeight();
+ int imageWidth = mDataAdapter.getImageData(info.getID()).getWidth();
+ int imageHeight = mDataAdapter.getImageData(info.getID()).getHeight();
+ if (imageWidth == ImageData.SIZE_FULL) imageWidth = boundWidth;
+ if (imageHeight == ImageData.SIZE_FULL) imageHeight = boundHeight;
int scaledWidth = boundWidth;
int scaledHeight = boundHeight;
+
if (imageWidth * scaledHeight > scaledWidth * imageHeight) {
scaledHeight = imageHeight * scaledWidth / imageWidth;
} else {
scaledWidth = imageWidth * scaledHeight / imageHeight;
}
- scaledWidth += H_PADDING * 2 * scale;
+ scaledWidth += H_PADDING * 2;
mViewInfo[i].getView().measure(
View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode)
, View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode));
}
- setMeasuredDimension(w, h);
+ setMeasuredDimension(boundWidth, boundHeight);
}
private int findTheNearestView(int pointX) {
@@ -188,14 +250,14 @@ public class FilmStripView extends ViewGroup {
nearest++);
// no existing available ViewInfo
if (nearest == BUFFER_SIZE) return -1;
- int min = Math.abs(pointX - mViewInfo[nearest].getCenterPosition());
+ int min = Math.abs(pointX - mViewInfo[nearest].getCenterX());
for (int infoID = nearest + 1;
infoID < BUFFER_SIZE && mViewInfo[infoID] != null; infoID++) {
// not measured yet.
if (mViewInfo[infoID].getLeftPosition() == -1) continue;
- int c = mViewInfo[infoID].getCenterPosition();
+ int c = mViewInfo[infoID].getCenterX();
int dist = Math.abs(pointX - c);
if (dist < min) {
min = dist;
@@ -205,6 +267,14 @@ public class FilmStripView extends ViewGroup {
return nearest;
}
+ private ViewInfo buildInfoFromData(int dataID) {
+ View v = mDataAdapter.getView(mContext, dataID);
+ if (v == null) return null;
+ v.setPadding(H_PADDING, 0, H_PADDING, 0);
+ addView(v);
+ return new ViewInfo(dataID, v);
+ }
+
// We try to keep the one closest to the center of the screen at position mCurrentInfo.
private void stepIfNeeded() {
int nearest = findTheNearestView(mCenterPosition);
@@ -223,7 +293,8 @@ public class FilmStripView extends ViewGroup {
}
for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
mViewInfo[k] = null;
- if (mViewInfo[k - 1] != null) getInfo(k, mViewInfo[k - 1].getId() + 1);
+ if (mViewInfo[k - 1] != null)
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k - 1].getID() + 1);
}
} else {
for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
@@ -236,47 +307,46 @@ public class FilmStripView extends ViewGroup {
}
for (int k = -1 - adjust; k >= 0; k--) {
mViewInfo[k] = null;
- if (mViewInfo[k + 1] != null) getInfo(k, mViewInfo[k + 1].getId() - 1);
+ if (mViewInfo[k + 1] != null)
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1);
}
}
}
- private void stopScroll() {
- mScroller.forceFinished(true);
- mIsScrolling = false;
- }
-
+ // Don't go out of bound.
private void adjustCenterPosition() {
ViewInfo curr = mViewInfo[mCurrentInfo];
if (curr == null) return;
- if (curr.getId() == 0 && mCenterPosition < curr.getCenterPosition()) {
- mCenterPosition = curr.getCenterPosition();
- if (mIsScrolling) stopScroll();
+ if (curr.getID() == 0 && mCenterPosition < curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
}
- if (curr.getId() == mDataAdapter.getTotalNumber() - 1
- && mCenterPosition > curr.getCenterPosition()) {
- mCenterPosition = curr.getCenterPosition();
- if (mIsScrolling) stopScroll();
+ if (curr.getID() == mDataAdapter.getTotalNumber() - 1
+ && mCenterPosition > curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
}
}
private void layoutChildren() {
- mIsScrolling = mScroller.computeScrollOffset();
-
- if (mIsScrolling) mCenterPosition = mScroller.getCurrX();
+ if (mGeometryAnimator.hasNewGeometry()) {
+ mCenterPosition = mGeometryAnimator.getNewPosition();
+ mScale = mGeometryAnimator.getNewScale();
+ }
adjustCenterPosition();
- mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition);
+ mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition, mScale);
// images on the left
for (int infoID = mCurrentInfo - 1; infoID >= 0; infoID--) {
ViewInfo curr = mViewInfo[infoID];
if (curr != null) {
ViewInfo next = mViewInfo[infoID + 1];
- curr.setLeftPosition(next.getLeftPosition() - curr.getView().getMeasuredWidth());
- curr.layoutIn(mDrawArea, mCenterPosition);
+ curr.setLeftPosition(
+ next.getLeftPosition() - curr.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
}
}
@@ -285,8 +355,9 @@ public class FilmStripView extends ViewGroup {
ViewInfo curr = mViewInfo[infoID];
if (curr != null) {
ViewInfo prev = mViewInfo[infoID - 1];
- curr.setLeftPosition(prev.getLeftPosition() + prev.getView().getMeasuredWidth());
- curr.layoutIn(mDrawArea, mCenterPosition);
+ curr.setLeftPosition(
+ prev.getLeftPosition() + prev.getView().getMeasuredWidth());
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
}
}
@@ -306,24 +377,43 @@ public class FilmStripView extends ViewGroup {
layoutChildren();
}
- public void setDataAdapter(
- DataAdapter adapter, ContentResolver resolver) {
+ public void setDataAdapter(DataAdapter adapter) {
mDataAdapter = adapter;
mDataAdapter.suggestSize(getMeasuredWidth(), getMeasuredHeight());
- mDataAdapter.setDataListener(this);
- mDataAdapter.requestLoad(resolver);
+ mDataAdapter.setListener(new DataAdapter.Listener() {
+ @Override
+ public void onDataLoaded() {
+ reload();
+ }
+
+ @Override
+ public void onDataInserted(int dataID) {
+ }
+
+ @Override
+ public void onDataRemoved(int dataID) {
+ }
+ });
}
- private void getInfo(int infoID, int dataID) {
- View v = mDataAdapter.getView(mContext, dataID);
- if (v == null) return;
- v.setPadding(H_PADDING, 0, H_PADDING, 0);
- addView(v);
- ViewInfo info = new ViewInfo(dataID, v);
- mViewInfo[infoID] = info;
+ public boolean isInCameraFullscreen() {
+ return (isAnchoredTo(0) && mScale == 1f
+ && getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW);
}
- public void onDataChanged() {
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (isInCameraFullscreen()) return false;
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mGestureRecognizer.onTouchEvent(ev);
+ return true;
+ }
+
+ public void reload() {
removeAllViews();
int dataNumber = mDataAdapter.getTotalNumber();
if (dataNumber == 0) return;
@@ -333,89 +423,315 @@ public class FilmStripView extends ViewGroup {
// previous data exists.
if (mViewInfo[mCurrentInfo] != null) {
currentLeft = mViewInfo[mCurrentInfo].getLeftPosition();
- currentData = mViewInfo[mCurrentInfo].getId();
+ currentData = mViewInfo[mCurrentInfo].getID();
}
- getInfo(mCurrentInfo, currentData);
+ mViewInfo[mCurrentInfo] = buildInfoFromData(currentData);
mViewInfo[mCurrentInfo].setLeftPosition(currentLeft);
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW
+ && currentLeft == 0) {
+ // we are in camera mode by default.
+ mGeometryAnimator.lockPosition(currentLeft);
+ }
for (int i = 1; mCurrentInfo + i < BUFFER_SIZE || mCurrentInfo - i >= 0; i++) {
int infoID = mCurrentInfo + i;
if (infoID < BUFFER_SIZE && mViewInfo[infoID - 1] != null) {
- getInfo(infoID, mViewInfo[infoID - 1].getId() + 1);
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID - 1].getID() + 1);
}
infoID = mCurrentInfo - i;
if (infoID >= 0 && mViewInfo[infoID + 1] != null) {
- getInfo(infoID, mViewInfo[infoID + 1].getId() - 1);
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID + 1].getID() - 1);
}
}
layoutChildren();
}
- private void movePositionTo(int position) {
- mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
- 0, BACK_SCROLL_DURATION);
- layoutChildren();
- }
+ // GeometryAnimator controls all the geometry animations. It passively
+ // tells the geometry information on demand.
+ private class GeometryAnimator implements
+ ValueAnimator.AnimatorUpdateListener,
+ Animator.AnimatorListener {
+
+ private ValueAnimator mScaleAnimator;
+ private boolean mHasNewScale;
+ private float mNewScale;
+
+ private Scroller mScroller;
+ private boolean mHasNewPosition;
+ private DecelerateInterpolator mDecelerateInterpolator;
+
+ private boolean mCanStopScroll;
+ private boolean mCanStopScale;
+
+ private boolean mIsPositionLocked;
+ private int mLockedPosition;
+
+ private Runnable mPostAction;
+
+ GeometryAnimator(Context context) {
+ mScroller = new Scroller(context);
+ mHasNewPosition = false;
+ mScaleAnimator = new ValueAnimator();
+ mScaleAnimator.addUpdateListener(GeometryAnimator.this);
+ mScaleAnimator.addListener(GeometryAnimator.this);
+ mDecelerateInterpolator = new DecelerateInterpolator();
+ mCanStopScroll = true;
+ mCanStopScale = true;
+ mHasNewScale = false;
+ }
- public void goToFirst() {
- movePositionTo(0);
- }
+ boolean hasNewGeometry() {
+ mHasNewPosition = mScroller.computeScrollOffset();
+ if (!mHasNewPosition) {
+ mCanStopScroll = true;
+ }
+ // If the position is locked, then we always return true to force
+ // the position value to use the locked value.
+ return (mHasNewPosition || mHasNewScale || mIsPositionLocked);
+ }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mGestureDetector.onTouchEvent(ev);
- }
+ // Always call hasNewGeometry() before getting the new scale value.
+ float getNewScale() {
+ if (!mHasNewScale) return mScale;
+ mHasNewScale = false;
+ return mNewScale;
+ }
+
+ // Always call hasNewGeometry() before getting the new position value.
+ int getNewPosition() {
+ if (mIsPositionLocked) return mLockedPosition;
+ if (!mHasNewPosition) return mCenterPosition;
+ return mScroller.getCurrX();
+ }
- private class MyGestureListener
- extends GestureDetector.SimpleOnGestureListener {
+ void lockPosition(int pos) {
+ mIsPositionLocked = true;
+ mLockedPosition = pos;
+ }
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- float x = (float) e.getX();
- float y = (float) e.getY();
- for (int i = 0; i < BUFFER_SIZE; i++) {
- if (mViewInfo[i] == null) continue;
- View v = mViewInfo[i].getView();
- if (x >= v.getLeft() && x < v.getRight()
- && y >= v.getTop() && y < v.getBottom()) {
- Log.v(TAG, "l, r, t, b " + v.getLeft() + ',' + v.getRight()
- + ',' + v.getTop() + ',' + v.getBottom());
- movePositionTo(mViewInfo[i].getCenterPosition());
- break;
- }
+ void unlockPosition() {
+ if (mIsPositionLocked) {
+ // only when the position is previously locked we set the current
+ // position to make it consistent.
+ mCenterPosition = mLockedPosition;
+ mIsPositionLocked = false;
}
+ }
+
+ void fling(int velocityX, int minX, int maxX) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mScroller.fling(mCenterPosition, 0, velocityX, 0, minX, maxX, 0, 0);
+ }
+
+ boolean stopScroll() {
+ if (!mCanStopScroll) return false;
+ mScroller.forceFinished(true);
+ mHasNewPosition = false;
return true;
}
+ boolean stopScale() {
+ if (!mCanStopScale) return false;
+ mScaleAnimator.cancel();
+ mHasNewScale = false;
+ return true;
+ }
+
+ void stop() {
+ stopScroll();
+ stopScale();
+ }
+
+ void scrollTo(int position, int duration, boolean interruptible) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mCanStopScroll = interruptible;
+ stopScroll();
+ mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
+ 0, duration);
+ }
+
+ void scrollTo(int position, int duration) {
+ scrollTo(position, duration, true);
+ }
+
+ void scaleTo(float scale, int duration, boolean interruptible) {
+ if (!stopScale()) return;
+ mCanStopScale = interruptible;
+ mScaleAnimator.setDuration(duration);
+ mScaleAnimator.setFloatValues(mScale, scale);
+ mScaleAnimator.setInterpolator(mDecelerateInterpolator);
+ mScaleAnimator.start();
+ mHasNewScale = true;
+ }
+
+ void scaleTo(float scale, int duration) {
+ scaleTo(scale, duration, true);
+ }
+
+ void setPostAction(Runnable act) {
+ mPostAction = act;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mHasNewScale = true;
+ mNewScale = (Float) animation.getAnimatedValue();
+ layoutChildren();
+ }
+
+ @Override
+ public void onAnimationStart(Animator anim) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ if (mPostAction != null) {
+ mPostAction.run();
+ mPostAction = null;
+ }
+ mCanStopScale = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator anim) {
+ mPostAction = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator anim) {
+ }
+ }
+
+ private class MyGestureReceiver implements FilmStripGestureRecognizer.Listener {
+ // Indicating the current trend of scaling is up (>1) or down (<1).
+ private float mScaleTrend;
+
+ @Override
+ public boolean onSingleTapUp(float x, float y) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(float x, float y) {
+ return false;
+ }
+
@Override
- public boolean onDown(MotionEvent ev) {
- if (mIsScrolling) stopScroll();
+ public boolean onDown(float x, float y) {
+ mGeometryAnimator.stop();
return true;
}
@Override
- public boolean onScroll(
- MotionEvent e1, MotionEvent e2, float dx, float dy) {
- stopScroll();
- mCenterPosition += dx;
+ public boolean onScroll(float x, float y, float dx, float dy) {
+ int deltaX = (int) (dx / mScale);
+ if (deltaX > 0 && isInCameraFullscreen() ) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
+
+ mCenterPosition += deltaX;
+
+ // Vertical part. Promote or demote.
+ int scaledDeltaY = (int) (dy / mScale);
+
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ if (mViewInfo[i] == null) continue;
+ Rect hitRect = new Rect();
+ View v = mViewInfo[i].getView();
+ v.getHitRect(hitRect);
+ if (hitRect.contains((int) x, (int) y)) {
+ ImageData data = mDataAdapter.getImageData(mViewInfo[i].getID());
+ if ((data.isActionSupported(ImageData.ACTION_DEMOTE) && dy > 0)
+ || (data.isActionSupported(ImageData.ACTION_PROMOTE) && dy < 0)) {
+ mViewInfo[i].setOffsetY(mViewInfo[i].getOffsetY() - dy);
+ }
+ break;
+ }
+ }
+
layoutChildren();
return true;
}
@Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
+ public boolean onFling(float velocityX, float velocityY) {
+ float scaledVelocityX = velocityX / mScale;
+ if (isInCameraFullscreen() && scaledVelocityX < 0) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
ViewInfo info = mViewInfo[mCurrentInfo];
int w = getWidth();
if (info == null) return true;
- mScroller.fling(mCenterPosition, 0, (int) -velocityX, (int) velocityY,
+ mGeometryAnimator.fling((int) -scaledVelocityX,
// estimation of possible length on the left
- info.getLeftPosition() - info.getId() * w * 2,
+ info.getLeftPosition() - info.getID() * w * 2,
// estimation of possible length on the right
info.getLeftPosition()
- + (mDataAdapter.getTotalNumber() - info.getId()) * w * 2,
- 0, 0);
+ + (mDataAdapter.getTotalNumber() - info.getID()) * w * 2);
+ layoutChildren();
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(float focusX, float focusY) {
+ if (isInCameraFullscreen()) return false;
+ mScaleTrend = 1f;
+ return true;
+ }
+
+ @Override
+ public boolean onScale(float focusX, float focusY, float scale) {
+ if (isInCameraFullscreen()) return false;
+
+ mScaleTrend = mScaleTrend * 0.5f + scale * 0.5f;
+ mScale *= scale;
+ if (mScale <= FILM_STRIP_SCALE) mScale = FILM_STRIP_SCALE;
+ if (mScale >= MAX_SCALE) mScale = MAX_SCALE;
layoutChildren();
return true;
}
+
+ @Override
+ public void onScaleEnd() {
+ if (mScaleTrend >= 1f) {
+ if (mScale != 1f) {
+ mGeometryAnimator.scaleTo(1f, DURATION_GEOMETRY_ADJUST, false);
+ }
+
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW) {
+ if (isAnchoredTo(0)) {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ } else {
+ mGeometryAnimator.scrollTo(
+ mViewInfo[mCurrentInfo].getCenterX(),
+ DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mLockPositionRunnable);
+ }
+ }
+ } else {
+ // Scale down to film strip mode.
+ if (mScale == FILM_STRIP_SCALE) {
+ mGeometryAnimator.unlockPosition();
+ return;
+ }
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mUnlockPositionRunnable);
+ }
+ }
+
+ private Runnable mLockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ }
+ };
+
+ private Runnable mUnlockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.unlockPosition();
+ }
+ };
}
}
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index 51591cc51..001ce87b7 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -39,8 +39,8 @@ import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.glrenderer.FadeTexture;
import com.android.gallery3d.glrenderer.GLCanvas;
import com.android.gallery3d.ui.ActionModeHandler;
diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java
index baab88990..1e7728162 100644
--- a/src/com/android/gallery3d/app/TrimVideo.java
+++ b/src/com/android/gallery3d/app/TrimVideo.java
@@ -237,7 +237,7 @@ public class TrimVideo extends Activity implements
public void run() {
try {
VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile,
- mTrimStartTime, mTrimEndTime, mVideoView.getDuration());
+ mTrimStartTime, mTrimEndTime);
// Update the database for adding a new video file.
SaveVideoFileUtils.insertContent(mDstFileInfo,
getContentResolver(), mUri);
diff --git a/src/com/android/gallery3d/app/VideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java
index 8ffc3d5eb..a3c3ef273 100644
--- a/src/com/android/gallery3d/app/VideoUtils.java
+++ b/src/com/android/gallery3d/app/VideoUtils.java
@@ -19,6 +19,14 @@
package com.android.gallery3d.app;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaMuxer;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.util.SaveVideoFileInfo;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.TimeToSampleBox;
@@ -29,17 +37,49 @@ import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
public class VideoUtils {
+ private static final String LOGTAG = "VideoUtils";
+ private static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024;
+
+ /**
+ * Remove the sound track.
+ */
+ public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo)
+ throws IOException {
+ if (ApiHelper.HAS_MEDIA_MUXER) {
+ genVideoUsingMuxer(filePath, dstFileInfo.mFile.getPath(), -1, -1,
+ false, true);
+ } else {
+ startMuteUsingMp4Parser(filePath, dstFileInfo);
+ }
+ }
- public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException {
+ /**
+ * Shortens/Crops tracks
+ */
+ public static void startTrim(File src, File dst, int startMs, int endMs)
+ throws IOException {
+ if (ApiHelper.HAS_MEDIA_MUXER) {
+ genVideoUsingMuxer(src.getPath(), dst.getPath(), startMs, endMs,
+ true, true);
+ } else {
+ trimUsingMp4Parser(src, dst, startMs, endMs);
+ }
+ }
+
+ private static void startMuteUsingMp4Parser(String filePath,
+ SaveVideoFileInfo dstFileInfo) throws FileNotFoundException, IOException {
File dst = dstFileInfo.mFile;
File src = new File(filePath);
RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
@@ -67,16 +107,123 @@ public class VideoUtils {
IsoFile out = new DefaultMp4Builder().build(movie);
FileOutputStream fos = new FileOutputStream(dst);
FileChannel fc = fos.getChannel();
- out.getBox(fc); // This one build up the memory.
+ out.getBox(fc); // This one build up the memory.
fc.close();
fos.close();
}
/**
- * Shortens/Crops a track
+ * @param srcPath the path of source video file.
+ * @param dstPath the path of destination video file.
+ * @param startMs starting time in milliseconds for trimming. Set to
+ * negative if starting from beginning.
+ * @param endMs end time for trimming in milliseconds. Set to negative if
+ * no trimming at the end.
+ * @param useAudio true if keep the audio track from the source.
+ * @param useVideo true if keep the video track from the source.
+ * @throws IOException
*/
- public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException {
+ private static void genVideoUsingMuxer(String srcPath, String dstPath,
+ int startMs, int endMs, boolean useAudio, boolean useVideo)
+ throws IOException {
+ // Set up MediaExtractor to read from the source.
+ MediaExtractor extractor = new MediaExtractor();
+ extractor.setDataSource(srcPath);
+
+ int trackCount = extractor.getTrackCount();
+
+ // Set up MediaMuxer for the destination.
+ MediaMuxer muxer;
+ muxer = new MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+ // Set up the tracks and retrieve the max buffer size for selected
+ // tracks.
+ HashMap<Integer, Integer> indexMap = new HashMap<Integer,
+ Integer>(trackCount);
+ int bufferSize = -1;
+ for (int i = 0; i < trackCount; i++) {
+ MediaFormat format = extractor.getTrackFormat(i);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+
+ boolean selectCurrentTrack = false;
+
+ if (mime.startsWith("audio/") && useAudio) {
+ selectCurrentTrack = true;
+ } else if (mime.startsWith("video/") && useVideo) {
+ selectCurrentTrack = true;
+ }
+
+ if (selectCurrentTrack) {
+ extractor.selectTrack(i);
+ int dstIndex = muxer.addTrack(format);
+ indexMap.put(i, dstIndex);
+ if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
+ int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
+ bufferSize = newSize > bufferSize ? newSize : bufferSize;
+ }
+ }
+ }
+
+ if (bufferSize < 0) {
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ }
+
+ // Set up the orientation and starting time for extractor.
+ MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
+ retrieverSrc.setDataSource(srcPath);
+ String degreesString = retrieverSrc.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ if (degreesString != null) {
+ int degrees = Integer.parseInt(degreesString);
+ if (degrees >= 0) {
+ muxer.setOrientationHint(degrees);
+ }
+ }
+
+ if (startMs > 0) {
+ extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ }
+
+ // Copy the samples from MediaExtractor to MediaMuxer. We will loop
+ // for copying each sample and stop when we get to the end of the source
+ // file or exceed the end time of the trimming.
+ int offset = 0;
+ int trackIndex = -1;
+ ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+ BufferInfo bufferInfo = new BufferInfo();
+
+ muxer.start();
+ while (true) {
+ bufferInfo.offset = offset;
+ bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+ if (bufferInfo.size < 0) {
+ Log.d(LOGTAG, "Saw input EOS.");
+ bufferInfo.size = 0;
+ break;
+ } else {
+ bufferInfo.presentationTimeUs = extractor.getSampleTime();
+ if (endMs > 0 && bufferInfo.presentationTimeUs > (endMs * 1000)) {
+ Log.d(LOGTAG, "The current sample is over the trim end time.");
+ break;
+ } else {
+ bufferInfo.flags = extractor.getSampleFlags();
+ trackIndex = extractor.getSampleTrackIndex();
+
+ muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
+ bufferInfo);
+ extractor.advance();
+ }
+ }
+ }
+
+ muxer.stop();
+ muxer.release();
+ return;
+ }
+
+ private static void trimUsingMp4Parser(File src, File dst, int startMs, int endMs)
+ throws FileNotFoundException, IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
Movie movie = MovieCreator.build(randomAccessFile.getChannel());
@@ -84,22 +231,25 @@ public class VideoUtils {
List<Track> tracks = movie.getTracks();
movie.setTracks(new LinkedList<Track>());
- double startTime = startMs/1000;
- double endTime = endMs/1000;
+ double startTime = startMs / 1000;
+ double endTime = endMs / 1000;
boolean timeCorrected = false;
- // Here we try to find a track that has sync samples. Since we can only start decoding
- // at such a sample we SHOULD make sure that the start of the new fragment is exactly
- // such a frame
+ // Here we try to find a track that has sync samples. Since we can only
+ // start decoding at such a sample we SHOULD make sure that the start of
+ // the new fragment is exactly such a frame.
for (Track track : tracks) {
if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
if (timeCorrected) {
- // This exception here could be a false positive in case we have multiple tracks
- // with sync samples at exactly the same positions. E.g. a single movie containing
- // multiple qualities of the same video (Microsoft Smooth Streaming file)
-
- throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
+ // This exception here could be a false positive in case we
+ // have multiple tracks with sync samples at exactly the
+ // same positions. E.g. a single movie containing multiple
+ // qualities of the same video (Microsoft Smooth Streaming
+ // file)
+ throw new RuntimeException(
+ "The startTime has already been corrected by" +
+ " another track with SyncSample. Not Supported.");
}
startTime = correctTimeToSyncSample(track, startTime, false);
endTime = correctTimeToSyncSample(track, endTime, true);
@@ -116,20 +266,23 @@ public class VideoUtils {
for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
for (int j = 0; j < entry.getCount(); j++) {
- // entry.getDelta() is the amount of time the current sample covers.
+ // entry.getDelta() is the amount of time the current sample
+ // covers.
if (currentTime <= startTime) {
// current sample is still before the new starttime
startSample = currentSample;
}
if (currentTime <= endTime) {
- // current sample is after the new start time and still before the new endtime
+ // current sample is after the new start time and still
+ // before the new endtime
endSample = currentSample;
} else {
// current sample is after the end of the cropped video
break;
}
- currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
+ currentTime += (double) entry.getDelta()
+ / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
}
@@ -139,15 +292,8 @@ public class VideoUtils {
randomAccessFile.close();
}
- protected static long getDuration(Track track) {
- long duration = 0;
- for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
- duration += entry.getCount() * entry.getDelta();
- }
- return duration;
- }
-
- private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
+ private static double correctTimeToSyncSample(Track track, double cutHere,
+ boolean next) {
double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
long currentSample = 0;
double currentTime = 0;
@@ -155,10 +301,13 @@ public class VideoUtils {
TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
for (int j = 0; j < entry.getCount(); j++) {
if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
- // samples always start with 1 but we start with zero therefore +1
- timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
+ // samples always start with 1 but we start with zero
+ // therefore +1
+ timeOfSyncSamples[Arrays.binarySearch(
+ track.getSyncSamples(), currentSample + 1)] = currentTime;
}
- currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
+ currentTime += (double) entry.getDelta()
+ / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
}
@@ -176,5 +325,4 @@ public class VideoUtils {
return timeOfSyncSamples[timeOfSyncSamples.length - 1];
}
-
}
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index 1bbe8d2c6..91bc77271 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -27,7 +27,7 @@ import android.view.Display;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
/**
* Wallpaper picker for the gallery application. This just redirects to the
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 81c171758..1a7d200f1 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -55,6 +55,7 @@ import com.android.gallery3d.data.LocalAlbum;
import com.android.gallery3d.filtershow.cache.CachingPipeline;
import com.android.gallery3d.filtershow.cache.FilteringPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.editors.BasicEditor;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.editors.EditorDraw;
@@ -133,6 +134,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
private FilterIconButton mNullBorderFilter;
private int mIconSeedSize = 140;
+ private View mImageCategoryPanel = null;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -219,6 +221,16 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
setupHistoryPanel();
setupStatePanel();
+
+ mImageCategoryPanel = findViewById(R.id.imageCategoryPanel);
+ }
+
+ public void hideCategoryPanel() {
+ mImageCategoryPanel.setVisibility(View.GONE);
+ }
+
+ public void showCategoryPanel() {
+ mImageCategoryPanel.setVisibility(View.VISIBLE);
}
public void setupHistoryPanel() {
@@ -348,15 +360,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
ImageFilter.setActivityForMemoryToasts(this);
Resources res = getResources();
- // TODO: add a mechanism to set the resources in FiltersManagmer
- ImageFilterBorder filterBorder = (ImageFilterBorder) FiltersManager.getManager().getFilter(ImageFilterBorder.class);
- filterBorder.setResources(res);
- filterBorder = (ImageFilterBorder) FiltersManager.getPreviewManager().getFilter(ImageFilterBorder.class);
- filterBorder.setResources(res);
- ImageFilterFx filterFx = (ImageFilterFx) FiltersManager.getManager().getFilter(ImageFilterFx.class);
- filterFx.setResources(res);
- filterFx = (ImageFilterFx) FiltersManager.getPreviewManager().getFilter(ImageFilterFx.class);
- filterFx.setResources(res);
+ FiltersManager.setResources(res);
ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
// TODO: get those values from XML.
@@ -542,6 +546,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
ImageFilter.resetStatics();
FiltersManager.getPreviewManager().freeRSFilterScripts();
FiltersManager.getManager().freeRSFilterScripts();
+ FiltersManager.getHighresManager().freeRSFilterScripts();
FiltersManager.reset();
CachingPipeline.destroyRenderScriptContext();
super.onDestroy();
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
index 6b20fe180..1d0118f85 100644
--- a/src/com/android/gallery3d/filtershow/PanelController.java
+++ b/src/com/android/gallery3d/filtershow/PanelController.java
@@ -31,6 +31,7 @@ import android.util.Log;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.Editor;
import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
@@ -358,7 +359,6 @@ public class PanelController implements OnClickListener {
if (mCurrentEditor != null) {
mCurrentEditor.reflectCurrentFilter();
}
-
}
}
@@ -369,6 +369,7 @@ public class PanelController implements OnClickListener {
HistoryAdapter adapter = MasterImage.getImage().getHistory();
int position = adapter.undo();
MasterImage.getImage().onHistoryItemClick(position);
+ mActivity.showCategoryPanel();
showPanel(mCurrentPanel);
mCurrentImage.select();
if (mCurrentEditor != null) {
@@ -586,7 +587,11 @@ public class PanelController implements OnClickListener {
}
mUtilityPanel.hideAccessoryViews();
mUtilityPanel.showMenu(false);
+
if (view instanceof FilterIconButton) {
+ if (mCurrentEditor != null) {
+ mCurrentEditor.detach();
+ }
mCurrentEditor = null;
FilterIconButton component = (FilterIconButton) view;
FilterRepresentation representation = component.getFilterRepresentation();
@@ -595,6 +600,9 @@ public class PanelController implements OnClickListener {
mUtilityPanel.setShowParameter(representation.showParameterValue());
if (representation.getEditorId() != 0) {
+ if (representation.getEditorId() != ImageOnlyEditor.ID) {
+ mActivity.hideCategoryPanel();
+ }
if (mEditorPlaceHolder.contains(representation.getEditorId())) {
mCurrentEditor = mEditorPlaceHolder.showEditor(
representation.getEditorId());
@@ -625,6 +633,7 @@ public class PanelController implements OnClickListener {
return;
}
+ mActivity.showCategoryPanel();
int id = view.getId();
if (id == EditorTinyPlanet.ID) {
mCurrentImage = showImageView(R.id.imageTinyPlanet);
diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
index 055309f22..32d4ab094 100644
--- a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
@@ -282,10 +282,13 @@ public class CachingPipeline {
|| request.getType() == RenderingRequest.FILTERS_RENDERING
|| request.getType() == RenderingRequest.ICON_RENDERING
|| request.getType() == RenderingRequest.PARTIAL_RENDERING
- || request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+ || request.getType() == RenderingRequest.HIGHRES_RENDERING
+ || request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
if (request.getType() == RenderingRequest.ICON_RENDERING) {
mEnvironment.setQuality(ImagePreset.QUALITY_ICON);
+ } else if (request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
+ mEnvironment.setQuality(ImagePreset.STYLE_ICON);
} else {
mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
}
diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
index a8c8d7b47..e63323b92 100644
--- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
@@ -34,12 +34,13 @@ public class FilteringPipeline implements Handler.Callback {
private static final String LOGTAG = "FilteringPipeline";
private boolean DEBUG = false;
- private static long HIRES_DELAY = 100; // in ms
+ private static long HIRES_DELAY = 300; // in ms
private volatile boolean mPipelineIsOn = false;
private CachingPipeline mAccessoryPipeline = null;
private CachingPipeline mPreviewPipeline = null;
+ private CachingPipeline mHighresPreviewPipeline = null;
private HandlerThread mHandlerThread = null;
private final static int NEW_PRESET = 0;
@@ -111,7 +112,11 @@ public class FilteringPipeline implements Handler.Callback {
}
RenderingRequest request = (RenderingRequest) msg.obj;
- mAccessoryPipeline.render(request);
+ if (msg.what == COMPUTE_HIGHRES_RENDERING_REQUEST) {
+ mHighresPreviewPipeline.render(request);
+ } else {
+ mAccessoryPipeline.render(request);
+ }
Message uimsg = mUIHandler.obtainMessage(NEW_RENDERING_REQUEST);
uimsg.obj = request;
mUIHandler.sendMessage(uimsg);
@@ -126,8 +131,12 @@ public class FilteringPipeline implements Handler.Callback {
Process.THREAD_PRIORITY_FOREGROUND);
mHandlerThread.start();
mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
- mAccessoryPipeline = new CachingPipeline(FiltersManager.getManager(), "Accessory");
- mPreviewPipeline = new CachingPipeline(FiltersManager.getPreviewManager(), "Preview");
+ mAccessoryPipeline = new CachingPipeline(
+ FiltersManager.getManager(), "Accessory");
+ mPreviewPipeline = new CachingPipeline(
+ FiltersManager.getPreviewManager(), "Preview");
+ mHighresPreviewPipeline = new CachingPipeline(
+ FiltersManager.getHighresManager(), "Highres");
}
public synchronized static FilteringPipeline getPipeline() {
@@ -145,6 +154,7 @@ public class FilteringPipeline implements Handler.Callback {
Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight());
mAccessoryPipeline.setOriginal(bitmap);
mPreviewPipeline.setOriginal(bitmap);
+ mHighresPreviewPipeline.setOriginal(bitmap);
}
public void postRenderingRequest(RenderingRequest request) {
@@ -194,16 +204,19 @@ public class FilteringPipeline implements Handler.Callback {
public void setPreviewScaleFactor(float previewScaleFactor) {
mAccessoryPipeline.setPreviewScaleFactor(previewScaleFactor);
mPreviewPipeline.setPreviewScaleFactor(previewScaleFactor);
+ mHighresPreviewPipeline.setPreviewScaleFactor(previewScaleFactor);
}
public void setHighResPreviewScaleFactor(float highResPreviewScaleFactor) {
mAccessoryPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
mPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
+ mHighresPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
}
public static synchronized void reset() {
sPipeline.mAccessoryPipeline.reset();
sPipeline.mPreviewPipeline.reset();
+ sPipeline.mHighresPreviewPipeline.reset();
sPipeline.mHandlerThread.quit();
sPipeline = null;
}
@@ -213,6 +226,7 @@ public class FilteringPipeline implements Handler.Callback {
if (mPipelineIsOn) {
assert(mPreviewPipeline.isInitialized());
assert(mAccessoryPipeline.isInitialized());
+ assert(mHighresPreviewPipeline.isInitialized());
updatePreviewBuffer();
}
}
diff --git a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
index 3416ddfc2..e5bc411de 100644
--- a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
+++ b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
@@ -41,6 +41,8 @@ public class RenderingRequest {
public static final int ICON_RENDERING = 3;
public static final int PARTIAL_RENDERING = 4;
public static final int HIGHRES_RENDERING = 5;
+ public static final int STYLE_ICON_RENDERING = 6;
+
private static final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
public static void post(Bitmap source, ImagePreset preset, int type, RenderingRequestCaller caller) {
@@ -59,7 +61,8 @@ public class RenderingRequest {
Bitmap bitmap = null;
if (type == FULL_RENDERING
|| type == GEOMETRY_RENDERING
- || type == ICON_RENDERING) {
+ || type == ICON_RENDERING
+ || type == STYLE_ICON_RENDERING) {
CachingPipeline pipeline = new CachingPipeline(
FiltersManager.getManager(), "Icon");
bitmap = pipeline.renderGeometryIcon(source, preset);
diff --git a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
index 3f75839ba..b3d0de715 100644
--- a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
+++ b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
@@ -17,13 +17,12 @@ import com.android.gallery3d.filtershow.editors.Editor;
import java.util.Vector;
-public class StyleChooser implements Control, RenderingRequestCaller {
+public class StyleChooser implements Control {
private final String LOGTAG = "StyleChooser";
protected ParameterStyles mParameter;
protected LinearLayout mLinearLayout;
protected Editor mEditor;
private View mTopView;
- private int mProcessingButton = 0;
private Vector<ImageButton> mIconButton = new Vector<ImageButton>();
protected int mLayoutID = R.layout.filtershow_control_style_chooser;
@@ -42,7 +41,7 @@ public class StyleChooser implements Control, RenderingRequestCaller {
mIconButton.clear();
LayoutParams lp = new LayoutParams(120, 120);
for (int i = 0; i < n; i++) {
- ImageButton button = new ImageButton(context);
+ final ImageButton button = new ImageButton(context);
button.setScaleType(ScaleType.CENTER_CROP);
button.setLayoutParams(lp);
button.setBackgroundResource(android.R.color.transparent);
@@ -55,9 +54,17 @@ public class StyleChooser implements Control, RenderingRequestCaller {
}
});
mLinearLayout.addView(button);
+ mParameter.getIcon(i, new RenderingRequestCaller() {
+ @Override
+ public void available(RenderingRequest request) {
+ Bitmap bmap = request.getBitmap();
+ if (bmap == null) {
+ return;
+ }
+ button.setImageBitmap(bmap);
+ }
+ });
}
- mProcessingButton = 0;
- mParameter.getIcon(mProcessingButton, this);
}
@Override
@@ -78,23 +85,4 @@ public class StyleChooser implements Control, RenderingRequestCaller {
}
}
- @Override
- public void available(RenderingRequest request) {
- Bitmap bmap = request.getBitmap();
- if (bmap == null) {
- return;
- }
-
- try {
- ImageButton button = mIconButton.get(mProcessingButton);
- button.setImageBitmap(bmap);
- } catch (Exception e) {
- return;
- }
-
- mProcessingButton++;
- if (mProcessingButton < mParameter.getNumberOfStyles())
- mParameter.getIcon(mProcessingButton, this);
- }
-
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
index e94d1ed9e..74ce7cdd5 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java
+++ b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.gallery3d.filtershow.imageshow;
+package com.android.gallery3d.filtershow.crop;
import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
import java.util.Arrays;
/**
@@ -30,11 +33,14 @@ public class BoundedRect {
private RectF inner;
private float[] innerRotated;
- public BoundedRect() {
- rot = 0;
- outer = new RectF();
- inner = new RectF();
- innerRotated = new float[8];
+ public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
+ rot = rotation;
+ outer = new RectF(outerRect);
+ inner = new RectF(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
}
public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
@@ -47,6 +53,16 @@ public class BoundedRect {
reconstrain();
}
+ public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
+ rot = rotation;
+ outer.set(outerRect);
+ inner.set(innerRect);
+ innerRotated = CropMath.getCornersFromRect(inner);
+ rotateInner();
+ if (!isConstrained())
+ reconstrain();
+ }
+
/**
* Sets inner, and re-constrains it to fit within the rotated bounding rect.
*/
@@ -73,10 +89,22 @@ public class BoundedRect {
reconstrain();
}
+ public void setToInner(RectF r) {
+ r.set(inner);
+ }
+
+ public void setToOuter(RectF r) {
+ r.set(outer);
+ }
+
public RectF getInner() {
return new RectF(inner);
}
+ public RectF getOuter() {
+ return new RectF(outer);
+ }
+
/**
* Tries to move the inner rectangle by (dx, dy). If this would cause it to leave
* the bounding rectangle, snaps the inner rectangle to the edge of the bounding
diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
new file mode 100644
index 000000000..26659a600
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Activity for cropping an image.
+ */
+public class CropActivity extends Activity {
+ private static final String LOGTAG = "CropActivity";
+ private CropExtras mCropExtras = null;
+ private LoadBitmapTask mLoadBitmapTask = null;
+ private SaveBitmapTask mSaveBitmapTask = null;
+ private SetWallpaperTask mSetWallpaperTask = null;
+ private Bitmap mOriginalBitmap = null;
+ private CropView mCropView = null;
+ private int mActiveBackgroundIO = 0;
+ private Intent mResultIntent = null;
+ private static final int SELECT_PICTURE = 1; // request code for picker
+ private static final int DEFAULT_DENSITY = 133;
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ public static final int MAX_BMAP_IN_INTENT = 990000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ mResultIntent = new Intent();
+ setResult(RESULT_CANCELED, mResultIntent);
+ mCropExtras = getExtrasFromIntent(intent);
+ if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+
+ setContentView(R.layout.crop_activity);
+ mCropView = (CropView) findViewById(R.id.cropView);
+
+ if (intent.getData() != null) {
+ startLoadBitmap(intent.getData());
+ } else {
+ pickImage();
+ }
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+ View saveButton = actionBar.getCustomView();
+ saveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startFinishOutput();
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mLoadBitmapTask != null) {
+ mLoadBitmapTask.cancel(false);
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * Opens a selector in Gallery to chose an image for use when none was given
+ * in the CROP intent.
+ */
+ public void pickImage() {
+ Intent intent = new Intent();
+ intent.setType("image/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
+ SELECT_PICTURE);
+ }
+
+ /**
+ * Callback for pickImage().
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
+ Uri selectedImageUri = data.getData();
+ startLoadBitmap(selectedImageUri);
+ }
+ }
+
+ /**
+ * Gets the crop extras from the intent, or null if none exist.
+ */
+ public static CropExtras getExtrasFromIntent(Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
+ extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
+ extras.getBoolean(CropExtras.KEY_SCALE, true) &&
+ extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
+ extras.getInt(CropExtras.KEY_ASPECT_X, 0),
+ extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
+ extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
+ extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
+ (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
+ extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
+ extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
+ extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
+ extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
+ }
+ return null;
+ }
+
+ /**
+ * Gets screen size metric.
+ */
+ private int getScreenImageSize() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ Display display = getWindowManager().getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+ display.getMetrics(metrics);
+ int msize = Math.min(size.x, size.y);
+ // TODO: WTF
+ return (DEFAULT_DENSITY * msize) / metrics.densityDpi + 512;
+ }
+
+ /**
+ * Method that loads a bitmap in an async task.
+ */
+ private void startLoadBitmap(Uri uri) {
+ mActiveBackgroundIO++;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ mLoadBitmapTask = new LoadBitmapTask();
+ mLoadBitmapTask.execute(uri);
+ }
+
+ /**
+ * Method called on UI thread with loaded bitmap.
+ */
+ private void doneLoadBitmap(Bitmap bitmap) {
+ mActiveBackgroundIO--;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ mOriginalBitmap = bitmap;
+ // TODO: move these to dimens folder
+ if (bitmap != null) {
+ mCropView.setup(bitmap, (int) getPixelsFromDip(55), (int) getPixelsFromDip(25));
+ } else {
+ Log.w(LOGTAG, "could not load image for cropping");
+ cannotLoadImage();
+ setResult(RESULT_CANCELED, mResultIntent);
+ done();
+ }
+ }
+
+ /**
+ * Display toast for image loading failure.
+ */
+ private void cannotLoadImage() {
+ CharSequence text = getString(R.string.cannot_load_image);
+ Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ /**
+ * AsyncTask for loading a bitmap into memory.
+ *
+ * @see #startLoadBitmap(Uri)
+ * @see #doneLoadBitmap(Bitmap)
+ */
+ private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
+ int mBitmapSize;
+ Context mContext;
+ Rect mOriginalBounds;
+
+ public LoadBitmapTask() {
+ mBitmapSize = getScreenImageSize();
+ Log.v(LOGTAG, "bitmap size: " + mBitmapSize);
+ mContext = getApplicationContext();
+ mOriginalBounds = new Rect();
+ }
+
+ @Override
+ protected Bitmap doInBackground(Uri... params) {
+ Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize,
+ mOriginalBounds);
+ return bmap;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ doneLoadBitmap(result);
+ // super.onPostExecute(result);
+ }
+ }
+
+ private void startSaveBitmap(Bitmap bmap, Uri uri, String format) {
+ if (bmap == null || uri == null) {
+ throw new IllegalArgumentException("bad argument to startSaveBitmap");
+ }
+ mActiveBackgroundIO++;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.VISIBLE);
+ mSaveBitmapTask = new SaveBitmapTask(uri, format);
+ mSaveBitmapTask.execute(bmap);
+ }
+
+ private void doneSaveBitmap(Uri uri) {
+ mActiveBackgroundIO--;
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
+ if (uri == null) {
+ Log.w(LOGTAG, "failed to save bitmap");
+ setResult(RESULT_CANCELED, mResultIntent);
+ done();
+ return;
+ }
+ done();
+ }
+
+ private class SaveBitmapTask extends AsyncTask<Bitmap, Void, Boolean> {
+
+ OutputStream mOutStream = null;
+ String mOutputFormat = null;
+ Uri mOutUri = null;
+
+ public SaveBitmapTask(Uri uri, String outputFormat) {
+ mOutputFormat = outputFormat;
+ mOutStream = null;
+ mOutUri = uri;
+ try {
+ mOutStream = getContentResolver().openOutputStream(uri);
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot write output: " + mOutUri.toString(), e);
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Bitmap... params) {
+ if (mOutStream == null) {
+ return false;
+ }
+ CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+ return params[0].compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result.booleanValue() == false) {
+ Log.w(LOGTAG, "could not compress to output: " + mOutUri.toString());
+ doneSaveBitmap(null);
+ }
+ doneSaveBitmap(mOutUri);
+ }
+ }
+
+ private void startSetWallpaper(Bitmap bmap) {
+ if (bmap == null) {
+ throw new IllegalArgumentException("bad argument to startSetWallpaper");
+ }
+ mActiveBackgroundIO++;
+ Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+ mSetWallpaperTask = new SetWallpaperTask();
+ mSetWallpaperTask.execute(bmap);
+
+ }
+
+ private void doneSetWallpaper() {
+ mActiveBackgroundIO--;
+ done();
+ }
+
+ private class SetWallpaperTask extends AsyncTask<Bitmap, Void, Boolean> {
+ private final WallpaperManager mWPManager;
+
+ public SetWallpaperTask() {
+ mWPManager = WallpaperManager.getInstance(getApplicationContext());
+ }
+
+ @Override
+ protected Boolean doInBackground(Bitmap... params) {
+ try {
+ mWPManager.setBitmap(params[0]);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "fail to set wall paper", e);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ doneSetWallpaper();
+ }
+ }
+
+ private void startFinishOutput() {
+ if (mOriginalBitmap != null && mCropExtras != null) {
+ Bitmap cropped = null;
+ if (mCropExtras.getExtraOutput() != null) {
+ if (cropped == null) {
+ cropped = getCroppedImage(mOriginalBitmap);
+ }
+ startSaveBitmap(cropped, mCropExtras.getExtraOutput(),
+ mCropExtras.getOutputFormat());
+ }
+ if (mCropExtras.getSetAsWallpaper()) {
+ if (cropped == null) {
+ cropped = getCroppedImage(mOriginalBitmap);
+ }
+ startSetWallpaper(cropped);
+ }
+ if (mCropExtras.getReturnData()) {
+ if (cropped == null) {
+ cropped = getCroppedImage(mOriginalBitmap);
+ }
+ int bmapSize = cropped.getRowBytes() * cropped.getHeight();
+ if (bmapSize > MAX_BMAP_IN_INTENT) {
+ Log.w(LOGTAG, "Bitmap too large to be returned via intent");
+ } else {
+ mResultIntent.putExtra(CropExtras.KEY_DATA, cropped);
+ }
+ }
+ setResult(RESULT_OK, mResultIntent);
+ } else {
+ setResult(RESULT_CANCELED, mResultIntent);
+ }
+ done();
+ }
+
+ private void done() {
+ if (mActiveBackgroundIO == 0) {
+ finish();
+ }
+ }
+
+ private Bitmap getCroppedImage(Bitmap image) {
+ RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
+ RectF crop = getBitmapCrop(imageBounds);
+ if (crop == null) {
+ return image;
+ }
+ Rect intCrop = new Rect();
+ crop.roundOut(intCrop);
+ return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
+ intCrop.height());
+ }
+
+ private RectF getBitmapCrop(RectF imageBounds) {
+ RectF crop = new RectF();
+ if (!mCropView.getCropBounds(crop, imageBounds)) {
+ Log.w(LOGTAG, "could not get crop");
+ return null;
+ }
+ return crop;
+ }
+
+ /**
+ * Helper method for unit conversions.
+ */
+ public float getPixelsFromDip(float value) {
+ Resources r = getResources();
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
+ r.getDisplayMetrics());
+ }
+
+ private static CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ }
+
+ private static String getFileExtension(String requestFormat) {
+ String outputFormat = (requestFormat == null)
+ ? "jpg"
+ : requestFormat;
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
new file mode 100644
index 000000000..749ba0e29
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.filtershow.crop;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+public abstract class CropDrawingUtils {
+
+ public static void drawRuleOfThird(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.argb(128, 255, 255, 255));
+ p.setStrokeWidth(2);
+ float stepX = bounds.width() / 3.0f;
+ float stepY = bounds.height() / 3.0f;
+ float x = bounds.left + stepX;
+ float y = bounds.top + stepY;
+ for (int i = 0; i < 2; i++) {
+ canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
+ x += stepX;
+ }
+ for (int j = 0; j < 2; j++) {
+ canvas.drawLine(bounds.left, y, bounds.right, y, p);
+ y += stepY;
+ }
+ }
+
+ public static void drawCropRect(Canvas canvas, RectF bounds) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setColor(Color.WHITE);
+ p.setStrokeWidth(3);
+ canvas.drawRect(bounds, p);
+ }
+
+ public static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
+ float centerX, float centerY) {
+ int left = (int) centerX - indicatorSize / 2;
+ int top = (int) centerY - indicatorSize / 2;
+ indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
+ indicator.draw(canvas);
+ }
+
+ public static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
+ RectF bounds, boolean fixedAspect, int selection) {
+ boolean notMoving = (selection == CropObject.MOVE_NONE);
+ if (fixedAspect) {
+ if ((selection == CropObject.TOP_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
+ }
+ if ((selection == CropObject.TOP_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
+ }
+ if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
+ }
+ if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
+ }
+ } else {
+ if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
+ }
+ if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
+ }
+ if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
+ }
+ if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
+ drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
+ }
+ }
+ }
+
+ public static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
+ Matrix m = new Matrix();
+ CropDrawingUtils.setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
+ return m;
+ }
+
+ public static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
+ RectF displayBounds) {
+ m.reset();
+ return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/CropExtras.java b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
index 7ed8f1eb5..60fe9af53 100644
--- a/src/com/android/gallery3d/filtershow/CropExtras.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.gallery3d.filtershow;
+package com.android.gallery3d.filtershow.crop;
import android.net.Uri;
diff --git a/src/com/android/gallery3d/filtershow/crop/CropLoader.java b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
new file mode 100644
index 000000000..132d6c1dc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class contains static methods for loading a bitmap and
+ * mantains no instance state.
+ */
+public abstract class CropLoader {
+ public static final String LOGTAG = "CropLoader";
+ public static final String JPEG_MIME_TYPE = "image/jpeg";
+ public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
+ public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
+ public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
+ public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
+
+ /**
+ * Returns the orientation of image at the given URI as one of 0, 90, 180,
+ * 270.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @return the orientation of the image. Defaults to 0.
+ */
+ public static int getMetadataOrientation(Uri uri, Context context) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ String mimeType = context.getContentResolver().getType(uri);
+ if (mimeType != JPEG_MIME_TYPE) {
+ return 0;
+ }
+ String path = uri.getPath();
+ int orientation = 0;
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(path);
+ orientation = ExifInterface.getRotationForOrientationValue(
+ exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Failed to read EXIF orientation", e);
+ }
+ return orientation;
+ }
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri,
+ new String[] {
+ MediaStore.Images.ImageColumns.ORIENTATION
+ },
+ null, null, null);
+ if (cursor.moveToNext()) {
+ int ori = cursor.getInt(0);
+
+ switch (ori) {
+ case 0:
+ return ORI_NORMAL;
+ case 90:
+ return ORI_ROTATE_90;
+ case 270:
+ return ORI_ROTATE_270;
+ case 180:
+ return ORI_ROTATE_180;
+ default:
+ return 0;
+ }
+ }
+ } catch (SQLiteException e) {
+ return 0;
+ } catch (IllegalArgumentException e) {
+ return 0;
+ } finally {
+ Utils.closeSilently(cursor);
+ }
+ return 0;
+ }
+
+ /**
+ * Gets a bitmap at a given URI that is downsampled so that both sides are
+ * smaller than maxSideLength. The Bitmap's original dimensions are stored
+ * in the rect originalBounds.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param maxSideLength max side length of returned bitmap.
+ * @param originalBounds set to the actual bounds of the stored bitmap.
+ * @return downsampled bitmap or null if this operation failed.
+ */
+ public static Bitmap getConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+ Rect originalBounds) {
+ if (maxSideLength <= 0 || originalBounds == null || uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ InputStream is = null;
+ try {
+ // Get width and height of stored bitmap
+ is = context.getContentResolver().openInputStream(uri);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ int w = options.outWidth;
+ int h = options.outHeight;
+ originalBounds.set(0, 0, w, h);
+
+ // If bitmap cannot be decoded, return null
+ if (w <= 0 || h <= 0) {
+ return null;
+ }
+
+ options = new BitmapFactory.Options();
+
+ // Find best downsampling size
+ int imageSide = Math.max(w, h);
+ options.inSampleSize = 1;
+ if (imageSide > maxSideLength) {
+ int shifts = 1 + Integer.numberOfLeadingZeros(maxSideLength)
+ - Integer.numberOfLeadingZeros(imageSide);
+ options.inSampleSize <<= shifts;
+ }
+
+ // Make sure sample size is reasonable
+ if (options.inSampleSize <= 0 ||
+ 0 >= (int) (Math.min(w, h) / options.inSampleSize)) {
+ return null;
+ }
+
+ // Decode actual bitmap.
+ options.inMutable = true;
+ is.close();
+ is = context.getContentResolver().openInputStream(uri);
+ return BitmapFactory.decodeStream(is, null, options);
+ } catch (FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "IOException: " + uri, e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ return null;
+ }
+
+ /**
+ * Gets a bitmap that has been downsampled using sampleSize.
+ *
+ * @param uri URI of image to open.
+ * @param context context whose ContentResolver to use.
+ * @param sampleSize downsampling amount.
+ * @return downsampled bitmap.
+ */
+ public static Bitmap getBitmap(Uri uri, Context context, int sampleSize) {
+ if (uri == null || context == null) {
+ throw new IllegalArgumentException("bad argument to getScaledBitmap");
+ }
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(uri);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ options.inSampleSize = sampleSize;
+ return BitmapFactory.decodeStream(is, null, options);
+ } catch (FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
+ } finally {
+ Utils.closeSilently(is);
+ }
+ return null;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java
index 9037ca043..5914f1cb8 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.gallery3d.filtershow.imageshow;
+package com.android.gallery3d.filtershow.crop;
import android.graphics.Matrix;
import android.graphics.RectF;
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
import java.util.Arrays;
public class CropMath {
@@ -176,6 +178,33 @@ public class CropMath {
r.set(centX - hw, centY - hh, centX + hw, centY + hh);
}
+ /**
+ * Resizes rectangle to have a certain aspect ratio (center remains
+ * stationary) while constraining it to remain within the original rect.
+ *
+ * @param r rectangle to resize
+ * @param w new width aspect
+ * @param h new height aspect
+ */
+ public static void fixAspectRatioContained(RectF r, float w, float h) {
+ float origW = r.width();
+ float origH = r.height();
+ float origA = origW / origH;
+ float a = w / h;
+ float finalW = origW;
+ float finalH = origH;
+ if (origA < a) {
+ finalH = origH / a;
+ } else {
+ finalW = origW * a;
+ }
+ float centX = r.centerX();
+ float centY = r.centerY();
+ float hw = finalW / 2;
+ float hh = finalH / 2;
+ r.set(centX - hw, centY - hh, centX + hw, centY + hh);
+ }
+
private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
float dy = rotatedRect[1] - rotatedRect[3];
float dx = rotatedRect[0] - rotatedRect[2];
diff --git a/src/com/android/gallery3d/filtershow/crop/CropObject.java b/src/com/android/gallery3d/filtershow/crop/CropObject.java
new file mode 100644
index 000000000..08cf158e1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropObject.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
+public class CropObject {
+
+ private BoundedRect mBoundedRect;
+ private float mAspectWidth = 1;
+ private float mAspectHeight = 1;
+ private boolean mFixAspectRatio = false;
+ private float mRotation = 0;
+ private float mTouchTolerance = 45;
+ private float mMinSideSize = 20;
+
+ public static final int MOVE_NONE = 0;
+ // Sides
+ public static final int MOVE_LEFT = 1;
+ public static final int MOVE_TOP = 2;
+ public static final int MOVE_RIGHT = 4;
+ public static final int MOVE_BOTTOM = 8;
+ public static final int MOVE_BLOCK = 16;
+
+ // Corners
+ public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
+ public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
+ public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
+ public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
+
+ private int mMovingEdges = MOVE_NONE;
+
+ public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
+ mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+ }
+
+ public void resetBoundsTo(RectF inner, RectF outer) {
+ mBoundedRect.resetTo(0, outer, inner);
+ }
+
+ public void getInnerBounds(RectF r) {
+ mBoundedRect.setToInner(r);
+ }
+
+ public void getOuterBounds(RectF r) {
+ mBoundedRect.setToOuter(r);
+ }
+
+ public RectF getInnerBounds() {
+ return mBoundedRect.getInner();
+ }
+
+ public RectF getOuterBounds() {
+ return mBoundedRect.getOuter();
+ }
+
+ public int getSelectState() {
+ return mMovingEdges;
+ }
+
+ public boolean isFixedAspect() {
+ return mFixAspectRatio;
+ }
+
+ public void rotateOuter(int angle) {
+ mRotation = angle % 360;
+ mBoundedRect.setRotation(mRotation);
+ clearSelectState();
+ }
+
+ public boolean setInnerAspectRatio(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Width and Height must be greater than zero");
+ }
+ RectF inner = mBoundedRect.getInner();
+ CropMath.fixAspectRatioContained(inner, width, height);
+ if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
+ return false;
+ }
+ mAspectWidth = width;
+ mAspectHeight = height;
+ mFixAspectRatio = true;
+ mBoundedRect.setInner(inner);
+ clearSelectState();
+ return true;
+ }
+
+ public void setTouchTolerance(float tolerance) {
+ if (tolerance <= 0) {
+ throw new IllegalArgumentException("Tolerance must be greater than zero");
+ }
+ mTouchTolerance = tolerance;
+ }
+
+ public void setMinInnerSideSize(float minSide) {
+ if (minSide <= 0) {
+ throw new IllegalArgumentException("Min dide must be greater than zero");
+ }
+ mMinSideSize = minSide;
+ }
+
+ public void unsetAspectRatio() {
+ mFixAspectRatio = false;
+ clearSelectState();
+ }
+
+ public boolean hasSelectedEdge() {
+ return mMovingEdges != MOVE_NONE;
+ }
+
+ public static boolean checkCorner(int selected) {
+ return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
+ || selected == BOTTOM_LEFT;
+ }
+
+ public static boolean checkEdge(int selected) {
+ return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
+ || selected == MOVE_BOTTOM;
+ }
+
+ public static boolean checkBlock(int selected) {
+ return selected == MOVE_BLOCK;
+ }
+
+ public static boolean checkValid(int selected) {
+ return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
+ || checkCorner(selected);
+ }
+
+ public void clearSelectState() {
+ mMovingEdges = MOVE_NONE;
+ }
+
+ public int wouldSelectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
+ return edgeSelected;
+ }
+ return MOVE_NONE;
+ }
+
+ public boolean selectEdge(int edge) {
+ if (!checkValid(edge)) {
+ // temporary
+ throw new IllegalArgumentException("bad edge selected");
+ // return false;
+ }
+ if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge)) {
+ // temporary
+ throw new IllegalArgumentException("bad corner selected");
+ // return false;
+ }
+ mMovingEdges = edge;
+ return true;
+ }
+
+ public boolean selectEdge(float x, float y) {
+ int edgeSelected = calculateSelectedEdge(x, y);
+ if (mFixAspectRatio) {
+ edgeSelected = fixEdgeToCorner(edgeSelected);
+ }
+ if (edgeSelected == MOVE_NONE) {
+ return false;
+ }
+ return selectEdge(edgeSelected);
+ }
+
+ public boolean moveCurrentSelection(float dX, float dY) {
+ if (mMovingEdges == MOVE_NONE) {
+ return false;
+ }
+ RectF crop = mBoundedRect.getInner();
+
+ float minWidthHeight = mMinSideSize;
+
+ int movingEdges = mMovingEdges;
+ if (movingEdges == MOVE_BLOCK) {
+ mBoundedRect.moveInner(dX, dY);
+ return true;
+ } else {
+ float dx = 0;
+ float dy = 0;
+
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
+ - crop.right;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
+ - crop.bottom;
+ }
+
+ if (mFixAspectRatio) {
+ float[] l1 = {
+ crop.left, crop.bottom
+ };
+ float[] l2 = {
+ crop.right, crop.top
+ };
+ if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
+ l1[1] = crop.top;
+ l2[1] = crop.bottom;
+ }
+ float[] b = {
+ l1[0] - l2[0], l1[1] - l2[1]
+ };
+ float[] disp = {
+ dx, dy
+ };
+ float[] bUnit = GeometryMath.normalize(b);
+ float sp = GeometryMath.scalarProjection(disp, bUnit);
+ dx = sp * bUnit[0];
+ dy = sp * bUnit[1];
+ RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
+
+ mBoundedRect.fixedAspectResizeInner(newCrop);
+ } else {
+ if ((movingEdges & MOVE_LEFT) != 0) {
+ crop.left += dx;
+ }
+ if ((movingEdges & MOVE_TOP) != 0) {
+ crop.top += dy;
+ }
+ if ((movingEdges & MOVE_RIGHT) != 0) {
+ crop.right += dx;
+ }
+ if ((movingEdges & MOVE_BOTTOM) != 0) {
+ crop.bottom += dy;
+ }
+ mBoundedRect.resizeInner(crop);
+ }
+ }
+ return true;
+ }
+
+ // Helper methods
+
+ private int calculateSelectedEdge(float x, float y) {
+ RectF cropped = mBoundedRect.getInner();
+
+ float left = Math.abs(x - cropped.left);
+ float right = Math.abs(x - cropped.right);
+ float top = Math.abs(y - cropped.top);
+ float bottom = Math.abs(y - cropped.bottom);
+
+ int edgeSelected = MOVE_NONE;
+ // Check left or right.
+ if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
+ edgeSelected |= MOVE_LEFT;
+ }
+ else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+ && ((y - mTouchTolerance) <= cropped.bottom)) {
+ edgeSelected |= MOVE_RIGHT;
+ }
+
+ // Check top or bottom.
+ if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
+ edgeSelected |= MOVE_TOP;
+ }
+ else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+ && ((x - mTouchTolerance) <= cropped.right)) {
+ edgeSelected |= MOVE_BOTTOM;
+ }
+ return edgeSelected;
+ }
+
+ private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
+ RectF newCrop = null;
+ // Fix opposite corner in place and move sides
+ if (moving_corner == BOTTOM_RIGHT) {
+ newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
+ + dy);
+ } else if (moving_corner == BOTTOM_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
+ + dy);
+ } else if (moving_corner == TOP_LEFT) {
+ newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
+ r.right, r.bottom);
+ } else if (moving_corner == TOP_RIGHT) {
+ newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
+ + r.width() + dx, r.bottom);
+ }
+ return newCrop;
+ }
+
+ private static int fixEdgeToCorner(int moving_edges) {
+ if (moving_edges == MOVE_LEFT) {
+ moving_edges |= MOVE_TOP;
+ }
+ if (moving_edges == MOVE_TOP) {
+ moving_edges |= MOVE_LEFT;
+ }
+ if (moving_edges == MOVE_RIGHT) {
+ moving_edges |= MOVE_BOTTOM;
+ }
+ if (moving_edges == MOVE_BOTTOM) {
+ moving_edges |= MOVE_RIGHT;
+ }
+ return moving_edges;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropView.java b/src/com/android/gallery3d/filtershow/crop/CropView.java
new file mode 100644
index 000000000..d762ad459
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropView.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.crop;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+public class CropView extends View {
+ private static final String LOGTAG = "CropView";
+
+ Bitmap mImage = null;
+ CropObject mCropObj = null;
+ private final Drawable mCropIndicator;
+ private final int mIndicatorSize;
+
+ private float mPrevX = 0;
+ private float mPrevY = 0;
+
+ private int mMinSideSize = 45;
+ private int mTouchTolerance = 20;
+ private boolean mMovingBlock = false;
+
+ private Matrix mDisplayMatrix = null;
+ private Matrix mDisplayMatrixInverse = null;
+
+ private enum Mode {
+ NONE, MOVE
+ }
+
+ private Mode mState = Mode.NONE;
+
+ public CropView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ Resources resources = context.getResources();
+ mCropIndicator = resources.getDrawable(R.drawable.camera_crop);
+ mIndicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
+ }
+
+ // For unchanging parameters
+ public void setup(Bitmap image, int minSideSize, int touchTolerance) {
+ mImage = image;
+ mMinSideSize = minSideSize;
+ mTouchTolerance = touchTolerance;
+ reset();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (mImage == null) {
+ return;
+ }
+ int displayWidth = getWidth();
+ int displayHeight = getHeight();
+ Rect imageBoundsOriginal = new Rect(0, 0, mImage.getWidth(), mImage.getHeight());
+ Rect displayBoundsOriginal = new Rect(0, 0, displayWidth, displayHeight);
+ if (mCropObj == null) {
+ reset();
+ mCropObj = new CropObject(imageBoundsOriginal, imageBoundsOriginal, 0);
+ }
+
+ RectF imageBounds = mCropObj.getInnerBounds();
+ RectF displayBounds = mCropObj.getOuterBounds();
+
+ // If display matrix doesn't exist, create it and its dependencies
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ mDisplayMatrix = CropDrawingUtils.getBitmapToDisplayMatrix(displayBounds, new RectF(
+ displayBoundsOriginal));
+ mDisplayMatrixInverse = new Matrix();
+ mDisplayMatrixInverse.reset();
+ if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
+ Log.w(LOGTAG, "could not invert display matrix");
+ }
+ // Scale min side and tolerance by display matrix scale factor
+ mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+ mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+ }
+ canvas.drawBitmap(mImage, mDisplayMatrix, new Paint());
+
+ if (mDisplayMatrix.mapRect(imageBounds)) {
+ CropDrawingUtils.drawCropRect(canvas, imageBounds);
+ CropDrawingUtils.drawRuleOfThird(canvas, imageBounds);
+ CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize, imageBounds,
+ mCropObj.isFixedAspect(), mCropObj.getSelectState());
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+ return true;
+ }
+ float[] touchPoint = {
+ x, y
+ };
+ mDisplayMatrixInverse.mapPoints(touchPoint);
+ x = touchPoint[0];
+ y = touchPoint[1];
+ switch (event.getActionMasked()) {
+ case (MotionEvent.ACTION_DOWN):
+ if (mState == Mode.NONE) {
+ if (!mCropObj.selectEdge(x, y)) {
+ mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
+ }
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.MOVE;
+ } else {
+ reset();
+ }
+ break;
+ case (MotionEvent.ACTION_UP):
+ if (mState == Mode.MOVE) {
+ mCropObj.selectEdge(CropObject.MOVE_NONE);
+ mMovingBlock = false;
+ mPrevX = x;
+ mPrevY = y;
+ mState = Mode.NONE;
+ } else {
+ reset();
+ }
+ break;
+ case (MotionEvent.ACTION_MOVE):
+ if (mState == Mode.MOVE) {
+ float dx = x - mPrevX;
+ float dy = y - mPrevY;
+ mCropObj.moveCurrentSelection(dx, dy);
+ mPrevX = x;
+ mPrevY = y;
+ } else {
+ reset();
+ }
+ break;
+ default:
+ reset();
+ break;
+ }
+ invalidate();
+ return true;
+ }
+
+ public void reset() {
+ Log.w(LOGTAG, "reset called");
+ mState = Mode.NONE;
+ mCropObj = null;
+ mDisplayMatrix = null;
+ mDisplayMatrixInverse = null;
+ mMovingBlock = false;
+ invalidate();
+ }
+
+ public boolean getCropBounds(RectF out_crop, RectF in_newContaining) {
+ Matrix m = new Matrix();
+ RectF inner = mCropObj.getInnerBounds();
+ RectF outer = mCropObj.getOuterBounds();
+ if (!m.setRectToRect(outer, in_newContaining, Matrix.ScaleToFit.FILL)) {
+ Log.w(LOGTAG, "failed to make transform matrix");
+ return false;
+ }
+ if (!m.mapRect(inner)) {
+ Log.w(LOGTAG, "failed to transform crop bounds");
+ return false;
+ }
+ out_crop.set(inner);
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/Editor.java b/src/com/android/gallery3d/filtershow/editors/Editor.java
index 013590c6d..036745d3c 100644
--- a/src/com/android/gallery3d/filtershow/editors/Editor.java
+++ b/src/com/android/gallery3d/filtershow/editors/Editor.java
@@ -18,8 +18,10 @@ package com.android.gallery3d.filtershow.editors;
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -41,7 +43,7 @@ import com.android.gallery3d.filtershow.presets.ImagePreset;
/**
* Base class for Editors Must contain a mImageShow and a top level view
*/
-public class Editor implements OnSeekBarChangeListener {
+public class Editor implements OnSeekBarChangeListener, SwapButton.SwapButtonListener {
protected Context mContext;
protected View mView;
protected ImageShow mImageShow;
@@ -79,7 +81,6 @@ public class Editor implements OnSeekBarChangeListener {
return mID;
}
-
public byte showParameterValue() {
return mShowParameter;
}
@@ -260,4 +261,17 @@ public class Editor implements OnSeekBarChangeListener {
}
+ @Override
+ public void swapLeft(MenuItem item) {
+
+ }
+
+ @Override
+ public void swapRight(MenuItem item) {
+
+ }
+
+ public void detach() {
+
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
index df922f10a..e2173ad77 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
@@ -21,7 +21,7 @@ import android.view.View;
import android.widget.FrameLayout;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.imageshow.ImageCrop;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
diff --git a/src/com/android/gallery3d/filtershow/editors/SwapButton.java b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
new file mode 100644
index 000000000..d8f3fc3a7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+public class SwapButton extends Button implements GestureDetector.OnGestureListener {
+
+ public static int ANIM_DURATION = 200;
+
+ public interface SwapButtonListener {
+ public void swapLeft(MenuItem item);
+ public void swapRight(MenuItem item);
+ }
+
+ private GestureDetector mDetector;
+ private SwapButtonListener mListener;
+ private Menu mMenu;
+ private int mCurrentMenuIndex;
+
+ public SwapButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDetector = new GestureDetector(context, this);
+ }
+
+ public SwapButtonListener getListener() {
+ return mListener;
+ }
+
+ public void setListener(SwapButtonListener listener) {
+ mListener = listener;
+ }
+
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ public void setMenu(Menu menu) {
+ mMenu = menu;
+ }
+
+ public boolean onTouchEvent(MotionEvent me) {
+ if (!mDetector.onTouchEvent(me)) {
+ return super.onTouchEvent(me);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ callOnClick();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ if (mMenu == null) {
+ return false;
+ }
+ if (e1.getX() - e2.getX() > 0) {
+ // right to left
+ mCurrentMenuIndex++;
+ if (mCurrentMenuIndex == mMenu.size()) {
+ mCurrentMenuIndex = 0;
+ }
+ if (mListener != null) {
+ mListener.swapRight(mMenu.getItem(mCurrentMenuIndex));
+ }
+ } else {
+ // left to right
+ mCurrentMenuIndex--;
+ if (mCurrentMenuIndex < 0) {
+ mCurrentMenuIndex = mMenu.size() - 1;
+ }
+ if (mListener != null) {
+ mListener.swapLeft(mMenu.getItem(mCurrentMenuIndex));
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index 232de6460..66ad10640 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -15,6 +15,7 @@
*/
package com.android.gallery3d.filtershow.filters;
+import android.content.res.Resources;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import java.util.HashMap;
@@ -140,4 +141,10 @@ public abstract class BaseFiltersManager {
representations.add(getRepresentation(ImageFilterDraw.class));
}
+ public void setFilterResources(Resources resources) {
+ ImageFilterBorder filterBorder = (ImageFilterBorder) getFilter(ImageFilterBorder.class);
+ filterBorder.setResources(resources);
+ ImageFilterFx filterFx = (ImageFilterFx) getFilter(ImageFilterFx.class);
+ filterFx.setResources(resources);
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
index 7e0e25d61..5bb0e5733 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -42,7 +42,7 @@ public class FilterRepresentation implements Cloneable {
public static final byte TYPE_NORMAL = 5;
public static final byte TYPE_TINYPLANET = 6;
- public FilterRepresentation mTempRepresentation = null;
+ private FilterRepresentation mTempRepresentation = null;
public FilterRepresentation(String name) {
mName = name;
@@ -127,6 +127,10 @@ public class FilterRepresentation implements Cloneable {
public void useParametersFrom(FilterRepresentation a) {
}
+ public void clearTempRepresentation() {
+ mTempRepresentation = null;
+ }
+
public synchronized void updateTempParametersFrom(FilterRepresentation representation) {
if (mTempRepresentation == null) {
try {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index 5a33cc823..4f46eed82 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -24,7 +24,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.imageshow.GeometryMath;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index ad2152ab1..898fdf021 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -21,8 +21,8 @@ import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.crop.CropExtras;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.editors.EditorFlip;
import com.android.gallery3d.filtershow.editors.EditorRotate;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
index 2ea6f6a42..6d62bbd1d 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -33,7 +33,9 @@ import android.widget.LinearLayout;
import android.widget.PopupMenu;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.BoundedRect;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropMath;
import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.ui.FramedTextButton;
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index a35a18a7d..2858ea6e5 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -39,6 +39,8 @@ public class ImagePreset {
public static final int QUALITY_ICON = 0;
public static final int QUALITY_PREVIEW = 1;
public static final int QUALITY_FINAL = 2;
+ public static final int STYLE_ICON = 3;
+
private ImageLoader mImageLoader = null;
private Vector<FilterRepresentation> mFilters = new Vector<FilterRepresentation>();
diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java
index 4818d261b..eb81b6ef3 100644
--- a/src/com/android/gallery3d/gadget/WidgetConfigure.java
+++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java
@@ -36,7 +36,7 @@ import com.android.gallery3d.data.LocalAlbum;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropExtras;
public class WidgetConfigure extends Activity {
@SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/ingest/ui/MtpImageView.java b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
index 5664d8a34..9ab78628d 100644
--- a/src/com/android/gallery3d/ingest/ui/MtpImageView.java
+++ b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
@@ -17,6 +17,7 @@
package com.android.gallery3d.ingest.ui;
import android.content.Context;
+import android.graphics.Matrix;
import android.mtp.MtpDevice;
import android.mtp.MtpObjectInfo;
import android.os.Handler;
@@ -91,10 +92,67 @@ public class MtpImageView extends ImageView {
return MtpBitmapFetch.getFullsize(device, info);
}
+ private float mLastBitmapWidth;
+ private float mLastBitmapHeight;
+ private int mLastRotationDegrees;
+ private Matrix mDrawMatrix = new Matrix();
+
+ private void updateDrawMatrix() {
+ mDrawMatrix.reset();
+ float dwidth;
+ float dheight;
+ float vheight = getHeight();
+ float vwidth = getWidth();
+ float scale;
+ boolean rotated90 = (mLastRotationDegrees % 180 != 0);
+ if (rotated90) {
+ dwidth = mLastBitmapHeight;
+ dheight = mLastBitmapWidth;
+ } else {
+ dwidth = mLastBitmapWidth;
+ dheight = mLastBitmapHeight;
+ }
+ if (dwidth <= vwidth && dheight <= vheight) {
+ scale = 1.0f;
+ } else {
+ scale = Math.min(vwidth / dwidth, vheight / dheight);
+ }
+ mDrawMatrix.setScale(scale, scale);
+ if (rotated90) {
+ mDrawMatrix.postTranslate(-dheight * scale * 0.5f,
+ -dwidth * scale * 0.5f);
+ mDrawMatrix.postRotate(mLastRotationDegrees);
+ mDrawMatrix.postTranslate(dwidth * scale * 0.5f,
+ dheight * scale * 0.5f);
+ }
+ mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f,
+ (vheight - dheight * scale) * 0.5f);
+ if (!rotated90 && mLastRotationDegrees > 0) {
+ // rotated by a multiple of 180
+ mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2);
+ }
+ setImageMatrix(mDrawMatrix);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed && getScaleType() == ScaleType.MATRIX) {
+ updateDrawMatrix();
+ }
+ }
+
protected void onMtpImageDataFetchedFromDevice(Object result) {
BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata)result;
+ if (getScaleType() == ScaleType.MATRIX) {
+ mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight();
+ mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth();
+ mLastRotationDegrees = bitmapWithMetadata.rotationDegrees;
+ updateDrawMatrix();
+ } else {
+ setRotation(bitmapWithMetadata.rotationDegrees);
+ }
setImageBitmap(bitmapWithMetadata.bitmap);
- setRotation(bitmapWithMetadata.rotationDegrees);
}
protected void cancelLoadingAndClear() {
@@ -104,7 +162,6 @@ public class MtpImageView extends ImageView {
mFetchResult = null;
}
setImageResource(android.R.color.transparent);
- setRotation(0);
}
@Override