Home | History | Annotate | Download | only in settings
      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 com.android.camera.util.ApiHelper;
     20 import com.android.ex.camera2.portability.Size;
     21 
     22 import java.math.BigInteger;
     23 import java.util.ArrayList;
     24 import java.util.Arrays;
     25 import java.util.Collections;
     26 import java.util.Comparator;
     27 import java.util.HashMap;
     28 import java.util.LinkedList;
     29 import java.util.List;
     30 
     31 /**
     32  * This class is used to help manage the many different resolutions available on
     33  * the device. <br/>
     34  * It allows you to specify which aspect ratios to offer the user, and then
     35  * chooses which resolutions are the most pertinent to avoid overloading the
     36  * user with so many options.
     37  */
     38 public class ResolutionUtil {
     39 
     40     public static final String NEXUS_5_LARGE_16_BY_9 = "1836x3264";
     41     public static final float NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO = 16f / 9f;
     42     public static Size NEXUS_5_LARGE_16_BY_9_SIZE = new Size(1836, 3264);
     43 
     44     /**
     45      * These are the preferred aspect ratios for the settings. We will take HAL
     46      * supported aspect ratios that are within RATIO_TOLERANCE of these values.
     47      * We will also take the maximum supported resolution for full sensor image.
     48      */
     49     private static Float[] sDesiredAspectRatios = {
     50             16.0f / 9.0f, 4.0f / 3.0f
     51     };
     52 
     53     private static Size[] sDesiredAspectRatioSizes = {
     54             new Size(16, 9), new Size(4, 3)
     55     };
     56 
     57     private static final float RATIO_TOLERANCE = .05f;
     58 
     59     /**
     60      * A resolution bucket holds a list of sizes that are of a given aspect
     61      * ratio.
     62      */
     63     private static class ResolutionBucket {
     64         public Float aspectRatio;
     65         /**
     66          * This is a sorted list of sizes, going from largest to smallest.
     67          */
     68         public List<Size> sizes = new LinkedList<Size>();
     69         /**
     70          * This is the head of the sizes array.
     71          */
     72         public Size largest;
     73         /**
     74          * This is the area of the largest size, used for sorting
     75          * ResolutionBuckets.
     76          */
     77         public Integer maxPixels = 0;
     78 
     79         /**
     80          * Use this to add a new resolution to this bucket. It will insert it
     81          * into the sizes array and update appropriate members.
     82          *
     83          * @param size the new size to be added
     84          */
     85         public void add(Size size) {
     86             sizes.add(size);
     87             Collections.sort(sizes, new Comparator<Size>() {
     88                 @Override
     89                 public int compare(Size size, Size size2) {
     90                     // sort area greatest to least
     91                     return Integer.compare(size2.width() * size2.height(),
     92                             size.width() * size.height());
     93                 }
     94             });
     95             maxPixels = sizes.get(0).width() * sizes.get(0).height();
     96         }
     97     }
     98 
     99     /**
    100      * Given a list of camera sizes, this uses some heuristics to decide which
    101      * options to present to a user. It currently returns up to 3 sizes for each
    102      * aspect ratio. The aspect ratios returned include the ones in
    103      * sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
    104      * that users can use a full-sensor size, as well as any of the preferred
    105      * aspect ratios from above;
    106      *
    107      * @param sizes A super set of all sizes to be displayed
    108      * @param isBackCamera true if these are sizes for the back camera
    109      * @return The list of sizes to display grouped first by aspect ratio
    110      *         (sorted by maximum area), and sorted within aspect ratio by area)
    111      */
    112     public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera) {
    113         List<ResolutionBucket> buckets = parseAvailableSizes(sizes, isBackCamera);
    114 
    115         List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
    116         // We want to make sure we support the maximum pixel aspect ratio, even
    117         // if it doesn't match a desired aspect ratio
    118         sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
    119 
    120         // Now go through the buckets from largest mp to smallest, adding
    121         // desired ratios
    122         for (ResolutionBucket bucket : buckets) {
    123             Float aspectRatio = bucket.aspectRatio;
    124             if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
    125                     && !sortedDesiredAspectRatios.contains(aspectRatio)) {
    126                 sortedDesiredAspectRatios.add(aspectRatio);
    127             }
    128         }
    129 
    130         List<Size> result = new ArrayList<Size>(sizes.size());
    131         for (Float targetRatio : sortedDesiredAspectRatios) {
    132             for (ResolutionBucket bucket : buckets) {
    133                 Number aspectRatio = bucket.aspectRatio;
    134                 if (Math.abs(aspectRatio.floatValue() - targetRatio) <= RATIO_TOLERANCE) {
    135                     result.addAll(pickUpToThree(bucket.sizes));
    136                 }
    137             }
    138         }
    139         return result;
    140     }
    141 
    142     /**
    143      * Get the area in pixels of a size.
    144      *
    145      * @param size the size to measure
    146      * @return the area.
    147      */
    148     private static int area(Size size) {
    149         if (size == null) {
    150             return 0;
    151         }
    152         return size.width() * size.height();
    153     }
    154 
    155     /**
    156      * Given a list of sizes of a similar aspect ratio, it tries to pick evenly
    157      * spaced out options. It starts with the largest, then tries to find one at
    158      * 50% of the last chosen size for the subsequent size.
    159      *
    160      * @param sizes A list of Sizes that are all of a similar aspect ratio
    161      * @return A list of at least one, and no more than three representative
    162      *         sizes from the list.
    163      */
    164     private static List<Size> pickUpToThree(List<Size> sizes) {
    165         List<Size> result = new ArrayList<Size>();
    166         Size largest = sizes.get(0);
    167         result.add(largest);
    168         Size lastSize = largest;
    169         for (Size size : sizes) {
    170             double targetArea = Math.pow(.5, result.size()) * area(largest);
    171             if (area(size) < targetArea) {
    172                 // This candidate is smaller than half the mega pixels of the
    173                 // last one. Let's see whether the previous size, or this size
    174                 // is closer to the desired target.
    175                 if (!result.contains(lastSize)
    176                         && (targetArea - area(lastSize) < area(size) - targetArea)) {
    177                     result.add(lastSize);
    178                 } else {
    179                     result.add(size);
    180                 }
    181             }
    182             lastSize = size;
    183             if (result.size() == 3) {
    184                 break;
    185             }
    186         }
    187 
    188         // If we have less than three, we can add the smallest size.
    189         if (result.size() < 3 && !result.contains(lastSize)) {
    190             result.add(lastSize);
    191         }
    192         return result;
    193     }
    194 
    195     /**
    196      * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
    197      * possible.
    198      *
    199      * @param aspectRatio the aspect ratio to fuzz
    200      * @return the closest desiredAspectRatio within RATIO_TOLERANCE, or the
    201      *         original ratio
    202      */
    203     private static float fuzzAspectRatio(float aspectRatio) {
    204         for (float desiredAspectRatio : sDesiredAspectRatios) {
    205             if ((Math.abs(aspectRatio - desiredAspectRatio)) < RATIO_TOLERANCE) {
    206                 return desiredAspectRatio;
    207             }
    208         }
    209         return aspectRatio;
    210     }
    211 
    212     /**
    213      * This takes a bunch of supported sizes and buckets them by aspect ratio.
    214      * The result is a list of buckets sorted by each bucket's largest area.
    215      * They are sorted from largest to smallest. This will bucket aspect ratios
    216      * that are close to the sDesiredAspectRatios in to the same bucket.
    217      *
    218      * @param sizes all supported sizes for a camera
    219      * @param isBackCamera true if these are sizes for the back camera
    220      * @return all of the sizes grouped by their closest aspect ratio
    221      */
    222     private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes, boolean isBackCamera) {
    223         HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
    224 
    225         for (Size size : sizes) {
    226             Float aspectRatio = size.width() / (float) size.height();
    227             // If this aspect ratio is close to a desired Aspect Ratio,
    228             // fuzz it so that they are bucketed together
    229             aspectRatio = fuzzAspectRatio(aspectRatio);
    230             ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
    231             if (bucket == null) {
    232                 bucket = new ResolutionBucket();
    233                 bucket.aspectRatio = aspectRatio;
    234                 aspectRatioToBuckets.put(aspectRatio, bucket);
    235             }
    236             bucket.add(size);
    237         }
    238         if (ApiHelper.IS_NEXUS_5 && isBackCamera) {
    239             aspectRatioToBuckets.get(16 / 9.0f).add(NEXUS_5_LARGE_16_BY_9_SIZE);
    240         }
    241         List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
    242                 aspectRatioToBuckets.values());
    243         Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
    244             @Override
    245             public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
    246                 return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
    247             }
    248         });
    249         return sortedBuckets;
    250     }
    251 
    252     /**
    253      * Given a size, return a string describing the aspect ratio by reducing the
    254      *
    255      * @param size the size to describe
    256      * @return a string description of the aspect ratio
    257      */
    258     public static String aspectRatioDescription(Size size) {
    259         Size aspectRatio = reduce(size);
    260         return aspectRatio.width() + "x" + aspectRatio.height();
    261     }
    262 
    263     /**
    264      * Reduce an aspect ratio to its lowest common denominator. The ratio of the
    265      * input and output sizes is guaranteed to be the same.
    266      *
    267      * @param aspectRatio the aspect ratio to reduce
    268      * @return The reduced aspect ratio which may equal the original.
    269      */
    270     public static Size reduce(Size aspectRatio) {
    271         BigInteger width = BigInteger.valueOf(aspectRatio.width());
    272         BigInteger height = BigInteger.valueOf(aspectRatio.height());
    273         BigInteger gcd = width.gcd(height);
    274         int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
    275         int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
    276         return new Size(numerator, denominator);
    277     }
    278 
    279     /**
    280      * Given a size return the numerator of its aspect ratio
    281      *
    282      * @param size the size to measure
    283      * @return the numerator
    284      */
    285     public static int aspectRatioNumerator(Size size) {
    286         Size aspectRatio = reduce(size);
    287         return aspectRatio.width();
    288     }
    289 
    290     /**
    291      * Given a size, return the closest aspect ratio that falls close to the
    292      * given size.
    293      *
    294      * @param size the size to approximate
    295      * @return the closest desired aspect ratio, or the original aspect ratio if
    296      *         none were close enough
    297      */
    298     public static Size getApproximateSize(Size size) {
    299         Size aspectRatio = reduce(size);
    300         float fuzzy = fuzzAspectRatio(size.width() / (float) size.height());
    301         int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
    302         if (index != -1) {
    303             aspectRatio = new Size(sDesiredAspectRatioSizes[index]);
    304         }
    305         return aspectRatio;
    306     }
    307 
    308     /**
    309      * See {@link #getApproximateSize(Size)}.
    310      * <p>
    311      * TODO: Move this whole util to {@link android.util.Size}
    312      */
    313     public static com.android.camera.util.Size getApproximateSize(
    314             com.android.camera.util.Size size) {
    315         Size result = getApproximateSize(new Size(size.getWidth(), size.getHeight()));
    316         return new com.android.camera.util.Size(result.width(), result.height());
    317     }
    318 
    319     /**
    320      * Given a size return the numerator of its aspect ratio
    321      *
    322      * @param size
    323      * @return the denominator
    324      */
    325     public static int aspectRatioDenominator(Size size) {
    326         BigInteger width = BigInteger.valueOf(size.width());
    327         BigInteger height = BigInteger.valueOf(size.height());
    328         BigInteger gcd = width.gcd(height);
    329         int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
    330         return denominator;
    331     }
    332 
    333 }
    334