[go: nahoru, domu]

blob: 2b986b440d9a160ab041de7b2a163428588f906e [file] [log] [blame]
Trevor McGuirec63cd732019-02-06 13:12:50 -08001/*
2 * Copyright (C) 2019 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 androidx.camera.core;
18
19import static com.google.common.truth.Truth.assertThat;
20import static org.mockito.Matchers.anyObject;
21import static org.mockito.Matchers.anyString;
22import static org.mockito.Matchers.eq;
23import static org.mockito.Mockito.mock;
24import static org.mockito.Mockito.verify;
25import static org.mockito.Mockito.when;
26
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.graphics.ImageFormat;
30import android.os.Handler;
31import android.os.HandlerThread;
32import android.support.annotation.Nullable;
33import android.util.Base64;
34import android.util.Rational;
35import androidx.camera.core.ImageSaver.OnImageSavedListener;
36import androidx.camera.core.ImageSaver.SaveError;
37import androidx.test.runner.AndroidJUnit4;
38import java.io.File;
39import java.io.IOException;
40import java.nio.ByteBuffer;
41import java.util.concurrent.Semaphore;
42import org.junit.After;
43import org.junit.Before;
44import org.junit.Test;
45import org.junit.runner.RunWith;
46import org.mockito.Mock;
47
48@RunWith(AndroidJUnit4.class)
49public class ImageSaverAndroidTest {
50
51 private static final int WIDTH = 160;
52 private static final int HEIGHT = 120;
53 private static final int Y_PIXEL_STRIDE = 1;
54 private static final int Y_ROW_STRIDE = WIDTH;
55 private static final int UV_PIXEL_STRIDE = 1;
56 private static final int UV_ROW_STRIDE = WIDTH / 2;
57
58 // The image used here has a YUV_420_888 format.
59 @Mock private final ImageProxy mockYuvImage = mock(ImageProxy.class);
60 @Mock private final ImageProxy.PlaneProxy yPlane = mock(ImageProxy.PlaneProxy.class);
61 @Mock private final ImageProxy.PlaneProxy uPlane = mock(ImageProxy.PlaneProxy.class);
62 @Mock private final ImageProxy.PlaneProxy vPlane = mock(ImageProxy.PlaneProxy.class);
63 private final ByteBuffer yBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT);
64 private final ByteBuffer uBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4);
65 private final ByteBuffer vBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4);
66
67 @Mock private final ImageProxy mockJpegImage = mock(ImageProxy.class);
68 @Mock private final ImageProxy.PlaneProxy jpegDataPlane = mock(ImageProxy.PlaneProxy.class);
69 private final String jpegImageDataBase64 =
70 "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
71 + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
72 + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAB4AKADASIA"
73 + "AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA"
74 + "AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3"
75 + "ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm"
76 + "p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA"
77 + "AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx"
78 + "BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK"
79 + "U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3"
80 + "uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD/AD/6"
81 + "KKK/8/8AP/P/AAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii"
82 + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKA"
83 + "CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK"
84 + "KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoo"
85 + "ooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii"
86 + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k=";
87 private final ByteBuffer jpegDataBuffer =
88 ByteBuffer.wrap(Base64.decode(jpegImageDataBase64, Base64.DEFAULT));
89
90 private final Semaphore semaphore = new Semaphore(0);
91 private final ImageSaver.OnImageSavedListener mockListener =
92 mock(ImageSaver.OnImageSavedListener.class);
93 private final ImageSaver.OnImageSavedListener syncListener =
94 new OnImageSavedListener() {
95 @Override
96 public void onImageSaved(File file) {
97 mockListener.onImageSaved(file);
98 semaphore.release();
99 }
100
101 @Override
102 public void onError(SaveError saveError, String message, @Nullable Throwable cause) {
103 mockListener.onError(saveError, message, cause);
104 semaphore.release();
105 }
106 };
107
108 private HandlerThread backgroundThread;
109 private Handler backgroundHandler;
110
111 @Before
112 public void setup() {
113 // The YUV image's behavior.
114 when(mockYuvImage.getFormat()).thenReturn(ImageFormat.YUV_420_888);
115 when(mockYuvImage.getWidth()).thenReturn(WIDTH);
116 when(mockYuvImage.getHeight()).thenReturn(HEIGHT);
117
118 when(yPlane.getBuffer()).thenReturn(yBuffer);
119 when(yPlane.getPixelStride()).thenReturn(Y_PIXEL_STRIDE);
120 when(yPlane.getRowStride()).thenReturn(Y_ROW_STRIDE);
121
122 when(uPlane.getBuffer()).thenReturn(uBuffer);
123 when(uPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE);
124 when(uPlane.getRowStride()).thenReturn(UV_ROW_STRIDE);
125
126 when(vPlane.getBuffer()).thenReturn(vBuffer);
127 when(vPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE);
128 when(vPlane.getRowStride()).thenReturn(UV_ROW_STRIDE);
129 when(mockYuvImage.getPlanes()).thenReturn(new ImageProxy.PlaneProxy[] {yPlane, uPlane, vPlane});
130
131 // The JPEG image's behavior
132 when(mockJpegImage.getFormat()).thenReturn(ImageFormat.JPEG);
133 when(mockJpegImage.getWidth()).thenReturn(WIDTH);
134 when(mockJpegImage.getHeight()).thenReturn(HEIGHT);
135
136 when(jpegDataPlane.getBuffer()).thenReturn(jpegDataBuffer);
137 when(mockJpegImage.getPlanes()).thenReturn(new ImageProxy.PlaneProxy[] {jpegDataPlane});
138
139 // Set up a background thread/handler for callbacks
140 backgroundThread = new HandlerThread("CallbackThread");
141 backgroundThread.start();
142 backgroundHandler = new Handler(backgroundThread.getLooper());
143 }
144
145 @After
146 public void tearDown() {
147 backgroundThread.quitSafely();
148 }
149
150 private ImageSaver getDefaultImageSaver(ImageProxy image, File file) {
151 return new ImageSaver(
152 image,
153 file,
154 /*orientation=*/ 0,
155 /*reversedHorizontal=*/ false,
156 /*reversedVertical=*/ false,
157 /*location=*/ null,
158 /*cropAspectRatio=*/ null,
159 syncListener,
160 backgroundHandler);
161 }
162
163 @Test
164 public void canSaveYuvImage() throws InterruptedException, IOException {
165 File saveLocation = File.createTempFile("test", ".jpg");
166 saveLocation.deleteOnExit();
167
168 ImageSaver imageSaver = getDefaultImageSaver(mockYuvImage, saveLocation);
169
170 imageSaver.run();
171
172 semaphore.acquire();
173
174 verify(mockListener).onImageSaved(anyObject());
175 }
176
177 @Test
178 public void canSaveJpegImage() throws InterruptedException, IOException {
179 File saveLocation = File.createTempFile("test", ".jpg");
180 saveLocation.deleteOnExit();
181
182 ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
183
184 imageSaver.run();
185
186 semaphore.acquire();
187
188 verify(mockListener).onImageSaved(anyObject());
189 }
190
191 @Test
192 public void errorCallbackWillBeCalledOnInvalidPath() throws InterruptedException, IOException {
193 // Invalid filename should cause error
194 File saveLocation = new File("/not/a/real/path.jpg");
195
196 ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
197
198 imageSaver.run();
199
200 semaphore.acquire();
201
202 verify(mockListener).onError(eq(SaveError.FILE_IO_FAILED), anyString(), anyObject());
203 }
204
205 @Test
206 public void imageIsClosedOnSuccess() throws InterruptedException, IOException {
207 File saveLocation = File.createTempFile("test", ".jpg");
208 saveLocation.deleteOnExit();
209
210 ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
211
212 imageSaver.run();
213
214 semaphore.acquire();
215
216 verify(mockJpegImage).close();
217 }
218
219 @Test
220 public void imageIsClosedOnError() throws InterruptedException, IOException {
221 // Invalid filename should cause error
222 File saveLocation = new File("/not/a/real/path.jpg");
223
224 ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
225
226 imageSaver.run();
227
228 semaphore.acquire();
229
230 verify(mockJpegImage).close();
231 }
232
233 private void imageCanBeCropped(ImageProxy image) throws InterruptedException, IOException {
234 File saveLocation = File.createTempFile("test", ".jpg");
235 saveLocation.deleteOnExit();
236
237 Rational viewRatio = new Rational(1, 1);
238
239 ImageSaver imageSaver = new ImageSaver(
240 image,
241 saveLocation,
242 /*orientation=*/ 0,
243 /*reversedHorizontal=*/ false,
244 /*reversedVertical=*/ false,
245 /*location=*/ null,
246 /*cropAspectRatio=*/ viewRatio,
247 syncListener,
248 backgroundHandler
249 );
250 imageSaver.run();
251
252 semaphore.acquire();
253
254 Bitmap bitmap = BitmapFactory.decodeFile(saveLocation.getPath());
255 assertThat(bitmap.getWidth()).isEqualTo(bitmap.getHeight());
256 }
257
258 @Test
259 public void jpegImageCanBeCropped() throws InterruptedException, IOException {
260 imageCanBeCropped(mockJpegImage);
261 }
262
263 @Test
264 public void yuvImageCanBeCropped() throws InterruptedException, IOException {
265 imageCanBeCropped(mockYuvImage);
266 }
267}