1 /*
2  * Copyright (C) 2015 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.one.v2.common;
18 
19 import android.graphics.Rect;
20 
21 import com.android.camera.one.OneCameraAccessException;
22 import com.android.camera.one.OneCameraCharacteristics;
23 import com.android.camera.util.AspectRatio;
24 import com.android.camera.util.Size;
25 import com.google.common.base.MoreObjects;
26 import com.google.common.base.Objects;
27 import com.google.common.base.Preconditions;
28 
29 import java.util.List;
30 
31 import javax.annotation.Nonnull;
32 import javax.annotation.ParametersAreNonnullByDefault;
33 
34 /**
35  * Selects the optimal picture size and crop for a particular target size.
36  * <p>
37  * A particular camera2 device will support a finite set of output resolutions.
38  * However, we may wish to take pictures with a size that is not directly
39  * supported.
40  * <p>
41  * For example, we may wish to use a large 4:3 output size to capture
42  * as-large-as-possible 16:9 images. This requires determining the smallest
43  * output size which can contain the target size, and then computing the
44  * appropriate crop region.
45  */
46 @ParametersAreNonnullByDefault
47 public final class PictureSizeCalculator {
48     private final OneCameraCharacteristics mCameraCharacteristics;
49 
PictureSizeCalculator(OneCameraCharacteristics cameraCharacteristics)50     public PictureSizeCalculator(OneCameraCharacteristics cameraCharacteristics) {
51         mCameraCharacteristics = cameraCharacteristics;
52     }
53 
54     public static final class Configuration {
55         private final Size mSize;
56         private final Rect mPostCrop;
57 
Configuration(Size size, Rect postCrop)58         private Configuration(Size size, Rect postCrop) {
59             mSize = size;
60             mPostCrop = postCrop;
61         }
62 
63         /**
64          * @return The crop to be applied to Images returned from the camera
65          *         device.
66          */
getPostCaptureCrop()67         public Rect getPostCaptureCrop() {
68             return mPostCrop;
69         }
70 
71         /**
72          * @return The best natively-supported size to use.
73          */
getNativeOutputSize()74         public Size getNativeOutputSize() {
75             return mSize;
76         }
77 
78         @Override
toString()79         public String toString() {
80             return MoreObjects.toStringHelper("PictureSizeCalculator.Configuration")
81                     .add("native size", mSize)
82                     .add("crop", mPostCrop)
83                     .toString();
84         }
85 
86         @Override
equals(Object o)87         public boolean equals(Object o) {
88             if (this == o) {
89                 return true;
90             } else if (!(o instanceof Configuration)) {
91                 return false;
92             }
93 
94             Configuration that = (Configuration) o;
95 
96             if (!mPostCrop.equals(that.mPostCrop)) {
97                 return false;
98             } else if (!mSize.equals(that.mSize)) {
99                 return false;
100             }
101 
102             return true;
103         }
104 
105         @Override
hashCode()106         public int hashCode() {
107             return Objects.hashCode(mSize, mPostCrop);
108         }
109     }
110 
111     @Nonnull
getSmallestSupportedSizeContainingTarget(List<Size> supported, Size target)112     private Size getSmallestSupportedSizeContainingTarget(List<Size> supported, Size target) {
113         Preconditions.checkState(!supported.isEmpty());
114         Size best = null;
115         long bestArea = Long.MAX_VALUE;
116         for (Size candidate : supported) {
117             long pixels = candidate.area();
118             if (candidate.getWidth() >= target.getWidth() &&
119                     candidate.getHeight() >= target.getHeight() &&
120                     pixels < bestArea) {
121                 best = candidate;
122                 bestArea = pixels;
123             }
124         }
125 
126         if (best == null) {
127             // If no supported sizes contain the target size, then select the
128             // largest one.
129             best = getLargestSupportedSize(supported);
130         }
131 
132         return best;
133     }
134 
135     /**
136      * A picture size Configuration consists of a device-supported size and a
137      * crop-region to apply to images retrieved from the device. The combination
138      * of these should achieve the desired image size specified in
139      * {@link #computeConfiguration}.
140      *
141      * @return The optimal configuration of device-supported picture size and
142      *         post-capture crop region to use.
143      * @throws com.android.camera.one.OneCameraAccessException if a
144      *             configuration could not be computed.
145      */
computeConfiguration(Size targetSize, int imageFormat)146     public Configuration computeConfiguration(Size targetSize, int imageFormat)
147             throws OneCameraAccessException {
148         List<Size> supportedPictureSizes = mCameraCharacteristics
149                 .getSupportedPictureSizes(imageFormat);
150         if (supportedPictureSizes.isEmpty()) {
151             throw new OneCameraAccessException("No picture sizes supported for format: "
152                     + imageFormat);
153         }
154         Size size = getSmallestSupportedSizeContainingTarget(supportedPictureSizes, targetSize);
155         Rect cropRegion = getPostCrop(AspectRatio.of(targetSize), size);
156         return new Configuration(size, cropRegion);
157     }
158 
159     @Nonnull
getLargestSupportedSize(List<Size> supported)160     private Size getLargestSupportedSize(List<Size> supported) {
161         Preconditions.checkState(!supported.isEmpty());
162         Size largestSize = supported.get(0);
163         long largestArea = largestSize.area();
164         for (Size candidate : supported) {
165             long area = candidate.area();
166             if (area > largestArea) {
167                 largestSize = candidate;
168             }
169         }
170         return largestSize;
171     }
172 
getPostCrop(AspectRatio targetAspect, Size actualSize)173     private Rect getPostCrop(AspectRatio targetAspect, Size actualSize) {
174         return targetAspect.getLargestCenterCrop(actualSize);
175     }
176 }
177