summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorWilhelm Fitzpatrick <rafial@cyngn.com>2014-11-14 16:31:35 -0800
committerSteve Kondik <steve@cyngn.com>2016-11-02 12:24:52 -0700
commitc76e6d8a2768487407fd5db3d1e917bc35f22c80 (patch)
tree0b8c0e1d21e79dc90560f8b4d8f49724fb39d84d /src/com
parente4351681fcb22d2b08562bf14ebeb32573e41d7f (diff)
downloadandroid_packages_apps_Snap-c76e6d8a2768487407fd5db3d1e917bc35f22c80.tar.gz
android_packages_apps_Snap-c76e6d8a2768487407fd5db3d1e917bc35f22c80.tar.bz2
android_packages_apps_Snap-c76e6d8a2768487407fd5db3d1e917bc35f22c80.zip
CameraNext: dynamically generate available photo resolutions
Instead of depending on a large matching table, generated the list of picture sizes to show to the user directly from the supported list returned by the camera. The list is filtered to remove uselessly small resolutions on modern devices, to group resolutions by aspect ratio, and to filter out fairly similar sizes. Change-Id: I47a67a89786543baec133cf7e71df9819793ebac
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/camera/CameraSettings.java226
-rw-r--r--src/com/android/camera/SettingsManager.java14
-rw-r--r--src/com/android/camera/util/MultiMap.java45
3 files changed, 259 insertions, 26 deletions
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
index 70bba97c8..90d81a3cb 100644
--- a/src/com/android/camera/CameraSettings.java
+++ b/src/com/android/camera/CameraSettings.java
@@ -38,9 +38,12 @@ import android.util.Log;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GcamHelper;
+import com.android.camera.util.MultiMap;
+
import org.codeaurora.snapcam.R;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import android.os.Build;
@@ -444,26 +447,41 @@ public class CameraSettings {
return supported.get(0);
}
- public static void initialCameraPictureSize(
- Context context, Parameters parameters) {
- // When launching the camera app first time, we will set the picture
- // size to the first one in the list defined in "arrays.xml" and is also
- // supported by the driver.
+ public static void initialCameraPictureSize(Context context, Parameters parameters) {
+ // set the picture size to the largest supported size
List<Size> supported = parameters.getSupportedPictureSizes();
- if (supported == null) return;
- for (String candidate : context.getResources().getStringArray(
- R.array.pref_camera_picturesize_entryvalues)) {
- if (setCameraPictureSize(candidate, supported, parameters)) {
- SharedPreferences.Editor editor = ComboPreferences
- .get(context).edit();
- editor.putString(KEY_PICTURE_SIZE, candidate);
- editor.apply();
- return;
- }
+ if (supported == null || supported.isEmpty()) { return; }
+ Size largest = getLargestSize(supported);
+ String candidate = largest.width + "x" + largest.height;
+ if (setCameraPictureSize(candidate, supported, parameters)) {
+ SharedPreferences.Editor editor =
+ ComboPreferences.get(context).edit();
+ editor.putString(KEY_PICTURE_SIZE, candidate);
+ editor.apply();
+ return;
}
Log.e(TAG, "No supported picture size found");
}
+ private static Size getLargestSize(List<Size> sizes) {
+ if (sizes == null || sizes.isEmpty()) {
+ return null;
+ }
+
+ Size max = sizes.get(0);
+ int maxSize = max.width * max.height;
+
+ for (Size candidate : sizes) {
+ int candidateSize = candidate.width * candidate.height;
+ if (candidateSize > maxSize) {
+ maxSize = candidateSize;
+ max = candidate;
+ }
+ }
+
+ return max;
+ }
+
public static void removePreferenceFromScreen(
PreferenceGroup group, String key) {
removePreference(group, key);
@@ -1014,9 +1032,10 @@ public class CameraSettings {
}
if (pictureSize != null) {
- filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
- mParameters.getSupportedPictureSizes()));
- filterSimilarPictureSize(group, pictureSize);
+ formatPictureSizes(pictureSize,
+ fromLegacySizes(mParameters.getSupportedPictureSizes()), mContext);
+ resetIfInvalid(pictureSize);
+
}
if (whiteBalance != null) {
filterUnsupportedOptions(group,
@@ -1218,7 +1237,7 @@ public class CameraSettings {
resetIfInvalid(pref);
}
- private static void resetIfInvalid(ListPreference pref) {
+ static void resetIfInvalid(ListPreference pref) {
// Set the value to the first entry if it is invalid.
String value = pref.getValue();
if (pref.findIndexOfValue(value) == NOT_FOUND) {
@@ -1557,4 +1576,173 @@ public class CameraSettings {
}
return ret;
}
+
+ // common aspect ratios used for bucketing camera picture sizes
+ private enum AspectRatio {
+ FourThree(1.27, 1.42, R.string.pref_camera_aspectratio_43),
+ ThreeTwo(1.42, 1.55, R.string.pref_camera_aspectratio_32),
+ SixteenTen(1.55, 1.63, R.string.pref_camera_aspectratio_1610),
+ FiveThree(1.63, 1.73, R.string.pref_camera_aspectratio_53),
+ SixteenNine(1.73, 1.81, R.string.pref_camera_aspectratio_169),
+ OneOne(0.95, 1.05, R.string.pref_camera_aspectratio_11),
+ Wide(1.81, Float.MAX_VALUE, R.string.pref_camera_aspectratio_wide),
+ Other(Float.MIN_VALUE, 1.27, 0);
+
+ public double min;
+ public double max;
+ public int resourceId;
+
+ AspectRatio(double min, double max, int rid) {
+ this.min = min;
+ this.max = max;
+ this.resourceId = rid;
+ }
+
+ boolean contains(double ratio) {
+ return (ratio >= min) && (ratio < max);
+ }
+
+ static AspectRatio of(int width, int height) {
+ double ratio = ((double)width)/height;
+ for(AspectRatio aspect : values()) {
+ if(aspect.contains(ratio)) { return aspect; }
+ }
+ return null;
+ }
+ }
+
+ // track a camera picture size along with some derived info
+ private static class SizeEntry implements Comparable<SizeEntry> {
+ AspectRatio ratio;
+ android.util.Size size;
+ int pixels;
+
+ SizeEntry(android.util.Size size) {
+ this.ratio = AspectRatio.of(size.getWidth(), size.getHeight());
+ this.size = size;
+ this.pixels = size.getWidth() * size.getHeight();
+ }
+
+ @Override
+ public int compareTo(SizeEntry another) {
+ return another.pixels - pixels;
+ }
+
+ String resolution() { return size.getWidth()+"x"+size.getHeight(); }
+
+ String formatted(Context ctx) {
+ double pixelCount = pixels/1000000d; // compute megapixels
+
+ if(pixelCount > 1.9 && pixelCount < 2.0) { //conventional rounding
+ pixelCount = 2.0;
+ }
+
+ pixelCount = adjustForLocale(pixelCount); // some locales group differently
+
+ if(pixelCount > 0.1) {
+ String ratioString = ratio.resourceId == 0
+ ? resolution() : ctx.getString(ratio.resourceId);
+ return ctx.getString(R.string.pref_camera_megapixel_format,
+ ratioString, pixelCount);
+ } else {
+ // for super tiny stuff, just give the raw resolution
+ return resolution();
+ }
+ }
+
+ private static final String KOREAN = Locale.KOREAN.getLanguage();
+ private static final String CHINESE = Locale.CHINESE.getLanguage();
+
+ private double adjustForLocale(double megapixels) {
+ Locale locale = Locale.getDefault();
+ if(locale == null) { return megapixels; }
+ String language = locale.getLanguage();
+ // chinese and korean locales perfer to count by ten thousands
+ // instead of by millions - so we multiply the megapixels by 100
+ // (with a little rounding on the way)
+ if(KOREAN.equals(language) || CHINESE.equals(language)) {
+ megapixels = Math.round(megapixels * 10);
+ return megapixels * 10; // wàn
+ }
+ return megapixels;
+ }
+ }
+
+ static List<android.util.Size> fromLegacySizes(List<Size> oldSizes) {
+ final List<android.util.Size> sizes = new ArrayList<>();
+ if (oldSizes == null || oldSizes.size() == 0) {
+ return sizes;
+ }
+ for (Size oldSize : oldSizes) {
+ sizes.add(new android.util.Size(oldSize.width, oldSize.height));
+ }
+ return sizes;
+ }
+
+ static void formatPictureSizes(
+ ListPreference pictureSize, List<android.util.Size> supported, Context ctx) {
+ if(supported == null || supported.isEmpty()) { return; }
+
+ // convert list of sizes to list of "size entries"
+ List<SizeEntry> sizes = new ArrayList<SizeEntry>(supported.size());
+ for (android.util.Size candidate : supported) { sizes.add(new SizeEntry(candidate)); }
+
+ // sort descending by pixel size
+ Collections.sort(sizes);
+
+ // trim really small sizes but never leave less than six choices
+ int minimum = ctx.getResources().getInteger(R.integer.minimum_picture_size);
+ while(sizes.size() > 6) {
+ int lastIndex = sizes.size()-1;
+ SizeEntry last = sizes.get(lastIndex);
+ if(last.pixels < minimum) { sizes.remove(lastIndex); }
+ else { break; }
+ }
+ // re-sort into aspect ratio buckets
+ MultiMap<AspectRatio,SizeEntry> buckets = new MultiMap<AspectRatio,SizeEntry>();
+ for(SizeEntry size : sizes) { buckets.put(size.ratio, size); }
+
+ // build the final lists - group by aspect ratio, with those
+ // buckets having the largest image sizes positioned first
+ List<String> entries = new ArrayList<String>(buckets.size());
+ List<String> entryValues = new ArrayList<String>(buckets.size());
+ while(!buckets.isEmpty()) {
+ // find the bucket with the largest first element
+ int maxSize = 0;
+ AspectRatio chosenKey = null;
+ for(AspectRatio ratio : buckets.keySet()) {
+ int size = buckets.get(ratio).get(0).pixels;
+ if(size > maxSize) {
+ maxSize = size;
+ chosenKey = ratio;
+ }
+ }
+
+ List<SizeEntry> bucket = buckets.remove(chosenKey);
+
+ // trim chosen bucket of similarly sized entries, but
+ // never leave less that three
+ int index = 0;
+ while(bucket.size() > 3 && bucket.size() > index+1) {
+ SizeEntry current = bucket.get(index);
+ SizeEntry next = bucket.get(index+1);
+ // if the two buckets differ in size by less than 30%
+ // remove the smaller one, otherwise advance through the list
+ if(((double)current.pixels)/next.pixels < 1.3) {
+ bucket.remove(index+1);
+ } else {
+ index++;
+ }
+ }
+
+ // transfer chosen, trimmed bucket to final list
+ for (SizeEntry size : bucket) {
+ entryValues.add(size.resolution());
+ entries.add(size.formatted(ctx));
+ }
+ }
+
+ pictureSize.setEntries(entries.toArray(new String[entries.size()]));
+ pictureSize.setEntryValues(entryValues.toArray(new String[entryValues.size()]));
+ }
}
diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java
index 374c6eb2b..7ac4257ee 100644
--- a/src/com/android/camera/SettingsManager.java
+++ b/src/com/android/camera/SettingsManager.java
@@ -517,9 +517,9 @@ public class SettingsManager implements ListMenu.SettingsListener {
if (cameraIdPref != null) buildCameraId();
if (pictureSize != null) {
- CameraSettings.filterUnsupportedOptions(mPreferenceGroup,
- pictureSize, getSupportedPictureSize(cameraId));
- CameraSettings.filterSimilarPictureSize(mPreferenceGroup, pictureSize);
+ CameraSettings.formatPictureSizes(pictureSize,
+ getSupportedPictureSize(cameraId), mContext);
+ CameraSettings.resetIfInvalid(pictureSize);
}
if (exposure != null) buildExposureCompensation(cameraId);
@@ -836,21 +836,21 @@ public class SettingsManager implements ListMenu.SettingsListener {
mValuesMap.get(KEY_FLASH_MODE) != null;
}
- private List<String> getSupportedPictureSize(int cameraId) {
+ private List<Size> getSupportedPictureSize(int cameraId) {
StreamConfigurationMap map = mCharacteristics.get(cameraId).get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
- List<String> res = new ArrayList<>();
+ List<Size> res = new ArrayList<>();
if (sizes != null) {
for (int i = 0; i < sizes.length; i++) {
- res.add(sizes[i].toString());
+ res.add(sizes[i]);
}
}
Size[] highResSizes = map.getHighResolutionOutputSizes(ImageFormat.JPEG);
if (highResSizes != null) {
for (int i = 0; i < highResSizes.length; i++) {
- res.add(highResSizes[i].toString());
+ res.add(highResSizes[i]);
}
}
diff --git a/src/com/android/camera/util/MultiMap.java b/src/com/android/camera/util/MultiMap.java
new file mode 100644
index 000000000..b2e9003fe
--- /dev/null
+++ b/src/com/android/camera/util/MultiMap.java
@@ -0,0 +1,45 @@
+package com.android.camera.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class MultiMap<K,V> {
+ Map<K,List<V>> map = new HashMap<K,List<V>>();
+
+ public void put(K key, V value) {
+ List<V> l = map.get(key);
+ if(l == null) {
+ l = new ArrayList<V>();
+ map.put(key, l);
+ }
+ l.add(value);
+ }
+
+ public List<V> get(K key) {
+ List<V> l = map.get(key);
+ if(l == null) { return Collections.emptyList(); }
+ else return l;
+ }
+
+ public List<V> remove(K key) {
+ List<V> l = map.remove(key);
+ if(l == null) { return Collections.emptyList(); }
+ else return l;
+ }
+
+ public Set<K> keySet() { return map.keySet(); }
+
+ public int size() {
+ int total = 0;
+ for(List<V> l : map.values()) {
+ total += l.size();
+ }
+ return total;
+ }
+
+ public boolean isEmpty() { return map.isEmpty(); }
+} \ No newline at end of file