1ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin/*
2ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * Copyright (C) 2014 The Android Open Source Project
3ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin *
4ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * Licensed under the Apache License, Version 2.0 (the "License");
5ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * you may not use this file except in compliance with the License.
6ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * You may obtain a copy of the License at
7ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin *
8ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin *      http://www.apache.org/licenses/LICENSE-2.0
9ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin *
10ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * Unless required by applicable law or agreed to in writing, software
11ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * distributed under the License is distributed on an "AS IS" BASIS,
12ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * See the License for the specific language governing permissions and
14ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin * limitations under the License.
15ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin */
16ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
173830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberlingpackage com.android.camera.processing.imagebackend;
18ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
19ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linimport android.graphics.ImageFormat;
20c56ef82017f772c5892bfc8a46f4ee5e3a9b65adPuneet Lallimport android.graphics.Rect;
21dfef32425e6184d5721bb7af9db823ec46bc2b4fSascha Haeberlingimport android.location.Location;
2202d42f96dc463f76beae47b9ac4d681e87c06c57Pengchong Jinimport android.media.CameraProfile;
235aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Huimport android.net.Uri;
24ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
25f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Linimport com.android.camera.Exif;
2609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.app.OrientationManager.DeviceOrientation;
276a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport com.android.camera.debug.Log;
283830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberlingimport com.android.camera.exif.ExifInterface;
29f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport com.android.camera.one.v2.camera2proxy.CaptureResultProxy;
304dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Linimport com.android.camera.one.v2.camera2proxy.ImageProxy;
31f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
32de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohdeimport com.android.camera.processing.memory.LruResourcePool;
33de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohdeimport com.android.camera.processing.memory.LruResourcePool.Resource;
3430ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.session.CaptureSession;
35f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport com.android.camera.util.ExifUtil;
3630ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.util.JpegUtilNative;
3709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.util.Size;
38f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport com.google.common.base.Optional;
3937036186326fb68c27063f97305405d3667a5eb3Sascha Haeberlingimport com.google.common.util.concurrent.FutureCallback;
4037036186326fb68c27063f97305405d3667a5eb3Sascha Haeberlingimport com.google.common.util.concurrent.Futures;
41f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport com.google.common.util.concurrent.ListenableFuture;
42ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
4330ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport java.nio.ByteBuffer;
446a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport java.util.HashMap;
456a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport java.util.Map;
46f6031c016d916db9789026dc0a6c559d8163a088Spike Spragueimport java.util.concurrent.ExecutionException;
47ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linimport java.util.concurrent.Executor;
48ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
49ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin/**
5030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * Implements the conversion of a YUV_420_888 image to compressed JPEG byte
514dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * array, using the native implementation of the Camera Application. If the
524dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * image is already JPEG, then it passes it through properly with the assumption
534dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * that the JPEG is already encoded in the proper orientation.
54ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin */
55ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linpublic class TaskCompressImageToJpeg extends TaskJpegEncode {
56de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
57de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde    /**
58de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde     *  Loss-less JPEG compression  is usually about a factor of 5,
59de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde     *  and is a safe lower bound for this value to use to reduce the memory
60de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde     *  footprint for encoding the final jpg.
61de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde     */
62ce10e6217be01d56238fac708a738f8d3313bac0Paul Rohde    private static final int MINIMUM_EXPECTED_JPG_COMPRESSION_FACTOR = 2;
63de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde    private final LruResourcePool<Integer, ByteBuffer> mByteBufferDirectPool;
64de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
6530ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin    /**
6630ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * Constructor
6730ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     *
683830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling     * @param image Image required for computation
6930ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * @param executor Executor to run events
703c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin     * @param imageTaskManager Link to ImageBackend for reference counting
7130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * @param captureSession Handler for UI/Disk events
7230ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     */
73a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin    TaskCompressImageToJpeg(ImageToProcess image, Executor executor,
744dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin            ImageTaskManager imageTaskManager,
75de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde            CaptureSession captureSession,
76de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde            LruResourcePool<Integer, ByteBuffer> byteBufferResourcePool) {
773c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin        super(image, executor, imageTaskManager, ProcessingPriority.SLOW, captureSession);
78de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde        mByteBufferDirectPool = byteBufferResourcePool;
79ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
80ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
814dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
824dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps the static call to JpegUtilNative for testability. {@see
834dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * JpegUtilNative#compressJpegFromYUV420Image}
844dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
854dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    public int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality,
86c56ef82017f772c5892bfc8a46f4ee5e3a9b65adPuneet Lall            Rect crop, int degrees) {
87c56ef82017f772c5892bfc8a46f4ee5e3a9b65adPuneet Lall        return JpegUtilNative.compressJpegFromYUV420Image(img, outBuf, quality, crop, degrees);
88ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
89ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
90f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin    /**
916a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin     * Encapsulates the required EXIF Tag parse for Image processing.
92f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin     *
937b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague     * @param exif EXIF data from which to extract data.
946a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin     * @return A Minimal Map from ExifInterface.Tag value to values required for Image processing
95f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin     */
967b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague    public Map<Integer, Integer> exifGetMinimalTags(ExifInterface exif) {
977b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        Map<Integer, Integer> map = new HashMap<>();
986a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_ORIENTATION,
996a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.getRotationForOrientationValue((short) Exif.getOrientation(exif)));
1006a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_PIXEL_X_DIMENSION, exif.getTagIntValue(
1016a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.TAG_PIXEL_X_DIMENSION));
1026a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, exif.getTagIntValue(
1036a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.TAG_PIXEL_Y_DIMENSION));
1046a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        return map;
105f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin    }
106f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
107ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    @Override
108ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    public void run() {
1093830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        ImageToProcess img = mImage;
110d2672f692de547859664be4e7f6950f8f9b0bc89Andy Huibers        mSession.getCollector().markProcessingTimeStart();
111c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin        final Rect safeCrop;
112ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
113f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // For JPEG, it is the capture devices responsibility to get proper
114f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // orientation.
115f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
116f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        TaskImage inputImage, resultImage;
1174dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        byte[] writeOut;
1184dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        int numBytes;
119f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        ByteBuffer compressedData;
1207b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        ExifInterface exifData = null;
121de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde        Resource<ByteBuffer> byteBufferResource = null;
122f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
123f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        switch (img.proxy.getFormat()) {
124f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            case ImageFormat.JPEG:
125f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                try {
126f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // In the cases, we will request a zero-oriented JPEG from
127f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // the HAL; the HAL may deliver its orientation in the JPEG
128f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // encoding __OR__ EXIF -- we don't know. We need to read
129f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // the EXIF setting from byte payload and the EXIF reader
130f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // doesn't work on direct buffers. So, we make a local
131f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // copy in a non-direct buffer.
132f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    ByteBuffer origBuffer = img.proxy.getPlanes().get(0).getBuffer();
1337a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin                    compressedData = ByteBuffer.allocate(origBuffer.limit());
13412df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin
13512df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    // On memory allocation failure, fail gracefully.
13612df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    if (compressedData == null) {
13712df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        // TODO: Put memory allocation failure code here.
13812df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        mSession.finishWithFailure(-1, true);
13912df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        return;
14012df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    }
14112df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin
142f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    origBuffer.rewind();
143f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    compressedData.put(origBuffer);
144f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    origBuffer.rewind();
145f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    compressedData.rewind();
146f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
1476a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // For JPEG, always use the EXIF orientation as ground
1486a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // truth on orientation, width and height.
1497b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                    Integer exifOrientation = null;
1507b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                    Integer exifPixelXDimension = null;
1517b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                    Integer exifPixelYDimension = null;
1527b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague
1537b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                    if (compressedData.array() != null) {
1547b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                        exifData = Exif.getExif(compressedData.array());
1557b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                        Map<Integer, Integer> minimalExifTags = exifGetMinimalTags(exifData);
1567b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague
1577b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                        exifOrientation = minimalExifTags.get(ExifInterface.TAG_ORIENTATION);
1587b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                        exifPixelXDimension = minimalExifTags
1597b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                                .get(ExifInterface.TAG_PIXEL_X_DIMENSION);
1607b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                        exifPixelYDimension = minimalExifTags
1617b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                                .get(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1627b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague                    }
1636a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
1646a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final DeviceOrientation exifDerivedRotation;
1656a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    if (exifOrientation == null) {
1666a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        // No existing rotation value is assumed to be 0
1676a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        // rotation.
1686a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        exifDerivedRotation = DeviceOrientation.CLOCKWISE_0;
1696a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    } else {
170e7c53cc907f5bb40c8d112830d12ac494e4c68c5I-Jong Lin                        exifDerivedRotation = DeviceOrientation
1716a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                                .from(exifOrientation);
1726a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    }
173f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
1746a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final int imageWidth;
1756a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final int imageHeight;
176c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    // Crop coordinate space is in original sensor coordinates.  We need
177c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    // to calculate the proper rotation of the crop to be applied to the
178c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    // final JPEG artifact.
179c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    final DeviceOrientation combinedRotationFromSensorToJpeg =
180c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                            addOrientation(img.rotation, exifDerivedRotation);
181c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin
1826a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    if (exifPixelXDimension == null || exifPixelYDimension == null) {
183c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        Log.w(TAG,
184c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                "Cannot parse EXIF for image dimensions, passing 0x0 dimensions");
1856a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageHeight = 0;
1866a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageWidth = 0;
187c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        // calculate crop from exif info with image proxy width/height
188c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        safeCrop = guaranteedSafeCrop(img.proxy,
189c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                rotateBoundingBox(img.crop, combinedRotationFromSensorToJpeg));
1906a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    } else {
1916a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageWidth = exifPixelXDimension;
1926a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageHeight = exifPixelYDimension;
193c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        // calculate crop from exif info with combined rotation
194c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        safeCrop = guaranteedSafeCrop(imageWidth, imageHeight,
195c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                rotateBoundingBox(img.crop, combinedRotationFromSensorToJpeg));
1966a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    }
1976a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
1986a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // Ignore the device rotation on ImageToProcess and use the EXIF from
1996a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // byte[] payload
200f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    inputImage = new TaskImage(
2016a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            exifDerivedRotation,
2026a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            imageWidth,
2036a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            imageHeight,
204725059d4d1ab78b5ffaa99cbba397b2dafc717d2I-Jong Lin                            img.proxy.getFormat(), safeCrop);
205c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin
206c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    if(requiresCropOperation(img.proxy, safeCrop)) {
207c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        // Crop the image
208c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        resultImage = new TaskImage(
209c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                exifDerivedRotation,
210c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                safeCrop.width(),
211c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                safeCrop.height(),
212c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                img.proxy.getFormat(), null);
213c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin
214c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        byte[] croppedResult = decompressCropAndRecompressJpegData(
215c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                compressedData.array(), safeCrop,
216c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                                getJpegCompressionQuality());
217c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin
218c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        compressedData = ByteBuffer.allocate(croppedResult.length);
219c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        compressedData.put(ByteBuffer.wrap(croppedResult));
220c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        compressedData.rewind();
221c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    } else {
222c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        // Pass-though the JPEG data
223c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                        resultImage = inputImage;
224c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                    }
225f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                } finally {
226f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Release the image now that you have a usable copy in
227f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // local memory
228f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Or you failed to process
229f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
230f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                }
231f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
232f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE);
233f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
2347a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin                numBytes = compressedData.limit();
235f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                break;
236f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            case ImageFormat.YUV_420_888:
237c36bcac14302b1bb3d7a316f221685d4d6ad95cbI-Jong Lin                safeCrop = guaranteedSafeCrop(img.proxy, img.crop);
238f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                try {
239f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    inputImage = new TaskImage(img.rotation, img.proxy.getWidth(),
240f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.proxy.getHeight(),
241725059d4d1ab78b5ffaa99cbba397b2dafc717d2I-Jong Lin                            img.proxy.getFormat(), safeCrop);
24256ccc3e5f74dcef523fd45a6cd32b96e00a15c6aPuneet Lall                    Size resultSize = getImageSizeForOrientation(img.crop.width(),
24356ccc3e5f74dcef523fd45a6cd32b96e00a15c6aPuneet Lall                            img.crop.height(),
244f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.rotation);
245f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
246f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Resulting image will be rotated so that viewers won't
247f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // have to rotate. That's why the resulting image will have 0
248f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // rotation.
249f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    resultImage = new TaskImage(
250f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            DeviceOrientation.CLOCKWISE_0, resultSize.getWidth(),
251f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            resultSize.getHeight(),
252725059d4d1ab78b5ffaa99cbba397b2dafc717d2I-Jong Lin                            ImageFormat.JPEG, null);
2536a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // Image rotation is already encoded into the bytes.
2546a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
255f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE);
2564dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
257de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // WARNING:
258de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // This reduces the size of the buffer that is created
259de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // to hold the final jpg. It is reduced by the "Minimum expected
260de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // jpg compression factor" to reduce memory allocation consumption.
261de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // If the final jpg is more than this size the image will be
262de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // corrupted. The maximum size of an image is width * height *
263de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // number_of_channels. We artificially reduce this number based on
264de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // what we expect the compression ratio to be to reduce the
265de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // amount of memory we are required to allocate.
266de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    int maxPossibleJpgSize = 3 * resultImage.width * resultImage.height;
267de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    int jpgBufferSize = maxPossibleJpgSize /
268de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                          MINIMUM_EXPECTED_JPG_COMPRESSION_FACTOR;
269de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
270de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    byteBufferResource = mByteBufferDirectPool.acquire(jpgBufferSize);
271de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    compressedData = byteBufferResource.get();
2724dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
27312df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    // On memory allocation failure, fail gracefully.
27412df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    if (compressedData == null) {
27512df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        // TODO: Put memory allocation failure code here.
27612df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        mSession.finishWithFailure(-1, true);
277de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        byteBufferResource.close();
27812df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                        return;
27912df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin                    }
28012df9856109645b9f874d3f007c100ed7130f94cI-Jong Lin
281f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Do the actual compression here.
2824dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    numBytes = compressJpegFromYUV420Image(
28322cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers                            img.proxy, compressedData, getJpegCompressionQuality(),
284c56ef82017f772c5892bfc8a46f4ee5e3a9b65adPuneet Lall                            img.crop, inputImage.orientation.getDegrees());
2854dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
286de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // If the compression overflows the size of the buffer, the
287de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    // actual number of bytes will be returned.
288de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    if (numBytes > jpgBufferSize) {
289de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        byteBufferResource.close();
290de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        mByteBufferDirectPool.acquire(maxPossibleJpgSize);
291de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        compressedData = byteBufferResource.get();
292de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
293de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        // On memory allocation failure, fail gracefully.
294de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        if (compressedData == null) {
295de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                            // TODO: Put memory allocation failure code here.
296de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                            mSession.finishWithFailure(-1, true);
297de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                            byteBufferResource.close();
298de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                            return;
299de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        }
300de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
301de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        numBytes = compressJpegFromYUV420Image(
302de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                              img.proxy, compressedData, getJpegCompressionQuality(),
303de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                              img.crop, inputImage.orientation.getDegrees());
304de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                    }
305de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
3064dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    if (numBytes < 0) {
307de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde                        byteBufferResource.close();
3084dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                        throw new RuntimeException("Error compressing jpeg.");
3094dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    }
3104dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    compressedData.limit(numBytes);
311f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                } finally {
312f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Release the image now that you have a usable copy in local memory
313f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Or you failed to process
314f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
315f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                }
316f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                break;
317f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            default:
318f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
319f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                throw new IllegalArgumentException(
320f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                        "Unsupported input image format for TaskCompressImageToJpeg");
32130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin        }
322ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
323f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        writeOut = new byte[numBytes];
324f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        compressedData.get(writeOut);
325f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        compressedData.rewind();
326f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
327de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde        if (byteBufferResource != null) {
328de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde            byteBufferResource.close();
329de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde        }
330de2c5f84c216d91a366d3361be78a8c8710bd65dPaul Rohde
3314dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        onJpegEncodeDone(mId, inputImage, resultImage, writeOut,
3324dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                TaskInfo.Destination.FINAL_IMAGE);
3335aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu
334f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // In rare cases, TaskCompressImageToJpeg might complete before
335f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // TaskConvertImageToRGBPreview. However, session should take care
336f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // of out-of-order completion.
337f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // EXIF tags are rewritten so that output from this task is normalized.
338f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        final TaskImage finalInput = inputImage;
339f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        final TaskImage finalResult = resultImage;
3406a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
341e7c53cc907f5bb40c8d112830d12ac494e4c68c5I-Jong Lin        final ExifInterface exif = createExif(Optional.fromNullable(exifData), resultImage,
342e7c53cc907f5bb40c8d112830d12ac494e4c68c5I-Jong Lin                img.metadata);
343e7c53cc907f5bb40c8d112830d12ac494e4c68c5I-Jong Lin        mSession.getCollector().decorateAtTimeWriteToDisk(exif);
34437036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling        ListenableFuture<Optional<Uri>> futureUri = mSession.saveAndFinish(writeOut,
34537036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling                resultImage.width, resultImage.height, resultImage.orientation.getDegrees(), exif);
34637036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling        Futures.addCallback(futureUri, new FutureCallback<Optional<Uri>>() {
34737036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            @Override
34837036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            public void onSuccess(Optional<Uri> uriOptional) {
34937036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling                if (uriOptional.isPresent()) {
35037036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling                    onUriResolved(mId, finalInput, finalResult, uriOptional.get(),
35137036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling                            TaskInfo.Destination.FINAL_IMAGE);
35237036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling                }
35337036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            }
35437036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling
35537036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            @Override
35637036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            public void onFailure(Throwable throwable) {
35737036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling            }
35837036186326fb68c27063f97305405d3667a5eb3Sascha Haeberling        });
359ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin
360ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        final ListenableFuture<TotalCaptureResultProxy> requestMetadata = img.metadata;
361ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        // If TotalCaptureResults are available add them to the capture event.
362ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        // Otherwise, do NOT wait for them, since we'd be stalling the ImageBackend
363ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        if (requestMetadata.isDone()) {
364ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            try {
365ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                mSession.getCollector()
366ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                        .decorateAtTimeOfCaptureRequestAvailable(requestMetadata.get());
367ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            } catch (InterruptedException e) {
368ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                Log.e(TAG,
369ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                        "CaptureResults not added to photoCaptureDoneEvent event due to Interrupted Exception.");
370ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            } catch (ExecutionException e) {
371ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                Log.w(TAG,
372ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                        "CaptureResults not added to photoCaptureDoneEvent event due to Execution Exception.");
373ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            } finally {
374ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin                mSession.getCollector().photoCaptureDoneEvent();
375ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            }
376ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        } else {
377ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            Log.w(TAG, "CaptureResults unavailable to photoCaptureDoneEvent event.");
378ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin            mSession.getCollector().photoCaptureDoneEvent();
379ad529c3073ce16c1cc2861f33658132b87bfea95I-Jong Lin        }
380ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
38130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin
3824dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
3834dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps a possible log message to be overridden for testability purposes.
384ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin     *
3854dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @param message
3864dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
3874dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    protected void logWrapper(String message) {
3884dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        // Do nothing.
3894dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    }
3904dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
3914dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
3924dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps EXIF Interface for JPEG Metadata creation. Can be overridden for
3934dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * testing
394ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin     *
3954dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @param image Metadata for a jpeg image to create EXIF Interface
3964dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @return the created Exif Interface
3974dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
3987b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague    protected ExifInterface createExif(Optional<ExifInterface> exifData, TaskImage image,
399f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague                                       ListenableFuture<TotalCaptureResultProxy> totalCaptureResultProxyFuture) {
4007b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        ExifInterface exif;
4017b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        if (exifData.isPresent()) {
4027b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague            exif = exifData.get();
4037b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        } else {
4047b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague            exif = new ExifInterface();
4057b132e3a555529a3add373fe169a1eca5cf006e8Spike Sprague        }
406dfef32425e6184d5721bb7af9db823ec46bc2b4fSascha Haeberling        Optional<Location> location = Optional.fromNullable(mSession.getLocation());
407f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague
408f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague        try {
409f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague            new ExifUtil(exif).populateExif(Optional.of(image),
410dfef32425e6184d5721bb7af9db823ec46bc2b4fSascha Haeberling                    Optional.<CaptureResultProxy>of(totalCaptureResultProxyFuture.get()), location);
411f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague        } catch (InterruptedException | ExecutionException e) {
412f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague            new ExifUtil(exif).populateExif(Optional.of(image),
413dfef32425e6184d5721bb7af9db823ec46bc2b4fSascha Haeberling                    Optional.<CaptureResultProxy>absent(), location);
414f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague        }
415f6031c016d916db9789026dc0a6c559d8163a088Spike Sprague
4163830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        return exif;
4173830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling    }
41822cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers
41922cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers    /**
42022cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers     * @return Quality level to use for JPEG compression.
42122cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers     */
42222cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers    protected int getJpegCompressionQuality () {
42322cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers        return CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH);
42422cc812406334dfe7f1d38ecac75b82b51c1c9d1Andy Huibers    }
42509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling
42609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    /**
42709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param originalWidth the width of the original image captured from the
42809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     *            camera
42909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param originalHeight the height of the original image captured from the
43009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     *            camera
43109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param orientation the rotation to apply, in degrees.
43209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @return The size of the final rotated image
43309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     */
43409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    private Size getImageSizeForOrientation(int originalWidth, int originalHeight,
43509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            DeviceOrientation orientation) {
43609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        if (orientation == DeviceOrientation.CLOCKWISE_0
43709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling                || orientation == DeviceOrientation.CLOCKWISE_180) {
43809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalWidth, originalHeight);
43909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        } else if (orientation == DeviceOrientation.CLOCKWISE_90
44009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling                || orientation == DeviceOrientation.CLOCKWISE_270) {
44109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalHeight, originalWidth);
44209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        } else {
44309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            // Unsupported orientation. Get rid of this once UNKNOWN is gone.
44409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalWidth, originalHeight);
44509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        }
44609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    }
447ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin}
448