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