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