TaskCompressImageToJpeg.java revision ec604214008248f7858b3a8b66d70919947399a9
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; 205aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Huimport android.net.Uri; 21ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 225aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Huimport com.android.camera.app.MediaSaver; 2309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.app.OrientationManager.DeviceOrientation; 243830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberlingimport com.android.camera.exif.ExifInterface; 254dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Linimport com.android.camera.one.v2.camera2proxy.ImageProxy; 2630ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.session.CaptureSession; 2730ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.util.JpegUtilNative; 2809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.util.Size; 29ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 3030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport java.nio.ByteBuffer; 31ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linimport java.util.concurrent.Executor; 32ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 33ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin/** 3430ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * Implements the conversion of a YUV_420_888 image to compressed JPEG byte 354dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * array, using the native implementation of the Camera Application. If the 364dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * image is already JPEG, then it passes it through properly with the assumption 374dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * that the JPEG is already encoded in the proper orientation. 38ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin */ 39ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linpublic class TaskCompressImageToJpeg extends TaskJpegEncode { 404dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 4130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin private static final int DEFAULT_JPEG_COMPRESSION_QUALITY = 90; 42ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 4330ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin /** 4430ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * Constructor 4530ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * 463830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling * @param image Image required for computation 4730ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * @param executor Executor to run events 483c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin * @param imageTaskManager Link to ImageBackend for reference counting 4930ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * @param captureSession Handler for UI/Disk events 5030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin */ 51a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin TaskCompressImageToJpeg(ImageToProcess image, Executor executor, 524dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin ImageTaskManager imageTaskManager, 534dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin CaptureSession captureSession) { 543c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin super(image, executor, imageTaskManager, ProcessingPriority.SLOW, captureSession); 55ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin } 56ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 574dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin /** 584dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * Wraps the static call to JpegUtilNative for testability. {@see 594dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * JpegUtilNative#compressJpegFromYUV420Image} 604dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin */ 614dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin public int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality, 624dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin int degrees) { 634dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin return JpegUtilNative.compressJpegFromYUV420Image(img, outBuf, quality, degrees); 64ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin } 65ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 66ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin @Override 67ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin public void run() { 683830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling ImageToProcess img = mImage; 694dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin if (img.rotation != DeviceOrientation.CLOCKWISE_0 704dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin && img.proxy.getFormat() == ImageFormat.JPEG) { 714dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // TODO: Ensure the capture for SimpleOneCameraFactory is always 724dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // at zero rotation 734dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // To avoid suboptimal rotation implementation, the JPEG should 744dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // come with zero orientation. Any post-rotation of JPEG would be 754dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // REALLY sub-optimal. 764dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 774dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // Release image references on error 784dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin mImageTaskManager.releaseSemaphoreReference(img, mExecutor); 794dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 804dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin throw new IllegalStateException( 814dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin "Image Rotation for JPEG should be zero, but is actually " 824dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin + img.rotation.getDegrees()); 834dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin } 84ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 85ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin final TaskImage inputImage = new TaskImage( 864dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin img.rotation, img.proxy.getWidth(), img.proxy.getHeight(), 874dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin img.proxy.getFormat()); 884dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin Size resultSize = getImageSizeForOrientation(img.proxy.getWidth(), 894dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin img.proxy.getHeight(), 9009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling img.rotation); 91ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin final TaskImage resultImage = new TaskImage( 9209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling DeviceOrientation.CLOCKWISE_0, resultSize.getWidth(), resultSize.getHeight(), 9309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling ImageFormat.JPEG); 944dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin byte[] writeOut; 954dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin int numBytes; 964dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 974dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin try { 984dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // Resulting image will be rotated so that viewers won't have to 994dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // rotate. That's why the resulting image will have 0 rotation. 1004dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1014dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE); 1024dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin logWrapper("TIMER_END Full-size YUV buffer available, w=" + img.proxy.getWidth() 1034dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin + " h=" 1044dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin + img.proxy.getHeight() + " of format " + img.proxy.getFormat() 1054dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin + " (35==YUV_420_888)"); 1064dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1074dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin ByteBuffer compressedData; 1084dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin switch (inputImage.format) { 1094dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin case ImageFormat.JPEG: 1104dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin compressedData = img.proxy.getPlanes().get(0).getBuffer(); 1114dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin numBytes = compressedData.capacity(); 1124dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin break; 1134dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin case ImageFormat.YUV_420_888: 1144dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin compressedData = ByteBuffer.allocateDirect(3 * resultImage.width 1154dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * resultImage.height); 1164dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1174dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // If Orientation is UNKNOWN, treat input image orientation 1184dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // as CLOCKWISE_0. 1194dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin numBytes = compressJpegFromYUV420Image( 1204dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin img.proxy, compressedData, DEFAULT_JPEG_COMPRESSION_QUALITY, 121ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin inputImage.orientation.getDegrees()); 1224dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1234dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin if (numBytes < 0) { 1244dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin throw new RuntimeException("Error compressing jpeg."); 1254dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin } 1264dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin compressedData.limit(numBytes); 1274dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin break; 1284dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin default: 1294dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin throw new IllegalArgumentException( 1304dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin "Unsupported input image format for TaskCompressImageToJpeg"); 1314dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin } 1324dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1334dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin writeOut = new byte[numBytes]; 1344dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin compressedData.get(writeOut); 1354dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin compressedData.rewind(); 1364dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin } finally { 1374dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // Release the image now that you have a usable copy in local memory 1384dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // Or you failed to process 1394dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin mImageTaskManager.releaseSemaphoreReference(img, mExecutor); 14030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin } 141ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin 1424dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin onJpegEncodeDone(mId, inputImage, resultImage, writeOut, 1434dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin TaskInfo.Destination.FINAL_IMAGE); 1445aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu 145a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin // TODO: the app actually crashes here on a race condition: 146a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin // TaskCompressImageToJpeg might complete before 147a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin // TaskConvertImageToRGBPreview. 1485aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu mSession.saveAndFinish(writeOut, resultImage.width, resultImage.height, 1495aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu resultImage.orientation.getDegrees(), createExif(resultImage), 1505aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu new MediaSaver.OnMediaSavedListener() { 1515aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu @Override 1525aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu public void onMediaSaved(Uri uri) { 15330ef96534cc65c0ba4665f6da2fc36e879edf196I-Jong Lin onUriResolved(mId, inputImage, resultImage, uri, 15430ef96534cc65c0ba4665f6da2fc36e879edf196I-Jong Lin TaskInfo.Destination.FINAL_IMAGE); 1555aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu } 1565aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu }); 157ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin } 15830ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin 1594dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin /** 1604dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * Wraps a possible log message to be overridden for testability purposes. 161ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin * 1624dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * @param message 1634dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin */ 1644dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin protected void logWrapper(String message) { 1654dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin // Do nothing. 1664dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin } 1674dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin 1684dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin /** 1694dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * Wraps EXIF Interface for JPEG Metadata creation. Can be overridden for 1704dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * testing 171ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin * 1724dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * @param image Metadata for a jpeg image to create EXIF Interface 1734dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * @return the created Exif Interface 1744dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin */ 1754dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin protected ExifInterface createExif(TaskImage image) { 1763830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling ExifInterface exif = new ExifInterface(); 1773830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling exif.setTag(exif.buildTag(ExifInterface.TAG_PIXEL_X_DIMENSION, image.width)); 1783830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling exif.setTag(exif.buildTag(ExifInterface.TAG_PIXEL_Y_DIMENSION, image.height)); 1793830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, 1803830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling ExifInterface.getOrientationValueForRotation(image.orientation.getDegrees()))); 1813830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling return exif; 1823830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling } 18309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling 18409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling /** 18509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * @param originalWidth the width of the original image captured from the 18609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * camera 18709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * @param originalHeight the height of the original image captured from the 18809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * camera 18909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * @param orientation the rotation to apply, in degrees. 19009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling * @return The size of the final rotated image 19109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling */ 19209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling private Size getImageSizeForOrientation(int originalWidth, int originalHeight, 19309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling DeviceOrientation orientation) { 19409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling if (orientation == DeviceOrientation.CLOCKWISE_0 19509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling || orientation == DeviceOrientation.CLOCKWISE_180) { 19609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling return new Size(originalWidth, originalHeight); 19709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling } else if (orientation == DeviceOrientation.CLOCKWISE_90 19809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling || orientation == DeviceOrientation.CLOCKWISE_270) { 19909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling return new Size(originalHeight, originalWidth); 20009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling } else { 20109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling // Unsupported orientation. Get rid of this once UNKNOWN is gone. 20209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling return new Size(originalWidth, originalHeight); 20309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling } 20409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling } 205ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin} 206