12f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall/*
22f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * Copyright (C) 2015 The Android Open Source Project
32f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall *
42f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * Licensed under the Apache License, Version 2.0 (the "License");
52f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * you may not use this file except in compliance with the License.
62f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * You may obtain a copy of the License at
72f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall *
82f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall *      http://www.apache.org/licenses/LICENSE-2.0
92f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall *
102f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * Unless required by applicable law or agreed to in writing, software
112f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * distributed under the License is distributed on an "AS IS" BASIS,
122f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * See the License for the specific language governing permissions and
142f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * limitations under the License.
152f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall */
162f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
172f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallpackage com.android.camera.one.v2.common;
182f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
192f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport android.graphics.Rect;
202f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
213ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberlingimport com.android.camera.one.OneCameraAccessException;
222f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport com.android.camera.one.OneCameraCharacteristics;
232f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport com.android.camera.util.AspectRatio;
242f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport com.android.camera.util.Size;
252f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport com.google.common.base.Objects;
262f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport com.google.common.base.Preconditions;
272f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
282f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport java.util.List;
292f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
302f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport javax.annotation.Nonnull;
312f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallimport javax.annotation.ParametersAreNonnullByDefault;
322f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
332f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall/**
342f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * Selects the optimal picture size and crop for a particular target size.
352f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * <p>
362f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * A particular camera2 device will support a finite set of output resolutions.
372f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * However, we may wish to take pictures with a size that is not directly
382f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * supported.
392f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * <p>
402f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * For example, we may wish to use a large 4:3 output size to capture
412f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * as-large-as-possible 16:9 images. This requires determining the smallest
422f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * output size which can contain the target size, and then computing the
432f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall * appropriate crop region.
442f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall */
452f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall@ParametersAreNonnullByDefault
462f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lallpublic final class PictureSizeCalculator {
472f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    private final OneCameraCharacteristics mCameraCharacteristics;
482f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
492f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    public PictureSizeCalculator(OneCameraCharacteristics cameraCharacteristics) {
502f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        mCameraCharacteristics = cameraCharacteristics;
512f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
522f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
532f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    public static final class Configuration {
542f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        private final Size mSize;
552f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        private final Rect mPostCrop;
562f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
572f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        private Configuration(Size size, Rect postCrop) {
582f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            mSize = size;
592f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            mPostCrop = postCrop;
602f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
612f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
622f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        /**
632f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall         * @return The crop to be applied to Images returned from the camera
642f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall         *         device.
652f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall         */
662f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        public Rect getPostCaptureCrop() {
672f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            return mPostCrop;
682f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
692f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
702f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        /**
712f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall         * @return The best natively-supported size to use.
722f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall         */
732f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        public Size getNativeOutputSize() {
742f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            return mSize;
752f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
762f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
772f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        @Override
782f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        public String toString() {
792f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            return Objects.toStringHelper("PictureSizeCalculator.Configuration")
802f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                    .add("native size", mSize)
812f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                    .add("crop", mPostCrop)
822f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                    .toString();
832f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
842f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
852f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        @Override
862f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        public boolean equals(Object o) {
872f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            if (this == o) {
882f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                return true;
892f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            } else if (!(o instanceof Configuration)) {
902f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                return false;
912f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            }
922f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
932f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            Configuration that = (Configuration) o;
942f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
952f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            if (!mPostCrop.equals(that.mPostCrop)) {
962f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                return false;
972f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            } else if (!mSize.equals(that.mSize)) {
982f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                return false;
992f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            }
1002f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1012f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            return true;
1022f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
1032f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1042f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        @Override
1052f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        public int hashCode() {
1062f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            return Objects.hashCode(mSize, mPostCrop);
1072f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
1082f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
1092f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1102f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    @Nonnull
1112f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    private Size getSmallestSupportedSizeContainingTarget(List<Size> supported, Size target) {
1122f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Preconditions.checkState(!supported.isEmpty());
1132f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Size best = null;
1142f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        long bestArea = Long.MAX_VALUE;
1152f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        for (Size candidate : supported) {
1162f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            long pixels = candidate.area();
1172f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            if (candidate.getWidth() >= target.getWidth() &&
1182f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                    candidate.getHeight() >= target.getHeight() &&
1192f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                    pixels < bestArea) {
1202f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                best = candidate;
1212f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                bestArea = pixels;
1222f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            }
1232f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
1242f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1252f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        if (best == null) {
1262f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            // If no supported sizes contain the target size, then select the
1272f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            // largest one.
1282f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            best = getLargestSupportedSize(supported);
1292f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
1302f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1312f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        return best;
1322f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
1332f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1342f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    /**
1352f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     * A picture size Configuration consists of a device-supported size and a
1362f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     * crop-region to apply to images retrieved from the device. The combination
1372f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     * of these should achieve the desired image size specified in
1382f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     * {@link #computeConfiguration}.
1393ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling     *
1402f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     * @return The optimal configuration of device-supported picture size and
1412f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     *         post-capture crop region to use.
1423ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling     * @throws com.android.camera.one.OneCameraAccessException if a
1433ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling     *             configuration could not be computed.
1442f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall     */
1453ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling    public Configuration computeConfiguration(Size targetSize, int imageFormat)
1463ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling            throws OneCameraAccessException {
1473ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling        List<Size> supportedPictureSizes = mCameraCharacteristics
1483ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling                .getSupportedPictureSizes(imageFormat);
1493ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling        if (supportedPictureSizes.isEmpty()) {
1503ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling            throw new OneCameraAccessException("No picture sizes supported for format: "
1513ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling                    + imageFormat);
1523ad9c9035f8be50f6b2ef8cac9943045f7cf8a74Sascha Haeberling        }
1532f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Size size = getSmallestSupportedSizeContainingTarget(supportedPictureSizes, targetSize);
1542f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Rect cropRegion = getPostCrop(AspectRatio.of(targetSize), size);
1552f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        return new Configuration(size, cropRegion);
1562f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
1572f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1582f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    @Nonnull
1592f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    private Size getLargestSupportedSize(List<Size> supported) {
1602f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Preconditions.checkState(!supported.isEmpty());
1612f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        Size largestSize = supported.get(0);
1622f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        long largestArea = largestSize.area();
1632f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        for (Size candidate : supported) {
1642f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            long area = candidate.area();
1652f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            if (area > largestArea) {
1662f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall                largestSize = candidate;
1672f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall            }
1682f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        }
1692f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        return largestSize;
1702f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
1712f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall
1722f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    private Rect getPostCrop(AspectRatio targetAspect, Size actualSize) {
1732f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall        return targetAspect.getLargestCenterCrop(actualSize);
1742f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall    }
1752f6fe6f07a6d43d8bb49156a7a73e86896ab7d1ePuneet Lall}
176