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.Matrix; 21 import android.graphics.Rect; 22 import android.net.Uri; 23 24 import com.android.camera.app.OrientationManager; 25 import com.android.camera.one.OneCamera; 26 import com.android.camera.one.v2.camera2proxy.ImageProxy; 27 import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy; 28 import com.android.camera.one.v2.photo.ImageRotationCalculator; 29 import com.android.camera.processing.imagebackend.ImageBackend; 30 import com.android.camera.processing.imagebackend.ImageConsumer; 31 import com.android.camera.processing.imagebackend.ImageProcessorListener; 32 import com.android.camera.processing.imagebackend.ImageToProcess; 33 import com.android.camera.processing.imagebackend.TaskImageContainer; 34 import com.android.camera.session.CaptureSession; 35 import com.android.camera2.R; 36 37 import com.google.common.annotations.VisibleForTesting; 38 import com.google.common.base.Optional; 39 import com.google.common.util.concurrent.ListenableFuture; 40 41 import java.util.HashSet; 42 import java.util.Set; 43 import java.util.concurrent.Executor; 44 import java.util.concurrent.Executors; 45 46 import javax.annotation.Nonnull; 47 import javax.annotation.ParametersAreNonnullByDefault; 48 49 /** 50 * Wires up the ImageBackend task submission process to save Yuv images. 51 */ 52 public class YuvImageBackendImageSaver implements ImageSaver.Builder { 53 /** Progress for JPEG saving once the intermediate thumbnail is done. */ 54 private static final int PERCENTAGE_INTERMEDIATE_THUMBNAIL_DONE = 25; 55 /** Progress for JPEG saving after compression, before writing to disk. */ 56 private static final int PERCENTAGE_COMPRESSION_DONE = 95; 57 58 59 @ParametersAreNonnullByDefault 60 private final class ImageSaverImpl implements SingleImageSaver { 61 private final CaptureSession mSession; 62 private final OrientationManager.DeviceOrientation mImageRotation; 63 private final ImageProcessorListener mImageProcessorListener; 64 ImageSaverImpl(CaptureSession session, OrientationManager.DeviceOrientation imageRotation, ImageProcessorListener imageProcessorListener)65 public ImageSaverImpl(CaptureSession session, 66 OrientationManager.DeviceOrientation imageRotation, 67 ImageProcessorListener imageProcessorListener) { 68 mSession = session; 69 mImageRotation = imageRotation; 70 mImageProcessorListener = imageProcessorListener; 71 } 72 73 @Override saveAndCloseImage(ImageProxy image, Optional<ImageProxy> thumbnail, ListenableFuture<TotalCaptureResultProxy> metadata)74 public void saveAndCloseImage(ImageProxy image, Optional<ImageProxy> thumbnail, 75 ListenableFuture<TotalCaptureResultProxy> metadata) { 76 // TODO Use thumbnail to speedup RGB thumbnail creation whenever 77 // possible. 78 if (thumbnail.isPresent()) { 79 thumbnail.get().close(); 80 } 81 82 Set<ImageConsumer.ImageTaskFlags> taskFlagsSet = new HashSet<>(); 83 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.CREATE_EARLY_FILMSTRIP_PREVIEW); 84 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.CONVERT_TO_RGB_PREVIEW); 85 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.COMPRESS_TO_JPEG_AND_WRITE_TO_DISK); 86 taskFlagsSet.add(ImageConsumer.ImageTaskFlags.CLOSE_ON_ALL_TASKS_RELEASE); 87 88 try { 89 mImageBackend.receiveImage(new ImageToProcess(image, mImageRotation, metadata, 90 mCrop), mExecutor, taskFlagsSet, mSession, 91 Optional.of(mImageProcessorListener)); 92 } catch (InterruptedException e) { 93 // Impossible exception because receiveImage is nonblocking 94 throw new RuntimeException(e); 95 } 96 } 97 } 98 99 private static class YuvImageProcessorListener implements ImageProcessorListener { 100 private final CaptureSession mSession; 101 private final OrientationManager.DeviceOrientation mImageRotation; 102 private final OneCamera.PictureSaverCallback mPictureSaverCallback; 103 YuvImageProcessorListener(CaptureSession session, OrientationManager.DeviceOrientation imageRotation, OneCamera.PictureSaverCallback pictureSaverCallback)104 private YuvImageProcessorListener(CaptureSession session, 105 OrientationManager.DeviceOrientation imageRotation, 106 OneCamera.PictureSaverCallback pictureSaverCallback) { 107 mSession = session; 108 mImageRotation = imageRotation; 109 mPictureSaverCallback = pictureSaverCallback; 110 } 111 112 @Override onStart(TaskImageContainer.TaskInfo task)113 public void onStart(TaskImageContainer.TaskInfo task) { 114 switch (task.destination) { 115 case FAST_THUMBNAIL: 116 // Signal start of processing 117 break; 118 case INTERMEDIATE_THUMBNAIL: 119 // Do nothing 120 break; 121 } 122 } 123 124 @Override onResultCompressed(TaskImageContainer.TaskInfo task, TaskImageContainer.CompressedPayload payload)125 public void onResultCompressed(TaskImageContainer.TaskInfo task, 126 TaskImageContainer.CompressedPayload payload) { 127 if (task.destination == TaskImageContainer.TaskInfo.Destination.FINAL_IMAGE) { 128 mSession.setProgress(PERCENTAGE_COMPRESSION_DONE); 129 mPictureSaverCallback.onRemoteThumbnailAvailable(payload.data); 130 } 131 } 132 133 @Override onResultUncompressed(TaskImageContainer.TaskInfo task, TaskImageContainer.UncompressedPayload payload)134 public void onResultUncompressed(TaskImageContainer.TaskInfo task, 135 TaskImageContainer.UncompressedPayload payload) { 136 // Load bitmap into CameraAppUI 137 switch (task.destination) { 138 case FAST_THUMBNAIL: 139 final Bitmap bitmap = Bitmap.createBitmap(payload.data, 140 task.result.width, 141 task.result.height, Bitmap.Config.ARGB_8888); 142 mSession.updateCaptureIndicatorThumbnail(bitmap, mImageRotation.getDegrees()); 143 break; 144 case INTERMEDIATE_THUMBNAIL: 145 final Bitmap bitmapIntermediate = Bitmap.createBitmap(payload.data, 146 task.result.width, 147 task.result.height, Bitmap.Config.ARGB_8888); 148 Matrix matrix = new Matrix(); 149 matrix.postRotate(mImageRotation.getDegrees()); 150 final Bitmap bitmapIntermediateRotated = Bitmap.createBitmap( 151 bitmapIntermediate, 0, 0, bitmapIntermediate.getWidth(), 152 bitmapIntermediate.getHeight(), matrix, true); 153 mSession.updateThumbnail(bitmapIntermediateRotated); 154 mSession.setProgressMessage(R.string.session_saving_image); 155 mSession.setProgress(PERCENTAGE_INTERMEDIATE_THUMBNAIL_DONE); 156 break; 157 } 158 } 159 160 @Override onResultUri(TaskImageContainer.TaskInfo task, Uri uri)161 public void onResultUri(TaskImageContainer.TaskInfo task, Uri uri) { 162 // Do Nothing 163 } 164 } 165 166 private final ImageRotationCalculator mImageRotationCalculator; 167 private final ImageBackend mImageBackend; 168 private final Rect mCrop; 169 private final Executor mExecutor; 170 171 /** 172 * Constructor 173 * 174 * @param imageRotationCalculator the image rotation calculator to determine 175 * @param imageBackend ImageBackend to run the image tasks 176 * @param crop the crop to apply. Note that crop must be done *before* any 177 * rotation of the images. 178 */ YuvImageBackendImageSaver(ImageRotationCalculator imageRotationCalculator, ImageBackend imageBackend, Rect crop)179 public YuvImageBackendImageSaver(ImageRotationCalculator imageRotationCalculator, 180 ImageBackend imageBackend, Rect crop) { 181 mImageRotationCalculator = imageRotationCalculator; 182 mImageBackend = imageBackend; 183 mCrop = crop; 184 mExecutor = Executors.newSingleThreadExecutor(); 185 } 186 187 /** 188 * Constructor for dependency injection/ testing. 189 * 190 * @param imageRotationCalculator the image rotation calculator to determine 191 * @param imageBackend ImageBackend to run the image tasks 192 * @param crop the crop to apply. Note that crop must be done *before* any 193 * rotation of the images. 194 * @param executor Executor to be used for listener events in ImageBackend. 195 */ 196 @VisibleForTesting YuvImageBackendImageSaver(ImageRotationCalculator imageRotationCalculator, ImageBackend imageBackend, Rect crop, Executor executor)197 public YuvImageBackendImageSaver(ImageRotationCalculator imageRotationCalculator, 198 ImageBackend imageBackend, Rect crop, Executor executor) { 199 mImageRotationCalculator = imageRotationCalculator; 200 mImageBackend = imageBackend; 201 mCrop = crop; 202 mExecutor = executor; 203 } 204 205 /** 206 * Builder for the Zsl/ImageBackend Interface 207 * 208 * @return Instantiated interface object 209 */ 210 @Override build( @onnull OneCamera.PictureSaverCallback pictureSaverCallback, @Nonnull OrientationManager.DeviceOrientation orientation, @Nonnull CaptureSession session)211 public ImageSaver build( 212 @Nonnull OneCamera.PictureSaverCallback pictureSaverCallback, 213 @Nonnull OrientationManager.DeviceOrientation orientation, 214 @Nonnull CaptureSession session) { 215 final OrientationManager.DeviceOrientation imageRotation = mImageRotationCalculator 216 .toImageRotation(); 217 218 YuvImageProcessorListener yuvImageProcessorListener = new YuvImageProcessorListener( 219 session, imageRotation, pictureSaverCallback); 220 return new MostRecentImageSaver(new ImageSaverImpl(session, imageRotation, 221 yuvImageProcessorListener)); 222 } 223 } 224