TaskCompressImageToJpeg.java revision 02d42f96dc463f76beae47b9ac4d681e87c06c57
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;
2002d42f96dc463f76beae47b9ac4d681e87c06c57Pengchong Jinimport android.media.CameraProfile;
215aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Huimport android.net.Uri;
22ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
23f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Linimport com.android.camera.Exif;
245aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Huimport com.android.camera.app.MediaSaver;
25f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Linimport com.android.camera.app.OrientationManager;
2609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.app.OrientationManager.DeviceOrientation;
276a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport com.android.camera.debug.Log;
283830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberlingimport com.android.camera.exif.ExifInterface;
294dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Linimport com.android.camera.one.v2.camera2proxy.ImageProxy;
3030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.session.CaptureSession;
3130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport com.android.camera.util.JpegUtilNative;
3209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberlingimport com.android.camera.util.Size;
33ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
3430ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Linimport java.nio.ByteBuffer;
356a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport java.util.HashMap;
366a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Linimport java.util.Map;
37ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linimport java.util.concurrent.Executor;
38ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
39ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin/**
4030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin * Implements the conversion of a YUV_420_888 image to compressed JPEG byte
414dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * array, using the native implementation of the Camera Application. If the
424dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * image is already JPEG, then it passes it through properly with the assumption
434dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin * that the JPEG is already encoded in the proper orientation.
44ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin */
45ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Linpublic class TaskCompressImageToJpeg extends TaskJpegEncode {
464dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
4702d42f96dc463f76beae47b9ac4d681e87c06c57Pengchong Jin    private static final int DEFAULT_JPEG_COMPRESSION_QUALITY = CameraProfile.
4802d42f96dc463f76beae47b9ac4d681e87c06c57Pengchong Jin        getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH);
49ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
5030ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin    /**
5130ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * Constructor
5230ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     *
533830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling     * @param image Image required for computation
5430ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * @param executor Executor to run events
553c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin     * @param imageTaskManager Link to ImageBackend for reference counting
5630ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     * @param captureSession Handler for UI/Disk events
5730ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin     */
58a1ea88071be696ea8dee99c7bdb4007128ed60efI-Jong Lin    TaskCompressImageToJpeg(ImageToProcess image, Executor executor,
594dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin            ImageTaskManager imageTaskManager,
604dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin            CaptureSession captureSession) {
613c7b7ec6aa2e51859718a6d6dead3c12d10ea370I-Jong Lin        super(image, executor, imageTaskManager, ProcessingPriority.SLOW, captureSession);
62ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
63ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
644dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
654dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps the static call to JpegUtilNative for testability. {@see
664dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * JpegUtilNative#compressJpegFromYUV420Image}
674dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
684dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    public int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality,
694dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin            int degrees) {
704dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        return JpegUtilNative.compressJpegFromYUV420Image(img, outBuf, quality, degrees);
71ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
72ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
73f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin    /**
746a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin     * Encapsulates the required EXIF Tag parse for Image processing.
75f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin     *
767a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin     * @param jpegData Binary data of the JPEG with EXIF flags
776a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin     * @return A Minimal Map from ExifInterface.Tag value to values required for Image processing
78f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin     */
796a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin    public Map<Integer, Integer> exifGetMinimalTags(byte[] jpegData) {
806a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        if (jpegData == null) {
816a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin            return null;
826a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        }
836a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
846a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        ExifInterface exif = Exif.getExif(jpegData);
856a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
866a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_ORIENTATION,
876a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.getRotationForOrientationValue((short) Exif.getOrientation(exif)));
886a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_PIXEL_X_DIMENSION, exif.getTagIntValue(
896a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.TAG_PIXEL_X_DIMENSION));
906a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        map.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, exif.getTagIntValue(
916a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                ExifInterface.TAG_PIXEL_Y_DIMENSION));
926a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        return map;
93f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin    }
94f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
95ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    @Override
96ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    public void run() {
973830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        ImageToProcess img = mImage;
98ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
99f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // For JPEG, it is the capture devices responsibility to get proper
100f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // orientation.
101f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
102f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        TaskImage inputImage, resultImage;
1034dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        byte[] writeOut;
1044dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        int numBytes;
105f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        ByteBuffer compressedData;
106f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
107f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        switch (img.proxy.getFormat()) {
108f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            case ImageFormat.JPEG:
109f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                try {
110f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // In the cases, we will request a zero-oriented JPEG from
111f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // the HAL; the HAL may deliver its orientation in the JPEG
112f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // encoding __OR__ EXIF -- we don't know. We need to read
113f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // the EXIF setting from byte payload and the EXIF reader
114f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // doesn't work on direct buffers. So, we make a local
115f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // copy in a non-direct buffer.
116f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    ByteBuffer origBuffer = img.proxy.getPlanes().get(0).getBuffer();
1177a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin                    compressedData = ByteBuffer.allocate(origBuffer.limit());
118f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    origBuffer.rewind();
119f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    compressedData.put(origBuffer);
120f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    origBuffer.rewind();
121f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    compressedData.rewind();
122f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
1236a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // For JPEG, always use the EXIF orientation as ground
1246a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // truth on orientation, width and height.
1256a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    Map<Integer, Integer> minimalExifTags = exifGetMinimalTags(compressedData
1266a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            .array());
1276a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
1286a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    Integer exifOrientation = minimalExifTags.get(ExifInterface.TAG_ORIENTATION);
1296a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    Integer exifPixelXDimension = minimalExifTags
1306a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            .get(ExifInterface.TAG_PIXEL_X_DIMENSION);
1316a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    Integer exifPixelYDimension = minimalExifTags
1326a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            .get(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1336a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
1346a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final DeviceOrientation exifDerivedRotation;
1356a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    if (exifOrientation == null) {
1366a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        // No existing rotation value is assumed to be 0
1376a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        // rotation.
1386a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        exifDerivedRotation = DeviceOrientation.CLOCKWISE_0;
1396a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    } else {
1406a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        exifDerivedRotation = OrientationManager.DeviceOrientation
1416a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                                .from(exifOrientation);
1426a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    }
143f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
1446a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final int imageWidth;
1456a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    final int imageHeight;
1466a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    if (exifPixelXDimension == null || exifPixelYDimension == null) {
1476a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        Log.w(TAG, "Cannot parse EXIF for image dimensions, passing 0x0 dimensions");
1486a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageHeight = 0;
1496a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageWidth = 0;
1506a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    } else {
1516a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageWidth = exifPixelXDimension;
1526a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                        imageHeight = exifPixelYDimension;
1536a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    }
1546a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
1556a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // Ignore the device rotation on ImageToProcess and use the EXIF from
1566a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // byte[] payload
157f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    inputImage = new TaskImage(
1586a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            exifDerivedRotation,
1596a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            imageWidth,
1606a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                            imageHeight,
161f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.proxy.getFormat());
162f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    resultImage = inputImage; // Pass through
163f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                } finally {
164f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Release the image now that you have a usable copy in
165f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // local memory
166f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Or you failed to process
167f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
168f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                }
169f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
170f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE);
171f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
1727a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin                numBytes = compressedData.limit();
173f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                break;
174f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            case ImageFormat.YUV_420_888:
175f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                try {
176f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    inputImage = new TaskImage(img.rotation, img.proxy.getWidth(),
177f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.proxy.getHeight(),
178f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.proxy.getFormat());
179f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    Size resultSize = getImageSizeForOrientation(img.proxy.getWidth(),
180f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.proxy.getHeight(),
181f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            img.rotation);
182f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
183f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Resulting image will be rotated so that viewers won't
184f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // have to rotate. That's why the resulting image will have 0
185f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // rotation.
186f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    resultImage = new TaskImage(
187f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            DeviceOrientation.CLOCKWISE_0, resultSize.getWidth(),
188f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            resultSize.getHeight(),
189f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                            ImageFormat.JPEG);
1906a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin                    // Image rotation is already encoded into the bytes.
1916a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
192f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE);
1934dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
1944dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    compressedData = ByteBuffer.allocateDirect(3 * resultImage.width
1954dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                            * resultImage.height);
1964dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
197f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Do the actual compression here.
1984dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    numBytes = compressJpegFromYUV420Image(
1994dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                            img.proxy, compressedData, DEFAULT_JPEG_COMPRESSION_QUALITY,
200ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin                            inputImage.orientation.getDegrees());
2014dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
2024dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    if (numBytes < 0) {
2034dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                        throw new RuntimeException("Error compressing jpeg.");
2044dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    }
2054dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                    compressedData.limit(numBytes);
206f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                } finally {
207f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Release the image now that you have a usable copy in local memory
208f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    // Or you failed to process
209f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                    mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
210f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                }
211f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                break;
212f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin            default:
213f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                mImageTaskManager.releaseSemaphoreReference(img, mExecutor);
214f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                throw new IllegalArgumentException(
215f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                        "Unsupported input image format for TaskCompressImageToJpeg");
21630ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin        }
217ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin
218f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        writeOut = new byte[numBytes];
219f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        compressedData.get(writeOut);
220f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        compressedData.rewind();
221f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin
2224dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        onJpegEncodeDone(mId, inputImage, resultImage, writeOut,
2234dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin                TaskInfo.Destination.FINAL_IMAGE);
2245aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu
225f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // In rare cases, TaskCompressImageToJpeg might complete before
226f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // TaskConvertImageToRGBPreview. However, session should take care
227f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // of out-of-order completion.
228f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        // EXIF tags are rewritten so that output from this task is normalized.
229f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        final TaskImage finalInput = inputImage;
230f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin        final TaskImage finalResult = resultImage;
2316a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin
2325aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu        mSession.saveAndFinish(writeOut, resultImage.width, resultImage.height,
2335aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                resultImage.orientation.getDegrees(), createExif(resultImage),
2345aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                new MediaSaver.OnMediaSavedListener() {
2355aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                    @Override
2365aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                    public void onMediaSaved(Uri uri) {
237f68a39a0ad362ff0543e11e3f0ce11741a7de48cI-Jong Lin                        onUriResolved(mId, finalInput, finalResult, uri,
23830ef96534cc65c0ba4665f6da2fc36e879edf196I-Jong Lin                                TaskInfo.Destination.FINAL_IMAGE);
2395aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                    }
2405aa7eb2930b813c97f3754d93fe7fa978651887bSenpo Hu                });
241ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin    }
24230ccdac56450e5b1927e14a6eede2b86a30c42ebI-Jong Lin
2434dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
2444dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps a possible log message to be overridden for testability purposes.
245ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin     *
2464dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @param message
2474dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
2484dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    protected void logWrapper(String message) {
2494dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin        // Do nothing.
2504dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    }
2514dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin
2524dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    /**
2534dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * Wraps EXIF Interface for JPEG Metadata creation. Can be overridden for
2544dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * testing
255ec604214008248f7858b3a8b66d70919947399a9I-Jong Lin     *
2564dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @param image Metadata for a jpeg image to create EXIF Interface
2574dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     * @return the created Exif Interface
2584dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin     */
2594dc301a073dab22b9bc12e0b846530d3a80bf8f7I-Jong Lin    protected ExifInterface createExif(TaskImage image) {
2603830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        ExifInterface exif = new ExifInterface();
2616a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        exif.setTag(exif.buildTag(ExifInterface.TAG_PIXEL_X_DIMENSION, image.width));
2626a7c7722d1eb49418b8ee02df0018f03719a8c61I-Jong Lin        exif.setTag(exif.buildTag(ExifInterface.TAG_PIXEL_Y_DIMENSION, image.height));
2637a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin        exif.setTag(exif.buildTag(ExifInterface.TAG_IMAGE_WIDTH, image.width));
2647a72d72e97fe280fd88127b7891efe9112bcaeacI-Jong Lin        exif.setTag(exif.buildTag(ExifInterface.TAG_IMAGE_LENGTH, image.height));
2653830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
2663830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling                ExifInterface.getOrientationValueForRotation(image.orientation.getDegrees())));
2673830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling        return exif;
2683830d419691ef865f01b362fee9618bac2aa8888Sascha Haeberling    }
26909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling
27009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    /**
27109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param originalWidth the width of the original image captured from the
27209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     *            camera
27309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param originalHeight the height of the original image captured from the
27409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     *            camera
27509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @param orientation the rotation to apply, in degrees.
27609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     * @return The size of the final rotated image
27709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling     */
27809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    private Size getImageSizeForOrientation(int originalWidth, int originalHeight,
27909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            DeviceOrientation orientation) {
28009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        if (orientation == DeviceOrientation.CLOCKWISE_0
28109d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling                || orientation == DeviceOrientation.CLOCKWISE_180) {
28209d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalWidth, originalHeight);
28309d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        } else if (orientation == DeviceOrientation.CLOCKWISE_90
28409d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling                || orientation == DeviceOrientation.CLOCKWISE_270) {
28509d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalHeight, originalWidth);
28609d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        } else {
28709d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            // Unsupported orientation. Get rid of this once UNKNOWN is gone.
28809d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling            return new Size(originalWidth, originalHeight);
28909d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling        }
29009d1f4473af1494572d77949ad87b95e21028135Sascha Haeberling    }
291ed68932f91b4b4ad6766e4e38732deb8be772426I-Jong Lin}
292