2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.camera.settings;
19 import android.content.Context;
20 import android.util.DisplayMetrics;
21 import android.view.WindowManager;
23 import com.android.camera.exif.Rational;
24 import com.android.camera.util.AndroidServices;
25 import com.android.camera.util.ApiHelper;
26 import com.android.camera.util.Size;
28 import com.google.common.collect.Lists;
30 import java.math.BigInteger;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.LinkedList;
38 import java.util.List;
41 import javax.annotation.Nonnull;
42 import javax.annotation.ParametersAreNonnullByDefault;
46 * This class is used to help manage the many different resolutions available on
48 * It allows you to specify which aspect ratios to offer the user, and then
49 * chooses which resolutions are the most pertinent to avoid overloading the
50 * user with so many options.
52 public class ResolutionUtil {
54 * Different aspect ratio constants.
56 public static final Rational ASPECT_RATIO_16x9 = new Rational(16, 9);
57 public static final Rational ASPECT_RATIO_4x3 = new Rational(4, 3);
58 private static final double ASPECT_RATIO_TOLERANCE = 0.05;
60 public static final String NEXUS_5_LARGE_16_BY_9 = "1836x3264";
61 public static final float NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO = 16f / 9f;
62 public static Size NEXUS_5_LARGE_16_BY_9_SIZE = new Size(3264, 1836);
65 * These are the preferred aspect ratios for the settings. We will take HAL
66 * supported aspect ratios that are within ASPECT_RATIO_TOLERANCE of these values.
67 * We will also take the maximum supported resolution for full sensor image.
69 private static Float[] sDesiredAspectRatios = {
70 16.0f / 9.0f, 4.0f / 3.0f
73 private static Size[] sDesiredAspectRatioSizes = {
74 new Size(16, 9), new Size(4, 3)
78 * A resolution bucket holds a list of sizes that are of a given aspect
81 private static class ResolutionBucket {
82 public Float aspectRatio;
84 * This is a sorted list of sizes, going from largest to smallest.
86 public List<Size> sizes = new LinkedList<Size>();
88 * This is the head of the sizes array.
92 * This is the area of the largest size, used for sorting
95 public Integer maxPixels = 0;
98 * Use this to add a new resolution to this bucket. It will insert it
99 * into the sizes array and update appropriate members.
101 * @param size the new size to be added
103 public void add(Size size) {
105 Collections.sort(sizes, new Comparator<Size>() {
107 public int compare(Size size, Size size2) {
108 // sort area greatest to least
109 return Integer.compare(size2.width() * size2.height(),
110 size.width() * size.height());
113 maxPixels = sizes.get(0).width() * sizes.get(0).height();
118 * Given a list of camera sizes, this uses some heuristics to decide which
119 * options to present to a user. It currently returns up to 3 sizes for each
120 * aspect ratio. The aspect ratios returned include the ones in
121 * sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
122 * that users can use a full-sensor size, as well as any of the preferred
123 * aspect ratios from above;
125 * @param sizes A super set of all sizes to be displayed
126 * @param isBackCamera true if these are sizes for the back camera
127 * @return The list of sizes to display grouped first by aspect ratio
128 * (sorted by maximum area), and sorted within aspect ratio by area)
130 public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera) {
131 List<ResolutionBucket> buckets = parseAvailableSizes(sizes, isBackCamera);
133 List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
134 // We want to make sure we support the maximum pixel aspect ratio, even
135 // if it doesn't match a desired aspect ratio
136 sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
138 // Now go through the buckets from largest mp to smallest, adding
140 for (ResolutionBucket bucket : buckets) {
141 Float aspectRatio = bucket.aspectRatio;
142 if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
143 && !sortedDesiredAspectRatios.contains(aspectRatio)) {
144 sortedDesiredAspectRatios.add(aspectRatio);
148 List<Size> result = new ArrayList<Size>(sizes.size());
149 for (Float targetRatio : sortedDesiredAspectRatios) {
150 for (ResolutionBucket bucket : buckets) {
151 Number aspectRatio = bucket.aspectRatio;
152 if (Math.abs(aspectRatio.floatValue() - targetRatio) <= ASPECT_RATIO_TOLERANCE) {
153 result.addAll(pickUpToThree(bucket.sizes));
161 * Get the area in pixels of a size.
163 * @param size the size to measure
166 private static int area(Size size) {
170 return size.width() * size.height();
174 * Given a list of sizes of a similar aspect ratio, it tries to pick evenly
175 * spaced out options. It starts with the largest, then tries to find one at
176 * 50% of the last chosen size for the subsequent size.
178 * @param sizes A list of Sizes that are all of a similar aspect ratio
179 * @return A list of at least one, and no more than three representative
180 * sizes from the list.
182 private static List<Size> pickUpToThree(List<Size> sizes) {
183 List<Size> result = new ArrayList<Size>();
184 Size largest = sizes.get(0);
186 Size lastSize = largest;
187 for (Size size : sizes) {
188 double targetArea = Math.pow(.5, result.size()) * area(largest);
189 if (area(size) < targetArea) {
190 // This candidate is smaller than half the mega pixels of the
191 // last one. Let's see whether the previous size, or this size
192 // is closer to the desired target.
193 if (!result.contains(lastSize)
194 && (targetArea - area(lastSize) < area(size) - targetArea)) {
195 result.add(lastSize);
201 if (result.size() == 3) {
206 // If we have less than three, we can add the smallest size.
207 if (result.size() < 3 && !result.contains(lastSize)) {
208 result.add(lastSize);
214 * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
217 * @param aspectRatio the aspect ratio to fuzz
218 * @return the closest desiredAspectRatio within ASPECT_RATIO_TOLERANCE, or the
221 private static float fuzzAspectRatio(float aspectRatio) {
222 for (float desiredAspectRatio : sDesiredAspectRatios) {
223 if ((Math.abs(aspectRatio - desiredAspectRatio)) < ASPECT_RATIO_TOLERANCE) {
224 return desiredAspectRatio;
231 * This takes a bunch of supported sizes and buckets them by aspect ratio.
232 * The result is a list of buckets sorted by each bucket's largest area.
233 * They are sorted from largest to smallest. This will bucket aspect ratios
234 * that are close to the sDesiredAspectRatios in to the same bucket.
236 * @param sizes all supported sizes for a camera
237 * @param isBackCamera true if these are sizes for the back camera
238 * @return all of the sizes grouped by their closest aspect ratio
240 private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes, boolean isBackCamera) {
241 HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
243 for (Size size : sizes) {
244 Float aspectRatio = (float) size.getWidth() / (float) size.getHeight();
245 // If this aspect ratio is close to a desired Aspect Ratio,
246 // fuzz it so that they are bucketed together
247 aspectRatio = fuzzAspectRatio(aspectRatio);
248 ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
249 if (bucket == null) {
250 bucket = new ResolutionBucket();
251 bucket.aspectRatio = aspectRatio;
252 aspectRatioToBuckets.put(aspectRatio, bucket);
256 if (ApiHelper.IS_NEXUS_5 && isBackCamera) {
257 aspectRatioToBuckets.get(16 / 9.0f).add(NEXUS_5_LARGE_16_BY_9_SIZE);
259 List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
260 aspectRatioToBuckets.values());
261 Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
263 public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
264 return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
267 return sortedBuckets;
271 * Given a size, return a string describing the aspect ratio by reducing the
273 * @param size the size to describe
274 * @return a string description of the aspect ratio
276 public static String aspectRatioDescription(Size size) {
277 Size aspectRatio = reduce(size);
278 return aspectRatio.width() + "x" + aspectRatio.height();
282 * Reduce an aspect ratio to its lowest common denominator. The ratio of the
283 * input and output sizes is guaranteed to be the same.
285 * @param aspectRatio the aspect ratio to reduce
286 * @return The reduced aspect ratio which may equal the original.
288 public static Size reduce(Size aspectRatio) {
289 BigInteger width = BigInteger.valueOf(aspectRatio.width());
290 BigInteger height = BigInteger.valueOf(aspectRatio.height());
291 BigInteger gcd = width.gcd(height);
292 int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
293 int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
294 return new Size(numerator, denominator);
298 * Given a size return the numerator of its aspect ratio
300 * @param size the size to measure
301 * @return the numerator
303 public static int aspectRatioNumerator(Size size) {
304 Size aspectRatio = reduce(size);
305 return aspectRatio.width();
309 * Given a size, return the closest aspect ratio that falls close to the
312 * @param size the size to approximate
313 * @return the closest desired aspect ratio, or the original aspect ratio if
314 * none were close enough
316 public static Size getApproximateSize(Size size) {
317 Size aspectRatio = reduce(size);
318 float fuzzy = fuzzAspectRatio(size.width() / (float) size.height());
319 int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
321 aspectRatio = sDesiredAspectRatioSizes[index];
327 * Given a size return the numerator of its aspect ratio
330 * @return the denominator
332 public static int aspectRatioDenominator(Size size) {
333 BigInteger width = BigInteger.valueOf(size.width());
334 BigInteger height = BigInteger.valueOf(size.height());
335 BigInteger gcd = width.gcd(height);
336 int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
341 * Returns the aspect ratio for the given size.
343 * @param size The given size.
344 * @return A {@link Rational} which represents the aspect ratio.
346 public static Rational getAspectRatio(Size size) {
347 int width = size.getWidth();
348 int height = size.getHeight();
349 int numerator = width;
350 int denominator = height;
351 if (height > width) {
355 return new Rational(numerator, denominator);
358 public static boolean hasSameAspectRatio(Rational ar1, Rational ar2) {
359 return Math.abs(ar1.toDouble() - ar2.toDouble()) < ASPECT_RATIO_TOLERANCE;
363 * Selects the maximal resolution for the given desired aspect ratio from all available
364 * resolutions. If no resolution exists for the desired aspect ratio, return a resolution
365 * with the maximum number of pixels.
367 * @param desiredAspectRatio The desired aspect ratio.
368 * @param sizes All available resolutions.
369 * @return The maximal resolution for desired aspect ratio ; if no sizes are found, then
370 * return size of (0,0)
372 public static Size getLargestPictureSize(Rational desiredAspectRatio, List<Size> sizes) {
373 int maxPixelNumNoAspect = 0;
374 Size maxSize = new Size(0, 0);
376 // Fix for b/21758681
377 // Do first pass with the candidate with closest size, regardless of aspect ratio,
378 // to loosen the requirement of valid preview sizes. As long as one size exists
379 // in the list, we should pass back a valid size.
380 for (Size size : sizes) {
381 int pixelNum = size.getWidth() * size.getHeight();
382 if (pixelNum > maxPixelNumNoAspect) {
383 maxPixelNumNoAspect = pixelNum;
388 // With second pass, override first pass with the candidate with closest
389 // size AND similar aspect ratio. If there are no valid candidates are found
390 // in the second pass, take the candidate from the first pass.
391 int maxPixelNumWithAspect = 0;
392 for (Size size : sizes) {
393 Rational aspectRatio = getAspectRatio(size);
394 // Skip if the aspect ratio is not desired.
395 if (!hasSameAspectRatio(aspectRatio, desiredAspectRatio)) {
398 int pixelNum = size.getWidth() * size.getHeight();
399 if (pixelNum > maxPixelNumWithAspect) {
400 maxPixelNumWithAspect = pixelNum;
408 public static DisplayMetrics getDisplayMetrics(Context context) {
409 DisplayMetrics displayMetrics = new DisplayMetrics();
410 WindowManager wm = AndroidServices.instance().provideWindowManager();
412 wm.getDefaultDisplay().getMetrics(displayMetrics);
414 return displayMetrics;
418 * Takes selected sizes and a list of blacklisted sizes. All the blacklistes
419 * sizes will be removed from the 'sizes' list.
421 * @param sizes the sizes to be filtered.
422 * @param blacklistString a String containing a comma-separated list of
423 * sizes that should be removed from the original list.
424 * @return A list that contains the filtered items.
426 @ParametersAreNonnullByDefault
427 public static List<Size> filterBlackListedSizes(List<Size> sizes, String blacklistString) {
428 String[] blacklistStringArray = blacklistString.split(",");
429 if (blacklistStringArray.length == 0) {
433 Set<String> blacklistedSizes = new HashSet(Lists.newArrayList(blacklistStringArray));
434 List<Size> newSizeList = new ArrayList<>();
435 for (Size size : sizes) {
436 if (!isBlackListed(size, blacklistedSizes)) {
437 newSizeList.add(size);
444 * Returns whether the given size is within the blacklist string.
446 * @param size the size to check
447 * @param blacklistString a String containing a comma-separated list of
448 * sizes that should not be available on the device.
449 * @return Whether the given size is blacklisted.
451 public static boolean isBlackListed(@Nonnull Size size, @Nonnull String blacklistString) {
452 String[] blacklistStringArray = blacklistString.split(",");
453 if (blacklistStringArray.length == 0) {
456 Set<String> blacklistedSizes = new HashSet(Lists.newArrayList(blacklistStringArray));
457 return isBlackListed(size, blacklistedSizes);
460 private static boolean isBlackListed(@Nonnull Size size, @Nonnull Set<String> blacklistedSizes) {
461 String sizeStr = size.getWidth() + "x" + size.getHeight();
462 return blacklistedSizes.contains(sizeStr);