1b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael/*
2b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * Copyright (C) 2013 The Android Open Source Project
3b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael *
4b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * Licensed under the Apache License, Version 2.0 (the "License");
5b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * you may not use this file except in compliance with the License.
6b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * You may obtain a copy of the License at
7b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael *
8b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael *      http://www.apache.org/licenses/LICENSE-2.0
9b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael *
10b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * Unless required by applicable law or agreed to in writing, software
11b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * distributed under the License is distributed on an "AS IS" BASIS,
12b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * See the License for the specific language governing permissions and
14b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * limitations under the License.
15b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael */
16b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
17b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelpackage com.android.camera.settings;
18b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
19454d63f617489281971fb151abfe14730209c047Angus Kongimport com.android.camera.util.ApiHelper;
205a344964cb003525727c31d295ca3a592c245606Sol Boucherimport com.android.ex.camera2.portability.Size;
21b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
22b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.math.BigInteger;
23b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.ArrayList;
24b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.Arrays;
25b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.Collections;
26b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.Comparator;
27b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.HashMap;
28b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.LinkedList;
29b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelimport java.util.List;
30b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
31b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael/**
32b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * This class is used to help manage the many different resolutions available on
33b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * the device. <br/>
34b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * It allows you to specify which aspect ratios to offer the user, and then
35b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * chooses which resolutions are the most pertinent to avoid overloading the
36b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael * user with so many options.
37b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael */
38b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphaelpublic class ResolutionUtil {
39b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
40454d63f617489281971fb151abfe14730209c047Angus Kong    public static final String NEXUS_5_LARGE_16_BY_9 = "1836x3264";
41454d63f617489281971fb151abfe14730209c047Angus Kong    public static final float NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO = 16f / 9f;
42454d63f617489281971fb151abfe14730209c047Angus Kong    public static Size NEXUS_5_LARGE_16_BY_9_SIZE = new Size(1836, 3264);
43454d63f617489281971fb151abfe14730209c047Angus Kong
44b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
45b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * These are the preferred aspect ratios for the settings. We will take HAL
46b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * supported aspect ratios that are within RATIO_TOLERANCE of these values.
47b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * We will also take the maximum supported resolution for full sensor image.
48b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
49b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static Float[] sDesiredAspectRatios = {
50b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            16.0f / 9.0f, 4.0f / 3.0f
51b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    };
52b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
531cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    private static Size[] sDesiredAspectRatioSizes = {
541cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael            new Size(16, 9), new Size(4, 3)
551cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    };
561cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael
57b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static final float RATIO_TOLERANCE = .05f;
58b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
59b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
60b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * A resolution bucket holds a list of sizes that are of a given aspect
61b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * ratio.
62b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
63b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static class ResolutionBucket {
64b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        public Float aspectRatio;
65b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        /**
66b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * This is a sorted list of sizes, going from largest to smallest.
67b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         */
68b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        public List<Size> sizes = new LinkedList<Size>();
69b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        /**
70b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * This is the head of the sizes array.
71b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         */
72b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        public Size largest;
73b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        /**
74b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * This is the area of the largest size, used for sorting
75b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * ResolutionBuckets.
76b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         */
77b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        public Integer maxPixels = 0;
78b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
79b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        /**
80b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * Use this to add a new resolution to this bucket. It will insert it
81b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * into the sizes array and update appropriate members.
8208b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling         *
83b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         * @param size the new size to be added
84b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael         */
85b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        public void add(Size size) {
86b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            sizes.add(size);
87b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            Collections.sort(sizes, new Comparator<Size>() {
88b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                @Override
89b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                public int compare(Size size, Size size2) {
90b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    // sort area greatest to least
91b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    return Integer.compare(size2.width() * size2.height(),
92b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                            size.width() * size.height());
93b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                }
94b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            });
95b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            maxPixels = sizes.get(0).width() * sizes.get(0).height();
96b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
97b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
98b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
99b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
100b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * Given a list of camera sizes, this uses some heuristics to decide which
101b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * options to present to a user. It currently returns up to 3 sizes for each
102b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * aspect ratio. The aspect ratios returned include the ones in
103b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
104b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * that users can use a full-sensor size, as well as any of the preferred
105b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * aspect ratios from above;
10608b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
107b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param sizes A super set of all sizes to be displayed
108a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael     * @param isBackCamera true if these are sizes for the back camera
109b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return The list of sizes to display grouped first by aspect ratio
110b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     *         (sorted by maximum area), and sorted within aspect ratio by area)
111b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
112a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael    public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera) {
113a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael        List<ResolutionBucket> buckets = parseAvailableSizes(sizes, isBackCamera);
114b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
115b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
116b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        // We want to make sure we support the maximum pixel aspect ratio, even
117b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        // if it doesn't match a desired aspect ratio
118b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
119b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
120b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        // Now go through the buckets from largest mp to smallest, adding
121b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        // desired ratios
122b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        for (ResolutionBucket bucket : buckets) {
123b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            Float aspectRatio = bucket.aspectRatio;
124b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
125b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    && !sortedDesiredAspectRatios.contains(aspectRatio)) {
126b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                sortedDesiredAspectRatios.add(aspectRatio);
127b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
128b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
129b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
130b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        List<Size> result = new ArrayList<Size>(sizes.size());
131b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        for (Float targetRatio : sortedDesiredAspectRatios) {
132b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            for (ResolutionBucket bucket : buckets) {
133b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                Number aspectRatio = bucket.aspectRatio;
134b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                if (Math.abs(aspectRatio.floatValue() - targetRatio) <= RATIO_TOLERANCE) {
135b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    result.addAll(pickUpToThree(bucket.sizes));
136b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                }
137b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
138b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
139b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        return result;
140b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
141b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
142b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
143b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * Get the area in pixels of a size.
14408b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
145b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param size the size to measure
146b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return the area.
147b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
148b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static int area(Size size) {
149b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        if (size == null) {
150b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            return 0;
151b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
152b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        return size.width() * size.height();
153b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
154b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
155b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
156b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * Given a list of sizes of a similar aspect ratio, it tries to pick evenly
157b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * spaced out options. It starts with the largest, then tries to find one at
158b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * 50% of the last chosen size for the subsequent size.
15908b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
160b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param sizes A list of Sizes that are all of a similar aspect ratio
161b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return A list of at least one, and no more than three representative
162b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     *         sizes from the list.
163b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
164b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static List<Size> pickUpToThree(List<Size> sizes) {
165b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        List<Size> result = new ArrayList<Size>();
166b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        Size largest = sizes.get(0);
167b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        result.add(largest);
168b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        Size lastSize = largest;
169b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        for (Size size : sizes) {
170b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            double targetArea = Math.pow(.5, result.size()) * area(largest);
171b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            if (area(size) < targetArea) {
172b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                // This candidate is smaller than half the mega pixels of the
173b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                // last one. Let's see whether the previous size, or this size
174b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                // is closer to the desired target.
175b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                if (!result.contains(lastSize)
176b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                        && (targetArea - area(lastSize) < area(size) - targetArea)) {
177b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    result.add(lastSize);
178b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                } else {
179b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                    result.add(size);
180b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                }
181b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
182b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            lastSize = size;
183b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            if (result.size() == 3) {
184b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                break;
185b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
186b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
187b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
188b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        // If we have less than three, we can add the smallest size.
189b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        if (result.size() < 3 && !result.contains(lastSize)) {
190b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            result.add(lastSize);
191b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
192b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        return result;
193b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
194b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
195b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
196b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
197b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * possible.
19808b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
199b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param aspectRatio the aspect ratio to fuzz
200b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return the closest desiredAspectRatio within RATIO_TOLERANCE, or the
201b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     *         original ratio
202b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
203b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    private static float fuzzAspectRatio(float aspectRatio) {
204b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        for (float desiredAspectRatio : sDesiredAspectRatios) {
205b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            if ((Math.abs(aspectRatio - desiredAspectRatio)) < RATIO_TOLERANCE) {
206b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                return desiredAspectRatio;
207b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
208b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
209b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        return aspectRatio;
210b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
211b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
212b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
213b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * This takes a bunch of supported sizes and buckets them by aspect ratio.
214b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * The result is a list of buckets sorted by each bucket's largest area.
215b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * They are sorted from largest to smallest. This will bucket aspect ratios
216b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * that are close to the sDesiredAspectRatios in to the same bucket.
21708b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
218b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param sizes all supported sizes for a camera
219a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael     * @param isBackCamera true if these are sizes for the back camera
220b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return all of the sizes grouped by their closest aspect ratio
221b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
222a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael    private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes, boolean isBackCamera) {
223b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
224b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
225b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        for (Size size : sizes) {
226b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            Float aspectRatio = size.width() / (float) size.height();
227b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            // If this aspect ratio is close to a desired Aspect Ratio,
228b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            // fuzz it so that they are bucketed together
229b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            aspectRatio = fuzzAspectRatio(aspectRatio);
230b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
231b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            if (bucket == null) {
232b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                bucket = new ResolutionBucket();
233b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                bucket.aspectRatio = aspectRatio;
234b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                aspectRatioToBuckets.put(aspectRatio, bucket);
235b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
236b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            bucket.add(size);
237b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        }
238a4b0118d15d4a3c6b77827feb63c3809c61c0fcfSeth Raphael        if (ApiHelper.IS_NEXUS_5 && isBackCamera) {
239454d63f617489281971fb151abfe14730209c047Angus Kong            aspectRatioToBuckets.get(16 / 9.0f).add(NEXUS_5_LARGE_16_BY_9_SIZE);
240454d63f617489281971fb151abfe14730209c047Angus Kong        }
241b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
242b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                aspectRatioToBuckets.values());
243b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
244b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            @Override
245b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
246b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael                return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
247b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael            }
248b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        });
249b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        return sortedBuckets;
250b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
251b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael
252b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    /**
253b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * Given a size, return a string describing the aspect ratio by reducing the
25408b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
255b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @param size the size to describe
256b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     * @return a string description of the aspect ratio
257b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael     */
258b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    public static String aspectRatioDescription(Size size) {
2591cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        Size aspectRatio = reduce(size);
2601cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        return aspectRatio.width() + "x" + aspectRatio.height();
2611cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    }
2621cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael
2631cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    /**
2641cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * Reduce an aspect ratio to its lowest common denominator. The ratio of the
2651cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * input and output sizes is guaranteed to be the same.
26608b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
2671cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * @param aspectRatio the aspect ratio to reduce
2681cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * @return The reduced aspect ratio which may equal the original.
2691cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     */
2701cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    public static Size reduce(Size aspectRatio) {
2711cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        BigInteger width = BigInteger.valueOf(aspectRatio.width());
2721cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        BigInteger height = BigInteger.valueOf(aspectRatio.height());
273b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        BigInteger gcd = width.gcd(height);
274b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
275b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael        int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
2761cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        return new Size(numerator, denominator);
277b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael    }
278d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael
279d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    /**
280d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * Given a size return the numerator of its aspect ratio
28108b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
282d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * @param size the size to measure
283d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * @return the numerator
284d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     */
285d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    public static int aspectRatioNumerator(Size size) {
2861cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        Size aspectRatio = reduce(size);
2871cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        return aspectRatio.width();
2881cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    }
2891cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael
2901cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    /**
2911cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * Given a size, return the closest aspect ratio that falls close to the
2921cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * given size.
29308b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
2941cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * @param size the size to approximate
2951cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     * @return the closest desired aspect ratio, or the original aspect ratio if
2961cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     *         none were close enough
2971cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael     */
2981cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael    public static Size getApproximateSize(Size size) {
2991cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        Size aspectRatio = reduce(size);
3001cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        float fuzzy = fuzzAspectRatio(size.width() / (float) size.height());
3011cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
3021cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        if (index != -1) {
3031cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael            aspectRatio = new Size(sDesiredAspectRatioSizes[index]);
3041cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        }
3051cced0e893b6a2f37d447a049c1c3c08affd2d41Seth Raphael        return aspectRatio;
306d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    }
307d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael
308d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    /**
30908b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     * See {@link #getApproximateSize(Size)}.
31008b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     * <p>
31108b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     * TODO: Move this whole util to {@link android.util.Size}
31208b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     */
313e3dfd5a433e39d76578b379fe1539864cf924ceeSascha Haeberling    public static com.android.camera.util.Size getApproximateSize(
314e3dfd5a433e39d76578b379fe1539864cf924ceeSascha Haeberling            com.android.camera.util.Size size) {
31508b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling        Size result = getApproximateSize(new Size(size.getWidth(), size.getHeight()));
316e3dfd5a433e39d76578b379fe1539864cf924ceeSascha Haeberling        return new com.android.camera.util.Size(result.width(), result.height());
31708b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling    }
31808b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling
31908b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling    /**
320d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * Given a size return the numerator of its aspect ratio
32108b3c94a7d7aff30b5da8e99649346e37b67a5c5Sascha Haeberling     *
322d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * @param size
323d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     * @return the denominator
324d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael     */
325d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    public static int aspectRatioDenominator(Size size) {
326d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael        BigInteger width = BigInteger.valueOf(size.width());
327d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael        BigInteger height = BigInteger.valueOf(size.height());
328d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael        BigInteger gcd = width.gcd(height);
329d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael        int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
330d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael        return denominator;
331d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael    }
332d074fffd7c85df39893125f29fb3d487864feaf1Seth Raphael
333b19eaa0bb37a1252015eafd3e965a011e6e98727Seth Raphael}
334