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