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
17package com.android.camera.one.v2.imagesaver;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.graphics.Rect;
22import android.net.Uri;
23
24import com.android.camera.Exif;
25import com.android.camera.app.OrientationManager;
26import com.android.camera.debug.Log;
27import com.android.camera.one.OneCamera;
28import com.android.camera.one.v2.camera2proxy.ImageProxy;
29import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
30import com.android.camera.one.v2.photo.ImageRotationCalculator;
31import com.android.camera.processing.imagebackend.ImageBackend;
32import com.android.camera.processing.imagebackend.ImageConsumer;
33import com.android.camera.processing.imagebackend.ImageProcessorListener;
34import com.android.camera.processing.imagebackend.ImageProcessorProxyListener;
35import com.android.camera.processing.imagebackend.ImageToProcess;
36import com.android.camera.processing.imagebackend.TaskImageContainer;
37import com.android.camera.session.CaptureSession;
38
39import com.google.common.annotations.VisibleForTesting;
40import com.google.common.base.Optional;
41import com.google.common.util.concurrent.ListenableFuture;
42
43import java.util.HashSet;
44import java.util.Set;
45import java.util.concurrent.Executor;
46import java.util.concurrent.Executors;
47
48import javax.annotation.Nonnull;
49import 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 */
56public 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
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
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
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
115        public void onStart(TaskImageContainer.TaskInfo task) {
116        }
117
118        @Override
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
143        public void onResultUncompressed(TaskImageContainer.TaskInfo task,
144                TaskImageContainer.UncompressedPayload payload) {
145            // Do Nothing
146        }
147
148        @Override
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     */
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
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
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