blob: 8f6c929399e88f6e6a5259e4afe851cf4e94e66e [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.settings;
import com.android.camera.util.ApiHelper;
import com.android.ex.camera2.portability.Size;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* This class is used to help manage the many different resolutions available on
* the device. <br/>
* It allows you to specify which aspect ratios to offer the user, and then
* chooses which resolutions are the most pertinent to avoid overloading the
* user with so many options.
*/
public class ResolutionUtil {
public static final String NEXUS_5_LARGE_16_BY_9 = "1836x3264";
public static final float NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO = 16f / 9f;
public static Size NEXUS_5_LARGE_16_BY_9_SIZE = new Size(1836, 3264);
/**
* These are the preferred aspect ratios for the settings. We will take HAL
* supported aspect ratios that are within RATIO_TOLERANCE of these values.
* We will also take the maximum supported resolution for full sensor image.
*/
private static Float[] sDesiredAspectRatios = {
16.0f / 9.0f, 4.0f / 3.0f
};
private static Size[] sDesiredAspectRatioSizes = {
new Size(16, 9), new Size(4, 3)
};
private static final float RATIO_TOLERANCE = .05f;
/**
* A resolution bucket holds a list of sizes that are of a given aspect
* ratio.
*/
private static class ResolutionBucket {
public Float aspectRatio;
/**
* This is a sorted list of sizes, going from largest to smallest.
*/
public List<Size> sizes = new LinkedList<Size>();
/**
* This is the head of the sizes array.
*/
public Size largest;
/**
* This is the area of the largest size, used for sorting
* ResolutionBuckets.
*/
public Integer maxPixels = 0;
/**
* Use this to add a new resolution to this bucket. It will insert it
* into the sizes array and update appropriate members.
*
* @param size the new size to be added
*/
public void add(Size size) {
sizes.add(size);
Collections.sort(sizes, new Comparator<Size>() {
@Override
public int compare(Size size, Size size2) {
// sort area greatest to least
return Integer.compare(size2.width() * size2.height(),
size.width() * size.height());
}
});
maxPixels = sizes.get(0).width() * sizes.get(0).height();
}
}
/**
* Given a list of camera sizes, this uses some heuristics to decide which
* options to present to a user. It currently returns up to 3 sizes for each
* aspect ratio. The aspect ratios returned include the ones in
* sDesiredAspectRatios, and the largest full sensor ratio. T his guarantees
* that users can use a full-sensor size, as well as any of the preferred
* aspect ratios from above;
*
* @param sizes A super set of all sizes to be displayed
* @param isBackCamera true if these are sizes for the back camera
* @return The list of sizes to display grouped first by aspect ratio
* (sorted by maximum area), and sorted within aspect ratio by area)
*/
public static List<Size> getDisplayableSizesFromSupported(List<Size> sizes, boolean isBackCamera) {
List<ResolutionBucket> buckets = parseAvailableSizes(sizes, isBackCamera);
List<Float> sortedDesiredAspectRatios = new ArrayList<Float>();
// We want to make sure we support the maximum pixel aspect ratio, even
// if it doesn't match a desired aspect ratio
sortedDesiredAspectRatios.add(buckets.get(0).aspectRatio.floatValue());
// Now go through the buckets from largest mp to smallest, adding
// desired ratios
for (ResolutionBucket bucket : buckets) {
Float aspectRatio = bucket.aspectRatio;
if (Arrays.asList(sDesiredAspectRatios).contains(aspectRatio)
&& !sortedDesiredAspectRatios.contains(aspectRatio)) {
sortedDesiredAspectRatios.add(aspectRatio);
}
}
List<Size> result = new ArrayList<Size>(sizes.size());
for (Float targetRatio : sortedDesiredAspectRatios) {
for (ResolutionBucket bucket : buckets) {
Number aspectRatio = bucket.aspectRatio;
if (Math.abs(aspectRatio.floatValue() - targetRatio) <= RATIO_TOLERANCE) {
result.addAll(pickUpToThree(bucket.sizes));
}
}
}
return result;
}
/**
* Get the area in pixels of a size.
*
* @param size the size to measure
* @return the area.
*/
private static int area(Size size) {
if (size == null) {
return 0;
}
return size.width() * size.height();
}
/**
* Given a list of sizes of a similar aspect ratio, it tries to pick evenly
* spaced out options. It starts with the largest, then tries to find one at
* 50% of the last chosen size for the subsequent size.
*
* @param sizes A list of Sizes that are all of a similar aspect ratio
* @return A list of at least one, and no more than three representative
* sizes from the list.
*/
private static List<Size> pickUpToThree(List<Size> sizes) {
List<Size> result = new ArrayList<Size>();
Size largest = sizes.get(0);
result.add(largest);
Size lastSize = largest;
for (Size size : sizes) {
double targetArea = Math.pow(.5, result.size()) * area(largest);
if (area(size) < targetArea) {
// This candidate is smaller than half the mega pixels of the
// last one. Let's see whether the previous size, or this size
// is closer to the desired target.
if (!result.contains(lastSize)
&& (targetArea - area(lastSize) < area(size) - targetArea)) {
result.add(lastSize);
} else {
result.add(size);
}
}
lastSize = size;
if (result.size() == 3) {
break;
}
}
// If we have less than three, we can add the smallest size.
if (result.size() < 3 && !result.contains(lastSize)) {
result.add(lastSize);
}
return result;
}
/**
* Take an aspect ratio and squish it into a nearby desired aspect ratio, if
* possible.
*
* @param aspectRatio the aspect ratio to fuzz
* @return the closest desiredAspectRatio within RATIO_TOLERANCE, or the
* original ratio
*/
private static float fuzzAspectRatio(float aspectRatio) {
for (float desiredAspectRatio : sDesiredAspectRatios) {
if ((Math.abs(aspectRatio - desiredAspectRatio)) < RATIO_TOLERANCE) {
return desiredAspectRatio;
}
}
return aspectRatio;
}
/**
* This takes a bunch of supported sizes and buckets them by aspect ratio.
* The result is a list of buckets sorted by each bucket's largest area.
* They are sorted from largest to smallest. This will bucket aspect ratios
* that are close to the sDesiredAspectRatios in to the same bucket.
*
* @param sizes all supported sizes for a camera
* @param isBackCamera true if these are sizes for the back camera
* @return all of the sizes grouped by their closest aspect ratio
*/
private static List<ResolutionBucket> parseAvailableSizes(List<Size> sizes, boolean isBackCamera) {
HashMap<Float, ResolutionBucket> aspectRatioToBuckets = new HashMap<Float, ResolutionBucket>();
for (Size size : sizes) {
Float aspectRatio = size.width() / (float) size.height();
// If this aspect ratio is close to a desired Aspect Ratio,
// fuzz it so that they are bucketed together
aspectRatio = fuzzAspectRatio(aspectRatio);
ResolutionBucket bucket = aspectRatioToBuckets.get(aspectRatio);
if (bucket == null) {
bucket = new ResolutionBucket();
bucket.aspectRatio = aspectRatio;
aspectRatioToBuckets.put(aspectRatio, bucket);
}
bucket.add(size);
}
if (ApiHelper.IS_NEXUS_5 && isBackCamera) {
aspectRatioToBuckets.get(16 / 9.0f).add(NEXUS_5_LARGE_16_BY_9_SIZE);
}
List<ResolutionBucket> sortedBuckets = new ArrayList<ResolutionBucket>(
aspectRatioToBuckets.values());
Collections.sort(sortedBuckets, new Comparator<ResolutionBucket>() {
@Override
public int compare(ResolutionBucket resolutionBucket, ResolutionBucket resolutionBucket2) {
return Integer.compare(resolutionBucket2.maxPixels, resolutionBucket.maxPixels);
}
});
return sortedBuckets;
}
/**
* Given a size, return a string describing the aspect ratio by reducing the
*
* @param size the size to describe
* @return a string description of the aspect ratio
*/
public static String aspectRatioDescription(Size size) {
Size aspectRatio = reduce(size);
return aspectRatio.width() + "x" + aspectRatio.height();
}
/**
* Reduce an aspect ratio to its lowest common denominator. The ratio of the
* input and output sizes is guaranteed to be the same.
*
* @param aspectRatio the aspect ratio to reduce
* @return The reduced aspect ratio which may equal the original.
*/
public static Size reduce(Size aspectRatio) {
BigInteger width = BigInteger.valueOf(aspectRatio.width());
BigInteger height = BigInteger.valueOf(aspectRatio.height());
BigInteger gcd = width.gcd(height);
int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
return new Size(numerator, denominator);
}
/**
* Given a size return the numerator of its aspect ratio
*
* @param size the size to measure
* @return the numerator
*/
public static int aspectRatioNumerator(Size size) {
Size aspectRatio = reduce(size);
return aspectRatio.width();
}
/**
* Given a size, return the closest aspect ratio that falls close to the
* given size.
*
* @param size the size to approximate
* @return the closest desired aspect ratio, or the original aspect ratio if
* none were close enough
*/
public static Size getApproximateSize(Size size) {
Size aspectRatio = reduce(size);
float fuzzy = fuzzAspectRatio(size.width() / (float) size.height());
int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
if (index != -1) {
aspectRatio = new Size(sDesiredAspectRatioSizes[index]);
}
return aspectRatio;
}
/**
* See {@link #getApproximateSize(Size)}.
* <p>
* TODO: Move this whole util to {@link android.util.Size}
*/
public static com.android.camera.util.Size getApproximateSize(
com.android.camera.util.Size size) {
Size result = getApproximateSize(new Size(size.getWidth(), size.getHeight()));
return new com.android.camera.util.Size(result.width(), result.height());
}
/**
* Given a size return the numerator of its aspect ratio
*
* @param size
* @return the denominator
*/
public static int aspectRatioDenominator(Size size) {
BigInteger width = BigInteger.valueOf(size.width());
BigInteger height = BigInteger.valueOf(size.height());
BigInteger gcd = width.gcd(height);
int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
return denominator;
}
}