summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java34
-rw-r--r--src/com/android/camera/data/LocalDataList.java101
-rw-r--r--src/com/android/camera/data/LocalMediaData.java6
-rw-r--r--src/com/android/camera/data/SimpleViewData.java5
-rw-r--r--src/com/android/camera/ui/FilmStripView.java111
-rw-r--r--src/com/android/camera/ui/ZoomView.java51
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();