diff options
-rw-r--r-- | res/values/strings.xml | 4 | ||||
-rw-r--r-- | src/com/android/camera/data/CameraDataAdapter.java | 34 | ||||
-rw-r--r-- | src/com/android/camera/data/LocalDataList.java | 101 | ||||
-rw-r--r-- | src/com/android/camera/data/LocalMediaData.java | 6 | ||||
-rw-r--r-- | src/com/android/camera/data/SimpleViewData.java | 5 | ||||
-rw-r--r-- | src/com/android/camera/ui/FilmStripView.java | 111 | ||||
-rw-r--r-- | src/com/android/camera/ui/ZoomView.java | 51 |
7 files changed, 247 insertions, 65 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml index fe4bbfc8c..cce3b1831 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -342,6 +342,10 @@ <!-- Settings menu, scene mode choices [CHAR LIMIT=16] --> <string name="pref_camera_scenemode_entry_auto">Auto</string> + <!-- Scene mode that uses HD (high definition) [CHAR LIMIT=16] --> + <string name="pref_camera_scenemode_entry_hd">HD</string> + <!-- Scene mode that uses HQ (high quality) [CHAR LIMIT=16] --> + <string name="pref_camera_scenemode_entry_hq">HQ</string> <!-- Scene mode that uses HDR (high dynamic range) [CHAR LIMIT=16] --> <string name="pref_camera_scenemode_entry_hdr">HDR</string> <!-- Scene mode that takes an image quickly with little motion blur. [CHAR LIMIT=16] --> diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java index 892aa1056..cf0fb2f98 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -31,9 +31,7 @@ import com.android.camera.Storage; import com.android.camera.ui.FilmStripView.ImageData; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; -import java.util.List; /** * A {@link LocalDataAdapter} that provides data in the camera folder. @@ -44,7 +42,7 @@ public class CameraDataAdapter implements LocalDataAdapter { private static final int DEFAULT_DECODE_SIZE = 1600; private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" }; - private List<LocalData> mImages; + private LocalDataList mImages; private Listener mListener; private Drawable mPlaceHolder; @@ -55,7 +53,7 @@ public class CameraDataAdapter implements LocalDataAdapter { private LocalData mLocalDataToDelete; public CameraDataAdapter(Drawable placeHolder) { - mImages = new ArrayList<LocalData>(); + mImages = new LocalDataList(); mPlaceHolder = placeHolder; } @@ -176,16 +174,10 @@ public class CameraDataAdapter implements LocalDataAdapter { @Override public int findDataByContentUri(Uri uri) { - for (int i = 0; i < mImages.size(); i++) { - Uri u = mImages.get(i).getContentUri(); - if (u == null) { - continue; - } - if (u.equals(uri)) { - return i; - } - } - return -1; + // LocalDataList will return in O(1) if the uri is not contained. + // Otherwise the performance is O(n), but this is acceptable as we will + // most often call this to find an element at the beginning of the list. + return mImages.indexOf(uri); } @Override @@ -209,7 +201,7 @@ public class CameraDataAdapter implements LocalDataAdapter { @Override public void flush() { - replaceData(new ArrayList<LocalData>()); + replaceData(new LocalDataList()); } @Override @@ -260,7 +252,7 @@ public class CameraDataAdapter implements LocalDataAdapter { } /** Update all the data */ - private void replaceData(List<LocalData> list) { + private void replaceData(LocalDataList list) { if (list.size() == 0 && mImages.size() == 0) { return; } @@ -270,7 +262,7 @@ public class CameraDataAdapter implements LocalDataAdapter { } } - private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> { + private class QueryTask extends AsyncTask<ContentResolver, Void, LocalDataList> { /** * Loads all the photo and video data in the camera folder in background @@ -280,8 +272,8 @@ public class CameraDataAdapter implements LocalDataAdapter { * @return An {@link ArrayList} of all loaded data. */ @Override - protected List<LocalData> doInBackground(ContentResolver... resolver) { - List<LocalData> l = new ArrayList<LocalData>(); + protected LocalDataList doInBackground(ContentResolver... resolver) { + LocalDataList l = new LocalDataList(); // Photos Cursor c = resolver[0].query( LocalMediaData.PhotoData.CONTENT_URI, @@ -336,14 +328,14 @@ public class CameraDataAdapter implements LocalDataAdapter { } if (l.size() != 0) { - Collections.sort(l, new LocalData.NewestFirstComparator()); + l.sort(new LocalData.NewestFirstComparator()); } return l; } @Override - protected void onPostExecute(List<LocalData> l) { + protected void onPostExecute(LocalDataList l) { replaceData(l); } } diff --git a/src/com/android/camera/data/LocalDataList.java b/src/com/android/camera/data/LocalDataList.java new file mode 100644 index 000000000..3ccc4de54 --- /dev/null +++ b/src/com/android/camera/data/LocalDataList.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.camera.data; + +import android.net.Uri; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; + +/** + * Fast access data structure for an ordered LocalData list. + */ +public class LocalDataList { + /** + * We use this as a way to compare a Uri to LocalData instances inside a + * LinkedList. A linked list in indexOf does a other.equals(get(i)). + */ + private static class UriWrapper { + private final Uri mUri; + + public UriWrapper(Uri uri) { + mUri = uri; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LocalData)) { + return false; + } + return mUri.equals(((LocalData) o).getContentUri()); + } + } + + private LinkedList<LocalData> mList = new LinkedList<LocalData>(); + private HashMap<Uri, LocalData> mUriMap = new HashMap<Uri, LocalData>(); + + public LocalData get(int index) { + return mList.get(index); + } + + public LocalData remove(int index) { + LocalData removedItem = mList.remove(index); + mUriMap.remove(removedItem); + return removedItem; + } + + public LocalData get(Uri uri) { + return mUriMap.get(uri); + } + + public void set(int pos, LocalData data) { + mList.set(pos, data); + mUriMap.put(data.getContentUri(), data); + } + + public void add(LocalData data) { + mList.add(data); + mUriMap.put(data.getContentUri(), data); + } + + public void add(int pos, LocalData data) { + mList.add(pos, data); + mUriMap.put(data.getContentUri(), data); + } + + public int size() { + return mList.size(); + } + + public void sort(Comparator<LocalData> comparator) { + Collections.sort(mList, comparator); + } + + /** + * This implementation routes through to LinkedList.indexOf, so performs in + * O(n) but has a fast exit path for when the uri is not contained in the + * list, and immediately returns -1; + */ + public int indexOf(Uri uri) { + if (!mUriMap.containsKey(uri)) { + return -1; + } + return mList.indexOf(new UriWrapper(uri)); + } +} diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java index 3679b08e4..573da45db 100644 --- a/src/com/android/camera/data/LocalMediaData.java +++ b/src/com/android/camera/data/LocalMediaData.java @@ -127,6 +127,11 @@ public abstract class LocalMediaData implements LocalData { } @Override + public int getOrientation() { + return 0; + } + + @Override public String getPath() { return mPath; } @@ -383,6 +388,7 @@ public abstract class LocalMediaData implements LocalData { return result; } + @Override public int getOrientation() { return mOrientation; } diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java index 3ff17226a..a73a52ece 100644 --- a/src/com/android/camera/data/SimpleViewData.java +++ b/src/com/android/camera/data/SimpleViewData.java @@ -75,6 +75,11 @@ public class SimpleViewData implements LocalData { } @Override + public int getOrientation() { + return 0; + } + + @Override public int getViewType() { return FilmStripView.ImageData.TYPE_REMOVABLE_VIEW; } diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java index 64f8a69d4..9b9e974b9 100644 --- a/src/com/android/camera/ui/FilmStripView.java +++ b/src/com/android/camera/ui/FilmStripView.java @@ -159,6 +159,11 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { */ public int getHeight(); + /** + * Returns the orientation of the image. + */ + public int getOrientation(); + /** Returns the image data type */ public int getViewType(); @@ -1048,7 +1053,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { * Translates the {@link ViewItem} on the left of the current one to match * the full-screen layout. In full-screen, we show only one {@link ViewItem} * which occupies the whole screen. The other left ones are put on the left - * side in full scales. + * side in full scales. Does nothing if there's no next item. * * @param currItem The item ID of the current one to be translated. * @param drawAreaWidth The width of the current draw area. @@ -1088,7 +1093,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { /** * Fade out the {@link ViewItem} on the right of the current one in - * full-screen layout. + * full-screen layout. Does nothing if there's no previous item. * * @param currItem The ID of the item to fade. */ @@ -1155,59 +1160,90 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { (mScale - FILM_STRIP_SCALE) / (FULL_SCREEN_SCALE - FILM_STRIP_SCALE)); final int fullScreenWidth = mDrawArea.width() + mViewGap; + // Decide the position for all view items on the left and the right first. + + // Left items. + for (int itemID = mCurrentItem - 1; itemID >= 0; itemID--) { + final ViewItem curr = mViewItem[itemID]; + if (curr == null) { + break; + } + + // First, layout relatively to the next one. + final int currLeft = mViewItem[itemID + 1].getLeftPosition() + - curr.getView().getMeasuredWidth() - mViewGap; + curr.setLeftPosition(currLeft); + } + // Right items. + for (int itemID = mCurrentItem + 1; itemID < BUFFER_SIZE; itemID++) { + ViewItem curr = mViewItem[itemID]; + if (curr == null) { + break; + } + + // First, layout relatively to the previous one. + ViewItem prev = mViewItem[itemID - 1]; + int currLeft = + prev.getLeftPosition() + prev.getView().getMeasuredWidth() + + mViewGap; + curr.setLeftPosition(currLeft); + } + // Layout the current ViewItem first. if (scaleFraction == 1f) { - if (mCenterX < mViewItem[mCurrentItem].getCenterX()) { - // In full-screen and it's not the first one and mCenterX is on - // the left of the center, we draw the current one to "fade down". + final ViewItem currItem = mViewItem[mCurrentItem]; + final int currCenterX = currItem.getCenterX(); + if (mCenterX < currCenterX) { + // In full-screen and mCenterX is on the left of the center, + // we draw the current one to "fade down". fadeAndScaleRightViewItem(mCurrentItem); - } else if(mCenterX > mViewItem[mCurrentItem].getCenterX()) { - // In full-screen and it's not the last one and mCenterX is on - // the right of the center, we draw the current one translated. + } else if(mCenterX > currCenterX) { + // In full-screen and mCenterX is on the right of the center, + // we draw the current one translated. translateLeftViewItem(mCurrentItem, fullScreenWidth, scaleFraction); } else { - mViewItem[mCurrentItem].layoutIn(mDrawArea, mCenterX, mScale); - mViewItem[mCurrentItem].setTranslationX(0f, mScale); - mViewItem[mCurrentItem].getView().setAlpha(1f); + currItem.layoutIn(mDrawArea, mCenterX, mScale); + currItem.setTranslationX(0f, mScale); + currItem.getView().setAlpha(1f); } } else { + final ViewItem currItem = mViewItem[mCurrentItem]; // The normal filmstrip has no translation for the current item. If it has // translation before, gradually set it to zero. - mViewItem[mCurrentItem].setTranslationX( - mViewItem[mCurrentItem].getScaledTranslationX(mScale) * scaleFraction, + currItem.setTranslationX( + currItem.getScaledTranslationX(mScale) * scaleFraction, mScale); - mViewItem[mCurrentItem].layoutIn(mDrawArea, mCenterX, mScale); + currItem.layoutIn(mDrawArea, mCenterX, mScale); + if (mViewItem[mCurrentItem - 1] == null) { + currItem.getView().setAlpha(1f); + } else { + final int currCenterX = currItem.getCenterX(); + final int prevCenterX = mViewItem[mCurrentItem - 1].getCenterX(); + final float fadeDownFraction = + ((float) mCenterX - prevCenterX) / (currCenterX - prevCenterX); + currItem.getView().setAlpha( + (1 - fadeDownFraction) * (1 - scaleFraction) + fadeDownFraction); + } } // Layout the rest dependent on the current scale. - // images on the left + // Items on the left for (int itemID = mCurrentItem - 1; itemID >= 0; itemID--) { final ViewItem curr = mViewItem[itemID]; if (curr == null) { break; } - - // First, layout relatively to the next one. - final int currLeft = mViewItem[itemID + 1].getLeftPosition() - - curr.getView().getMeasuredWidth() - mViewGap; - curr.setLeftPosition(currLeft); translateLeftViewItem(itemID, fullScreenWidth, scaleFraction); } - // images on the right + // Items on the right for (int itemID = mCurrentItem + 1; itemID < BUFFER_SIZE; itemID++) { ViewItem curr = mViewItem[itemID]; if (curr == null) { - continue; + break; } - // First, layout relatively to the previous one. - ViewItem prev = mViewItem[itemID - 1]; - int currLeft = - prev.getLeftPosition() + prev.getView().getMeasuredWidth() - + mViewGap; - curr.setLeftPosition(currLeft); curr.layoutIn(mDrawArea, mCenterX, mScale); View currView = curr.getView(); if (scaleFraction == 1) { @@ -1227,7 +1263,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { } } curr.setTranslationX( - (mViewItem[mCurrentItem].getLeftPosition() - currLeft) + (mViewItem[mCurrentItem].getLeftPosition() - curr.getLeftPosition()) * scaleFraction, mScale); } } @@ -2109,8 +2145,11 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { return; } ViewItem curr = mViewItem[mCurrentItem]; - if(curr == null || !mDataAdapter.getImageData(curr.getId()) - .isUIActionSupported(ImageData.ACTION_ZOOM)) { + if (curr == null) { + return; + } + ImageData imageData = mDataAdapter.getImageData(curr.getId()); + if(!imageData.isUIActionSupported(ImageData.ACTION_ZOOM)) { return; } Uri uri = getCurrentContentUri(); @@ -2118,7 +2157,8 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (uri == null || uri == Uri.EMPTY) { return; } - mZoomView.loadBitmap(uri, viewRect); + int orientation = imageData.getOrientation(); + mZoomView.loadBitmap(uri, orientation, viewRect); } private void cancelLoadingZoomedImage() { @@ -2392,7 +2432,9 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { return false; } mScaleTrend = 1f; - mMaxScale = mController.getCurrentDataMaxScale(); + // If the image is smaller than screen size, we should allow to zoom + // in to full screen size + mMaxScale = Math.max(mController.getCurrentDataMaxScale(), FULL_SCREEN_SCALE); return true; } @@ -2433,6 +2475,9 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { ViewItem curr = mViewItem[mCurrentItem]; // Make sure the image is not overly scaled newScale = Math.min(newScale, mMaxScale); + if (newScale == mScale) { + return true; + } float postScale = newScale / mScale; curr.postScale(focusX, focusY, postScale, mDrawArea.width(), mDrawArea.height()); mScale = newScale; diff --git a/src/com/android/camera/ui/ZoomView.java b/src/com/android/camera/ui/ZoomView.java index c4a49042c..65c4f2e4a 100644 --- a/src/com/android/camera/ui/ZoomView.java +++ b/src/com/android/camera/ui/ZoomView.java @@ -47,35 +47,58 @@ public class ZoomView extends ImageView { private DecodePartialBitmap mPartialDecodingTask; private Uri mUri; + private int mOrientation; private class DecodePartialBitmap extends AsyncTask<RectF, Void, Bitmap> { @Override protected Bitmap doInBackground(RectF... params) { RectF endRect = params[0]; + + // Calculate the rotation matrix to apply orientation on the original image + // rect. + RectF fullResRect = new RectF(0, 0, mFullResImageWidth - 1, mFullResImageHeight - 1); + Matrix rotationMatrix = new Matrix(); + rotationMatrix.setRotate(mOrientation, 0, 0); + rotationMatrix.mapRect(fullResRect); + // Set the translation of the matrix so that after rotation, the top left + // of the image rect is at (0, 0) + rotationMatrix.postTranslate(-fullResRect.left, -fullResRect.top); + rotationMatrix.mapRect(fullResRect, new RectF(0, 0, mFullResImageWidth - 1, + mFullResImageHeight - 1)); + // Find intersection with the screen RectF visibleRect = new RectF(endRect); visibleRect.intersect(0, 0, mViewportWidth - 1, mViewportHeight - 1); + // Calculate the mapping (i.e. transform) between current low res rect + // and full res image rect, and apply the mapping on current visible rect + // to find out the partial region in the full res image that we need + // to decode. + Matrix mapping = new Matrix(); + mapping.setRectToRect(endRect, fullResRect, Matrix.ScaleToFit.CENTER); + RectF visibleAfterRotation = new RectF(); + mapping.mapRect(visibleAfterRotation, visibleRect); - Matrix m2 = new Matrix(); - m2.setRectToRect(endRect, new RectF(0, 0, mFullResImageWidth - 1, - mFullResImageHeight - 1), Matrix.ScaleToFit.CENTER); + // Now the visible region we have is rotated, we need to reverse the + // rotation to find out the region in the original image RectF visibleInImage = new RectF(); - m2.mapRect(visibleInImage, visibleRect); + Matrix invertRotation = new Matrix(); + rotationMatrix.invert(invertRotation); + invertRotation.mapRect(visibleInImage, visibleAfterRotation); // Decode region - Rect v = new Rect(); - visibleInImage.round(v); + Rect region = new Rect(); + visibleInImage.round(region); // Make sure region to decode is inside the image. - v.intersect(0, 0, mFullResImageWidth - 1, mFullResImageHeight - 1); + region.intersect(0, 0, mFullResImageWidth - 1, mFullResImageHeight - 1); if (isCancelled()) { return null; } BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = getSampleFactor(v.width(), v.height()); + options.inSampleSize = getSampleFactor(region.width(), region.height()); if (mRegionDecoder == null) { InputStream is = getInputStream(); try { @@ -88,8 +111,13 @@ public class ZoomView extends ImageView { if (mRegionDecoder == null) { return null; } - Bitmap b = mRegionDecoder.decodeRegion(v, options); - return b; + Bitmap b = mRegionDecoder.decodeRegion(region, options); + if (isCancelled()) { + return null; + } + Matrix rotation = new Matrix(); + rotation.setRotate(mOrientation); + return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), rotation, false); } @Override @@ -120,9 +148,10 @@ public class ZoomView extends ImageView { }); } - public void loadBitmap(Uri uri, RectF imageRect) { + public void loadBitmap(Uri uri, int orientation, RectF imageRect) { if (!uri.equals(mUri)) { mUri = uri; + mOrientation = orientation; mFullResImageHeight = 0; mFullResImageWidth = 0; decodeImageSize(); |