1 /*
2 * Copyright 2017 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
18 #define LOG_TAG "BitmapTest"
19
20 #include <jni.h>
21 #include <android/bitmap.h>
22 #include <android/data_space.h>
23 #include <android/hardware_buffer.h>
24 #include <android/hardware_buffer_jni.h>
25
26 #include "NativeTestHelpers.h"
27
28 #include <initializer_list>
29 #include <limits>
30
31 static jclass gOutputStream_class;
32 static jmethodID gOutputStream_writeMethodID;
33
validateBitmapInfo(JNIEnv * env,jclass,jobject jbitmap,jint width,jint height,jboolean is565)34 static void validateBitmapInfo(JNIEnv* env, jclass, jobject jbitmap, jint width, jint height,
35 jboolean is565) {
36 AndroidBitmapInfo info;
37 int err = 0;
38 err = AndroidBitmap_getInfo(env, jbitmap, &info);
39 ASSERT_EQ(ANDROID_BITMAP_RESULT_SUCCESS, err);
40 ASSERT_TRUE(width >= 0 && height >= 0);
41 ASSERT_EQ((uint32_t) width, info.width);
42 ASSERT_EQ((uint32_t) height, info.height);
43 int32_t format = is565 ? ANDROID_BITMAP_FORMAT_RGB_565 : ANDROID_BITMAP_FORMAT_RGBA_8888;
44 ASSERT_EQ(format, info.format);
45 }
46
validateNdkAccessFails(JNIEnv * env,jclass,jobject jbitmap)47 static void validateNdkAccessFails(JNIEnv* env, jclass, jobject jbitmap) {
48 void* pixels = nullptr;
49 int err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
50 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
51
52 int32_t dataSpace = AndroidBitmap_getDataSpace(env, jbitmap);
53 ASSERT_EQ(ADATASPACE_UNKNOWN, dataSpace);
54
55 AHardwareBuffer* buffer;
56 err = AndroidBitmap_getHardwareBuffer(env, jbitmap, &buffer);
57 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
58 }
59
fillRgbaHardwareBuffer(JNIEnv * env,jclass,jobject hwBuffer)60 static void fillRgbaHardwareBuffer(JNIEnv* env, jclass, jobject hwBuffer) {
61 AHardwareBuffer* hardware_buffer = AHardwareBuffer_fromHardwareBuffer(env, hwBuffer);
62 AHardwareBuffer_Desc description;
63 AHardwareBuffer_describe(hardware_buffer, &description);
64 ASSERT_EQ(AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, description.format);
65
66 uint8_t* rgbaBytes;
67 AHardwareBuffer_lock(hardware_buffer,
68 AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
69 -1,
70 nullptr,
71 reinterpret_cast<void**>(&rgbaBytes));
72 int c = 0;
73 for (int y = 0; y < description.width; ++y) {
74 for (int x = 0; x < description.height; ++x) {
75 rgbaBytes[c++] = static_cast<uint8_t>(x % 255);
76 rgbaBytes[c++] = static_cast<uint8_t>(y % 255);
77 rgbaBytes[c++] = 42;
78 rgbaBytes[c++] = 255;
79 }
80 }
81 AHardwareBuffer_unlock(hardware_buffer, nullptr);
82 }
83
getFormat(JNIEnv * env,jclass,jobject jbitmap)84 static jint getFormat(JNIEnv* env, jclass, jobject jbitmap) {
85 AndroidBitmapInfo info;
86 info.format = ANDROID_BITMAP_FORMAT_NONE;
87 int err = 0;
88 err = AndroidBitmap_getInfo(env, jbitmap, &info);
89 if (err != ANDROID_BITMAP_RESULT_SUCCESS) {
90 fail(env, "AndroidBitmap_getInfo failed, err=%d", err);
91 return ANDROID_BITMAP_FORMAT_NONE;
92 }
93 return info.format;
94 }
95
testNullBitmap(JNIEnv * env,jclass,jobject jbitmap)96 static void testNullBitmap(JNIEnv* env, jclass, jobject jbitmap) {
97 ASSERT_NE(nullptr, env);
98 AndroidBitmapInfo info;
99 int err = AndroidBitmap_getInfo(env, nullptr, &info);
100 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
101
102 err = AndroidBitmap_getInfo(env, jbitmap, nullptr);
103 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
104
105 err = AndroidBitmap_getInfo(nullptr, jbitmap, &info);
106 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
107
108 void* pixels = nullptr;
109 err = AndroidBitmap_lockPixels(env, nullptr, &pixels);
110 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
111
112 err = AndroidBitmap_lockPixels(env, jbitmap, nullptr);
113 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
114
115 err = AndroidBitmap_lockPixels(nullptr, jbitmap, &pixels);
116 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
117
118 err = AndroidBitmap_unlockPixels(env, nullptr);
119 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
120
121 err = AndroidBitmap_unlockPixels(nullptr, jbitmap);
122 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
123
124 int32_t dataSpace = AndroidBitmap_getDataSpace(env, nullptr);
125 ASSERT_EQ(dataSpace, ADATASPACE_UNKNOWN);
126
127 dataSpace = AndroidBitmap_getDataSpace(nullptr, jbitmap);
128 ASSERT_EQ(dataSpace, ADATASPACE_UNKNOWN);
129
130 err = AndroidBitmap_getHardwareBuffer(env, jbitmap, nullptr);
131 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
132
133 AHardwareBuffer* buffer;
134 err = AndroidBitmap_getHardwareBuffer(env, nullptr, &buffer);
135 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
136
137 err = AndroidBitmap_getHardwareBuffer(nullptr, jbitmap, &buffer);
138 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
139 }
140
testInfo(JNIEnv * env,jclass,jobject jbitmap,jint androidBitmapFormat,jint width,jint height,jboolean hasAlpha,jboolean premultiplied,jboolean hardware)141 static void testInfo(JNIEnv* env, jclass, jobject jbitmap, jint androidBitmapFormat,
142 jint width, jint height, jboolean hasAlpha, jboolean premultiplied,
143 jboolean hardware) {
144 AndroidBitmapInfo info;
145 int err = AndroidBitmap_getInfo(env, jbitmap, &info);
146 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
147
148 ASSERT_EQ(androidBitmapFormat, info.format);
149 ASSERT_EQ(width, info.width);
150 ASSERT_EQ(height, info.height);
151
152 int ndkAlpha = (info.flags << ANDROID_BITMAP_FLAGS_ALPHA_SHIFT)
153 & ANDROID_BITMAP_FLAGS_ALPHA_MASK;
154 if (!hasAlpha) {
155 ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE);
156 } else if (premultiplied) {
157 ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_PREMUL);
158 } else {
159 ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL);
160 }
161
162 bool ndkHardware = info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE;
163 ASSERT_EQ(ndkHardware, hardware);
164
165 AHardwareBuffer* buffer;
166 err = AndroidBitmap_getHardwareBuffer(env, jbitmap, &buffer);
167 if (hardware) {
168 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
169 ASSERT_NE(buffer, nullptr);
170 AHardwareBuffer_release(buffer);
171 } else {
172 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
173 ASSERT_EQ(buffer, nullptr);
174 }
175
176 void* pixels = nullptr;
177 err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
178 if (hardware) {
179 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
180 } else {
181 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
182 err = AndroidBitmap_unlockPixels(env, jbitmap);
183 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
184 }
185 }
186
getDataSpace(JNIEnv * env,jclass,jobject jbitmap)187 static jint getDataSpace(JNIEnv* env, jclass, jobject jbitmap) {
188 return AndroidBitmap_getDataSpace(env, jbitmap);
189 }
190
191 /**
192 * Class for writing to a java.io.OutputStream. Passed to
193 * AndroidBitmap_compress. This is modelled after SkJavaOutputStream.
194 */
195 class Context {
196 public:
Context(JNIEnv * env,jobject outputStream,jbyteArray storage)197 Context(JNIEnv* env, jobject outputStream, jbyteArray storage)
198 : mEnv(env)
199 , mOutputStream(outputStream)
200 , mStorage(storage)
201 , mCapacity(mEnv->GetArrayLength(mStorage))
202 {}
203
compress(const void * data,size_t size)204 bool compress(const void* data, size_t size) {
205 while (size > 0) {
206 jint requested = 0;
207 if (size > static_cast<size_t>(mCapacity)) {
208 requested = mCapacity;
209 } else {
210 // This is safe because |requested| is clamped to (jint) mCapacity.
211 requested = static_cast<jint>(size);
212 }
213
214 mEnv->SetByteArrayRegion(mStorage, 0, requested,
215 reinterpret_cast<const jbyte*>(data));
216 if (mEnv->ExceptionCheck()) {
217 mEnv->ExceptionDescribe();
218 mEnv->ExceptionClear();
219 ALOGE("SetByteArrayRegion threw an exception!");
220 return false;
221 }
222
223 mEnv->CallVoidMethod(mOutputStream, gOutputStream_writeMethodID, mStorage, 0,
224 requested);
225 if (mEnv->ExceptionCheck()) {
226 mEnv->ExceptionDescribe();
227 mEnv->ExceptionClear();
228 ALOGE("write threw an exception!");
229 return false;
230 }
231
232 data = (void*)((char*) data + requested);
233 size -= requested;
234 }
235 return true;
236 }
237
238 private:
239 JNIEnv* mEnv;
240 jobject mOutputStream;
241 jbyteArray mStorage;
242 jint mCapacity;
243
244 Context(const Context& other) = delete;
245 Context& operator=(const Context& other) = delete;
246 };
247
compressWriteFn(void * userContext,const void * data,size_t size)248 static bool compressWriteFn(void* userContext, const void* data, size_t size) {
249 Context* c = reinterpret_cast<Context*>(userContext);
250 return c->compress(data, size);
251 }
252
253 #define EXPECT_EQ(msg, a, b) \
254 if ((a) != (b)) { \
255 ALOGE(msg); \
256 return false; \
257 }
258
compress(JNIEnv * env,jclass,jobject jbitmap,jint format,jint quality,jobject joutputStream,jbyteArray jstorage)259 static jboolean compress(JNIEnv* env, jclass, jobject jbitmap, jint format, jint quality,
260 jobject joutputStream, jbyteArray jstorage) {
261 AndroidBitmapInfo info;
262 int err = AndroidBitmap_getInfo(env, jbitmap, &info);
263 EXPECT_EQ("Failed to getInfo!", err, ANDROID_BITMAP_RESULT_SUCCESS);
264
265 void* pixels = nullptr;
266 err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
267 EXPECT_EQ("Failed to lockPixels!", err, ANDROID_BITMAP_RESULT_SUCCESS);
268
269 Context context(env, joutputStream, jstorage);
270 err = AndroidBitmap_compress(&info, AndroidBitmap_getDataSpace(env, jbitmap), pixels, format,
271 quality, &context, compressWriteFn);
272 if (AndroidBitmap_unlockPixels(env, jbitmap) != ANDROID_BITMAP_RESULT_SUCCESS) {
273 fail(env, "Failed to unlock pixels!");
274 }
275 return err == ANDROID_BITMAP_RESULT_SUCCESS;
276 }
277
278 constexpr AndroidBitmapCompressFormat gFormats[] = {
279 ANDROID_BITMAP_COMPRESS_FORMAT_JPEG,
280 ANDROID_BITMAP_COMPRESS_FORMAT_PNG,
281 ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY,
282 ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS,
283 };
284
285 constexpr auto kMaxInt32 = std::numeric_limits<int32_t>::max();
286
287 /**
288 * Class for generating invalid AndroidBitmapInfos based on a valid one.
289 */
290 class BadInfoGenerator {
291 public:
BadInfoGenerator(const AndroidBitmapInfo & info)292 BadInfoGenerator(const AndroidBitmapInfo& info)
293 : mOriginalInfo(info)
294 , mIter(0)
295 {}
296
297 /**
298 * Each call to this method will return a pointer to an invalid
299 * AndroidBitmapInfo, until it has run out of infos to generate, when it
300 * will return null.
301 */
next()302 AndroidBitmapInfo* next() {
303 mBadInfo = mOriginalInfo;
304 switch (mIter) {
305 case 0:
306 mBadInfo.width = -mBadInfo.width;
307 break;
308 case 1:
309 mBadInfo.height = -mBadInfo.height;
310 break;
311 case 2:
312 mBadInfo.width = - mBadInfo.width;
313 break;
314 case 3:
315 // AndroidBitmap_compress uses ANDROID_BITMAP_FLAGS_ALPHA_MASK
316 // to ignore bits outside of alpha, so the only invalid value is
317 // 3.
318 mBadInfo.flags = 3;
319 break;
320 case 4:
321 mBadInfo.stride = mBadInfo.stride / 2;
322 break;
323 case 5:
324 mBadInfo.format = ANDROID_BITMAP_FORMAT_NONE;
325 break;
326 case 6:
327 mBadInfo.format = ANDROID_BITMAP_FORMAT_RGBA_4444;
328 break;
329 case 7:
330 mBadInfo.format = -1;
331 break;
332 case 8:
333 mBadInfo.format = 2;
334 break;
335 case 9:
336 mBadInfo.format = 3;
337 break;
338 case 10:
339 mBadInfo.format = 5;
340 break;
341 case 11:
342 mBadInfo.format = 6;
343 break;
344 case 12:
345 mBadInfo.format = 10;
346 break;
347 case 13:
348 mBadInfo.width = static_cast<uint32_t>(kMaxInt32) + 1;
349 mBadInfo.height = 1;
350 break;
351 case 14:
352 mBadInfo.width = 1;
353 mBadInfo.height = static_cast<uint32_t>(kMaxInt32) + 1;
354 break;
355 case 15:
356 mBadInfo.width = 3;
357 mBadInfo.height = kMaxInt32 / 2;
358 break;
359 default:
360 return nullptr;
361 }
362 mIter++;
363 return &mBadInfo;
364 }
365 private:
366 const AndroidBitmapInfo& mOriginalInfo;
367 AndroidBitmapInfo mBadInfo;
368 int mIter;
369
370 BadInfoGenerator(const BadInfoGenerator&) = delete;
371 BadInfoGenerator& operator=(const BadInfoGenerator&) = delete;
372 };
373
testNdkCompressBadParameter(JNIEnv * env,jclass,jobject jbitmap,jobject joutputStream,jbyteArray jstorage)374 static void testNdkCompressBadParameter(JNIEnv* env, jclass, jobject jbitmap,
375 jobject joutputStream, jbyteArray jstorage) {
376 AndroidBitmapInfo info;
377 int err = AndroidBitmap_getInfo(env, jbitmap, &info);
378 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
379
380 void* pixels = nullptr;
381 err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
382 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
383
384 Context context(env, joutputStream, jstorage);
385 int32_t dataSpace = AndroidBitmap_getDataSpace(env, jbitmap);
386
387 // Bad info.
388 for (auto format : gFormats) {
389 err = AndroidBitmap_compress(nullptr, dataSpace, pixels, format, 100,
390 &context, compressWriteFn);
391 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
392 }
393
394 {
395 BadInfoGenerator gen(info);
396 while (const auto* badInfo = gen.next()) {
397 for (auto format : gFormats) {
398 err = AndroidBitmap_compress(badInfo, dataSpace, pixels, format, 100,
399 &context, compressWriteFn);
400 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
401 }
402 }
403 }
404
405 for (int32_t badDataSpace : std::initializer_list<int32_t>{ ADATASPACE_UNKNOWN, -1 }) {
406 for (auto format : gFormats) {
407 err = AndroidBitmap_compress(&info, badDataSpace, pixels, format, 100,
408 &context, compressWriteFn);
409 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
410 }
411 }
412
413 // Bad pixels
414 for (auto format : gFormats) {
415 err = AndroidBitmap_compress(&info, dataSpace, nullptr, format, 100,
416 &context, compressWriteFn);
417 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
418 }
419
420 // Bad formats
421 for (int32_t badFormat : { -1, 2, 5, 16 }) {
422 err = AndroidBitmap_compress(&info, dataSpace, pixels, badFormat, 100,
423 &context, compressWriteFn);
424 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
425 }
426
427 // Bad qualities
428 for (int32_t badQuality : { -1, 101, 1024 }) {
429 for (auto format : gFormats) {
430 err = AndroidBitmap_compress(&info, dataSpace, pixels, format, badQuality,
431 &context, compressWriteFn);
432 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
433 }
434 }
435
436 // Missing compress function
437 for (auto format : gFormats) {
438 err = AndroidBitmap_compress(&info, dataSpace, pixels, format, 100,
439 &context, nullptr);
440 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
441 }
442
443 // No reason to force client to have a context. (Maybe they'll use a global
444 // variable?) Demonstrate that it's possible to use a null context.
445 auto emptyCompress = [](void*, const void*, size_t) {
446 return true;
447 };
448 for (auto format : gFormats) {
449 err = AndroidBitmap_compress(&info, dataSpace, pixels, format, 100,
450 nullptr, emptyCompress);
451 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
452 }
453 }
454
455 static JNINativeMethod gMethods[] = {
456 { "nValidateBitmapInfo", "(Landroid/graphics/Bitmap;IIZ)V",
457 (void*) validateBitmapInfo },
458 { "nValidateNdkAccessFails", "(Landroid/graphics/Bitmap;)V",
459 (void*) validateNdkAccessFails },
460 { "nFillRgbaHwBuffer", "(Landroid/hardware/HardwareBuffer;)V",
461 (void*) fillRgbaHardwareBuffer },
462 { "nGetFormat", "(Landroid/graphics/Bitmap;)I", (void*) getFormat },
463 { "nTestNullBitmap", "(Landroid/graphics/Bitmap;)V", (void*) testNullBitmap },
464 { "nTestInfo", "(Landroid/graphics/Bitmap;IIIZZZ)V", (void*) testInfo },
465 { "nGetDataSpace", "(Landroid/graphics/Bitmap;)I", (void*) getDataSpace },
466 { "nCompress", "(Landroid/graphics/Bitmap;IILjava/io/OutputStream;[B)Z", (void*) compress },
467 { "nTestNdkCompressBadParameter", "(Landroid/graphics/Bitmap;Ljava/io/OutputStream;[B)V",
468 (void*) testNdkCompressBadParameter },
469 };
470
register_android_graphics_cts_BitmapTest(JNIEnv * env)471 int register_android_graphics_cts_BitmapTest(JNIEnv* env) {
472 gOutputStream_class = reinterpret_cast<jclass>(env->NewGlobalRef(
473 env->FindClass("java/io/OutputStream")));
474 if (!gOutputStream_class) {
475 ALOGE("Could not find (or create a global ref on) OutputStream!");
476 return JNI_ERR;
477 }
478 gOutputStream_writeMethodID = env->GetMethodID(gOutputStream_class, "write", "([BII)V");
479 if (!gOutputStream_writeMethodID) {
480 ALOGE("Could not find method write()!");
481 return JNI_ERR;
482 }
483 jclass clazz = env->FindClass("android/graphics/cts/BitmapTest");
484 return env->RegisterNatives(clazz, gMethods,
485 sizeof(gMethods) / sizeof(JNINativeMethod));
486 }
487