1/*
2 * Copyright (C) 2014 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.camera2proxy;
18
19import android.graphics.Rect;
20import android.media.Image;
21
22import com.google.common.base.Objects;
23import com.google.common.collect.ImmutableList;
24
25import java.nio.ByteBuffer;
26import java.util.ArrayList;
27import java.util.List;
28
29import javax.annotation.concurrent.GuardedBy;
30import javax.annotation.concurrent.ThreadSafe;
31
32/**
33 * An {@link ImageProxy} backed by an {@link android.media.Image}.
34 */
35@ThreadSafe
36public class AndroidImageProxy implements ImageProxy {
37
38    /**
39     * An {@link ImageProxy.Plane} backed by an
40     * {@link android.media.Image.Plane}.
41     */
42    public class Plane implements ImageProxy.Plane {
43        private final int mPixelStride;
44        private final int mRowStride;
45        private final ByteBuffer mBuffer;
46
47        public Plane(Image.Plane imagePlane) {
48            // Copying out the contents of the Image.Plane means that this Plane
49            // implementation can be thread-safe (without requiring any locking)
50            // and can have getters which do not throw a RuntimeException if
51            // the underlying Image is closed.
52            mPixelStride = imagePlane.getPixelStride();
53            mRowStride = imagePlane.getRowStride();
54            mBuffer = imagePlane.getBuffer();
55        }
56
57        /**
58         * @see {@link android.media.Image.Plane#getRowStride}
59         */
60        @Override
61        public int getRowStride() {
62            return mRowStride;
63        }
64
65        /**
66         * @see {@link android.media.Image.Plane#getPixelStride}
67         */
68        @Override
69        public int getPixelStride() {
70            return mPixelStride;
71        }
72
73        /**
74         * @see {@link android.media.Image.Plane#getBuffer}
75         */
76        @Override
77        public ByteBuffer getBuffer() {
78            return mBuffer;
79        }
80    }
81
82    private final Object mLock;
83    /**
84     * {@link android.media.Image} is not thread-safe, so all interactions must
85     * be guarded by {@link #mLock}.
86     */
87    @GuardedBy("mLock")
88    private final android.media.Image mImage;
89    private final int mFormat;
90    private final int mWidth;
91    private final int mHeight;
92    private final long mTimestamp;
93    private final ImmutableList<ImageProxy.Plane> mPlanes;
94    @GuardedBy("mLock")
95    private Rect mCropRect;
96
97    public AndroidImageProxy(android.media.Image image) {
98        mLock = new Object();
99
100        mImage = image;
101        // Copying out the contents of the Image means that this Image
102        // implementation can be thread-safe (without requiring any locking)
103        // and can have getters which do not throw a RuntimeException if
104        // the underlying Image is closed.
105        mFormat = mImage.getFormat();
106        mWidth = mImage.getWidth();
107        mHeight = mImage.getHeight();
108        mTimestamp = mImage.getTimestamp();
109
110        Image.Plane[] planes;
111        planes = mImage.getPlanes();
112        if (planes == null) {
113            mPlanes = ImmutableList.of();
114        } else {
115            List<ImageProxy.Plane> wrappedPlanes = new ArrayList<>(planes.length);
116            for (int i = 0; i < planes.length; i++) {
117                wrappedPlanes.add(new Plane(planes[i]));
118            }
119            mPlanes = ImmutableList.copyOf(wrappedPlanes);
120        }
121    }
122
123    /**
124     * @see {@link android.media.Image#getCropRect}
125     */
126    @Override
127    public Rect getCropRect() {
128        synchronized (mLock) {
129            try {
130                mCropRect = mImage.getCropRect();
131            } catch (IllegalStateException imageClosedException) {
132                // If the image is closed, then just return the cached CropRect.
133                return mCropRect;
134            }
135            return mCropRect;
136        }
137    }
138
139    /**
140     * @see {@link android.media.Image#setCropRect}
141     */
142    @Override
143    public void setCropRect(Rect cropRect) {
144        synchronized (mLock) {
145            mCropRect = cropRect;
146            try {
147                mImage.setCropRect(cropRect);
148            } catch (IllegalStateException imageClosedException) {
149                // Ignore.
150            }
151        }
152    }
153
154    /**
155     * @see {@link android.media.Image#getFormat}
156     */
157    @Override
158    public int getFormat() {
159        return mFormat;
160    }
161
162    /**
163     * @see {@link android.media.Image#getHeight}
164     */
165    @Override
166    public int getHeight() {
167        return mHeight;
168    }
169
170    /**
171     * @see {@link android.media.Image#getPlanes}
172     */
173    @Override
174    public List<ImageProxy.Plane> getPlanes() {
175        return mPlanes;
176    }
177
178    /**
179     * @see {@link android.media.Image#getTimestamp}
180     */
181    @Override
182    public long getTimestamp() {
183        return mTimestamp;
184    }
185
186    /**
187     * @see {@link android.media.Image#getWidth}
188     */
189    @Override
190    public int getWidth() {
191        return mWidth;
192    }
193
194    /**
195     * @see {@link android.media.Image#close}
196     */
197    @Override
198    public void close() {
199        synchronized (mLock) {
200            mImage.close();
201        }
202    }
203
204    @Override
205    public String toString() {
206        return Objects.toStringHelper(this)
207                .add("format", getFormat())
208                .add("timestamp", getTimestamp())
209                .add("width", getWidth())
210                .add("height", getHeight())
211                .toString();
212    }
213
214    @Override
215    public boolean equals(Object other) {
216        if (other == null) {
217            return false;
218        }
219        if (!(other instanceof ImageProxy)) {
220            return false;
221        }
222        ImageProxy otherImage = (ImageProxy) other;
223        return otherImage.getFormat() == getFormat() &&
224                otherImage.getWidth() == getWidth() &&
225                otherImage.getHeight() == getHeight() &&
226                otherImage.getTimestamp() == getTimestamp();
227    }
228
229    @Override
230    public int hashCode() {
231        return Objects.hashCode(getFormat(), getWidth(), getHeight(), getTimestamp());
232    }
233}
234