1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.camera.one.v2.imagesaver; 18 19 import android.graphics.Bitmap; 20 import android.graphics.BitmapFactory; 21 import android.graphics.Rect; 22 import android.net.Uri; 23 24 import com.android.camera.Exif; 25 import com.android.camera.app.OrientationManager; 26 import com.android.camera.debug.Log; 27 import com.android.camera.one.OneCamera; 28 import com.android.camera.one.v2.camera2proxy.ImageProxy; 29 import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy; 30 import com.android.camera.one.v2.photo.ImageRotationCalculator; 31 import com.android.camera.processing.imagebackend.ImageBackend; 32 import com.android.camera.processing.imagebackend.ImageConsumer; 33 import com.android.camera.processing.imagebackend.ImageProcessorListener; 34 import com.android.camera.processing.imagebackend.ImageProcessorProxyListener; 35 import com.android.camera.processing.imagebackend.ImageToProcess; 36 import com.android.camera.processing.imagebackend.TaskImageContainer; 37 import com.android.camera.session.CaptureSession; 38 39 import com.google.common.annotations.VisibleForTesting; 40 import com.google.common.base.Optional; 41 import com.google.common.util.concurrent.ListenableFuture; 42 43 import java.util.HashSet; 44 import java.util.Set; 45 import java.util.concurrent.Executor; 46 import java.util.concurrent.Executors; 47 48 import javax.annotation.Nonnull; 49 import javax.annotation.ParametersAreNonnullByDefault; 50 51 /** 52 * Wires up the ImageBackend task submission process to save JPEG images. Camera 53 * delivers a JPEG-compressed full-size image. This class does very little work 54 * and just routes this image artifact as the thumbnail and to remote devices. 55 */ 56 public class JpegImageBackendImageSaver implements ImageSaver.Builder { 57 58 @ParametersAreNonnullByDefault 59 private final class ImageSaverImpl implements SingleImageSaver { 60 private final CaptureSession mSession; 61 private final OrientationManager.DeviceOrientation mImageRotation; 62 private final ImageBackend mImageBackend; 63 private final ImageProcessorListener mImageProcessorListener; 64 ImageSaverImpl(CaptureSession session, OrientationManager.DeviceOrientation imageRotation, ImageBackend imageBackend, ImageProcessorListener imageProcessorListener)65 public ImageSaverImpl(CaptureSession session, 66 OrientationManager.DeviceOrientation imageRotation, 67 ImageBackend imageBackend, ImageProcessorListener imageProcessorListener) { 68 mSession = session; 69 mImageRotation = imageRotation; 70 mImageBackend = imageBackend; 71 mImageProcessorListener = imageProcessorListener; 72 } 73 74 @Override saveAndCloseImage(ImageProxy image, Optional<ImageProxy> thumbnail, ListenableFuture<TotalCaptureResultProxy> metadata)75 public void saveAndCloseImage(ImageProxy image, Optional<ImageProxy> thumbnail, 76 ListenableFuture<TotalCaptureResultProxy> metadata) { 77 // TODO: Use thumbnail to speed up RGB thumbnail creation whenever 78 // possible. For now, just close it. 79 if (thumbnail.isPresent()) { 80 thumbnail.get().close(); 81 } 82 83 Set<ImageConsumer.ImageTaskFlags> taskFlagsSet = new HashSet<>(); 84 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.COMPRESS_TO_JPEG_AND_WRITE_TO_DISK); 85 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.CLOSE_ON_ALL_TASKS_RELEASE); 86 87 try { 88 mImageBackend.receiveImage(new ImageToProcess(image, mImageRotation, metadata, 89 mCrop), mExecutor, taskFlagsSet, mSession, 90 Optional.of(mImageProcessorListener)); 91 } catch (InterruptedException e) { 92 // Impossible exception because receiveImage is nonblocking 93 throw new RuntimeException(e); 94 } 95 } 96 } 97 98 private static class JpegImageProcessorListener implements ImageProcessorListener { 99 private final ImageProcessorProxyListener mListenerProxy; 100 private final CaptureSession mSession; 101 private final OrientationManager.DeviceOrientation mImageRotation; 102 private final OneCamera.PictureSaverCallback mPictureSaverCallback; 103 JpegImageProcessorListener(ImageProcessorProxyListener listenerProxy, CaptureSession session, OrientationManager.DeviceOrientation imageRotation, OneCamera.PictureSaverCallback pictureSaverCallback)104 private JpegImageProcessorListener(ImageProcessorProxyListener listenerProxy, 105 CaptureSession session, 106 OrientationManager.DeviceOrientation imageRotation, 107 OneCamera.PictureSaverCallback pictureSaverCallback) { 108 mListenerProxy = listenerProxy; 109 mSession = session; 110 mImageRotation = imageRotation; 111 mPictureSaverCallback = pictureSaverCallback; 112 } 113 114 @Override onStart(TaskImageContainer.TaskInfo task)115 public void onStart(TaskImageContainer.TaskInfo task) { 116 } 117 118 @Override onResultCompressed(TaskImageContainer.TaskInfo task, TaskImageContainer.CompressedPayload payload)119 public void onResultCompressed(TaskImageContainer.TaskInfo task, 120 TaskImageContainer.CompressedPayload payload) { 121 if (task.destination == TaskImageContainer.TaskInfo.Destination.FINAL_IMAGE) { 122 // Just start the thumbnail now, since there's no earlier event. 123 124 // Downsample and convert the JPEG payload to a reasonably-sized 125 // Bitmap 126 BitmapFactory.Options options = new BitmapFactory.Options(); 127 options.inSampleSize = JPEG_DOWNSAMPLE_FOR_FAST_INDICATOR; 128 final Bitmap bitmap = BitmapFactory.decodeByteArray(payload.data, 0, 129 payload.data.length, options); 130 131 // If the rotation is implemented as an EXIF flag, we need to 132 // pass this information onto the UI call, since the rotation is 133 // NOT applied to the bitmap directly. 134 int rotation = Exif.getOrientation(payload.data); 135 mSession.updateCaptureIndicatorThumbnail(bitmap, rotation); 136 // Send image to remote devices 137 mPictureSaverCallback.onRemoteThumbnailAvailable(payload.data); 138 } 139 140 } 141 142 @Override onResultUncompressed(TaskImageContainer.TaskInfo task, TaskImageContainer.UncompressedPayload payload)143 public void onResultUncompressed(TaskImageContainer.TaskInfo task, 144 TaskImageContainer.UncompressedPayload payload) { 145 // Do Nothing 146 } 147 148 @Override onResultUri(TaskImageContainer.TaskInfo task, Uri uri)149 public void onResultUri(TaskImageContainer.TaskInfo task, Uri uri) { 150 // Do Nothing 151 } 152 } 153 154 /** Factor to downsample full-size JPEG image for use in thumbnail bitmap. */ 155 private static final int JPEG_DOWNSAMPLE_FOR_FAST_INDICATOR = 4; 156 private static Log.Tag TAG = new Log.Tag("JpegImgBESaver"); 157 private final ImageRotationCalculator mImageRotationCalculator; 158 private final ImageBackend mImageBackend; 159 private final Executor mExecutor; 160 private final Rect mCrop; 161 162 163 /** 164 * Constructor Instantiate a local instance executor for all JPEG ImageSaver 165 * factory requests via constructor. 166 * 167 * @param imageRotationCalculator the image rotation calculator to determine 168 * @param imageBackend ImageBackend to run the image tasks 169 */ JpegImageBackendImageSaver( ImageRotationCalculator imageRotationCalculator, ImageBackend imageBackend, Rect crop)170 public JpegImageBackendImageSaver( 171 ImageRotationCalculator imageRotationCalculator, 172 ImageBackend imageBackend, Rect crop) { 173 mImageRotationCalculator = imageRotationCalculator; 174 mImageBackend = imageBackend; 175 mExecutor = Executors.newSingleThreadExecutor(); 176 mCrop = crop; 177 } 178 179 /** 180 * Constructor for dependency injection/ testing. 181 * 182 * @param imageRotationCalculator the image rotation calculator to determine 183 * @param imageBackend ImageBackend to run the image tasks 184 * @param executor Executor to be used for listener events in ImageBackend. 185 */ 186 @VisibleForTesting JpegImageBackendImageSaver( ImageRotationCalculator imageRotationCalculator, ImageBackend imageBackend, Executor executor, Rect crop)187 public JpegImageBackendImageSaver( 188 ImageRotationCalculator imageRotationCalculator, 189 ImageBackend imageBackend, Executor executor, Rect crop) { 190 mImageRotationCalculator = imageRotationCalculator; 191 mImageBackend = imageBackend; 192 mExecutor = executor; 193 mCrop = crop; 194 } 195 196 /** 197 * Builder for the Zsl/ImageBackend Interface 198 * 199 * @return Instantiated interface object 200 */ 201 @Override build( @onnull OneCamera.PictureSaverCallback pictureSaverCallback, @Nonnull OrientationManager.DeviceOrientation orientation, @Nonnull CaptureSession session)202 public ImageSaver build( 203 @Nonnull OneCamera.PictureSaverCallback pictureSaverCallback, 204 @Nonnull OrientationManager.DeviceOrientation orientation, 205 @Nonnull CaptureSession session) { 206 final OrientationManager.DeviceOrientation imageRotation = mImageRotationCalculator 207 .toImageRotation(); 208 209 ImageProcessorProxyListener proxyListener = mImageBackend.getProxyListener(); 210 211 JpegImageProcessorListener jpegImageProcessorListener = new JpegImageProcessorListener( 212 proxyListener, session, imageRotation, pictureSaverCallback); 213 return new MostRecentImageSaver(new ImageSaverImpl(session, 214 imageRotation, mImageBackend, jpegImageProcessorListener)); 215 } 216 } 217