• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.camera.settings;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.util.DisplayMetrics;
22 import android.view.Display;
23 import android.view.WindowManager;
24 
25 import com.android.camera.exif.Rational;
26 import com.android.camera.util.AndroidServices;
27 import com.android.camera.util.ApiHelper;
28 import com.android.camera.util.Size;
29 
30 import com.google.common.collect.Lists;
31 
32 import java.math.BigInteger;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Set;
42 
43 import javax.annotation.Nonnull;
44 import javax.annotation.ParametersAreNonnullByDefault;
45 
46 
47 /**
48  * This class is used to help manage the many different resolutions available on
49  * the device. <br/>
50  * It allows you to specify which aspect ratios to offer the user, and then
51  * chooses which resolutions are the most pertinent to avoid overloading the
52  * user with so many options.
53  */
54 public class ResolutionUtil {
55     /**
56      * Different aspect ratio constants.
57      */
58     public static final Rational ASPECT_RATIO_16x9 = new Rational(16, 9);
59     public static final Rational ASPECT_RATIO_4x3 = new Rational(4, 3);
60     private static final double ASPECT_RATIO_TOLERANCE = 0.05;
61 
62     public static final String NEXUS_5_LARGE_16_BY_9 = "1836x3264";
63     public static final float NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO = 16f / 9f;
64     public static Size NEXUS_5_LARGE_16_BY_9_SIZE = new Size(3264, 1836);
65 
66     /**
67      * These are the preferred aspect ratios for the settings. We will take HAL
68      * supported aspect ratios that are within ASPECT_RATIO_TOLERANCE of these values.
69      * We will also take the maximum supported resolution for full sensor image.
70      */
71     private static Float[] sDesiredAspectRatios = {
72             16.0f / 9.0f, 4.0f / 3.0f
73     };
74 
75     private static Size[] sDesiredAspectRatioSizes = {
76             new Size(16, 9), new Size(4, 3)
77     };
78 
79     /**
80      * A resolution bucket holds a list of sizes that are of a given aspect
81      * ratio.
82      */
83     private static class ResolutionBucket {
84         public Float aspectRatio;
85         /**
86          * This is a sorted list of sizes, going from largest to smallest.
87          */
88         public List<Size> sizes = new LinkedList<Size>();
89         /**
90          * This is the head of the sizes array.
91          */
92         public Size largest;
93         /**
94          * This is the area of the largest size, used for sorting
95          * ResolutionBuckets.
96          */
97         public Integer maxPixels = 0;
98 
99         /**
100          * Use this to add a new resolution to this bucket. It will insert it
101          * into the sizes array and update appropriate members.
102          *
103          * @param size the new size to be added
104          */
add(Size size)105         public void add(Size size) {
106             sizes.add(size);
107             Collections.sort(sizes, new Comparator<Size>() {
108                 @Override
109                 public int compare(Size size, Size size2) {
110                     // sort area greatest to least
111                     return Integer.compare(size2.width() * size2.height(),
112                             size.width() * size.height());
113                 }
114             });
115             maxPixels = sizes.get(0).width() * sizes.get(0).height();
116         }
117     }
118 
119     /**
120      * Given a list of camera sizes, this uses some heuristics to decide which
121      * options to present to a user. It currently returns up to 3 sizes for each
122      * aspect ratio. The aspect ratios returned include the ones in
123      * sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
124      * that users can use a full-sensor size, as well as any of the preferred
125      * aspect ratios from above;
126      *
127      * @param sizes A super set of all sizes to be displayed
128      * @param isBackCamera true if these are sizes for the back camera
129      * @return The list of sizes to display grouped first by aspect ratio
130      *         (sorted by maximum area), and sorted within aspect ratio by area)
131      */
getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera)132     public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera) {
133         List<ResolutionBucket> buckets = parseAvailableSizes(sizes, isBackCamera);
134 
135         List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
136         // We want to make sure we support the maximum pixel aspect ratio, even
137         // if it doesn't match a desired aspect ratio
138         sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
139 
140         // Now go through the buckets from largest mp to smallest, adding
141         // desired ratios
142         for (ResolutionBucket bucket : buckets) {
143             Float aspectRatio = bucket.aspectRatio;
144             if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
145                     && !sortedDesiredAspectRatios.contains(aspectRatio)) {
146                 sortedDesiredAspectRatios.add(aspectRatio);
147             }
148         }
149 
150         List<Size> result = new ArrayList<Size>(sizes.size());
151         for (Float targetRatio : sortedDesiredAspectRatios) {
152             for (ResolutionBucket bucket : buckets) {
153                 Number aspectRatio = bucket.aspectRatio;
154                 if (Math.abs(aspectRatio.floatValue() - targetRatio) <= ASPECT_RATIO_TOLERANCE) {
155                     result.addAll(pickUpToThree(bucket.sizes));
156                 }
157             }
158         }
159         return result;
160     }
161 
162     /**
163      * Get the area in pixels of a size.
164      *
165      * @param size the size to measure
166      * @return the area.
167      */
area(Size size)168     private static int area(Size size) {
169         if (size == null) {
170             return 0;
171         }
172         return size.width() * size.height();
173     }
174 
175     /**
176      * Given a list of sizes of a similar aspect ratio, it tries to pick evenly
177      * spaced out options. It starts with the largest, then tries to find one at
178      * 50% of the last chosen size for the subsequent size.
179      *
180      * @param sizes A list of Sizes that are all of a similar aspect ratio
181      * @return A list of at least one, and no more than three representative
182      *         sizes from the list.
183      */
pickUpToThree(List<Size> sizes)184     private static List<Size> pickUpToThree(List<Size> sizes) {
185         List<Size> result = new ArrayList<Size>();
186         Size largest = sizes.get(0);
187         result.add(largest);
188         Size lastSize = largest;
189         for (Size size : sizes) {
190             double targetArea = Math.pow(.5, result.size()) * area(largest);
191             if (area(size) < targetArea) {
192                 // This candidate is smaller than half the mega pixels of the
193                 // last one. Let's see whether the previous size, or this size
194                 // is closer to the desired target.
195                 if (!result.contains(lastSize)
196                         && (targetArea - area(lastSize) < area(size) - targetArea)) {
197                     result.add(lastSize);
198                 } else {
199                     result.add(size);
200                 }
201             }
202             lastSize = size;
203             if (result.size() == 3) {
204                 break;
205             }
206         }
207 
208         // If we have less than three, we can add the smallest size.
209         if (result.size() < 3 && !result.contains(lastSize)) {
210             result.add(lastSize);
211         }
212         return result;
213     }
214 
215     /**
216      * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
217      * possible.
218      *
219      * @param aspectRatio the aspect ratio to fuzz
220      * @return the closest desiredAspectRatio within ASPECT_RATIO_TOLERANCE, or the
221      *         original ratio
222      */
fuzzAspectRatio(float aspectRatio)223     private static float fuzzAspectRatio(float aspectRatio) {
224         for (float desiredAspectRatio : sDesiredAspectRatios) {
225             if ((Math.abs(aspectRatio - desiredAspectRatio)) < ASPECT_RATIO_TOLERANCE) {
226                 return desiredAspectRatio;
227             }
228         }
229         return aspectRatio;
230     }
231 
232     /**
233      * This takes a bunch of supported sizes and buckets them by aspect ratio.
234      * The result is a list of buckets sorted by each bucket's largest area.
235      * They are sorted from largest to smallest. This will bucket aspect ratios
236      * that are close to the sDesiredAspectRatios in to the same bucket.
237      *
238      * @param sizes all supported sizes for a camera
239      * @param isBackCamera true if these are sizes for the back camera
240      * @return all of the sizes grouped by their closest aspect ratio
241      */
parseAvailableSizes(List<Size> sizes, boolean isBackCamera)242     private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes, boolean isBackCamera) {
243         HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
244 
245         for (Size size : sizes) {
246             Float aspectRatio = (float) size.getWidth() / (float) size.getHeight();
247             // If this aspect ratio is close to a desired Aspect Ratio,
248             // fuzz it so that they are bucketed together
249             aspectRatio = fuzzAspectRatio(aspectRatio);
250             ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
251             if (bucket == null) {
252                 bucket = new ResolutionBucket();
253                 bucket.aspectRatio = aspectRatio;
254                 aspectRatioToBuckets.put(aspectRatio, bucket);
255             }
256             bucket.add(size);
257         }
258         if (ApiHelper.IS_NEXUS_5 && isBackCamera) {
259             aspectRatioToBuckets.get(16 / 9.0f).add(NEXUS_5_LARGE_16_BY_9_SIZE);
260         }
261         List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
262                 aspectRatioToBuckets.values());
263         Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
264             @Override
265             public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
266                 return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
267             }
268         });
269         return sortedBuckets;
270     }
271 
272     /**
273      * Given a size, return a string describing the aspect ratio by reducing the
274      *
275      * @param size the size to describe
276      * @return a string description of the aspect ratio
277      */
aspectRatioDescription(Size size)278     public static String aspectRatioDescription(Size size) {
279         Size aspectRatio = reduce(size);
280         return aspectRatio.width() + "x" + aspectRatio.height();
281     }
282 
283     /**
284      * Reduce an aspect ratio to its lowest common denominator. The ratio of the
285      * input and output sizes is guaranteed to be the same.
286      *
287      * @param aspectRatio the aspect ratio to reduce
288      * @return The reduced aspect ratio which may equal the original.
289      */
reduce(Size aspectRatio)290     public static Size reduce(Size aspectRatio) {
291         BigInteger width = BigInteger.valueOf(aspectRatio.width());
292         BigInteger height = BigInteger.valueOf(aspectRatio.height());
293         BigInteger gcd = width.gcd(height);
294         int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
295         int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
296         return new Size(numerator, denominator);
297     }
298 
299     /**
300      * Given a size return the numerator of its aspect ratio
301      *
302      * @param size the size to measure
303      * @return the numerator
304      */
aspectRatioNumerator(Size size)305     public static int aspectRatioNumerator(Size size) {
306         Size aspectRatio = reduce(size);
307         return aspectRatio.width();
308     }
309 
310     /**
311      * Given a size, return the closest aspect ratio that falls close to the
312      * given size.
313      *
314      * @param size the size to approximate
315      * @return the closest desired aspect ratio, or the original aspect ratio if
316      *         none were close enough
317      */
getApproximateSize(Size size)318     public static Size getApproximateSize(Size size) {
319         Size aspectRatio = reduce(size);
320         float fuzzy = fuzzAspectRatio(size.width() / (float) size.height());
321         int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
322         if (index != -1) {
323             aspectRatio = sDesiredAspectRatioSizes[index];
324         }
325         return aspectRatio;
326     }
327 
328     /**
329      * Given a size return the numerator of its aspect ratio
330      *
331      * @param size
332      * @return the denominator
333      */
aspectRatioDenominator(Size size)334     public static int aspectRatioDenominator(Size size) {
335         BigInteger width = BigInteger.valueOf(size.width());
336         BigInteger height = BigInteger.valueOf(size.height());
337         BigInteger gcd = width.gcd(height);
338         int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
339         return denominator;
340     }
341 
342     /**
343      * Returns the aspect ratio for the given size.
344      *
345      * @param size The given size.
346      * @return A {@link Rational} which represents the aspect ratio.
347      */
getAspectRatio(Size size)348     public static Rational getAspectRatio(Size size) {
349         int width = size.getWidth();
350         int height = size.getHeight();
351         int numerator = width;
352         int denominator = height;
353         if (height > width) {
354             numerator = height;
355             denominator = width;
356         }
357         return new Rational(numerator, denominator);
358     }
359 
hasSameAspectRatio(Rational ar1, Rational ar2)360     public static boolean hasSameAspectRatio(Rational ar1, Rational ar2) {
361         return Math.abs(ar1.toDouble() - ar2.toDouble()) < ASPECT_RATIO_TOLERANCE;
362     }
363 
364     /**
365      * Selects the maximal resolution for the given desired aspect ratio from all available
366      * resolutions.  If no resolution exists for the desired aspect ratio, return a resolution
367      * with the maximum number of pixels.
368      *
369      * @param desiredAspectRatio The desired aspect ratio.
370      * @param sizes All available resolutions.
371      * @return The maximal resolution for desired aspect ratio ; if no sizes are found, then
372      *      return size of (0,0)
373      */
getLargestPictureSize(Rational desiredAspectRatio, List<Size> sizes)374     public static Size getLargestPictureSize(Rational desiredAspectRatio, List<Size> sizes) {
375         int maxPixelNumNoAspect = 0;
376         Size maxSize = new Size(0, 0);
377 
378         // Fix for b/21758681
379         // Do first pass with the candidate with closest size, regardless of aspect ratio,
380         // to loosen the requirement of valid preview sizes.  As long as one size exists
381         // in the list, we should pass back a valid size.
382         for (Size size : sizes) {
383             int pixelNum = size.getWidth() * size.getHeight();
384             if (pixelNum > maxPixelNumNoAspect) {
385                 maxPixelNumNoAspect = pixelNum;
386                 maxSize = size;
387             }
388         }
389 
390         // With second pass, override first pass with the candidate with closest
391         // size AND similar aspect ratio.  If there are no valid candidates are found
392         // in the second pass, take the candidate from the first pass.
393         int maxPixelNumWithAspect = 0;
394         for (Size size : sizes) {
395             Rational aspectRatio = getAspectRatio(size);
396             // Skip if the aspect ratio is not desired.
397             if (!hasSameAspectRatio(aspectRatio, desiredAspectRatio)) {
398                 continue;
399             }
400             int pixelNum = size.getWidth() * size.getHeight();
401             if (pixelNum > maxPixelNumWithAspect) {
402                 maxPixelNumWithAspect = pixelNum;
403                 maxSize = size;
404             }
405         }
406 
407         return maxSize;
408     }
409 
getDisplayMetrics(Activity context)410     public static DisplayMetrics getDisplayMetrics(Activity context) {
411         DisplayMetrics displayMetrics = new DisplayMetrics();
412         Display d = context.getDisplay();
413         if (d != null) {
414             d.getMetrics(displayMetrics);
415         }
416         return displayMetrics;
417     }
418 
419     /**
420      * Takes selected sizes and a list of disallowedlisted sizes. All the disallowedlistes
421      * sizes will be removed from the 'sizes' list.
422      *
423      * @param sizes the sizes to be filtered.
424      * @param disallowedlistString a String containing a comma-separated list of
425      *            sizes that should be removed from the original list.
426      * @return A list that contains the filtered items.
427      */
428     @ParametersAreNonnullByDefault
filterDisallowedListedSizes(List<Size> sizes, String disallowedlistString)429     public static List<Size> filterDisallowedListedSizes(List<Size> sizes,
430             String disallowedlistString) {
431         String[] disallowedlistStringArray = disallowedlistString.split(",");
432         if (disallowedlistStringArray.length == 0) {
433             return sizes;
434         }
435 
436         Set<String> disallowedlistedSizes = new HashSet(Lists.newArrayList(
437                 disallowedlistStringArray));
438         List<Size> newSizeList = new ArrayList<>();
439         for (Size size : sizes) {
440             if (!isDisallowedListed(size, disallowedlistedSizes)) {
441                 newSizeList.add(size);
442             }
443         }
444         return newSizeList;
445     }
446 
447     /**
448      * Returns whether the given size is within the disallowedlist string.
449      *
450      * @param size the size to check
451      * @param disallowedlistString a String containing a comma-separated list of
452      *            sizes that should not be available on the device.
453      * @return Whether the given size is disallowedlisted.
454      */
isDisallowedListed(@onnull Size size, @Nonnull String disallowedlistString)455     public static boolean isDisallowedListed(@Nonnull Size size,
456             @Nonnull String disallowedlistString) {
457         String[] disallowedlistStringArray = disallowedlistString.split(",");
458         if (disallowedlistStringArray.length == 0) {
459             return false;
460         }
461         Set<String> disallowedlistedSizes = new HashSet(Lists.newArrayList(
462                 disallowedlistStringArray));
463         return isDisallowedListed(size, disallowedlistedSizes);
464     }
465 
isDisallowedListed(@onnull Size size, @Nonnull Set<String> disallowedlistedSizes)466     private static boolean isDisallowedListed(@Nonnull Size size,
467             @Nonnull Set<String> disallowedlistedSizes) {
468         String sizeStr = size.getWidth() + "x" + size.getHeight();
469         return disallowedlistedSizes.contains(sizeStr);
470     }
471 }
472