diff options
Diffstat (limited to 'src')
54 files changed, 2732 insertions, 118 deletions
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java new file mode 100644 index 000000000..1fb9465a6 --- /dev/null +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -0,0 +1,335 @@ +/* + * 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.data; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Video; +import android.util.Log; +import android.view.View; + +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.Collections; +import java.util.List; + +/** + * A FilmStrip.DataProvider that provide data in the camera folder. + * + * The given view for camera preview won't be added until the preview info + * has been set by setCameraPreviewInfo(int, int). + */ +public class CameraDataAdapter implements FilmStripView.DataAdapter { + private static final String TAG = CameraDataAdapter.class.getSimpleName(); + + private static final int DEFAULT_DECODE_SIZE = 3000; + private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" }; + + private List<LocalData> mImages; + + private Listener mListener; + private View mCameraPreviewView; + private Drawable mPlaceHolder; + + private int mSuggestedWidth = DEFAULT_DECODE_SIZE; + private int mSuggestedHeight = DEFAULT_DECODE_SIZE; + + public CameraDataAdapter(Drawable placeHolder) { + mPlaceHolder = placeHolder; + } + + public void setCameraPreviewInfo(View cameraPreview, int width, int height) { + mCameraPreviewView = cameraPreview; + addOrReplaceCameraData(buildCameraImageData(width, height)); + } + + public void requestLoad(ContentResolver resolver) { + QueryTask qtask = new QueryTask(); + qtask.execute(resolver); + } + + @Override + public int getTotalNumber() { + if (mImages == null) { + return 0; + } + return mImages.size(); + } + + @Override + public ImageData getImageData(int id) { + if (mImages == null || id >= mImages.size() || id < 0) { + return null; + } + return mImages.get(id); + } + + @Override + public void suggestDecodeSize(int w, int h) { + if (w <= 0 || h <= 0) { + mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE; + } else { + mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE); + mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE); + } + } + + @Override + public View getView(Context c, int dataID) { + if (mImages == null) { + return null; + } + if (dataID >= mImages.size() || dataID < 0) { + return null; + } + + return mImages.get(dataID).getView( + c, mSuggestedWidth, mSuggestedHeight, mPlaceHolder); + } + + @Override + public void setListener(Listener listener) { + mListener = listener; + if (mImages != null) { + mListener.onDataLoaded(); + } + } + + private LocalData buildCameraImageData(int width, int height) { + LocalData d = new CameraPreviewData(width, height); + return d; + } + + private void addOrReplaceCameraData(LocalData data) { + if (mImages == null) { + mImages = new ArrayList<LocalData>(); + } + if (mImages.size() == 0) { + // No data at all. + mImages.add(0, data); + if (mListener != null) { + mListener.onDataLoaded(); + } + return; + } + + LocalData first = mImages.get(0); + if (first.getType() == ImageData.TYPE_CAMERA_PREVIEW) { + // Replace the old camera data. + mImages.set(0, data); + if (mListener != null) { + mListener.onDataUpdated(new UpdateReporter() { + @Override + public boolean isDataRemoved(int id) { + return false; + } + + @Override + public boolean isDataUpdated(int id) { + if (id == 0) { + return true; + } + return false; + } + }); + } + } else { + // Add a new camera data. + mImages.add(0, data); + if (mListener != null) { + mListener.onDataLoaded(); + } + } + } + + private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> { + @Override + protected List<LocalData> doInBackground(ContentResolver... resolver) { + List<LocalData> l = new ArrayList<LocalData>(); + // Photos + Cursor c = resolver[0].query( + Images.Media.EXTERNAL_CONTENT_URI, + LocalData.Photo.QUERY_PROJECTION, + MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, + LocalData.Photo.QUERY_ORDER); + if (c != null && c.moveToFirst()) { + // build up the list. + while (true) { + LocalData data = LocalData.Photo.buildFromCursor(c); + if (data != null) { + l.add(data); + } else { + Log.e(TAG, "Error loading data:" + + c.getString(LocalData.Photo.COL_DATA)); + } + if (c.isLast()) { + break; + } + c.moveToNext(); + } + } + if (c != null) { + c.close(); + } + + c = resolver[0].query( + Video.Media.EXTERNAL_CONTENT_URI, + LocalData.Video.QUERY_PROJECTION, + MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH, + LocalData.Video.QUERY_ORDER); + if (c != null && c.moveToFirst()) { + // build up the list. + c.moveToFirst(); + while (true) { + LocalData data = LocalData.Video.buildFromCursor(c); + if (data != null) { + l.add(data); + Log.v(TAG, "video data added:" + data); + } else { + Log.e(TAG, "Error loading data:" + + c.getString(LocalData.Video.COL_DATA)); + } + if (!c.isLast()) { + c.moveToNext(); + } else { + break; + } + } + } + if (c != null) { + c.close(); + } + + if (l.size() == 0) return null; + + Collections.sort(l, new LocalData.NewestFirstComparator()); + return l; + } + + @Override + protected void onPostExecute(List<LocalData> l) { + boolean changed = (l != mImages); + LocalData cameraData = null; + if (mImages != null && mImages.size() > 0) { + cameraData = mImages.get(0); + if (cameraData.getType() != ImageData.TYPE_CAMERA_PREVIEW) { + cameraData = null; + } + } + + mImages = l; + if (cameraData != null) { + // camera view exists, so we make sure at least 1 data is in the list. + if (mImages == null) { + mImages = new ArrayList<LocalData>(); + } + mImages.add(0, cameraData); + if (mListener != null) { + // Only the camera data is not changed, everything else is changed. + mListener.onDataUpdated(new UpdateReporter() { + @Override + public boolean isDataRemoved(int id) { + return false; + } + + @Override + public boolean isDataUpdated(int id) { + if (id == 0) return false; + return true; + } + }); + } + } else { + // both might be null. + if (changed) { + mListener.onDataLoaded(); + } + } + } + } + + private class CameraPreviewData implements LocalData { + private int width; + private int height; + + CameraPreviewData(int w, int h) { + width = w; + height = h; + } + + @Override + public long getDateTaken() { + // This value is used for sorting. + return -1; + } + + @Override + public long getDateModified() { + // This value might be used for sorting. + return -1; + } + + @Override + public String getTitle() { + return ""; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public int getType() { + return ImageData.TYPE_CAMERA_PREVIEW; + } + + @Override + public boolean isActionSupported(int action) { + return false; + } + + @Override + public View getView(Context c, int width, int height, Drawable placeHolder) { + return mCameraPreviewView; + } + + @Override + public void prepare() { + // do nothing. + } + + @Override + public void recycle() { + // do nothing. + } + } + +} diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java new file mode 100644 index 000000000..1fda9eb67 --- /dev/null +++ b/src/com/android/camera/data/LocalData.java @@ -0,0 +1,421 @@ +/* + * 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.data; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; +import android.os.AsyncTask; +import android.provider.MediaStore.Images.ImageColumns; +import android.provider.MediaStore.Video; +import android.provider.MediaStore.Video.VideoColumns; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.android.camera.ui.FilmStripView; + +import java.util.Comparator; +import java.util.Date; + +/* An abstract interface that represents the local media data. Also implements + * Comparable interface so we can sort in DataAdapter. + */ +abstract interface LocalData extends FilmStripView.ImageData { + static final String TAG = "LocalData"; + + abstract View getView(Context c, int width, int height, Drawable placeHolder); + abstract long getDateTaken(); + abstract long getDateModified(); + abstract String getTitle(); + + static class NewestFirstComparator implements Comparator<LocalData> { + + private static int compare(long v1, long v2) { + if (v1 == -1) { + if (v2 == -1) return 0; + return -1; + } + if (v2 == -1) return 0; + + return ((v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0)); + } + + @Override + public int compare(LocalData d1, LocalData d2) { + int cmp = compare(d1.getDateTaken(), d2.getDateTaken()); + if (cmp == 0) { + cmp = compare(d1.getDateModified(), d2.getDateModified()); + } + if (cmp == 0) { + cmp = d1.getTitle().compareTo(d2.getTitle()); + } + return cmp; + } + } + + /* + * A base class for all the local media files. The bitmap is loaded in background + * thread. Subclasses should implement their own background loading thread by + * subclassing BitmapLoadTask and overriding doInBackground() to return a bitmap. + */ + abstract static class LocalMediaData implements LocalData { + protected long id; + protected String title; + protected String mimeType; + protected long dateTaken; + protected long dateModified; + protected String path; + // width and height should be adjusted according to orientation. + protected int width; + protected int height; + + // true if this data has a corresponding visible view. + protected Boolean mUsing = false; + + @Override + public long getDateTaken() { + return dateTaken; + } + + @Override + public long getDateModified() { + return dateModified; + } + + @Override + public String getTitle() { + return new String(title); + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public boolean isActionSupported(int action) { + return false; + } + + @Override + public View getView(Context c, + int decodeWidth, int decodeHeight, Drawable placeHolder) { + ImageView v = new ImageView(c); + v.setImageDrawable(placeHolder); + + v.setScaleType(ImageView.ScaleType.FIT_XY); + BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight); + task.execute(); + return v; + } + + @Override + public void prepare() { + synchronized (mUsing) { + mUsing = true; + } + } + + @Override + public void recycle() { + synchronized (mUsing) { + mUsing = false; + } + } + + protected boolean isUsing() { + synchronized (mUsing) { + return mUsing; + } + } + + @Override + public abstract int getType(); + + protected abstract BitmapLoadTask getBitmapLoadTask( + ImageView v, int decodeWidth, int decodeHeight); + + /* + * An AsyncTask class that loads the bitmap in the background thread. + * Sub-classes should implement their own "protected Bitmap doInBackground(Void... )" + */ + protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> { + protected ImageView mView; + + protected BitmapLoadTask(ImageView v) { + mView = v; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (!isUsing()) return; + if (bitmap == null) { + Log.e(TAG, "Failed decoding bitmap for file:" + path); + return; + } + mView.setScaleType(ImageView.ScaleType.FIT_XY); + mView.setImageBitmap(bitmap); + } + } + } + + static class Photo extends LocalMediaData { + public static final int COL_ID = 0; + public static final int COL_TITLE = 1; + public static final int COL_MIME_TYPE = 2; + public static final int COL_DATE_TAKEN = 3; + public static final int COL_DATE_MODIFIED = 4; + public static final int COL_DATA = 5; + public static final int COL_ORIENTATION = 6; + public static final int COL_WIDTH = 7; + public static final int COL_HEIGHT = 8; + + 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, string + 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 + }; + + // 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 Photo buildFromCursor(Cursor c) { + Photo d = new Photo(); + d.id = c.getLong(COL_ID); + d.title = c.getString(COL_TITLE); + d.mimeType = c.getString(COL_MIME_TYPE); + d.dateTaken = c.getLong(COL_DATE_TAKEN); + d.dateModified = c.getLong(COL_DATE_MODIFIED); + 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); + BitmapFactory.Options opts = decodeDimension(d.path); + if (opts != null) { + d.width = opts.outWidth; + d.height = opts.outHeight; + } 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; + } + + @Override + public String toString() { + return "Photo:" + ",data=" + path + ",mimeType=" + mimeType + + "," + width + "x" + height + ",orientation=" + orientation + + ",date=" + new Date(dateTaken); + } + + @Override + public int getType() { + return TYPE_PHOTO; + } + + @Override + protected BitmapLoadTask getBitmapLoadTask( + ImageView v, int decodeWidth, int decodeHeight) { + return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight); + } + + private static BitmapFactory.Options decodeDimension(String path) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + Bitmap b = BitmapFactory.decodeFile(path, opts); + if (b == null) { + return null; + } + return opts; + } + + private final class PhotoBitmapLoadTask extends BitmapLoadTask { + private int mDecodeWidth; + private int mDecodeHeight; + + public PhotoBitmapLoadTask(ImageView v, int decodeWidth, int decodeHeight) { + super(v); + mDecodeWidth = decodeWidth; + mDecodeHeight = decodeHeight; + } + + @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() || !isUsing()) { + return null; + } + b = BitmapFactory.decodeFile(path, opts); + if (orientation != 0) { + if (isCancelled() || !isUsing()) { + return null; + } + Matrix m = new Matrix(); + m.setRotate((float) orientation); + b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false); + } + return b; + } + } + } + + static class Video extends LocalMediaData { + public static final int COL_ID = 0; + public static final int COL_TITLE = 1; + public static final int COL_MIME_TYPE = 2; + public static final int COL_DATE_TAKEN = 3; + public static final int COL_DATE_MODIFIED = 4; + public static final int COL_DATA = 5; + public static final int COL_WIDTH = 6; + public static final int COL_HEIGHT = 7; + + static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, " + + VideoColumns._ID + " DESC"; + static final String[] QUERY_PROJECTION = { + VideoColumns._ID, // 0, int + VideoColumns.TITLE, // 1, string + VideoColumns.MIME_TYPE, // 2, string + VideoColumns.DATE_TAKEN, // 3, int + VideoColumns.DATE_MODIFIED, // 4, int + VideoColumns.DATA, // 5, string + VideoColumns.WIDTH, // 6, int + VideoColumns.HEIGHT, // 7, int + VideoColumns.RESOLUTION + }; + + static Video buildFromCursor(Cursor c) { + Video d = new Video(); + d.id = c.getLong(COL_ID); + d.title = c.getString(COL_TITLE); + d.mimeType = c.getString(COL_MIME_TYPE); + d.dateTaken = c.getLong(COL_DATE_TAKEN); + d.dateModified = c.getLong(COL_DATE_MODIFIED); + d.path = c.getString(COL_DATA); + d.width = c.getInt(COL_WIDTH); + d.height = c.getInt(COL_HEIGHT); + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(d.path); + String rotation = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + if (d.width == 0 || d.height == 0) { + d.width = Integer.parseInt(retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); + d.height = Integer.parseInt(retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); + } + retriever.release(); + if (rotation.equals("90") || rotation.equals("270")) { + int b = d.width; + d.width = d.height; + d.height = b; + } + return d; + } + + @Override + public String toString() { + return "Video:" + ",data=" + path + ",mimeType=" + mimeType + + "," + width + "x" + height + ",date=" + new Date(dateTaken); + } + + @Override + public int getType() { + return TYPE_PHOTO; + } + + @Override + protected BitmapLoadTask getBitmapLoadTask( + ImageView v, int decodeWidth, int decodeHeight) { + return new VideoBitmapLoadTask(v); + } + + private final class VideoBitmapLoadTask extends BitmapLoadTask { + + public VideoBitmapLoadTask(ImageView v) { + super(v); + } + + @Override + protected Bitmap doInBackground(Void... v) { + if (isCancelled() || !isUsing()) { + return null; + } + android.media.MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(path); + byte[] data = retriever.getEmbeddedPicture(); + Bitmap bitmap = null; + if (isCancelled() || !isUsing()) { + retriever.release(); + return null; + } + if (data != null) { + bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + } + if (bitmap == null) { + bitmap = (Bitmap) retriever.getFrameAtTime(); + } + retriever.release(); + return 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 new file mode 100644 index 000000000..c836c9168 --- /dev/null +++ b/src/com/android/camera/ui/FilmStripView.java @@ -0,0 +1,846 @@ +/* + * 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.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.util.AttributeSet; +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.class.getSimpleName(); + 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 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 FilmStripGestureRecognizer mGestureRecognizer; + private DataAdapter mDataAdapter; + private final Rect mDrawArea = new Rect(); + + private int mCurrentInfo; + private float mScale; + private GeometryAnimator mGeometryAnimator; + private int mCenterPosition = -1; + private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE]; + + // This is used to resolve the misalignment problem when the device + // orientation is changed. If the current item is in fullscreen, it might + // be shifted because mCenterPosition is not adjusted with the orientation. + // Set this to true when onSizeChanged is called to make sure we adjust + // mCenterPosition accordingly. + private boolean mAnchorPending; + + 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); + + // prepare() should be called first time before using it. + public void prepare(); + + // recycle() should be called before we nullify the reference to this + // data. + public void recycle(); + } + + public interface DataAdapter { + public interface UpdateReporter { + public boolean isDataRemoved(int id); + public boolean isDataUpdated(int id); + } + + public interface Listener { + // Called when the whole data loading is done. No any assumption + // on previous data. + public void onDataLoaded(); + // Only some of the data is changed. The listener should check + // if any thing needs to be updated. + public void onDataUpdated(UpdateReporter reporter); + 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 suggestDecodeSize(int w, int h); + + 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 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() { + return mDataID; + } + + public void setLeftPosition(int pos) { + mLeftPosition = pos; + } + + public int getLeftPosition() { + return mLeftPosition; + } + + public float getOffsetY() { + return mOffsetY; + } + + public void setOffsetY(float offset) { + mOffsetY = offset; + } + + public int getCenterX() { + return mLeftPosition + mView.getWidth() / 2; + } + + public View getView() { + return mView; + } + + private void layoutAt(int left, int top) { + mView.layout(left, top, left + mView.getMeasuredWidth(), + top + mView.getMeasuredHeight()); + } + + 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. + 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); + } + } + + public FilmStripView(Context context) { + super(context); + init(context); + } + + public FilmStripView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public FilmStripView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + mCurrentInfo = (BUFFER_SIZE - 1) / 2; + // This is for positioning camera controller at the same place in + // different orientations. + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + + setWillNotDraw(false); + mContext = context; + 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() { + if (mDataAdapter == null) return ImageData.TYPE_NONE; + ViewInfo curr = mViewInfo[mCurrentInfo]; + if (curr == null) return ImageData.TYPE_NONE; + return mDataAdapter.getImageData(curr.getID()).getType(); + } + + @Override + public void onDraw(Canvas c) { + if (mGeometryAnimator.hasNewGeometry()) { + layoutChildren(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int boundWidth = MeasureSpec.getSize(widthMeasureSpec); + int boundHeight = MeasureSpec.getSize(heightMeasureSpec); + if (mDataAdapter != null) { + mDataAdapter.suggestDecodeSize(boundWidth / 2, boundHeight / 2); + } + + int wMode = View.MeasureSpec.EXACTLY; + int hMode = View.MeasureSpec.EXACTLY; + + for (int i = 0; i < mViewInfo.length; i++) { + ViewInfo info = mViewInfo[i]; + if (mViewInfo[i] == null) continue; + + 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; + mViewInfo[i].getView().measure( + View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode) + , View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode)); + } + setMeasuredDimension(boundWidth, boundHeight); + } + + private int findTheNearestView(int pointX) { + + int nearest = 0; + // find the first non-null ViewInfo. + for (; nearest < BUFFER_SIZE + && (mViewInfo[nearest] == null || mViewInfo[nearest].getLeftPosition() == -1); + nearest++); + // no existing available ViewInfo + if (nearest == BUFFER_SIZE) return -1; + 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].getCenterX(); + int dist = Math.abs(pointX - c); + if (dist < min) { + min = dist; + nearest = infoID; + } + } + return nearest; + } + + private ViewInfo buildInfoFromData(int dataID) { + ImageData data = mDataAdapter.getImageData(dataID); + if (data == null) return null; + data.prepare(); + View v = mDataAdapter.getView(mContext, dataID); + if (v == null) return null; + v.setPadding(H_PADDING, 0, H_PADDING, 0); + ViewInfo info = new ViewInfo(dataID, v); + addView(info.getView()); + return info; + } + + private void removeInfo(int infoID) { + if (infoID >= mViewInfo.length || mViewInfo[infoID] == null) return; + + removeView(mViewInfo[infoID].getView()); + mDataAdapter.getImageData(mViewInfo[infoID].getID()).recycle(); + mViewInfo[infoID] = null; + } + + // We try to keep the one closest to the center of the screen at position mCurrentInfo. + private void stepIfNeeded() { + int nearest = findTheNearestView(mCenterPosition); + // no change made. + if (nearest == -1 || nearest == mCurrentInfo) return; + + int adjust = nearest - mCurrentInfo; + if (adjust > 0) { + for (int k = 0; k < adjust; k++) { + removeInfo(k); + } + for (int k = 0; k + adjust < BUFFER_SIZE; k++) { + mViewInfo[k] = mViewInfo[k + adjust]; + } + for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) { + mViewInfo[k] = null; + 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--) { + removeInfo(k); + } + for (int k = BUFFER_SIZE - 1; k + adjust >= 0; k--) { + mViewInfo[k] = mViewInfo[k + adjust]; + } + for (int k = -1 - adjust; k >= 0; k--) { + mViewInfo[k] = null; + if (mViewInfo[k + 1] != null) { + mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1); + } + } + } + } + + // Don't go beyond the bound. + private void adjustCenterPosition() { + ViewInfo curr = mViewInfo[mCurrentInfo]; + if (curr == null) return; + + if (curr.getID() == 0 && mCenterPosition < curr.getCenterX()) { + mCenterPosition = curr.getCenterX(); + mGeometryAnimator.stopScroll(); + } + if (curr.getID() == mDataAdapter.getTotalNumber() - 1 + && mCenterPosition > curr.getCenterX()) { + mCenterPosition = curr.getCenterX(); + mGeometryAnimator.stopScroll(); + } + } + + private void layoutChildren() { + if (mAnchorPending) { + mCenterPosition = mViewInfo[mCurrentInfo].getCenterX(); + mAnchorPending = false; + } + + if (mGeometryAnimator.hasNewGeometry()) { + mCenterPosition = mGeometryAnimator.getNewPosition(); + mScale = mGeometryAnimator.getNewScale(); + } + + adjustCenterPosition(); + + 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, mScale); + } + } + + // images on the right + for (int infoID = mCurrentInfo + 1; infoID < BUFFER_SIZE; infoID++) { + ViewInfo curr = mViewInfo[infoID]; + if (curr != null) { + ViewInfo prev = mViewInfo[infoID - 1]; + curr.setLeftPosition( + prev.getLeftPosition() + prev.getView().getMeasuredWidth()); + curr.layoutIn(mDrawArea, mCenterPosition, mScale); + } + } + + stepIfNeeded(); + invalidate(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (mViewInfo[mCurrentInfo] == null) return; + + mDrawArea.left = l; + mDrawArea.top = t; + mDrawArea.right = r; + mDrawArea.bottom = b; + + layoutChildren(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + if (w == oldw && h == oldh) return; + if (mViewInfo[mCurrentInfo] != null && mScale == 1f + && isAnchoredTo(mViewInfo[mCurrentInfo].getID())) { + mAnchorPending = true; + } + } + + public void setDataAdapter(DataAdapter adapter) { + mDataAdapter = adapter; + mDataAdapter.suggestDecodeSize(getMeasuredWidth(), getMeasuredHeight()); + mDataAdapter.setListener(new DataAdapter.Listener() { + @Override + public void onDataLoaded() { + reload(); + } + + @Override + public void onDataUpdated(DataAdapter.UpdateReporter reporter) { + update(reporter); + } + + @Override + public void onDataInserted(int dataID) { + } + + @Override + public void onDataRemoved(int dataID) { + } + }); + } + + public boolean isInCameraFullscreen() { + return (isAnchoredTo(0) && mScale == 1f + && getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (isInCameraFullscreen()) return false; + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + mGestureRecognizer.onTouchEvent(ev); + return true; + } + + private void updateViewInfo(int infoID) { + ViewInfo info = mViewInfo[infoID]; + removeView(info.getView()); + mViewInfo[infoID] = buildInfoFromData(info.getID()); + } + + // Some of the data is changed. + private void update(DataAdapter.UpdateReporter reporter) { + // No data yet. + if (mViewInfo[mCurrentInfo] == null) { + reload(); + return; + } + + // Check the current one. + ViewInfo curr = mViewInfo[mCurrentInfo]; + int dataID = curr.getID(); + if (reporter.isDataRemoved(dataID)) { + mCenterPosition = -1; + reload(); + return; + } + if (reporter.isDataUpdated(dataID)) { + updateViewInfo(mCurrentInfo); + } + + // Check left + for (int i = mCurrentInfo - 1; i >= 0; i--) { + curr = mViewInfo[i]; + if (curr != null) { + dataID = curr.getID(); + if (reporter.isDataRemoved(dataID) || reporter.isDataUpdated(dataID)) { + updateViewInfo(i); + } + } else { + ViewInfo next = mViewInfo[i + 1]; + if (next != null) mViewInfo[i] = buildInfoFromData(next.getID() - 1); + } + } + + // Check right + for (int i = mCurrentInfo + 1; i < BUFFER_SIZE; i++) { + curr = mViewInfo[i]; + if (curr != null) { + dataID = curr.getID(); + if (reporter.isDataRemoved(dataID) || reporter.isDataUpdated(dataID)) { + updateViewInfo(i); + } + } else { + ViewInfo prev = mViewInfo[i - 1]; + if (prev != null) mViewInfo[i] = buildInfoFromData(prev.getID() + 1); + } + } + } + + // The whole data might be totally different. Flush all and load from the start. + private void reload() { + removeAllViews(); + int dataNumber = mDataAdapter.getTotalNumber(); + if (dataNumber == 0) return; + + int currentData = 0; + int currentLeft = 0; + 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) { + mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID - 1].getID() + 1); + } + infoID = mCurrentInfo - i; + if (infoID >= 0 && mViewInfo[infoID + 1] != null) { + mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID + 1].getID() - 1); + } + } + 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; + } + + 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); + } + + // 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(); + } + + void lockPosition(int pos) { + mIsPositionLocked = true; + mLockedPosition = pos; + } + + 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(float x, float y) { + mGeometryAnimator.stop(); + return true; + } + + @Override + 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(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; + mGeometryAnimator.fling((int) -scaledVelocityX, + // estimation of possible length on the left + info.getLeftPosition() - info.getID() * w * 2, + // estimation of possible length on the right + info.getLeftPosition() + + (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/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java index a4f5807ae..9adb4e7a8 100644 --- a/src/com/android/gallery3d/app/CommonControllerOverlay.java +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -66,6 +66,10 @@ public abstract class CommonControllerOverlay extends FrameLayout implements protected boolean mCanReplay = true; + public void setSeekable(boolean canSeek) { + mTimeBar.setSeekable(canSeek); + } + public CommonControllerOverlay(Context context) { super(context); diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 00e4cd63b..ce9183483 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -25,7 +25,6 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Color; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; @@ -135,6 +134,17 @@ public class MoviePlayer implements return true; } }); + mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer player) { + if (!mVideoView.canSeekForward() || !mVideoView.canSeekBackward()) { + mController.setSeekable(false); + } else { + mController.setSeekable(true); + } + setProgress(); + } + }); // The SurfaceView is transparent before drawing the first frame. // This makes the UI flashing when open a video. (black -> old screen diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java index 402dfcfab..246346a56 100644 --- a/src/com/android/gallery3d/app/TimeBar.java +++ b/src/com/android/gallery3d/app/TimeBar.java @@ -259,4 +259,8 @@ public class TimeBar extends View { } } + public void setSeekable(boolean canSeek) { + mShowScrubber = canSeek; + } + } diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index d13d261a8..4b653780e 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -69,6 +69,8 @@ import com.android.gallery3d.filtershow.provider.SharedImageProvider; import com.android.gallery3d.filtershow.state.StateAdapter; import com.android.gallery3d.filtershow.tools.BitmapTask; import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.tools.XmpPresets; +import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults; import com.android.gallery3d.filtershow.ui.FramedTextButton; import com.android.gallery3d.filtershow.ui.Spline; import com.android.gallery3d.util.GalleryUtils; @@ -119,6 +121,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private LoadBitmapTask mLoadBitmapTask; private boolean mLoading = true; + private Uri mOriginalImageUri = null; + private ImagePreset mOriginalPreset = null; + private CategoryAdapter mCategoryLooksAdapter = null; private CategoryAdapter mCategoryBordersAdapter = null; private CategoryAdapter mCategoryGeometryAdapter = null; @@ -147,6 +152,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL setDefaultPreset(); + extractXMPData(); processIntent(); } @@ -285,9 +291,12 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } mAction = intent.getAction(); - - if (intent.getData() != null) { - startLoadBitmap(intent.getData()); + Uri srcUri = intent.getData(); + if (mOriginalImageUri != null) { + srcUri = mOriginalImageUri; + } + if (srcUri != null) { + startLoadBitmap(srcUri); } else { pickImage(); } @@ -364,8 +373,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL for (int i = 0; i < borders.size(); i++) { FilterRepresentation filter = borders.elementAt(i); + filter.setScrName(getString(R.string.borders)); if (i == 0) { - filter.setName(getString(R.string.none)); + filter.setScrName(getString(R.string.none)); } } @@ -516,6 +526,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mCategoryFiltersAdapter.imageLoaded(); mLoadBitmapTask = null; + if (mOriginalPreset != null) { + MasterImage.getImage().setPreset(mOriginalPreset, true); + mOriginalPreset = null; + } + if (mAction == TINY_PLANET_ACTION) { showRepresentation(mCategoryFiltersAdapter.getTinyPlanet()); } @@ -1049,4 +1064,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL System.loadLibrary("jni_filtershow_filters"); } + private void extractXMPData() { + XMresults res = XmpPresets.extractXMPData( + getBaseContext(), mMasterImage, getIntent().getData()); + if (res == null) + return; + + mOriginalImageUri = res.originalimage; + mOriginalPreset = res.preset; + } } diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java index 8760c4a09..1ea40f202 100644 --- a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java +++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java @@ -29,8 +29,9 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.presets.FilterEnvironment; import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.filtershow.presets.PipelineInterface; -public class CachingPipeline { +public class CachingPipeline implements PipelineInterface { private static final String LOGTAG = "CachingPipeline"; private boolean DEBUG = false; @@ -65,22 +66,10 @@ public class CachingPipeline { mName = name; } - public static synchronized Resources getResources() { - return sResources; - } - - public static synchronized void setResources(Resources resources) { - sResources = resources; - } - public static synchronized RenderScript getRenderScriptContext() { return sRS; } - public static synchronized void setRenderScriptContext(RenderScript RS) { - sRS = RS; - } - public static synchronized void createRenderscriptContext(Activity context) { if (sRS != null) { Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext"); @@ -128,6 +117,10 @@ public class CachingPipeline { } } + public Resources getResources() { + return sRS.getApplicationContext().getResources(); + } + private synchronized void destroyPixelAllocations() { if (DEBUG) { Log.v(LOGTAG, "destroyPixelAllocations in " + getName()); @@ -167,14 +160,14 @@ public class CachingPipeline { } private void setupEnvironment(ImagePreset preset, boolean highResPreview) { - mEnvironment.setCachingPipeline(this); + mEnvironment.setPipeline(this); mEnvironment.setFiltersManager(mFiltersManager); if (highResPreview) { mEnvironment.setScaleFactor(mHighResPreviewScaleFactor); } else { mEnvironment.setScaleFactor(mPreviewScaleFactor); } - mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW); + mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW); mEnvironment.setImagePreset(preset); mEnvironment.setStop(false); } @@ -293,11 +286,11 @@ public class CachingPipeline { || request.getType() == RenderingRequest.STYLE_ICON_RENDERING) { if (request.getType() == RenderingRequest.ICON_RENDERING) { - mEnvironment.setQuality(ImagePreset.QUALITY_ICON); + mEnvironment.setQuality(FilterEnvironment.QUALITY_ICON); } else if (request.getType() == RenderingRequest.STYLE_ICON_RENDERING) { mEnvironment.setQuality(ImagePreset.STYLE_ICON); } else { - mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW); + mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW); } Bitmap bmp = preset.apply(bitmap, mEnvironment); @@ -317,8 +310,11 @@ public class CachingPipeline { setupEnvironment(preset, false); mFiltersManager.freeFilterResources(preset); preset.applyFilters(-1, -1, in, out, mEnvironment); - // TODO: we should render the border onto a different bitmap instead - preset.applyBorder(in, out, mEnvironment); + boolean copyOut = false; + if (preset.nbFilters() > 0) { + copyOut = true; + } + preset.applyBorder(in, out, copyOut, mEnvironment); } } @@ -328,7 +324,7 @@ public class CachingPipeline { return bitmap; } setupEnvironment(preset, false); - mEnvironment.setQuality(ImagePreset.QUALITY_FINAL); + mEnvironment.setQuality(FilterEnvironment.QUALITY_FINAL); mEnvironment.setScaleFactor(1.0f); mFiltersManager.freeFilterResources(preset); bitmap = preset.applyGeometry(bitmap, mEnvironment); @@ -345,7 +341,7 @@ public class CachingPipeline { } mGeometry.useRepresentation(preset.getGeometry()); return mGeometry.apply(bitmap, mPreviewScaleFactor, - ImagePreset.QUALITY_PREVIEW); + FilterEnvironment.QUALITY_PREVIEW); } public synchronized void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) { @@ -458,4 +454,7 @@ public class CachingPipeline { return mName; } + public RenderScript getRSContext() { + return CachingPipeline.getRenderScriptContext(); + } } diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java index 072edd72a..25169c2d8 100644 --- a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java +++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java @@ -109,5 +109,4 @@ public class BasicParameterStyle implements ParameterStyles { public void setFilterView(FilterView editor) { mEditor = editor; } - } diff --git a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java index df5b6ae73..9d8278d52 100644 --- a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java +++ b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java @@ -84,5 +84,4 @@ public class BasicSlider implements Control { mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum()); mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum()); } - } diff --git a/src/com/android/gallery3d/filtershow/crop/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java index 849ac60ef..671554f16 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropMath.java +++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java @@ -196,14 +196,13 @@ public class CropMath { float finalH = origH; if (origA < a) { finalH = origW / a; + r.top = r.centerY() - finalH / 2; + r.bottom = r.top + finalH; } else { finalW = origH * a; + r.left = r.centerX() - finalW / 2; + r.right = r.left + finalW; } - 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); } /** diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java new file mode 100644 index 000000000..e18d3104f --- /dev/null +++ b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java @@ -0,0 +1,101 @@ +/* + * 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.data; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class FilterStackDBHelper extends SQLiteOpenHelper { + + public static final int DATABASE_VERSION = 1; + public static final String DATABASE_NAME = "filterstacks.db"; + private static final String SQL_CREATE_TABLE = "CREATE TABLE "; + + public static interface FilterStack { + /** The row uid */ + public static final String _ID = "_id"; + /** The table name */ + public static final String TABLE = "filterstack"; + /** The stack name */ + public static final String STACK_ID = "stack_id"; + /** A serialized stack of filters. */ + public static final String FILTER_STACK= "stack"; + } + + private static final String[][] CREATE_FILTER_STACK = { + { FilterStack._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" }, + { FilterStack.STACK_ID, "TEXT" }, + { FilterStack.FILTER_STACK, "BLOB" }, + }; + + public FilterStackDBHelper(Context context, String name, int version) { + super(context, name, null, version); + } + + public FilterStackDBHelper(Context context, String name) { + this(context, name, DATABASE_VERSION); + } + + public FilterStackDBHelper(Context context) { + this(context, DATABASE_NAME); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createTable(db, FilterStack.TABLE, CREATE_FILTER_STACK); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + dropTable(db, FilterStack.TABLE); + onCreate(db); + } + + protected static void createTable(SQLiteDatabase db, String table, String[][] columns) { + StringBuilder create = new StringBuilder(SQL_CREATE_TABLE); + create.append(table).append('('); + boolean first = true; + for (String[] column : columns) { + if (!first) { + create.append(','); + } + first = false; + for (String val : column) { + create.append(val).append(' '); + } + } + create.append(')'); + db.beginTransaction(); + try { + db.execSQL(create.toString()); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + protected static void dropTable(SQLiteDatabase db, String table) { + db.beginTransaction(); + try { + db.execSQL("drop table if exists " + table); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } +} diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackSource.java b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java new file mode 100644 index 000000000..4e343777d --- /dev/null +++ b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java @@ -0,0 +1,143 @@ +/* + * 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.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; +import android.util.Pair; + +import com.android.gallery3d.filtershow.data.FilterStackDBHelper.FilterStack; + +import java.util.ArrayList; +import java.util.List; + +public class FilterStackSource { + private static final String LOGTAG = "FilterStackSource"; + + private SQLiteDatabase database = null;; + private final FilterStackDBHelper dbHelper; + + public FilterStackSource(Context context) { + dbHelper = new FilterStackDBHelper(context); + } + + public void open() { + try { + database = dbHelper.getWritableDatabase(); + } catch (SQLiteException e) { + Log.w(LOGTAG, "could not open database", e); + } + } + + public void close() { + database = null; + dbHelper.close(); + } + + public boolean insertStack(String stackName, byte[] stackBlob) { + boolean ret = true; + ContentValues val = new ContentValues(); + val.put(FilterStack.STACK_ID, stackName); + val.put(FilterStack.FILTER_STACK, stackBlob); + database.beginTransaction(); + try { + ret = (-1 != database.insert(FilterStack.TABLE, null, val)); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + return ret; + } + + public boolean removeStack(String stackName) { + boolean ret = true; + database.beginTransaction(); + try { + ret = (0 != database.delete(FilterStack.TABLE, FilterStack.STACK_ID + " = ?", + new String[] { stackName})); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + return ret; + } + + public void removeAllStacks() { + database.beginTransaction(); + try { + database.delete(FilterStack.TABLE, null, null); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + } + + public byte[] getStack(String stackName) { + byte[] ret = null; + Cursor c = null; + database.beginTransaction(); + try { + c = database.query(FilterStack.TABLE, + new String[] { FilterStack.FILTER_STACK }, + FilterStack.STACK_ID + " = ?", + new String[] { stackName }, null, null, null, null); + if (c != null && c.moveToFirst() && !c.isNull(0)) { + ret = c.getBlob(0); + } + database.setTransactionSuccessful(); + } finally { + if (c != null) { + c.close(); + } + database.endTransaction(); + } + return ret; + } + + public List<Pair<String, byte[]>> getAllStacks() { + List<Pair<String, byte[]>> ret = new ArrayList<Pair<String, byte[]>>(); + Cursor c = null; + database.beginTransaction(); + try { + c = database.query(FilterStack.TABLE, + new String[] { FilterStack.STACK_ID, FilterStack.FILTER_STACK }, + null, null, null, null, null, null); + if (c != null) { + boolean loopCheck = c.moveToFirst(); + while (loopCheck) { + String name = (c.isNull(0)) ? null : c.getString(0); + byte[] b = (c.isNull(1)) ? null : c.getBlob(1); + ret.add(new Pair<String, byte[]>(name, b)); + loopCheck = c.moveToNext(); + } + } + database.setTransactionSuccessful(); + } finally { + if (c != null) { + c.close(); + } + database.endTransaction(); + } + if (ret.size() <= 0) { + return null; + } + return ret; + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index 9927a0a5e..1c7294c3a 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -17,6 +17,8 @@ package com.android.gallery3d.filtershow.filters; import android.content.Context; import android.content.res.Resources; +import android.util.Log; + import com.android.gallery3d.R; import com.android.gallery3d.filtershow.presets.ImagePreset; @@ -24,11 +26,14 @@ import com.android.gallery3d.filtershow.presets.ImagePreset; import java.util.HashMap; import java.util.Vector; -public abstract class BaseFiltersManager { +public abstract class BaseFiltersManager implements FiltersManagerInterface { protected HashMap<Class, ImageFilter> mFilters = null; + protected HashMap<String, FilterRepresentation> mRepresentationLookup = null; + private static final String LOGTAG = "BaseFiltersManager"; protected void init() { mFilters = new HashMap<Class, ImageFilter>(); + mRepresentationLookup = new HashMap<String, FilterRepresentation>(); Vector<Class> filters = new Vector<Class>(); addFilterClasses(filters); for (Class filterClass : filters) { @@ -36,6 +41,12 @@ public abstract class BaseFiltersManager { Object filterInstance = filterClass.newInstance(); if (filterInstance instanceof ImageFilter) { mFilters.put(filterClass, (ImageFilter) filterInstance); + + FilterRepresentation rep = + ((ImageFilter) filterInstance).getDefaultRepresentation(); + if (rep != null) { + addRepresentation(rep); + } } } catch (InstantiationException e) { e.printStackTrace(); @@ -45,6 +56,20 @@ public abstract class BaseFiltersManager { } } + public void addRepresentation(FilterRepresentation rep) { + mRepresentationLookup.put(rep.getSerializationName(), rep); + } + + public FilterRepresentation createFilterFromName(String name) { + try { + return mRepresentationLookup.get(name).clone(); + } catch (Exception e) { + Log.v(LOGTAG, "unable to generate a filter representation for \"" + name + "\""); + e.printStackTrace(); + } + return null; + } + public ImageFilter getFilter(Class c) { return mFilters.get(c); } @@ -53,10 +78,6 @@ public abstract class BaseFiltersManager { return mFilters.get(representation.getFilterClass()); } - public void addFilter(Class filterClass, ImageFilter filter) { - mFilters.put(filterClass, filter); - } - public FilterRepresentation getRepresentation(Class c) { ImageFilter filter = mFilters.get(c); if (filter != null) { @@ -89,7 +110,7 @@ public abstract class BaseFiltersManager { protected void addFilterClasses(Vector<Class> filters) { filters.add(ImageFilterTinyPlanet.class); - //filters.add(ImageFilterRedEye.class); + filters.add(ImageFilterRedEye.class); filters.add(ImageFilterWBalance.class); filters.add(ImageFilterExposure.class); filters.add(ImageFilterVignette.class); @@ -99,7 +120,7 @@ public abstract class BaseFiltersManager { filters.add(ImageFilterVibrance.class); filters.add(ImageFilterSharpen.class); filters.add(ImageFilterCurves.class); - // filters.add(ImageFilterDraw.class); + filters.add(ImageFilterDraw.class); filters.add(ImageFilterHue.class); filters.add(ImageFilterSaturated.class); filters.add(ImageFilterBwFilter.class); @@ -168,8 +189,8 @@ public abstract class BaseFiltersManager { } public void addTools(Vector<FilterRepresentation> representations) { - //representations.add(getRepresentation(ImageFilterRedEye.class)); - // representations.add(getRepresentation(ImageFilterDraw.class)); + representations.add(getRepresentation(ImageFilterRedEye.class)); + representations.add(getRepresentation(ImageFilterDraw.class)); } public void setFilterResources(Resources resources) { diff --git a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java index 4d0651edb..368e5c029 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java @@ -31,6 +31,8 @@ public class FilterBasicRepresentation extends FilterRepresentation implements P private int mMaximum; private int mDefaultValue; private int mPreviewValue; + public static final String SERIAL_NAME = "Name"; + public static final String SERIAL_VALUE = "Value"; private boolean mLogVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE); public FilterBasicRepresentation(String name, int minimum, int value, int maximum) { @@ -171,4 +173,23 @@ public class FilterBasicRepresentation extends FilterRepresentation implements P public void copyFrom(Parameter src) { useParametersFrom((FilterBasicRepresentation) src); } + + @Override + public String[][] serializeRepresentation() { + String[][] ret = { + {SERIAL_NAME , getName() }, + {SERIAL_VALUE , Integer.toString(mValue)}}; + return ret; + } + + @Override + public void deSerializeRepresentation(String[][] rep) { + super.deSerializeRepresentation(rep); + for (int i = 0; i < rep.length; i++) { + if (SERIAL_VALUE.equals(rep[i][0])) { + mValue = Integer.parseInt(rep[i][1]); + break; + } + } + } } diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java index cbcae4b37..a32068aeb 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java @@ -15,6 +15,7 @@ public class FilterCurvesRepresentation extends FilterRepresentation { public FilterCurvesRepresentation() { super("Curves"); + setSerializationName("CURVES"); setFilterClass(ImageFilterCurves.class); setTextId(R.string.curvesRGB); setButtonId(R.id.curvesButtonRGB); diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java index dc59b0cfc..9b144b9e9 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java @@ -49,6 +49,7 @@ public class FilterDrawRepresentation extends FilterRepresentation { public FilterDrawRepresentation() { super("Draw"); + setSerializationName("DRAW"); setFilterClass(ImageFilterDraw.class); setPriority(FilterRepresentation.TYPE_VIGNETTE); setTextId(R.string.imageDraw); diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java index 6e2e7ea16..1ceffb4a2 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java @@ -21,7 +21,8 @@ import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.editors.ImageOnlyEditor; public class FilterFxRepresentation extends FilterRepresentation { - private static final String LOGTAG = "FilterFxRepresentation"; + private static final String SERIALIZATION_NAME = "LUT3D"; + private static final String LOGTAG = "FilterFxRepresentation"; // TODO: When implementing serialization, we should find a unique way of // specifying bitmaps / names (the resource IDs being random) private int mBitmapResource = 0; @@ -29,6 +30,8 @@ public class FilterFxRepresentation extends FilterRepresentation { public FilterFxRepresentation(String name, int bitmapResource, int nameResource) { super(name); + setSerializationName(SERIALIZATION_NAME); + mBitmapResource = bitmapResource; mNameResource = nameResource; setFilterClass(ImageFilterFx.class); diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java index 3f823ea1e..8a878415c 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java @@ -28,6 +28,7 @@ public class FilterRedEyeRepresentation extends FilterPointRepresentation { public FilterRedEyeRepresentation() { super("RedEye",R.string.redeye,EditorRedEye.ID); + setSerializationName("REDEYE"); setFilterClass(ImageFilterRedEye.class); setOverlayId(R.drawable.photoeditor_effect_redeye); setOverlayOnly(true); diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java index 82012b992..91bf676f3 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java @@ -16,7 +16,7 @@ package com.android.gallery3d.filtershow.filters; -import com.android.gallery3d.app.Log; +import android.util.Log; import com.android.gallery3d.filtershow.editors.BasicEditor; public class FilterRepresentation implements Cloneable { @@ -34,7 +34,7 @@ public class FilterRepresentation implements Cloneable { private boolean mShowEditingControls = true; private boolean mShowParameterValue = true; private boolean mShowUtilityPanel = true; - + private String mSerializationName; public static final byte TYPE_BORDER = 1; public static final byte TYPE_FX = 2; public static final byte TYPE_WBALANCE = 3; @@ -63,6 +63,8 @@ public class FilterRepresentation implements Cloneable { representation.setShowEditingControls(showEditingControls()); representation.setShowParameterValue(showParameterValue()); representation.setShowUtilityPanel(showUtilityPanel()); + representation.mSerializationName = mSerializationName; + representation.mTempRepresentation = mTempRepresentation != null ? mTempRepresentation.clone() : null; if (DEBUG) { @@ -96,6 +98,10 @@ public class FilterRepresentation implements Cloneable { return mName; } + public void setScrName(String name) { + mName = name; + } + public void setName(String name) { mName = name; } @@ -104,6 +110,14 @@ public class FilterRepresentation implements Cloneable { return mName; } + public void setSerializationName(String sname) { + mSerializationName = sname; + } + + public String getSerializationName() { + return mSerializationName; + } + public void setPriority(int priority) { mPriority = priority; } @@ -241,4 +255,17 @@ public class FilterRepresentation implements Cloneable { return ""; } + public String[][] serializeRepresentation() { + String[][] ret = { { "Name" , getName() }}; + return ret; + } + + public void deSerializeRepresentation(String[][] rep) { + for (int i = 0; i < rep.length; i++) { + if ("Name".equals(rep[i][0])) { + mName = rep[i][0]; + break; + } + } + } } diff --git a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java index ac5e04601..48c8b380e 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java @@ -20,11 +20,14 @@ import com.android.gallery3d.R; import com.android.gallery3d.filtershow.editors.EditorTinyPlanet; public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation { + private static final String SERIALIZATION_NAME = "TINYPLANET"; private static final String LOGTAG = "FilterTinyPlanetRepresentation"; + private static final String SERIAL_ANGLE = "Angle"; private float mAngle = 0; public FilterTinyPlanetRepresentation() { super("TinyPlanet", 0, 50, 100); + setSerializationName(SERIALIZATION_NAME); setShowParameterValue(true); setFilterClass(ImageFilterTinyPlanet.class); setPriority(FilterRepresentation.TYPE_TINYPLANET); @@ -71,4 +74,25 @@ public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation { // TinyPlanet always has an effect return false; } + + @Override + public String[][] serializeRepresentation() { + String[][] ret = { + {SERIAL_NAME , getName() }, + {SERIAL_VALUE , Integer.toString(getValue())}, + {SERIAL_ANGLE , Float.toString(mAngle)}}; + return ret; + } + + @Override + public void deSerializeRepresentation(String[][] rep) { + super.deSerializeRepresentation(rep); + for (int i = 0; i < rep.length; i++) { + if (SERIAL_VALUE.equals(rep[i][0])) { + setValue(Integer.parseInt(rep[i][1])); + } else if (SERIAL_ANGLE.equals(rep[i][0])) { + setAngle(Float.parseFloat(rep[i][1])); + } + } + } } diff --git a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java index eef54ef58..9827088ff 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java @@ -29,6 +29,7 @@ public class FilterVignetteRepresentation extends FilterBasicRepresentation impl public FilterVignetteRepresentation() { super("Vignette", -100, 50, 100); + setSerializationName("VIGNETTE"); setShowParameterValue(true); setPriority(FilterRepresentation.TYPE_VIGNETTE); setTextId(R.string.vignette); @@ -111,4 +112,44 @@ public class FilterVignetteRepresentation extends FilterBasicRepresentation impl public boolean isNil() { return getValue() == 0; } + + private static final String[] sParams = { + "Name", "value", "mCenterX", "mCenterY", "mRadiusX", + "mRadiusY" + }; + + @Override + public String[][] serializeRepresentation() { + String[][] ret = { + { sParams[0], getName() }, + { sParams[1], Integer.toString(getValue()) }, + { sParams[2], Float.toString(mCenterX) }, + { sParams[3], Float.toString(mCenterY) }, + { sParams[4], Float.toString(mRadiusX) }, + { sParams[5], Float.toString(mRadiusY) } + }; + return ret; + } + + @Override + public void deSerializeRepresentation(String[][] rep) { + super.deSerializeRepresentation(rep); + for (int i = 0; i < rep.length; i++) { + String key = rep[i][0]; + String value = rep[i][1]; + if (sParams[0].equals(key)) { + setName(value); + } else if (sParams[1].equals(key)) { + setValue(Integer.parseInt(value)); + } else if (sParams[2].equals(key)) { + mCenterX = Float.parseFloat(value); + } else if (sParams[3].equals(key)) { + mCenterY = Float.parseFloat(value); + } else if (sParams[4].equals(key)) { + mRadiusX = Float.parseFloat(value); + } else if (sParams[5].equals(key)) { + mRadiusY = Float.parseFloat(value); + } + } + } } diff --git a/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java new file mode 100644 index 000000000..710128f99 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java @@ -0,0 +1,21 @@ +/* + * 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.filters; + +public interface FiltersManagerInterface { + ImageFilter getFilterForRepresentation(FilterRepresentation representation); +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java index 96ab84f9d..b80fc7f15 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java @@ -16,12 +16,12 @@ package com.android.gallery3d.filtershow.filters; +import android.app.Activity; import android.graphics.Bitmap; import android.graphics.Matrix; import android.support.v8.renderscript.Allocation; import android.widget.Toast; -import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; import com.android.gallery3d.filtershow.presets.FilterEnvironment; import com.android.gallery3d.filtershow.presets.ImagePreset; @@ -35,9 +35,9 @@ public abstract class ImageFilter implements Cloneable { // TODO: Temporary, for dogfood note memory issues with toasts for better // feedback. Remove this when filters actually work in low memory // situations. - private static FilterShowActivity sActivity = null; + private static Activity sActivity = null; - public static void setActivityForMemoryToasts(FilterShowActivity activity) { + public static void setActivityForMemoryToasts(Activity activity) { sActivity = activity; } @@ -76,10 +76,6 @@ public abstract class ImageFilter implements Cloneable { return bitmap; } - public ImagePreset getImagePreset() { - return getEnvironment().getImagePreset(); - } - public abstract void useRepresentation(FilterRepresentation representation); native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h, @@ -90,10 +86,11 @@ public abstract class ImageFilter implements Cloneable { } protected Matrix getOriginalToScreenMatrix(int w, int h) { - GeometryMetadata geo = getImagePreset().mGeoData; + ImagePreset preset = getEnvironment().getImagePreset(); + GeometryMetadata geo = getEnvironment().getImagePreset().mGeoData; Matrix originalToScreen = geo.getOriginalToScreen(true, - getImagePreset().getImageLoader().getOriginalBounds().width(), - getImagePreset().getImageLoader().getOriginalBounds().height(), + preset.getImageLoader().getOriginalBounds().width(), + preset.getImageLoader().getOriginalBounds().height(), w, h); return originalToScreen; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java index a4626cdb2..64c48dffa 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java @@ -23,6 +23,7 @@ import android.graphics.Color; public class ImageFilterBwFilter extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "BWFILTER"; public ImageFilterBwFilter() { mName = "BW Filter"; @@ -31,6 +32,8 @@ public class ImageFilterBwFilter extends SimpleImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("BW Filter"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterBwFilter.class); representation.setMaximum(180); representation.setMinimum(-180); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java index 2097f0d6e..c8b41c248 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java @@ -21,6 +21,7 @@ import com.android.gallery3d.R; import android.graphics.Bitmap; public class ImageFilterContrast extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "CONTRAST"; public ImageFilterContrast() { mName = "Contrast"; @@ -30,6 +31,8 @@ public class ImageFilterContrast extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Contrast"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterContrast.class); representation.setTextId(R.string.contrast); representation.setButtonId(R.id.contrastButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java index 0b02fc4f6..ea2ff351d 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java @@ -24,6 +24,7 @@ import com.android.gallery3d.R; import com.android.gallery3d.filtershow.cache.ImageLoader; public class ImageFilterDownsample extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "DOWNSAMPLE"; private static final int ICON_DOWNSAMPLE_FRACTION = 8; private ImageLoader mImageLoader; @@ -35,6 +36,8 @@ public class ImageFilterDownsample extends SimpleImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Downsample"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterDownsample.class); representation.setMaximum(100); representation.setMinimum(1); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java index 1fd9071f7..812ab02f0 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java @@ -31,6 +31,7 @@ import android.graphics.PorterDuffColorFilter; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData; import com.android.gallery3d.filtershow.imageshow.MasterImage; +import com.android.gallery3d.filtershow.presets.FilterEnvironment; import com.android.gallery3d.filtershow.presets.ImagePreset; import java.util.Vector; @@ -204,7 +205,7 @@ public class ImageFilterDraw extends ImageFilter { public void drawData(Canvas canvas, Matrix originalRotateToScreen, int quality) { Paint paint = new Paint(); - if (quality == ImagePreset.QUALITY_FINAL) { + if (quality == FilterEnvironment.QUALITY_FINAL) { paint.setAntiAlias(true); } paint.setStyle(Style.STROKE); @@ -214,7 +215,7 @@ public class ImageFilterDraw extends ImageFilter { if (mParameters.getDrawing().isEmpty() && mParameters.getCurrentDrawing() == null) { return; } - if (quality == ImagePreset.QUALITY_FINAL) { + if (quality == FilterEnvironment.QUALITY_FINAL) { for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) { paint(strokeData, canvas, originalRotateToScreen, quality); } @@ -248,17 +249,17 @@ public class ImageFilterDraw extends ImageFilter { int n = v.size(); for (int i = mCachedStrokes; i < n; i++) { - paint(v.get(i), drawCache, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW); + paint(v.get(i), drawCache, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW); } mCachedStrokes = n; } public void draw(Canvas canvas, Matrix originalRotateToScreen) { for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) { - paint(strokeData, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW); + paint(strokeData, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW); } mDrawingsTypes[mCurrentStyle].paint( - null, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW); + null, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW); } @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java index 46a9a294c..82de2b73a 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java @@ -21,7 +21,7 @@ import android.graphics.Bitmap; import com.android.gallery3d.R; public class ImageFilterEdge extends SimpleImageFilter { - + private static final String SERIALIZATION_NAME = "EDGE"; public ImageFilterEdge() { mName = "Edge"; } @@ -29,6 +29,7 @@ public class ImageFilterEdge extends SimpleImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterRepresentation representation = super.getDefaultRepresentation(); representation.setName("Edge"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterEdge.class); representation.setTextId(R.string.edge); representation.setButtonId(R.id.edgeButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java index b0b0b2dd8..6fdcd249b 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java @@ -21,7 +21,7 @@ import com.android.gallery3d.R; import android.graphics.Bitmap; public class ImageFilterExposure extends SimpleImageFilter { - + private static final String SERIALIZATION_NAME = "EXPOSURE"; public ImageFilterExposure() { mName = "Exposure"; } @@ -30,6 +30,7 @@ public class ImageFilterExposure extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Exposure"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterExposure.class); representation.setTextId(R.string.exposure); representation.setButtonId(R.id.exposureButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java index 68e8a7c9d..51c66127b 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java @@ -37,6 +37,11 @@ public class ImageFilterFx extends ImageFilter { mFxBitmap = null; } + @Override + public FilterRepresentation getDefaultRepresentation() { + return null; + } + public void useRepresentation(FilterRepresentation representation) { FilterFxRepresentation parameters = (FilterFxRepresentation) representation; mParameters = parameters; @@ -87,4 +92,5 @@ public class ImageFilterFx extends ImageFilter { public void setResources(Resources resources) { mResources = resources; } + } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java index 0022a9eae..0725dd1c4 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import com.android.gallery3d.R; public class ImageFilterHighlights extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "HIGHLIGHTS"; private static final String LOGTAG = "ImageFilterVignette"; public ImageFilterHighlights() { @@ -33,7 +34,8 @@ public class ImageFilterHighlights extends SimpleImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); - representation.setName("Shadows"); + representation.setName("Highlights"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterHighlights.class); representation.setTextId(R.string.highlight_recovery); representation.setButtonId(R.id.highlightRecoveryButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java index b1f9f7365..7e6f68548 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java @@ -22,6 +22,7 @@ import com.android.gallery3d.filtershow.editors.BasicEditor; import android.graphics.Bitmap; public class ImageFilterHue extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "HUE"; private ColorSpaceMatrix cmatrix = null; public ImageFilterHue() { @@ -33,6 +34,7 @@ public class ImageFilterHue extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Hue"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterHue.class); representation.setMinimum(-180); representation.setMaximum(180); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java index 29e6d162f..93813813f 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java @@ -22,6 +22,7 @@ import android.text.format.Time; import com.android.gallery3d.R; public class ImageFilterKMeans extends SimpleImageFilter { + private static final String SERIALIZATION_NAME = "KMEANS"; private int mSeed = 0; public ImageFilterKMeans() { @@ -36,6 +37,7 @@ public class ImageFilterKMeans extends SimpleImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("KMeans"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterKMeans.class); representation.setMaximum(20); representation.setMinimum(2); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java index c256686fb..0747190fa 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java @@ -6,13 +6,14 @@ import com.android.gallery3d.R; import com.android.gallery3d.filtershow.editors.ImageOnlyEditor; public class ImageFilterNegative extends ImageFilter { - + private static final String SERIALIZATION_NAME = "NEGATIVE"; public ImageFilterNegative() { mName = "Negative"; } public FilterRepresentation getDefaultRepresentation() { FilterRepresentation representation = new FilterDirectRepresentation("Negative"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterNegative.class); representation.setTextId(R.string.negative); representation.setButtonId(R.id.negativeButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java index cfbb560c7..69d18f805 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java @@ -22,13 +22,14 @@ import android.support.v8.renderscript.*; import android.util.Log; import android.content.res.Resources; import com.android.gallery3d.R; -import com.android.gallery3d.filtershow.cache.CachingPipeline; +import com.android.gallery3d.filtershow.presets.PipelineInterface; public abstract class ImageFilterRS extends ImageFilter { private static final String LOGTAG = "ImageFilterRS"; private boolean DEBUG = false; private int mLastInputWidth = 0; private int mLastInputHeight = 0; + private long mLastTimeCalled; public static boolean PERF_LOGGING = false; @@ -51,26 +52,36 @@ public abstract class ImageFilterRS extends ImageFilter { } protected RenderScript getRenderScriptContext() { - return CachingPipeline.getRenderScriptContext(); + PipelineInterface pipeline = getEnvironment().getPipeline(); + return pipeline.getRSContext(); } protected Allocation getInPixelsAllocation() { - CachingPipeline pipeline = getEnvironment().getCachingPipeline(); + PipelineInterface pipeline = getEnvironment().getPipeline(); return pipeline.getInPixelsAllocation(); } protected Allocation getOutPixelsAllocation() { - CachingPipeline pipeline = getEnvironment().getCachingPipeline(); + PipelineInterface pipeline = getEnvironment().getPipeline(); return pipeline.getOutPixelsAllocation(); } @Override public void apply(Allocation in, Allocation out) { long startOverAll = System.nanoTime(); + if (PERF_LOGGING) { + long delay = (startOverAll - mLastTimeCalled) / 1000; + String msg = String.format("%s; image size %dx%d; ", getName(), + in.getType().getX(), in.getType().getY()); + msg += String.format("called after %.2f ms (%.2f FPS); ", + delay / 1000.f, 1000000.f / delay); + Log.i(LOGTAG, msg); + } + mLastTimeCalled = startOverAll; long startFilter = 0; long endFilter = 0; if (!mResourcesLoaded) { - CachingPipeline pipeline = getEnvironment().getCachingPipeline(); + PipelineInterface pipeline = getEnvironment().getPipeline(); createFilter(pipeline.getResources(), getEnvironment().getScaleFactor(), getEnvironment().getQuality(), in); mResourcesLoaded = true; @@ -102,7 +113,7 @@ public abstract class ImageFilterRS extends ImageFilter { return bitmap; } try { - CachingPipeline pipeline = getEnvironment().getCachingPipeline(); + PipelineInterface pipeline = getEnvironment().getPipeline(); if (DEBUG) { Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName()); } @@ -137,18 +148,16 @@ public abstract class ImageFilterRS extends ImageFilter { displayLowMemoryToast(); Log.e(LOGTAG, "not enough memory for filter " + getName(), e); } - return bitmap; } - protected static Allocation convertBitmap(Bitmap bitmap) { - return Allocation.createFromBitmap(CachingPipeline.getRenderScriptContext(), bitmap, + protected static Allocation convertBitmap(RenderScript RS, Bitmap bitmap) { + return Allocation.createFromBitmap(RS, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE); } - private static Allocation convertRGBAtoA(Bitmap bitmap) { - RenderScript RS = CachingPipeline.getRenderScriptContext(); + private static Allocation convertRGBAtoA(RenderScript RS, Bitmap bitmap) { if (RS != mRScache || mGreyConvert == null) { mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(), R.raw.grey); @@ -157,7 +166,7 @@ public abstract class ImageFilterRS extends ImageFilter { Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS)); - Allocation bitmapTemp = convertBitmap(bitmap); + Allocation bitmapTemp = convertBitmap(RS, bitmap); if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) { return bitmapTemp; } @@ -173,20 +182,20 @@ public abstract class ImageFilterRS extends ImageFilter { } public Allocation loadScaledResourceAlpha(int resource, int inSampleSize) { - Resources res = CachingPipeline.getResources(); + Resources res = getEnvironment().getPipeline().getResources(); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ALPHA_8; options.inSampleSize = inSampleSize; Bitmap bitmap = BitmapFactory.decodeResource( res, resource, options); - Allocation ret = convertRGBAtoA(bitmap); + Allocation ret = convertRGBAtoA(getRenderScriptContext(), bitmap); bitmap.recycle(); return ret; } public Allocation loadScaledResourceAlpha(int resource, int w, int h, int inSampleSize) { - Resources res = CachingPipeline.getResources(); + Resources res = getEnvironment().getPipeline().getResources(); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ALPHA_8; options.inSampleSize = inSampleSize; @@ -194,7 +203,7 @@ public abstract class ImageFilterRS extends ImageFilter { res, resource, options); Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); - Allocation ret = convertRGBAtoA(resizeBitmap); + Allocation ret = convertRGBAtoA(getRenderScriptContext(), resizeBitmap); resizeBitmap.recycle(); bitmap.recycle(); return ret; @@ -205,13 +214,13 @@ public abstract class ImageFilterRS extends ImageFilter { } public Allocation loadResource(int resource) { - Resources res = CachingPipeline.getResources(); + Resources res = getEnvironment().getPipeline().getResources(); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap bitmap = BitmapFactory.decodeResource( res, resource, options); - Allocation ret = convertBitmap(bitmap); + Allocation ret = convertBitmap(getRenderScriptContext(), bitmap); bitmap.recycle(); return ret; } @@ -232,7 +241,7 @@ public abstract class ImageFilterRS extends ImageFilter { /** * RS Script objects (and all other RS objects) should be cleared here */ - abstract protected void resetScripts(); + public abstract void resetScripts(); /** * Scripts values should be bound here @@ -244,6 +253,8 @@ public abstract class ImageFilterRS extends ImageFilter { return; } resetAllocations(); + mLastInputWidth = 0; + mLastInputHeight = 0; setResourcesLoaded(false); } } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java index 0febe4957..adc74c5ec 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java @@ -21,7 +21,7 @@ import com.android.gallery3d.R; import android.graphics.Bitmap; public class ImageFilterSaturated extends SimpleImageFilter { - + private static final String SERIALIZATION_NAME = "SATURATED"; public ImageFilterSaturated() { mName = "Saturated"; } @@ -31,6 +31,7 @@ public class ImageFilterSaturated extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Saturated"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterSaturated.class); representation.setTextId(R.string.saturation); representation.setButtonId(R.id.saturationButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java index fd67ee8fc..845290b80 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java @@ -21,7 +21,7 @@ import com.android.gallery3d.R; import android.graphics.Bitmap; public class ImageFilterShadows extends SimpleImageFilter { - + private static final String SERIALIZATION_NAME = "SHADOWS"; public ImageFilterShadows() { mName = "Shadows"; @@ -31,6 +31,7 @@ public class ImageFilterShadows extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Shadows"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterShadows.class); representation.setTextId(R.string.shadow_recovery); representation.setButtonId(R.id.shadowRecoveryButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java index 76ae475ac..1dc2c0516 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java @@ -19,7 +19,7 @@ package com.android.gallery3d.filtershow.filters; import com.android.gallery3d.R; public class ImageFilterSharpen extends ImageFilterRS { - + private static final String SERIALIZATION_NAME = "SHARPEN"; private static final String LOGTAG = "ImageFilterSharpen"; private ScriptC_convolve3x3 mScript; @@ -31,6 +31,7 @@ public class ImageFilterSharpen extends ImageFilterRS { public FilterRepresentation getDefaultRepresentation() { FilterRepresentation representation = new FilterBasicRepresentation("Sharpen", 0, 0, 100); + representation.setSerializationName(SERIALIZATION_NAME); representation.setShowParameterValue(true); representation.setFilterClass(ImageFilterSharpen.class); representation.setTextId(R.string.sharpness); @@ -52,7 +53,7 @@ public class ImageFilterSharpen extends ImageFilterRS { } @Override - protected void resetScripts() { + public void resetScripts() { if (mScript != null) { mScript.destroy(); mScript = null; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java index 37d5739a0..f265c4dcc 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java @@ -76,7 +76,7 @@ public class ImageFilterTinyPlanet extends SimpleImageFilter { int w = bitmapIn.getWidth(); int h = bitmapIn.getHeight(); int outputSize = (int) (w / 2f); - ImagePreset preset = getImagePreset(); + ImagePreset preset = getEnvironment().getImagePreset(); Bitmap mBitmapOut = null; if (preset != null) { XMPMeta xmp = preset.getImageLoader().getXmpObject(); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java index ea315d326..900fd906c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java @@ -21,7 +21,7 @@ import com.android.gallery3d.R; import android.graphics.Bitmap; public class ImageFilterVibrance extends SimpleImageFilter { - + private static final String SERIALIZATION_NAME = "VIBRANCE"; public ImageFilterVibrance() { mName = "Vibrance"; } @@ -30,6 +30,7 @@ public class ImageFilterVibrance extends SimpleImageFilter { FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation(); representation.setName("Vibrance"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterVibrance.class); representation.setTextId(R.string.vibrance); representation.setButtonId(R.id.vibranceButton); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java index e06f54493..cfe135033 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java @@ -22,7 +22,7 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import com.android.gallery3d.R; -import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.filtershow.presets.FilterEnvironment; public class ImageFilterVignette extends SimpleImageFilter { private static final String LOGTAG = "ImageFilterVignette"; @@ -57,9 +57,9 @@ public class ImageFilterVignette extends SimpleImageFilter { @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) { - if (SIMPLE_ICONS && ImagePreset.QUALITY_ICON == quality) { + if (SIMPLE_ICONS && FilterEnvironment.QUALITY_ICON == quality) { if (mOverlayBitmap == null) { - Resources res = getEnvironment().getCachingPipeline().getResources(); + Resources res = getEnvironment().getPipeline().getResources(); mOverlayBitmap = IconUtilities.getFXBitmap(res, R.drawable.filtershow_icon_vignette); } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java index c4c293a4b..84a14c902 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java @@ -22,6 +22,7 @@ import com.android.gallery3d.filtershow.editors.ImageOnlyEditor; import android.graphics.Bitmap; public class ImageFilterWBalance extends ImageFilter { + private static final String SERIALIZATION_NAME = "WBALANCE"; private static final String TAG = "ImageFilterWBalance"; public ImageFilterWBalance() { @@ -30,6 +31,7 @@ public class ImageFilterWBalance extends ImageFilter { public FilterRepresentation getDefaultRepresentation() { FilterRepresentation representation = new FilterDirectRepresentation("WBalance"); + representation.setSerializationName(SERIALIZATION_NAME); representation.setFilterClass(ImageFilterWBalance.class); representation.setPriority(FilterRepresentation.TYPE_WBALANCE); representation.setTextId(R.string.wbalance); diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index e5820a8f2..77dbd5e7b 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -20,6 +20,7 @@ import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.util.Log; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.crop.CropExtras; @@ -30,7 +31,14 @@ import com.android.gallery3d.filtershow.editors.EditorStraighten; import com.android.gallery3d.filtershow.filters.FilterRepresentation; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; + public class GeometryMetadata extends FilterRepresentation { + private static final String SERIALIZATION_NAME = "GEOM"; private static final String LOGTAG = "GeometryMetadata"; private float mScaleFactor = 1.0f; private float mRotation = 0; @@ -40,7 +48,26 @@ public class GeometryMetadata extends FilterRepresentation { private FLIP mFlip = FLIP.NONE; public enum FLIP { - NONE, VERTICAL, HORIZONTAL, BOTH + NONE("N"), VERTICAL("V"), HORIZONTAL("H"), BOTH("B"); + String mValue; + + FLIP(String name) { + mValue = name; + } + + public static FLIP parse(String name){ + switch (name.charAt(0)) { + case 'N': + return NONE; + case 'V': + return VERTICAL; + case 'H': + return HORIZONTAL; + case 'B': + return BOTH; + }; + return NONE; + } } // Output format data from intent extras @@ -64,6 +91,7 @@ public class GeometryMetadata extends FilterRepresentation { public GeometryMetadata() { super("GeometryMetadata"); + setSerializationName(SERIALIZATION_NAME); setFilterClass(ImageFilterGeometry.class); setEditorId(EditorCrop.ID); setTextId(0); @@ -492,4 +520,87 @@ public class GeometryMetadata extends FilterRepresentation { representation.useParametersFrom(this); return representation; } + + private static final String[] sParams = { + "Name", "ScaleFactor", "Rotation", "StraightenRotation", "CropBoundsLeft", + "CropBoundsTop", "CropBoundsRight", "CropBoundsBottom", "PhotoBoundsLeft", + "PhotoBoundsTop", "PhotoBoundsRight", "PhotoBoundsBottom", "Flip" + }; + + @Override + public String[][] serializeRepresentation() { + String[][] ret = { + { "Name", getName() }, + { "ScaleFactor", Float.toString(mScaleFactor) }, + { "Rotation", Float.toString(mRotation) }, + { "StraightenRotation", Float.toString(mStraightenRotation) }, + { "CropBoundsLeft", Float.toString(mCropBounds.left) }, + { "CropBoundsTop", Float.toString(mCropBounds.top) }, + { "CropBoundsRight", Float.toString(mCropBounds.right) }, + { "CropBoundsBottom", Float.toString(mCropBounds.bottom) }, + { "PhotoBoundsLeft", Float.toString(mPhotoBounds.left) }, + { "PhotoBoundsTop", Float.toString(mPhotoBounds.top) }, + { "PhotoBoundsRight", Float.toString(mPhotoBounds.right) }, + { "PhotoBoundsBottom", Float.toString(mPhotoBounds.bottom) }, + { "Flip", mFlip.mValue } }; + return ret; + } + + @Override + public void deSerializeRepresentation(String[][] rep) { + HashMap<String, Integer> map = new HashMap<String, Integer>(); + for (int i = 0; i < sParams.length; i++) { + map.put(sParams[i], i); + } + for (int i = 0; i < rep.length; i++) { + String key = rep[i][0]; + String value = rep[i][1]; + + switch (map.get(key)) { + case -1: // Unknown + break; + case 0: + if (!getName().equals(value)) { + throw new IllegalArgumentException("Not a "+getName()); + } + break; + case 1: // "ScaleFactor", Float + mScaleFactor = Float.parseFloat(value); + break; + case 2: // "Rotation", Float + mRotation = Float.parseFloat(value); + break; + case 3: // "StraightenRotation", Float + mStraightenRotation = Float.parseFloat(value); + break; + case 4: // "mCropBoundsLeft", Float + mCropBounds.left = Float.parseFloat(value); + break; + case 5: // "mCropBoundsTop", Float + mCropBounds.top = Float.parseFloat(value); + break; + case 6: // "mCropBoundsRight", Float + mCropBounds.right = Float.parseFloat(value); + break; + case 7: // "mCropBoundsBottom", Float + mCropBounds.bottom = Float.parseFloat(value); + break; + case 8: // "mPhotoBoundsLeft", Float + mPhotoBounds.left = Float.parseFloat(value); + break; + case 9: // "mPhotoBoundsTop", Float + mPhotoBounds.top = Float.parseFloat(value); + break; + case 10: // "mPhotoBoundsRight", Float + mPhotoBounds.right = Float.parseFloat(value); + break; + case 11: // "mPhotoBoundsBottom", Float + mPhotoBounds.bottom = Float.parseFloat(value); + break; + case 12: // "Flip", enum + mFlip = FLIP.parse(value); + break; + } + } + } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java index 5e5d70b7c..7e086b0ca 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java +++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java @@ -20,6 +20,7 @@ import android.graphics.*; import android.os.Handler; import android.os.Message; +import android.util.Log; import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.cache.*; @@ -139,6 +140,7 @@ public class MasterImage implements RenderingRequestCaller { } public synchronized void setPreset(ImagePreset preset, boolean addToHistory) { + preset.showFilters(); mPreset = preset; mPreset.setImageLoader(mLoader); setGeometry(); diff --git a/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java index c454c1ab6..47f8dfccb 100644 --- a/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java +++ b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java @@ -18,11 +18,9 @@ package com.android.gallery3d.filtershow.presets; import android.graphics.Bitmap; import android.support.v8.renderscript.Allocation; -import android.util.Log; -import com.android.gallery3d.filtershow.cache.CachingPipeline; import com.android.gallery3d.filtershow.filters.FilterRepresentation; -import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.filters.FiltersManagerInterface; import com.android.gallery3d.filtershow.filters.ImageFilter; import java.lang.ref.WeakReference; @@ -33,10 +31,14 @@ public class FilterEnvironment { private ImagePreset mImagePreset; private float mScaleFactor; private int mQuality; - private FiltersManager mFiltersManager; - private CachingPipeline mCachingPipeline; + private FiltersManagerInterface mFiltersManager; + private PipelineInterface mPipeline; private volatile boolean mStop = false; + public static final int QUALITY_ICON = 0; + public static final int QUALITY_PREVIEW = 1; + public static final int QUALITY_FINAL = 2; + public synchronized boolean needsStop() { return mStop; } @@ -98,11 +100,11 @@ public class FilterEnvironment { return mQuality; } - public void setFiltersManager(FiltersManager filtersManager) { + public void setFiltersManager(FiltersManagerInterface filtersManager) { mFiltersManager = filtersManager; } - public FiltersManager getFiltersManager() { + public FiltersManagerInterface getFiltersManager() { return mFiltersManager; } @@ -126,12 +128,12 @@ public class FilterEnvironment { return ret; } - public CachingPipeline getCachingPipeline() { - return mCachingPipeline; + public PipelineInterface getPipeline() { + return mPipeline; } - public void setCachingPipeline(CachingPipeline cachingPipeline) { - mCachingPipeline = cachingPipeline; + public void setPipeline(PipelineInterface cachingPipeline) { + mPipeline = cachingPipeline; } } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index 2a7e601ee..84766958d 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -18,12 +18,19 @@ package com.android.gallery3d.filtershow.presets; import android.graphics.Bitmap; import android.graphics.Rect; +import android.net.Uri; import android.support.v8.renderscript.Allocation; +import android.util.JsonReader; +import android.util.JsonWriter; import android.util.Log; +import com.adobe.xmp.XMPException; +import com.adobe.xmp.XMPMeta; +import com.adobe.xmp.options.PropertyOptions; import com.android.gallery3d.filtershow.cache.CachingPipeline; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.BaseFiltersManager; +import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.filters.FilterRepresentation; import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; @@ -32,6 +39,10 @@ import com.android.gallery3d.filtershow.state.State; import com.android.gallery3d.filtershow.state.StateAdapter; import com.android.gallery3d.util.UsageStatistics; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; import java.util.Vector; public class ImagePreset { @@ -39,10 +50,8 @@ public class ImagePreset { private static final String LOGTAG = "ImagePreset"; private FilterRepresentation mBorder = null; - 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; + public static final String PRESET_NAME = "PresetName"; private ImageLoader mImageLoader = null; @@ -210,11 +219,11 @@ public class ImagePreset { } for (FilterRepresentation representation : mFilters) { if (representation.getPriority() == FilterRepresentation.TYPE_VIGNETTE - && !representation.isNil()) { + && !representation.isNil()) { return false; } if (representation.getPriority() == FilterRepresentation.TYPE_TINYPLANET - && !representation.isNil()) { + && !representation.isNil()) { return false; } } @@ -460,7 +469,7 @@ public class ImagePreset { if (mBorder != null && mDoApplyGeometry) { mBorder.synchronizeRepresentation(); bitmap = environment.applyRepresentation(mBorder, bitmap); - if (environment.getQuality() == QUALITY_FINAL) { + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, "SaveBorder", mBorder.getName(), 1); } @@ -468,6 +477,10 @@ public class ImagePreset { return bitmap; } + public int nbFilters() { + return mFilters.size(); + } + public Bitmap applyFilters(Bitmap bitmap, int from, int to, FilterEnvironment environment) { if (mDoApplyFilters) { if (from < 0) { @@ -483,7 +496,7 @@ public class ImagePreset { representation.synchronizeRepresentation(); } bitmap = environment.applyRepresentation(representation, bitmap); - if (environment.getQuality() == QUALITY_FINAL) { + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, "SaveFilter", representation.getName(), 1); } @@ -496,17 +509,23 @@ public class ImagePreset { return bitmap; } - public void applyBorder(Allocation in, Allocation out, FilterEnvironment environment) { + public void applyBorder(Allocation in, Allocation out, + boolean copyOut, FilterEnvironment environment) { if (mBorder != null && mDoApplyGeometry) { mBorder.synchronizeRepresentation(); // TODO: should keep the bitmap around - Allocation bitmapIn = Allocation.createTyped(CachingPipeline.getRenderScriptContext(), in.getType()); - bitmapIn.copyFrom(out); + Allocation bitmapIn = in; + if (copyOut) { + bitmapIn = Allocation.createTyped( + CachingPipeline.getRenderScriptContext(), in.getType()); + bitmapIn.copyFrom(out); + } environment.applyRepresentation(mBorder, bitmapIn, out); } } - public void applyFilters(int from, int to, Allocation in, Allocation out, FilterEnvironment environment) { + public void applyFilters(int from, int to, Allocation in, Allocation out, + FilterEnvironment environment) { if (mDoApplyFilters) { if (from < 0) { from = 0; @@ -605,4 +624,109 @@ public class ImagePreset { return usedFilters; } + public String getJsonString(String name) { + StringWriter swriter = new StringWriter(); + try { + JsonWriter writer = new JsonWriter(swriter); + writeJson(writer, name); + writer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return swriter.toString(); + } + + public void writeJson(JsonWriter writer, String name) { + int numFilters = mFilters.size(); + try { + writer.beginObject(); + writer.name(PRESET_NAME).value(name); + writer.name(mGeoData.getSerializationName()); + writer.beginObject(); + { + String[][] rep = mGeoData.serializeRepresentation(); + for (int i = 0; i < rep.length; i++) { + writer.name(rep[i][0]); + writer.value(rep[i][1]); + } + } + writer.endObject(); + + for (int i = 0; i < numFilters; i++) { + FilterRepresentation filter = mFilters.get(i); + String sname = filter.getSerializationName(); + writer.name(sname); + writer.beginObject(); + { + String[][] rep = filter.serializeRepresentation(); + for (int k = 0; k < rep.length; k++) { + writer.name(rep[k][0]); + writer.value(rep[k][1]); + } + } + writer.endObject(); + } + writer.endObject(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public boolean readJsonFromString(String filterString) { + StringReader sreader = new StringReader(filterString); + try { + JsonReader reader = new JsonReader(sreader); + boolean ok = readJson(reader); + if (!ok) { + return false; + } + reader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + + public boolean readJson(JsonReader sreader) throws IOException { + sreader.beginObject(); + sreader.nextName(); + mName = sreader.nextString(); + + while (sreader.hasNext()) { + String name = sreader.nextName(); + + if (mGeoData.getSerializationName().equals(name)) { + mGeoData.deSerializeRepresentation(read(sreader)); + } else { + FilterRepresentation filter = creatFilterFromName(name); + if (filter == null) + return false; + filter.deSerializeRepresentation(read(sreader)); + addFilter(filter); + } + } + sreader.endObject(); + return true; + } + + FilterRepresentation creatFilterFromName(String name) { + FiltersManager filtersManager = FiltersManager.getManager(); + return filtersManager.createFilterFromName(name); + } + + String[][] read(JsonReader reader) throws IOException { + ArrayList <String[]> al = new ArrayList<String[]>(); + + reader.beginObject(); + + while (reader.hasNext()) { + String[]kv = { reader.nextName(),reader.nextString()}; + al.add(kv); + + } + reader.endObject(); + return al.toArray(new String[al.size()][]); + } } diff --git a/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java b/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java new file mode 100644 index 000000000..05f0a1aa8 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java @@ -0,0 +1,31 @@ +/* + * 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.presets; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.RenderScript; + +public interface PipelineInterface { + public String getName(); + public Resources getResources(); + public Allocation getInPixelsAllocation(); + public Allocation getOutPixelsAllocation(); + public boolean prepareRenderscriptAllocations(Bitmap bitmap); + public RenderScript getRSContext(); +} diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index c5851c476..b5de6929a 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -206,6 +206,8 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { uri = insertContent(context, sourceUri, this.destinationFile, saveFileName, time); } + XmpPresets.writeFilterXMP(context, sourceUri, this.destinationFile, preset); + noBitmap = false; } catch (java.lang.OutOfMemoryError e) { // Try 5 times before failing for good. @@ -219,6 +221,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return uri; } + @Override protected void onPostExecute(Uri result) { if (callback != null) { diff --git a/src/com/android/gallery3d/filtershow/tools/XmpPresets.java b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java new file mode 100644 index 000000000..be75f0253 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java @@ -0,0 +1,133 @@ +/* + * 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.tools; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.adobe.xmp.XMPException; +import com.adobe.xmp.XMPMeta; +import com.adobe.xmp.XMPMetaFactory; +import com.android.gallery3d.R; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.filtershow.imageshow.MasterImage; +import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.util.XmpUtilHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class XmpPresets { + public static final String + XMP_GOOGLE_FILTER_NAMESPACE = "http://ns.google.com/photos/1.0/filter/"; + public static final String XMP_GOOGLE_FILTER_PREFIX = "AFltr"; + public static final String XMP_SRC_FILE_URI = "SourceFileUri"; + public static final String XMP_FILTERSTACK = "filterstack"; + private static final String LOGTAG = "XmpPresets"; + + public static class XMresults { + public String presetString; + public ImagePreset preset; + public Uri originalimage; + } + + static { + try { + XMPMetaFactory.getSchemaRegistry().registerNamespace( + XMP_GOOGLE_FILTER_NAMESPACE, XMP_GOOGLE_FILTER_PREFIX); + } catch (XMPException e) { + Log.e(LOGTAG, "Register XMP name space failed", e); + } + } + + public static void writeFilterXMP( + Context context, Uri srcUri, File dstFile, ImagePreset preset) { + InputStream is = null; + XMPMeta xmpMeta = null; + try { + is = context.getContentResolver().openInputStream(srcUri); + xmpMeta = XmpUtilHelper.extractXMPMeta(is); + } catch (FileNotFoundException e) { + + } finally { + Utils.closeSilently(is); + } + + if (xmpMeta == null) { + xmpMeta = XMPMetaFactory.create(); + } + try { + xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE, + XMP_SRC_FILE_URI, srcUri.toString()); + xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE, + XMP_FILTERSTACK, preset.getJsonString(context.getString(R.string.saved))); + } catch (XMPException e) { + Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath()); + return; + } + + if (!XmpUtilHelper.writeXMPMeta(dstFile.getAbsolutePath(), xmpMeta)) { + Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath()); + } + } + + public static XMresults extractXMPData( + Context context, MasterImage mMasterImage, Uri uriToEdit) { + XMresults ret = new XMresults(); + + InputStream is = null; + XMPMeta xmpMeta = null; + try { + is = context.getContentResolver().openInputStream(uriToEdit); + xmpMeta = XmpUtilHelper.extractXMPMeta(is); + } catch (FileNotFoundException e) { + } finally { + Utils.closeSilently(is); + } + + if (xmpMeta == null) { + return null; + } + + try { + String strSrcUri = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE, + XMP_SRC_FILE_URI); + + if (strSrcUri != null) { + String filterString = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE, + XMP_FILTERSTACK); + + Uri srcUri = Uri.parse(strSrcUri); + ret.originalimage = srcUri; + + ret.preset = new ImagePreset(mMasterImage.getPreset()); + ret.presetString = filterString; + boolean ok = ret.preset.readJsonFromString(filterString); + if (!ok) { + return null; + } + return ret; + } + } catch (XMPException e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index e5e77beb4..f5d3dc397 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -190,7 +190,7 @@ public class MenuExecutor { setMenuItemVisible(menu, R.id.action_setas, supportSetAs); setMenuItemVisible(menu, R.id.action_show_on_map, supportShowOnMap); setMenuItemVisible(menu, R.id.action_edit, supportEdit); - // setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit); + setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit); setMenuItemVisible(menu, R.id.action_details, supportInfo); } diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java index c281dd3e7..e2c5f51b9 100644 --- a/src/com/android/gallery3d/util/SaveVideoFileUtils.java +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -19,6 +19,7 @@ package com.android.gallery3d.util; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; +import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore.Video; @@ -95,7 +96,7 @@ public class SaveVideoFileUtils { ContentResolver contentResolver, Uri uri ) { long nowInMs = System.currentTimeMillis(); long nowInSec = nowInMs / 1000; - final ContentValues values = new ContentValues(12); + final ContentValues values = new ContentValues(13); values.put(Video.Media.TITLE, mDstFileInfo.mFileName); values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName()); values.put(Video.Media.MIME_TYPE, "video/mp4"); @@ -104,6 +105,8 @@ public class SaveVideoFileUtils { values.put(Video.Media.DATE_ADDED, nowInSec); values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath()); values.put(Video.Media.SIZE, mDstFileInfo.mFile.length()); + int durationMs = retriveVideoDurationMs(mDstFileInfo.mFile.getPath()); + values.put(Video.Media.DURATION, durationMs); // Copy the data taken and location info from src. String[] projection = new String[] { VideoColumns.DATE_TAKEN, @@ -138,4 +141,18 @@ public class SaveVideoFileUtils { return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values); } + public static int retriveVideoDurationMs(String path) { + int durationMs = 0; + // Calculate the duration of the destination file. + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(path); + String duration = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION); + if (duration != null) { + durationMs = Integer.parseInt(duration); + } + retriever.release(); + return durationMs; + } + } diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java index a0ab4105a..f19808d06 100644 --- a/src/com/android/photos/data/BitmapDecoder.java +++ b/src/com/android/photos/data/BitmapDecoder.java @@ -41,7 +41,7 @@ public class BitmapDecoder { private static final String TAG = BitmapDecoder.class.getSimpleName(); private static final int POOL_SIZE = 4; private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024; - private static final int HEADER_MAX_SIZE = 16 * 1024; + private static final int HEADER_MAX_SIZE = 128 * 1024; private static final Pool<BitmapFactory.Options> sOptions = new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE); |