Home | History | Annotate | Download | only in imagesaver
      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 
     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