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