Allow multiple aspect ratios to be set for the
resolution setting.
Bug: 13328191
Change-Id: Ie067d18b70bc1ae84dc284f881b7f6030f6a6622
diff --git a/src/com/android/camera/settings/CameraSettingsActivity.java b/src/com/android/camera/settings/CameraSettingsActivity.java
index f1e0ec4..4f1b0af 100644
--- a/src/com/android/camera/settings/CameraSettingsActivity.java
+++ b/src/com/android/camera/settings/CameraSettingsActivity.java
@@ -84,8 +84,10 @@
private String[] mCamcorderProfileNames;
// Selected resolutions for the different cameras and sizes.
- private SelectedPictureSizes mPictureSizesBack;
- private SelectedPictureSizes mPictureSizesFront;
+ private SelectedPictureSizes mOldPictureSizesBack;
+ private SelectedPictureSizes mOldPictureSizesFront;
+ private List<Size> mPictureSizesBack;
+ private List<Size> mPictureSizesFront;
private SelectedVideoQualities mVideoQualitiesBack;
private SelectedVideoQualities mVideoQualitiesFront;
@@ -284,9 +286,9 @@
ListPreference listPreference = (ListPreference) preference;
if (listPreference.getKey().equals(SettingsManager.KEY_PICTURE_SIZE_BACK)) {
- setSummaryForSelection(mPictureSizesBack, listPreference);
+ setSummaryForSelection(mOldPictureSizesBack, mPictureSizesBack, listPreference);
} else if (listPreference.getKey().equals(SettingsManager.KEY_PICTURE_SIZE_FRONT)) {
- setSummaryForSelection(mPictureSizesFront, listPreference);
+ setSummaryForSelection(mOldPictureSizesFront, mPictureSizesFront, listPreference);
} else if (listPreference.getKey().equals(SettingsManager.KEY_VIDEO_QUALITY_BACK)) {
setSummaryForSelection(mVideoQualitiesBack, listPreference);
} else if (listPreference.getKey().equals(SettingsManager.KEY_VIDEO_QUALITY_FRONT)) {
@@ -303,23 +305,21 @@
* choose from.
* @param preference The preference to set the entries for.
*/
- private void setEntriesForSelection(SelectedPictureSizes selectedSizes,
+ private void setEntriesForSelection(List<Size> selectedSizes,
ListPreference preference) {
if (selectedSizes == null) {
return;
}
- // Avoid adding double entries at the bottom of the list which
- // indicates that not at least 3 sizes are supported.
- ArrayList<String> entries = new ArrayList<String>();
- entries.add(getSizeSummaryString(selectedSizes.large));
- if (selectedSizes.medium != selectedSizes.large) {
- entries.add(getSizeSummaryString(selectedSizes.medium));
+ String[] entries = new String[selectedSizes.size()];
+ String[] entryValues = new String[selectedSizes.size()];
+ for (int i = 0; i < selectedSizes.size(); i++) {
+ Size size = selectedSizes.get(i);
+ entries[i] = getSizeSummaryString(size);
+ entryValues[i] = SettingsUtil.sizeToSetting(size);
}
- if (selectedSizes.small != selectedSizes.medium) {
- entries.add(getSizeSummaryString(selectedSizes.small));
- }
- preference.setEntries(entries.toArray(new String[0]));
+ preference.setEntries(entries);
+ preference.setEntryValues(entryValues);
}
/**
@@ -351,16 +351,19 @@
/**
* Sets the summary for the given list preference.
*
- * @param selectedSizes The selected picture sizes.
+ * @param oldPictureSizes The old selected picture sizes for small medium and large
+ * @param displayableSizes The human readable preferred sizes
* @param preference The preference for which to set the summary.
*/
- private void setSummaryForSelection(SelectedPictureSizes selectedSizes,
- ListPreference preference) {
- if (selectedSizes == null) {
+ private void setSummaryForSelection(SelectedPictureSizes oldPictureSizes,
+ List<Size> displayableSizes, ListPreference preference) {
+ if (oldPictureSizes == null) {
return;
}
- Size selectedSize = selectedSizes.getFromSetting(preference.getValue());
+ String setting = preference.getValue();
+ Size selectedSize = oldPictureSizes.getFromSetting(setting, displayableSizes);
+
preference.setSummary(getSizeSummaryString(selectedSize));
}
@@ -390,17 +393,13 @@
// Back camera.
int backCameraId = getCameraId(CameraInfo.CAMERA_FACING_BACK);
if (backCameraId >= 0) {
- // Check whether we cached the sizes:
- mPictureSizesBack = SettingsUtil.getSelectedCameraPictureSizes(null, backCameraId);
- if (mPictureSizesBack == null) {
- Camera backCamera = Camera.open(backCameraId);
- if (backCamera != null) {
- List<Size> sizes = Size.buildListFromCameraSizes(
- backCamera.getParameters().getSupportedPictureSizes());
- backCamera.release();
- mPictureSizesBack = SettingsUtil.getSelectedCameraPictureSizes(sizes,
- backCameraId);
- }
+ Camera backCamera = Camera.open(backCameraId);
+ if (backCamera != null) {
+ List<Size> sizes = Size.buildListFromCameraSizes(backCamera.getParameters().getSupportedPictureSizes());
+ backCamera.release();
+ mOldPictureSizesBack = SettingsUtil.getSelectedCameraPictureSizes(sizes,
+ backCameraId);
+ mPictureSizesBack = ResolutionUtil.getDisplayableSizesFromSupported(sizes);
}
mVideoQualitiesBack = SettingsUtil.getSelectedVideoQualities(backCameraId);
} else {
@@ -411,17 +410,14 @@
// Front camera.
int frontCameraId = getCameraId(CameraInfo.CAMERA_FACING_FRONT);
if (frontCameraId >= 0) {
- mPictureSizesFront = SettingsUtil.getSelectedCameraPictureSizes(null,
- frontCameraId);
- if (mPictureSizesFront == null) {
- Camera frontCamera = Camera.open(frontCameraId);
- if (frontCamera != null) {
- List<Size> sizes = Size.buildListFromCameraSizes(
- frontCamera.getParameters().getSupportedPictureSizes());
- frontCamera.release();
- mPictureSizesFront = SettingsUtil.getSelectedCameraPictureSizes(sizes,
- frontCameraId);
- }
+ Camera frontCamera = Camera.open(frontCameraId);
+ if (frontCamera != null) {
+ List<Size> sizes = Size.buildListFromCameraSizes(frontCamera.getParameters().getSupportedPictureSizes());
+ frontCamera.release();
+ mOldPictureSizesFront= SettingsUtil.getSelectedCameraPictureSizes(sizes,
+ frontCameraId);
+ mPictureSizesFront =
+ ResolutionUtil.getDisplayableSizesFromSupported(sizes);
}
mVideoQualitiesFront = SettingsUtil.getSelectedVideoQualities(frontCameraId);
} else {
@@ -456,8 +452,9 @@
* picture size in megapixels.
*/
private String getSizeSummaryString(Size size) {
+ String aspectRatio = ResolutionUtil.aspectRatioDescription(size);
String megaPixels = sMegaPixelFormat.format((size.width() * size.height()) / 1e6);
- return getResources().getString(R.string.setting_summary_x_megapixels, megaPixels);
+ return "(" + aspectRatio + ") " + getResources().getString(R.string.setting_summary_x_megapixels, megaPixels);
}
}
}
diff --git a/src/com/android/camera/settings/ResolutionUtil.java b/src/com/android/camera/settings/ResolutionUtil.java
new file mode 100644
index 0000000..ba914bc
--- /dev/null
+++ b/src/com/android/camera/settings/ResolutionUtil.java
@@ -0,0 +1,253 @@
+/*
+ * 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.settings;
+
+import com.android.camera.util.Size;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class is used to help manage the many different resolutions available on
+ * the device. <br/>
+ * It allows you to specify which aspect ratios to offer the user, and then
+ * chooses which resolutions are the most pertinent to avoid overloading the
+ * user with so many options.
+ */
+public class ResolutionUtil {
+
+ /**
+ * These are the preferred aspect ratios for the settings. We will take HAL
+ * supported aspect ratios that are within RATIO_TOLERANCE of these values.
+ * We will also take the maximum supported resolution for full sensor image.
+ */
+ private static Float[] sDesiredAspectRatios = {
+ 16.0f / 9.0f, 4.0f / 3.0f
+ };
+
+ private static final float RATIO_TOLERANCE = .05f;
+
+ /**
+ * A resolution bucket holds a list of sizes that are of a given aspect
+ * ratio.
+ */
+ private static class ResolutionBucket {
+ public Float aspectRatio;
+ /**
+ * This is a sorted list of sizes, going from largest to smallest.
+ */
+ public List<Size> sizes = new LinkedList<Size>();
+ /**
+ * This is the head of the sizes array.
+ */
+ public Size largest;
+ /**
+ * This is the area of the largest size, used for sorting
+ * ResolutionBuckets.
+ */
+ public Integer maxPixels = 0;
+
+ /**
+ * Use this to add a new resolution to this bucket. It will insert it
+ * into the sizes array and update appropriate members.
+ *
+ * @param size the new size to be added
+ */
+ public void add(Size size) {
+ sizes.add(size);
+ Collections.sort(sizes, new Comparator<Size>() {
+ @Override
+ public int compare(Size size, Size size2) {
+ // sort area greatest to least
+ return Integer.compare(size2.width() * size2.height(),
+ size.width() * size.height());
+ }
+ });
+ maxPixels = sizes.get(0).width() * sizes.get(0).height();
+ }
+ }
+
+ /**
+ * Given a list of camera sizes, this uses some heuristics to decide which
+ * options to present to a user. It currently returns up to 3 sizes for each
+ * aspect ratio. The aspect ratios returned include the ones in
+ * sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
+ * that users can use a full-sensor size, as well as any of the preferred
+ * aspect ratios from above;
+ *
+ * @param sizes A super set of all sizes to be displayed
+ * @return The list of sizes to display grouped first by aspect ratio
+ * (sorted by maximum area), and sorted within aspect ratio by area)
+ */
+ public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes) {
+ List<ResolutionBucket> buckets = parseAvailableSizes(sizes);
+
+ List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
+ // We want to make sure we support the maximum pixel aspect ratio, even
+ // if it doesn't match a desired aspect ratio
+ sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
+
+ // Now go through the buckets from largest mp to smallest, adding
+ // desired ratios
+ for (ResolutionBucket bucket : buckets) {
+ Float aspectRatio = bucket.aspectRatio;
+ if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
+ && !sortedDesiredAspectRatios.contains(aspectRatio)) {
+ sortedDesiredAspectRatios.add(aspectRatio);
+ }
+ }
+
+ List<Size> result = new ArrayList<Size>(sizes.size());
+ for (Float targetRatio : sortedDesiredAspectRatios) {
+ for (ResolutionBucket bucket : buckets) {
+ Number aspectRatio = bucket.aspectRatio;
+ if (Math.abs(aspectRatio.floatValue() - targetRatio) <= RATIO_TOLERANCE) {
+ result.addAll(pickUpToThree(bucket.sizes));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get the area in pixels of a size.
+ *
+ * @param size the size to measure
+ * @return the area.
+ */
+ private static int area(Size size) {
+ if (size == null) {
+ return 0;
+ }
+ return size.width() * size.height();
+ }
+
+ /**
+ * Given a list of sizes of a similar aspect ratio, it tries to pick evenly
+ * spaced out options. It starts with the largest, then tries to find one at
+ * 50% of the last chosen size for the subsequent size.
+ *
+ * @param sizes A list of Sizes that are all of a similar aspect ratio
+ * @return A list of at least one, and no more than three representative
+ * sizes from the list.
+ */
+ private static List<Size> pickUpToThree(List<Size> sizes) {
+ List<Size> result = new ArrayList<Size>();
+ Size largest = sizes.get(0);
+ result.add(largest);
+ Size lastSize = largest;
+ for (Size size : sizes) {
+ double targetArea = Math.pow(.5, result.size()) * area(largest);
+ if (area(size) < targetArea) {
+ // This candidate is smaller than half the mega pixels of the
+ // last one. Let's see whether the previous size, or this size
+ // is closer to the desired target.
+ if (!result.contains(lastSize)
+ && (targetArea - area(lastSize) < area(size) - targetArea)) {
+ result.add(lastSize);
+ } else {
+ result.add(size);
+ }
+ }
+ lastSize = size;
+ if (result.size() == 3) {
+ break;
+ }
+ }
+
+ // If we have less than three, we can add the smallest size.
+ if (result.size() < 3 && !result.contains(lastSize)) {
+ result.add(lastSize);
+ }
+ return result;
+ }
+
+ /**
+ * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
+ * possible.
+ *
+ * @param aspectRatio the aspect ratio to fuzz
+ * @return the closest desiredAspectRatio within RATIO_TOLERANCE, or the
+ * original ratio
+ */
+ private static float fuzzAspectRatio(float aspectRatio) {
+ for (float desiredAspectRatio : sDesiredAspectRatios) {
+ if ((Math.abs(aspectRatio - desiredAspectRatio)) < RATIO_TOLERANCE) {
+ return desiredAspectRatio;
+ }
+ }
+ return aspectRatio;
+ }
+
+ /**
+ * This takes a bunch of supported sizes and buckets them by aspect ratio.
+ * The result is a list of buckets sorted by each bucket's largest area.
+ * They are sorted from largest to smallest. This will bucket aspect ratios
+ * that are close to the sDesiredAspectRatios in to the same bucket.
+ *
+ * @param sizes all supported sizes for a camera
+ * @return all of the sizes grouped by their closest aspect ratio
+ */
+ private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes) {
+ HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
+
+ for (Size size : sizes) {
+ Float aspectRatio = size.width() / (float) size.height();
+ // If this aspect ratio is close to a desired Aspect Ratio,
+ // fuzz it so that they are bucketed together
+ aspectRatio = fuzzAspectRatio(aspectRatio);
+ ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
+ if (bucket == null) {
+ bucket = new ResolutionBucket();
+ bucket.aspectRatio = aspectRatio;
+ aspectRatioToBuckets.put(aspectRatio, bucket);
+ }
+ bucket.add(size);
+ }
+ List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
+ aspectRatioToBuckets.values());
+ Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
+ @Override
+ public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
+ return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
+ }
+ });
+ return sortedBuckets;
+ }
+
+ /**
+ * Given a size, return a string describing the aspect ratio by reducing the
+ *
+ * @param size the size to describe
+ * @return a string description of the aspect ratio
+ */
+ public static String aspectRatioDescription(Size size) {
+ BigInteger width = BigInteger.valueOf(size.width());
+ BigInteger height = BigInteger.valueOf(size.height());
+ BigInteger gcd = width.gcd(height);
+ int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
+ int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
+
+ return numerator + "x" + denominator;
+ }
+}
diff --git a/src/com/android/camera/settings/SettingsManager.java b/src/com/android/camera/settings/SettingsManager.java
index 3e2e6c3..3a48af2 100644
--- a/src/com/android/camera/settings/SettingsManager.java
+++ b/src/com/android/camera/settings/SettingsManager.java
@@ -960,16 +960,14 @@
}
public static Setting getPictureSizeBackSetting(Context context) {
String defaultValue = null;
- String[] values = context.getResources().getStringArray(
- R.array.pref_camera_picturesize_entryvalues);
+ String[] values = null;
return new Setting(SOURCE_DEFAULT, TYPE_STRING, defaultValue, KEY_PICTURE_SIZE_BACK,
values, FLUSH_OFF);
}
public static Setting getPictureSizeFrontSetting(Context context) {
String defaultValue = null;
- String[] values = context.getResources().getStringArray(
- R.array.pref_camera_picturesize_entryvalues);
+ String[] values = null;
return new Setting(SOURCE_DEFAULT, TYPE_STRING, defaultValue, KEY_PICTURE_SIZE_FRONT,
values, FLUSH_OFF);
}
diff --git a/src/com/android/camera/settings/SettingsUtil.java b/src/com/android/camera/settings/SettingsUtil.java
index b14f5e2..db37c9a 100644
--- a/src/com/android/camera/settings/SettingsUtil.java
+++ b/src/com/android/camera/settings/SettingsUtil.java
@@ -45,20 +45,35 @@
public Size medium;
public Size small;
- public Size getFromSetting(String sizeSetting) {
- // Sanitize the value to be either small, medium or large. Default
- // to the latter.
- if (!SIZE_SMALL.equals(sizeSetting) && !SIZE_MEDIUM.equals(sizeSetting)) {
- sizeSetting = SIZE_LARGE;
- }
-
+ /**
+ * This takes a string preference describing the desired resolution and
+ * returns the camera size it represents. <br/>
+ * It supports historical values of SIZE_LARGE, SIZE_MEDIUM, and
+ * SIZE_SMALL as well as resolutions separated by an x i.e. "1024x576" <br/>
+ * If it fails to parse the string, it will return the old SIZE_LARGE
+ * value.
+ *
+ * @param sizeSetting the preference string to convert to a size
+ * @param supportedSizes all possible camera sizes that are supported
+ * @return the size that this setting represents
+ */
+ public Size getFromSetting(String sizeSetting, List<Size> supportedSizes) {
if (SIZE_LARGE.equals(sizeSetting)) {
return large;
} else if (SIZE_MEDIUM.equals(sizeSetting)) {
return medium;
- } else {
+ } else if (SIZE_SMALL.equals(sizeSetting)) {
return small;
+ } else if (sizeSetting != null && sizeSetting.split("x").length == 2) {
+ String[] parts = sizeSetting.split("x");
+ for (Size size : supportedSizes) {
+ if (size.width() == Integer.valueOf(parts[0]) &&
+ size.height() == Integer.valueOf(parts[1])) {
+ return size;
+ }
+ }
}
+ return large;
}
@Override
@@ -159,7 +174,8 @@
*/
private static Size getCameraPictureSize(String sizeSetting, List<Size> supported,
int cameraId) {
- return getSelectedCameraPictureSizes(supported, cameraId).getFromSetting(sizeSetting);
+ return getSelectedCameraPictureSizes(supported, cameraId).getFromSetting(sizeSetting,
+ supported);
}
/**
@@ -349,6 +365,16 @@
}
/**
+ * This is used to serialize a size to a string for storage in settings
+ *
+ * @param size The size to serialize.
+ * @return the string to be saved in preferences
+ */
+ public static String sizeToSetting(Size size) {
+ return ((Integer) size.width()).toString() + "x" + ((Integer) size.height()).toString();
+ }
+
+ /**
* Determines and returns the capabilities of the given camera.
*/
public static SettingsCapabilities