blob: 829855aeca5baceeb53d6876925ce05cb90369b8 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#define LOG_TAG "AImageDecoderTest"
#include <jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/data_space.h>
#include <android/bitmap.h>
#include <android/imagedecoder.h>
#include <android/rect.h>
#include "NativeTestHelpers.h"
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <limits>
#include <memory>
#include <stdio.h>
#include <unistd.h>
#include <vector>
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
using AssetCloser = std::unique_ptr<AAsset, decltype(&AAsset_close)>;
using DecoderDeleter = std::unique_ptr<AImageDecoder, decltype(&AImageDecoder_delete)>;
static void testEmptyCreate(JNIEnv* env, jclass) {
AImageDecoder* decoderPtr = nullptr;
for (AImageDecoder** outDecoder : { &decoderPtr, (AImageDecoder**) nullptr }) {
for (AAsset* asset : { nullptr }) {
int result = AImageDecoder_createFromAAsset(asset, outDecoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
if (outDecoder) {
ASSERT_EQ(nullptr, *outDecoder);
}
}
for (int fd : { 0, -1 }) {
int result = AImageDecoder_createFromFd(fd, outDecoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
if (outDecoder) {
ASSERT_EQ(nullptr, *outDecoder);
}
}
auto testEmptyBuffer = [env, outDecoder](void* buffer, size_t length) {
int result = AImageDecoder_createFromBuffer(buffer, length, outDecoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
if (outDecoder) {
ASSERT_EQ(nullptr, *outDecoder);
}
};
testEmptyBuffer(nullptr, 0);
char buf[4];
testEmptyBuffer(buf, 0);
}
}
static AAsset* openAsset(JNIEnv* env, jobject jAssets, jstring jFile, int mode) {
AAssetManager* nativeManager = AAssetManager_fromJava(env, jAssets);
const char* file = env->GetStringUTFChars(jFile, nullptr);
AAsset* asset = AAssetManager_open(nativeManager, file, mode);
if (!asset) {
ALOGE("Could not open %s", file);
} else {
ALOGD("Testing %s", file);
}
env->ReleaseStringUTFChars(jFile, file);
return asset;
}
static void testNullDecoder(JNIEnv* env, jclass, jobject jAssets, jstring jFile) {
AAsset* asset = openAsset(env, jAssets, jFile, AASSET_MODE_BUFFER);
ASSERT_NE(asset, nullptr);
AssetCloser assetCloser(asset, AAsset_close);
AImageDecoder_delete(nullptr);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
{
int result = AImageDecoder_createFromAAsset(asset, nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
{
const void* buffer = AAsset_getBuffer(asset);
ASSERT_NE(buffer, nullptr);
int result = AImageDecoder_createFromBuffer(buffer, AAsset_getLength(asset), nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
{
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
ASSERT_GT(fd, 0);
off_t offset = ::lseek(fd, start, SEEK_SET);
ASSERT_EQ(start, offset);
int result = AImageDecoder_createFromFd(fd, nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
close(fd);
}
{
auto stride = AImageDecoder_getMinimumStride(nullptr);
ASSERT_EQ(0, stride);
}
{
char buf[4];
int result = AImageDecoder_decodeImage(nullptr, buf, 4, 4);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
{
int result = AImageDecoder_setAndroidBitmapFormat(nullptr, ANDROID_BITMAP_FORMAT_RGBA_8888);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
auto format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(nullptr);
ASSERT_EQ(ANDROID_BITMAP_FORMAT_NONE, format);
}
{
int result = AImageDecoder_setUnpremultipliedRequired(nullptr, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
int alpha = AImageDecoderHeaderInfo_getAlphaFlags(nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, alpha);
}
ASSERT_EQ(0, AImageDecoderHeaderInfo_getWidth(nullptr));
ASSERT_EQ(0, AImageDecoderHeaderInfo_getHeight(nullptr));
ASSERT_EQ(nullptr, AImageDecoderHeaderInfo_getMimeType(nullptr));
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderHeaderInfo_getDataSpace(nullptr));
{
int result = AImageDecoder_setTargetSize(nullptr, 1, 1);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
{
ARect rect {0, 0, 10, 10};
int result = AImageDecoder_setCrop(nullptr, rect);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
for (ADataSpace dataSpace : { ADATASPACE_UNKNOWN, ADATASPACE_SCRGB_LINEAR, ADATASPACE_SRGB,
ADATASPACE_SCRGB, ADATASPACE_DISPLAY_P3, ADATASPACE_BT2020_PQ,
ADATASPACE_ADOBE_RGB, ADATASPACE_BT2020, ADATASPACE_BT709,
ADATASPACE_DCI_P3, ADATASPACE_SRGB_LINEAR }) {
int result = AImageDecoder_setDataSpace(nullptr, dataSpace);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
ASSERT_FALSE(AImageDecoder_isAnimated(nullptr));
{
int result = AImageDecoder_getRepeatCount(nullptr);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
#pragma clang diagnostic pop
}
static void testInfo(JNIEnv* env, jclass, jlong imageDecoderPtr, jint width, jint height,
jstring jMimeType, jboolean isF16, jint dataSpace) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
ASSERT_NE(decoder, nullptr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
int32_t actualWidth = AImageDecoderHeaderInfo_getWidth(info);
ASSERT_EQ(width, actualWidth);
int32_t actualHeight = AImageDecoderHeaderInfo_getHeight(info);
ASSERT_EQ(height, actualHeight);
const char* mimeType = env->GetStringUTFChars(jMimeType, nullptr);
ASSERT_NE(mimeType, nullptr);
ASSERT_EQ(0, strcmp(mimeType, AImageDecoderHeaderInfo_getMimeType(info)));
env->ReleaseStringUTFChars(jMimeType, mimeType);
auto format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(info);
if (isF16) {
ASSERT_EQ(ANDROID_BITMAP_FORMAT_RGBA_F16, format);
} else {
ASSERT_EQ(ANDROID_BITMAP_FORMAT_RGBA_8888, format);
}
ASSERT_EQ(dataSpace, AImageDecoderHeaderInfo_getDataSpace(info));
}
static jlong openAssetNative(JNIEnv* env, jclass, jobject jAssets, jstring jFile) {
// FIXME: Test the other modes? Or more to the point, pass in the mode? It
// seems that when we want a buffer we should use AASSET_MODE_BUFFER.
AAsset* asset = openAsset(env, jAssets, jFile, AASSET_MODE_UNKNOWN);
if (!asset) {
fail(env, "Failed to open native asset!");
}
return reinterpret_cast<jlong>(asset);
}
static void closeAsset(JNIEnv*, jclass, jlong asset) {
AAsset_close(reinterpret_cast<AAsset*>(asset));
}
static jlong createFromAsset(JNIEnv* env, jclass, jlong asset) {
AImageDecoder* decoder = nullptr;
int result = AImageDecoder_createFromAAsset(reinterpret_cast<AAsset*>(asset), &decoder);
if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
fail(env, "Failed to create AImageDecoder!");
}
return reinterpret_cast<jlong>(decoder);
}
static jlong createFromFd(JNIEnv* env, jclass, int fd) {
AImageDecoder* decoder = nullptr;
int result = AImageDecoder_createFromFd(fd, &decoder);
if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
fail(env, "Failed to create AImageDecoder!");
}
return reinterpret_cast<jlong>(decoder);
}
static jlong createFromAssetFd(JNIEnv* env, jclass, jlong assetPtr) {
AAsset* asset = reinterpret_cast<AAsset*>(assetPtr);
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
if (fd <= 0) {
fail(env, "Failed to open file descriptor!");
return -1;
}
off_t offset = ::lseek(fd, start, SEEK_SET);
if (offset != start) {
fail(env, "Failed to seek file descriptor!");
return -1;
}
return createFromFd(env, nullptr, fd);
}
static jlong createFromAssetBuffer(JNIEnv* env, jclass, jlong assetPtr) {
AAsset* asset = reinterpret_cast<AAsset*>(assetPtr);
const void* buffer = AAsset_getBuffer(asset);
if (!buffer) {
fail(env, "AAsset_getBuffer failed!");
return -1;
}
AImageDecoder* decoder = nullptr;
int result = AImageDecoder_createFromBuffer(buffer, AAsset_getLength(asset), &decoder);
if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
fail(env, "AImageDecoder_createFromBuffer failed!");
return -1;
}
return reinterpret_cast<jlong>(decoder);
}
static void testCreateIncomplete(JNIEnv* env, jclass, jobject jAssets, jstring jFile,
jint truncatedLength) {
AAsset* asset = openAsset(env, jAssets, jFile, AASSET_MODE_UNKNOWN);
ASSERT_NE(asset, nullptr);
AssetCloser assetCloser(asset, AAsset_close);
const void* buffer = AAsset_getBuffer(asset);
ASSERT_NE(buffer, nullptr);
AImageDecoder* decoder;
int result = AImageDecoder_createFromBuffer(buffer, truncatedLength, &decoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INCOMPLETE, result);
ASSERT_EQ(decoder, nullptr);
}
static void testCreateUnsupported(JNIEnv* env, jclass, jobject jAssets, jstring jFile) {
AAsset* asset = openAsset(env, jAssets, jFile, AASSET_MODE_UNKNOWN);
ASSERT_NE(asset, nullptr);
AssetCloser assetCloser(asset, AAsset_close);
AImageDecoder* decoder;
int result = AImageDecoder_createFromAAsset(asset, &decoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT, result);
ASSERT_EQ(decoder, nullptr);
}
static void testSetFormat(JNIEnv* env, jclass, jlong imageDecoderPtr,
jboolean isF16, jboolean isGray) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
// Store the format so we can ensure that it doesn't change when we call
// AImageDecoder_setAndroidBitmapFormat.
const auto format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(info);
if (isF16) {
ASSERT_EQ(ANDROID_BITMAP_FORMAT_RGBA_F16, format);
} else {
ASSERT_EQ(ANDROID_BITMAP_FORMAT_RGBA_8888, format);
}
int result = AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_A_8);
if (isGray) {
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
} else {
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
}
ASSERT_EQ(format, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
result = AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGB_565);
int alpha = AImageDecoderHeaderInfo_getAlphaFlags(info);
if (alpha == ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
} else {
ASSERT_EQ(ANDROID_BITMAP_FLAGS_ALPHA_PREMUL, alpha);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
}
ASSERT_EQ(format, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
for (auto newFormat : { ANDROID_BITMAP_FORMAT_RGBA_4444, ANDROID_BITMAP_FORMAT_NONE }) {
result = AImageDecoder_setAndroidBitmapFormat(decoder, newFormat);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
ASSERT_EQ(format, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
}
for (auto newFormat : { ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16 }) {
result = AImageDecoder_setAndroidBitmapFormat(decoder, newFormat);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_EQ(format, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
}
for (auto invalidFormat : { -1, 42, 67 }) {
result = AImageDecoder_setAndroidBitmapFormat(decoder, invalidFormat);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
ASSERT_EQ(format, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
}
}
static void testSetUnpremul(JNIEnv* env, jclass, jlong imageDecoderPtr, jboolean hasAlpha) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
// Store the alpha so we can ensure that it doesn't change when we call
// AImageDecoder_setUnpremultipliedRequired.
const int alpha = AImageDecoderHeaderInfo_getAlphaFlags(info);
if (hasAlpha) {
ASSERT_EQ(ANDROID_BITMAP_FLAGS_ALPHA_PREMUL, alpha);
} else {
ASSERT_EQ(ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE, alpha);
}
for (bool required : { true, false }) {
int result = AImageDecoder_setUnpremultipliedRequired(decoder, required);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_EQ(alpha, AImageDecoderHeaderInfo_getAlphaFlags(info));
}
}
static int bytesPerPixel(int32_t format) {
switch (format) {
case ANDROID_BITMAP_FORMAT_RGBA_8888:
return 4;
case ANDROID_BITMAP_FORMAT_RGB_565:
return 2;
case ANDROID_BITMAP_FORMAT_A_8:
return 1;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return 8;
case ANDROID_BITMAP_FORMAT_NONE:
case ANDROID_BITMAP_FORMAT_RGBA_4444:
default:
return 0;
}
}
static void testGetMinimumStride(JNIEnv* env, jclass, jlong imageDecoderPtr,
jboolean isF16, jboolean isGray) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
size_t stride = AImageDecoder_getMinimumStride(decoder);
if (isF16) {
ASSERT_EQ(bytesPerPixel(ANDROID_BITMAP_FORMAT_RGBA_F16) * width, stride);
} else {
ASSERT_EQ(bytesPerPixel(ANDROID_BITMAP_FORMAT_RGBA_8888) * width, stride);
}
auto setFormatAndCheckStride = [env, decoder, width, &stride](AndroidBitmapFormat format) {
int result = AImageDecoder_setAndroidBitmapFormat(decoder, format);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
stride = AImageDecoder_getMinimumStride(decoder);
ASSERT_EQ(bytesPerPixel(format) * width, stride);
};
int alpha = AImageDecoderHeaderInfo_getAlphaFlags(info);
if (alpha == ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
setFormatAndCheckStride(ANDROID_BITMAP_FORMAT_RGB_565);
}
if (isGray) {
setFormatAndCheckStride(ANDROID_BITMAP_FORMAT_A_8);
}
for (auto newFormat : { ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16 }) {
setFormatAndCheckStride(newFormat);
}
for (auto badFormat : { ANDROID_BITMAP_FORMAT_RGBA_4444, ANDROID_BITMAP_FORMAT_NONE }) {
int result = AImageDecoder_setAndroidBitmapFormat(decoder, badFormat);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
// Stride is unchanged.
ASSERT_EQ(stride, AImageDecoder_getMinimumStride(decoder));
}
}
static bool bitmapsEqual(size_t minStride, int height,
void* pixelsA, size_t strideA,
void* pixelsB, size_t strideB) {
for (int y = 0; y < height; ++y) {
auto* rowA = reinterpret_cast<char*>(pixelsA) + strideA * y;
auto* rowB = reinterpret_cast<char*>(pixelsB) + strideB * y;
if (memcmp(rowA, rowB, minStride) != 0) {
ALOGE("Bitmap mismatch on line %i", y);
return false;
}
}
return true;
}
#define EXPECT_EQ(msg, a, b) \
if ((a) != (b)) { \
ALOGE(msg); \
return false; \
}
#define EXPECT_GE(msg, a, b) \
if ((a) < (b)) { \
ALOGE(msg); \
return false; \
}
static bool bitmapsEqual(JNIEnv* env, jobject jbitmap, int32_t androidBitmapFormat,
int width, int height, int alphaFlags, size_t minStride,
void* pixelsA, size_t strideA) {
AndroidBitmapInfo jInfo;
int bitmapResult = AndroidBitmap_getInfo(env, jbitmap, &jInfo);
EXPECT_EQ("Failed to getInfo on Bitmap", ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
EXPECT_EQ("Wrong format", jInfo.format, androidBitmapFormat);
// If the image is truly opaque, the Java Bitmap will report OPAQUE, even if
// the AImageDecoder requested PREMUL/UNPREMUL. In that case, it is okay for
// the two to disagree. We must ensure that we don't end up with one PREMUL
// and the other UNPREMUL, though.
int jAlphaFlags = jInfo.flags & ANDROID_BITMAP_FLAGS_ALPHA_MASK;
if (jAlphaFlags != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE
&& alphaFlags != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
EXPECT_EQ("Wrong alpha type", jAlphaFlags, alphaFlags);
}
EXPECT_EQ("Wrong width", jInfo.width, width);
EXPECT_EQ("Wrong height", jInfo.height, height);
EXPECT_GE("Stride too small", jInfo.stride, minStride);
void* jPixels;
bitmapResult = AndroidBitmap_lockPixels(env, jbitmap, &jPixels);
EXPECT_EQ("Failed to lockPixels", ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
bool equal = bitmapsEqual(minStride, height, pixelsA, strideA, jPixels, jInfo.stride);
bitmapResult = AndroidBitmap_unlockPixels(env, jbitmap);
EXPECT_EQ("Failed to unlockPixels", ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
return equal;
}
static void testDecode(JNIEnv* env, jclass, jlong imageDecoderPtr,
jint androidBitmapFormat, jboolean unpremul, jobject jbitmap) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
int result;
int alphaFlags = AImageDecoderHeaderInfo_getAlphaFlags(info);
if (androidBitmapFormat == ANDROID_BITMAP_FORMAT_NONE) {
androidBitmapFormat = AImageDecoderHeaderInfo_getAndroidBitmapFormat(info);
} else {
result = AImageDecoder_setAndroidBitmapFormat(decoder, androidBitmapFormat);
if (androidBitmapFormat == ANDROID_BITMAP_FORMAT_RGB_565) {
if (alphaFlags != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
// The caller only passes down the Bitmap if it is opaque.
ASSERT_EQ(nullptr, jbitmap);
return;
}
}
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
}
if (unpremul) {
result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
alphaFlags = ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL;
}
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
const int32_t height = AImageDecoderHeaderInfo_getHeight(info);
size_t minStride = AImageDecoder_getMinimumStride(decoder);
size_t size = minStride * height;
std::unique_ptr<char[]> pixelsBuffer(new char[size]);
void* pixels = (void*) pixelsBuffer.get();
{
// Try some invalid parameters.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size - minStride);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
result = AImageDecoder_decodeImage(decoder, pixels, 0, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_NE(jbitmap, nullptr);
ASSERT_TRUE(bitmapsEqual(env, jbitmap, (AndroidBitmapFormat) androidBitmapFormat,
width, height, alphaFlags, minStride, pixels, minStride));
// Setting to an invalid data space is unsupported, and has no effect on the
// decodes below.
for (int32_t dataSpace : std::initializer_list<int32_t>{ -1, ADATASPACE_UNKNOWN, 400 }) {
result = AImageDecoder_setDataSpace(decoder, dataSpace);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
// Used for subsequent decodes, to ensure they are identical to the
// original. For opaque images, this verifies that using PREMUL or UNPREMUL
// look the same. For all images, this verifies that decodeImage can be
// called multiple times.
auto decodeAgain = [=](bool unpremultipliedRequired) {
int r = AImageDecoder_setUnpremultipliedRequired(decoder, unpremultipliedRequired);
if (ANDROID_IMAGE_DECODER_SUCCESS != r) {
fail(env, "Failed to set alpha");
return false;
}
std::unique_ptr<char[]> otherPixelsBuffer(new char[size]);
void* otherPixels = (void*) otherPixelsBuffer.get();
r = AImageDecoder_decodeImage(decoder, otherPixels, minStride, size);
if (ANDROID_IMAGE_DECODER_SUCCESS != r) {
fail(env, "Failed to decode again with different settings");
return false;
}
if (!bitmapsEqual(minStride, height, pixels, minStride, otherPixels, minStride)) {
fail(env, "Decoding again with different settings did not match!");
return false;
}
return true;
};
if (alphaFlags == ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
for (bool unpremultipliedRequired: { true, false }) {
if (!decodeAgain(unpremultipliedRequired)) return;
}
} else {
if (!decodeAgain(unpremul)) return;
}
if (androidBitmapFormat == ANDROID_BITMAP_FORMAT_A_8) {
// Attempting to set an ADataSpace is ignored by an A_8 decode.
for (ADataSpace dataSpace : { ADATASPACE_DCI_P3, ADATASPACE_ADOBE_RGB }) {
result = AImageDecoder_setDataSpace(decoder, dataSpace);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
if (!decodeAgain(alphaFlags)) return;
}
}
}
static void testDecodeStride(JNIEnv* env, jclass, jlong imageDecoderPtr) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int height = AImageDecoderHeaderInfo_getHeight(info);
const int origWidth = AImageDecoderHeaderInfo_getWidth(info);
for (int width : { origWidth, origWidth / 3 }) {
if (width == 0) {
// The 1 x 1 image cannot be downscaled.
continue;
}
int result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
for (AndroidBitmapFormat format : {
ANDROID_BITMAP_FORMAT_RGBA_8888,
ANDROID_BITMAP_FORMAT_RGB_565,
ANDROID_BITMAP_FORMAT_A_8,
ANDROID_BITMAP_FORMAT_RGBA_F16,
}) {
result = AImageDecoder_setAndroidBitmapFormat(decoder, format);
if (result != ANDROID_IMAGE_DECODER_SUCCESS) {
// Not all images can be decoded to all formats. This is okay, and
// we've tested that we can decode to the expected formats elsewhere.
continue;
}
size_t minStride = AImageDecoder_getMinimumStride(decoder);
void* pixels = nullptr;
// The code in the below loop relies on minStride being used first.
std::vector<size_t> strides;
strides.push_back(minStride);
for (int i = 1; i <= 16; i++) {
strides.push_back(i + minStride);
}
strides.push_back(minStride * 2);
strides.push_back(minStride * 3);
for (size_t stride : strides) {
size_t size = stride * (height - 1) + minStride;
std::unique_ptr<char[]> decodePixelsBuffer(new char[size]);
void* decodePixels = (void*) decodePixelsBuffer.get();
result = AImageDecoder_decodeImage(decoder, decodePixels, stride, size);
if ((stride - minStride) % bytesPerPixel(format) != 0) {
// The stride is not pixel aligned.
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
continue;
}
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
if (pixels == nullptr) {
pixels = (void*) decodePixelsBuffer.release();
} else {
ASSERT_TRUE(bitmapsEqual(minStride, height, pixels, minStride, decodePixels,
stride));
}
}
}
}
}
static void testSetTargetSize(JNIEnv* env, jclass, jlong imageDecoderPtr) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const size_t defaultStride = AImageDecoder_getMinimumStride(decoder);
for (int32_t width : { -1, 0, -500 }) {
int result = AImageDecoder_setTargetSize(decoder, width, 100);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
// stride is unchanged, as the target size did not change.
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
}
for (int32_t height : { -1, 0, -300 }) {
int result = AImageDecoder_setTargetSize(decoder, 100, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
// stride is unchanged, as the target size did not change.
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
}
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int bpp = bytesPerPixel(AImageDecoderHeaderInfo_getAndroidBitmapFormat(info));
for (int32_t width : { 7, 100, 275, 300 }) {
int result = AImageDecoder_setTargetSize(decoder, width, 100);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bpp * width;
ASSERT_EQ(expectedStride, actualStride);
}
// Verify that setting a large enough width to overflow 31 bits fails.
constexpr auto kMaxInt32 = std::numeric_limits<int32_t>::max();
int32_t maxWidth = kMaxInt32 / bpp;
for (int32_t width : { maxWidth / 2, maxWidth - 1, maxWidth }) {
int result = AImageDecoder_setTargetSize(decoder, width, 1);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bpp * width;
ASSERT_EQ(expectedStride, actualStride);
}
for (int32_t width : { maxWidth + 1, (int32_t) (maxWidth * 1.5) }) {
int result = AImageDecoder_setTargetSize(decoder, width, 1);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
}
// A height that results in overflowing 31 bits also fails.
int32_t maxHeight = kMaxInt32 / defaultStride;
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
for (int32_t height : { maxHeight / 2, maxHeight - 1, maxHeight }) {
int result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bpp * width;
ASSERT_EQ(expectedStride, actualStride);
}
for (int32_t height : { maxHeight + 1, (int32_t) (maxHeight * 1.5) }) {
int result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
}
}
struct SampledSizeParams {
AImageDecoder* decoder;
int sampleSize;
int32_t* width;
int32_t* height;
};
static void testComputeSampledSize(JNIEnv* env, jclass, jlong imageDecoderPtr,
jobject jbitmap, jint sampleSize) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int32_t origWidth = AImageDecoderHeaderInfo_getWidth(info);
const int32_t origHeight = AImageDecoderHeaderInfo_getHeight(info);
int32_t width, height;
// Test some bad parameters.
for (SampledSizeParams p : std::initializer_list<SampledSizeParams>{
{ nullptr, 2, &width, &height },
{ decoder, 0, &width, &height },
{ decoder, -1, &width, &height },
{ decoder, 2, nullptr, &height },
{ decoder, 2, &width, nullptr },
}) {
int result = AImageDecoder_computeSampledSize(p.decoder, p.sampleSize, p.width, p.height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
// Verify that width and height will never be less than one.
for (SampledSizeParams p : std::initializer_list<SampledSizeParams>{
{ decoder, origWidth, &width, &height },
{ decoder, origWidth + 5, &width, &height },
{ decoder, origWidth * 2, &width, &height },
{ decoder, origHeight, &width, &height },
{ decoder, origHeight + 5, &width, &height },
{ decoder, origHeight * 2, &width, &height },
}) {
width = height = 0;
int result = AImageDecoder_computeSampledSize(p.decoder, p.sampleSize, p.width, p.height);
if (ANDROID_IMAGE_DECODER_SUCCESS != result) {
fail(env, "computeSampledSize(%i) failed on image with dims %i x %i",
p.sampleSize, origWidth, origHeight);
return;
}
ASSERT_GE(width, 1);
ASSERT_GE(height, 1);
}
// jbitmap was created with the same sampleSize. Verify that AImageDecoder
// computes the same output dimensions, that using those dimensions succeeds
// with AImageDecoder, and the output matches.
AndroidBitmapInfo jInfo;
int bitmapResult = AndroidBitmap_getInfo(env, jbitmap, &jInfo);
ASSERT_EQ(ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
width = height = 0;
int result = AImageDecoder_computeSampledSize(decoder, sampleSize, &width, &height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
if (jInfo.width != width) {
fail(env, "Orig image: %i x %i sampled by %i yields %i x %i expected %i x %i",
origWidth, origHeight, sampleSize, width, height, jInfo.width, jInfo.height);
return;
}
ASSERT_EQ(jInfo.width, width);
ASSERT_EQ(jInfo.height, height);
{
ASSERT_LT(width, origWidth);
ASSERT_LT(height, origHeight);
ASSERT_LT(width, origWidth / sampleSize + sampleSize);
ASSERT_LT(height, origHeight / sampleSize + sampleSize);
}
result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t minStride = AImageDecoder_getMinimumStride(decoder);
size_t size = minStride * height;
std::unique_ptr<char[]> pixelsBuffer(new char[size]);
void* pixels = (void*) pixelsBuffer.get();
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_TRUE(bitmapsEqual(env, jbitmap, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info),
width, height, AImageDecoderHeaderInfo_getAlphaFlags(info),
minStride, pixels, minStride));
}
static void testDecodeScaled(JNIEnv* env, jclass, jlong imageDecoderPtr,
jobject jbitmap) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
AndroidBitmapInfo jInfo;
int bitmapResult = AndroidBitmap_getInfo(env, jbitmap, &jInfo);
ASSERT_EQ(ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
int result = AImageDecoder_setTargetSize(decoder, jInfo.width, jInfo.height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t minStride = AImageDecoder_getMinimumStride(decoder);
size_t size = minStride * jInfo.height;
std::unique_ptr<char[]> pixelsBuffer(new char[size]);
void* pixels = (void*) pixelsBuffer.get();
{
// Try some invalid parameters.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size - minStride);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
ASSERT_NE(jbitmap, nullptr);
ASSERT_TRUE(bitmapsEqual(env, jbitmap, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info),
jInfo.width, jInfo.height, AImageDecoderHeaderInfo_getAlphaFlags(info),
minStride, pixels, minStride));
// Verify that larger strides still behave as expected.
for (size_t stride : { minStride * 2, minStride * 3 }) {
size_t size = stride * (jInfo.height - 1) + minStride;
std::unique_ptr<char[]> decodePixelsBuffer(new char[size]);
void* decodePixels = (void*) decodePixelsBuffer.get();
result = AImageDecoder_decodeImage(decoder, decodePixels, stride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_TRUE(bitmapsEqual(minStride, jInfo.height, pixels, minStride, decodePixels, stride));
}
}
static void testSetCrop(JNIEnv* env, jclass, jobject jAssets, jstring jFile) {
AAsset* asset = openAsset(env, jAssets, jFile, AASSET_MODE_UNKNOWN);
ASSERT_NE(asset, nullptr);
AssetCloser assetCloser(asset, AAsset_close);
AImageDecoder* decoder;
int result = AImageDecoder_createFromAAsset(asset, &decoder);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_NE(decoder, nullptr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
const int32_t height = AImageDecoderHeaderInfo_getHeight(info);
const auto format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(info);
const size_t defaultStride = AImageDecoder_getMinimumStride(decoder);
if (width == 1 && height == 1) {
// The more general crop tests do not map well to this image. Test 1 x 1
// specifically.
for (ARect invalidCrop : std::initializer_list<ARect> {
{ -1, 0, width, height },
{ 0, -1, width, height },
{ width, 0, 2 * width, height },
{ 0, height, width, 2 * height },
{ 1, 0, width + 1, height },
{ 0, 1, width, height + 1 },
{ 0, 0, 0, height },
{ 0, 0, width, 0 },
}) {
int result = AImageDecoder_setCrop(decoder, invalidCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
}
return;
}
for (ARect invalidCrop : std::initializer_list<ARect> {
{ -1, 0, width, height },
{ 0, -1, width, height },
{ width, 0, 2 * width, height },
{ 0, height, width, 2 * height },
{ 1, 0, width + 1, height },
{ 0, 1, width, height + 1 },
{ width - 1, 0, 1, height },
{ 0, height - 1, width, 1 },
{ 0, 0, 0, height },
{ 0, 0, width, 0 },
{ 1, 1, 1, 1 },
{ width, height, 0, 0 },
}) {
int result = AImageDecoder_setCrop(decoder, invalidCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
}
for (ARect validCrop : std::initializer_list<ARect> {
{ 0, 0, width, height },
{ 0, 0, width / 2, height / 2},
{ 0, 0, width / 3, height },
{ 0, 0, width, height / 4},
{ width / 2, 0, width, height / 2},
{ 0, height / 2, width / 2, height },
{ width / 2, height / 2, width, height },
{ 1, 1, width - 1, height - 1 },
}) {
int result = AImageDecoder_setCrop(decoder, validCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bytesPerPixel(format) * (validCrop.right - validCrop.left);
ASSERT_EQ(expectedStride, actualStride);
}
// Reset the crop, so we can test setting a crop *after* changing the
// target size.
result = AImageDecoder_setCrop(decoder, { 0, 0, 0, 0 });
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
int32_t newWidth = width / 2, newHeight = height / 2;
result = AImageDecoder_setTargetSize(decoder, newWidth, newHeight);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const size_t halfStride = AImageDecoder_getMinimumStride(decoder);
{
size_t expectedStride = bytesPerPixel(format) * newWidth;
ASSERT_EQ(expectedStride, halfStride);
}
// At the smaller target size, crops that were previously valid no longer
// are.
for (ARect invalidCrop : std::initializer_list<ARect> {
{ 0, 0, width / 3, height },
{ 0, 0, width, height / 4},
{ width / 2, 0, width, height / 2},
{ 0, height / 2, width / 2, height },
{ width / 2, height / 2, width, height },
{ 1, 1, width - 1, height - 1 },
}) {
int result = AImageDecoder_setCrop(decoder, invalidCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
ASSERT_EQ(halfStride, AImageDecoder_getMinimumStride(decoder));
}
for (ARect validCrop : std::initializer_list<ARect> {
{ 0, 0, newWidth, newHeight },
{ 0, 0, newWidth / 3, newHeight },
{ 0, 0, newWidth, newHeight / 4},
{ newWidth / 2, 0, newWidth, newHeight / 2},
{ 0, newHeight / 2, newWidth / 2, newHeight },
{ newWidth / 2, newHeight / 2, newWidth, newHeight },
{ 1, 1, newWidth - 1, newHeight - 1 },
}) {
int result = AImageDecoder_setCrop(decoder, validCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bytesPerPixel(format) * (validCrop.right - validCrop.left);
ASSERT_EQ(expectedStride, actualStride);
}
newWidth = width * 2;
newHeight = height * 2;
result = AImageDecoder_setTargetSize(decoder, newWidth, newHeight);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
for (ARect validCrop : std::initializer_list<ARect> {
{ width, 0, newWidth, height },
{ 0, height * 3 / 4, width * 4 / 3, height }
}) {
int result = AImageDecoder_setCrop(decoder, validCrop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
size_t actualStride = AImageDecoder_getMinimumStride(decoder);
size_t expectedStride = bytesPerPixel(format) * (validCrop.right - validCrop.left);
ASSERT_EQ(expectedStride, actualStride);
}
// Reset crop and target size, so that we can verify that setting a crop and
// then setting target size that will not support the crop fails.
result = AImageDecoder_setCrop(decoder, { 0, 0, 0, 0 });
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_EQ(defaultStride, AImageDecoder_getMinimumStride(decoder));
ARect crop{ width / 2, height / 2, width, height };
result = AImageDecoder_setCrop(decoder, crop);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const size_t croppedStride = AImageDecoder_getMinimumStride(decoder);
{
size_t expectedStride = bytesPerPixel(format) * (crop.right - crop.left);
ASSERT_EQ(expectedStride, croppedStride);
}
result = AImageDecoder_setTargetSize(decoder, width / 2, height / 2);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
ASSERT_EQ(croppedStride, AImageDecoder_getMinimumStride(decoder));
}
static void testDecodeCrop(JNIEnv* env, jclass, jlong imageDecoderPtr,
jobject jbitmap, jint targetWidth, jint targetHeight,
jint left, jint top, jint right, jint bottom) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
int result;
if (targetWidth != 0 && targetHeight != 0) {
result = AImageDecoder_setTargetSize(decoder, targetWidth, targetHeight);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
}
ARect rect { left, top, right, bottom };
result = AImageDecoder_setCrop(decoder, rect);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const int32_t width = right - left;
const int32_t height = bottom - top;
size_t minStride = AImageDecoder_getMinimumStride(decoder);
size_t size = minStride * height;
std::unique_ptr<char[]> pixelsBuffer(new char[size]);
void* pixels = (void*) pixelsBuffer.get();
{
// Try some invalid parameters.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
#pragma clang diagnostic pop
result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size - minStride);
ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
}
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
ASSERT_NE(jbitmap, nullptr);
ASSERT_TRUE(bitmapsEqual(env, jbitmap, AImageDecoderHeaderInfo_getAndroidBitmapFormat(info),
width, height, AImageDecoderHeaderInfo_getAlphaFlags(info),
minStride, pixels, minStride));
// Verify that larger strides still behave as expected.
for (size_t stride : { minStride * 2, minStride * 3 }) {
size_t size = stride * (height - 1) + minStride;
std::unique_ptr<char[]> decodePixelsBuffer(new char[size]);
void* decodePixels = (void*) decodePixelsBuffer.get();
result = AImageDecoder_decodeImage(decoder, decodePixels, stride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_TRUE(bitmapsEqual(minStride, height, pixels, minStride, decodePixels, stride));
}
}
static void testScalePlusUnpremul(JNIEnv* env, jclass, jlong imageDecoderPtr) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(nullptr, info);
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
const int32_t height = AImageDecoderHeaderInfo_getHeight(info);
const int alpha = AImageDecoderHeaderInfo_getAlphaFlags(info);
if (alpha == ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE) {
// Set unpremul, then scale. This succeeds for an opaque image.
int result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width * 2, height * 2);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width * 2/3, height * 2/3);
if (width * 2/3 == 0 || height * 2/3 == 0) {
// The image that is 1x1 cannot be downscaled.
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
} else {
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
}
// Reset to the original settings to test the other order.
result = AImageDecoder_setUnpremultipliedRequired(decoder, false);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width, height);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
// Specify scale and then unpremul.
if (width * 2/3 == 0 || height * 2/3 == 0) {
// The image that is 1x1 cannot be downscaled. Scale up instead.
result = AImageDecoder_setTargetSize(decoder, width * 2, height * 2);
} else {
result = AImageDecoder_setTargetSize(decoder, width * 2/3, height * 2/3);
}
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
} else {
// Use unpremul and then scale. Setting to unpremul is successful, but
// later calls to change the scale fail.
int result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width * 2, height * 2);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
result = AImageDecoder_setTargetSize(decoder, width * 2/3, height * 2/3);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_SCALE, result);
// Set back to premul to verify that the opposite order also fails.
result = AImageDecoder_setUnpremultipliedRequired(decoder, false);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setTargetSize(decoder, width * 2, height * 2);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
result = AImageDecoder_setTargetSize(decoder, width * 2/3, height * 2/3);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
result = AImageDecoder_setUnpremultipliedRequired(decoder, true);
ASSERT_EQ(ANDROID_IMAGE_DECODER_INVALID_CONVERSION, result);
}
}
static void testDecodeSetDataSpace(JNIEnv* env, jclass, jlong imageDecoderPtr,
jobject jbitmap, jint dataSpace) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
ASSERT_EQ(dataSpace, AndroidBitmap_getDataSpace(env, jbitmap));
int result = AImageDecoder_setDataSpace(decoder, dataSpace);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
AndroidBitmapInfo jInfo;
int bitmapResult = AndroidBitmap_getInfo(env, jbitmap, &jInfo);
ASSERT_EQ(ANDROID_BITMAP_RESULT_SUCCESS, bitmapResult);
result = AImageDecoder_setAndroidBitmapFormat(decoder, jInfo.format);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder);
ASSERT_NE(info, nullptr);
const int32_t width = AImageDecoderHeaderInfo_getWidth(info);
const int32_t height = AImageDecoderHeaderInfo_getHeight(info);
int alphaFlags = AImageDecoderHeaderInfo_getAlphaFlags(info);
size_t minStride = AImageDecoder_getMinimumStride(decoder);
size_t size = minStride * height;
std::unique_ptr<char[]> pixelsBuffer(new char[size]);
void* pixels = (void*) pixelsBuffer.get();
result = AImageDecoder_decodeImage(decoder, pixels, minStride, size);
ASSERT_EQ(ANDROID_IMAGE_DECODER_SUCCESS, result);
ASSERT_TRUE(bitmapsEqual(env, jbitmap, (AndroidBitmapFormat) jInfo.format,
width, height, alphaFlags, minStride, pixels, minStride));
}
static void testIsAnimated(JNIEnv* env, jclass, jlong imageDecoderPtr, jboolean animated) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
ASSERT_TRUE(decoder);
ASSERT_EQ(animated, AImageDecoder_isAnimated(decoder));
}
static void testRepeatCount(JNIEnv* env, jclass, jlong imageDecoderPtr, jint repeatCount) {
AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
if (repeatCount == -1) { // AnimatedImageDrawable.REPEAT_INFINITE
repeatCount = ANDROID_IMAGE_DECODER_INFINITE;
}
ASSERT_TRUE(decoder);
ASSERT_EQ(repeatCount, AImageDecoder_getRepeatCount(decoder));
}
#define ASSET_MANAGER "Landroid/content/res/AssetManager;"
#define STRING "Ljava/lang/String;"
#define BITMAP "Landroid/graphics/Bitmap;"
static JNINativeMethod gMethods[] = {
{ "nTestEmptyCreate", "()V", (void*) testEmptyCreate },
{ "nTestNullDecoder", "(" ASSET_MANAGER STRING ")V", (void*) testNullDecoder },
{ "nTestInfo", "(JII" STRING "ZI)V", (void*) testInfo },
{ "nOpenAsset", "(" ASSET_MANAGER STRING ")J", (void*) openAssetNative },
{ "nCloseAsset", "(J)V", (void*) closeAsset },
{ "nCreateFromAsset", "(J)J", (void*) createFromAsset },
{ "nCreateFromAssetFd", "(J)J", (void*) createFromAssetFd },
{ "nCreateFromAssetBuffer", "(J)J", (void*) createFromAssetBuffer },
{ "nCreateFromFd", "(I)J", (void*) createFromFd },
{ "nTestCreateIncomplete", "(" ASSET_MANAGER STRING "I)V", (void*) testCreateIncomplete },
{ "nTestCreateUnsupported", "(" ASSET_MANAGER STRING ")V", (void*) testCreateUnsupported },
{ "nTestSetFormat", "(JZZ)V", (void*) testSetFormat },
{ "nTestSetUnpremul", "(JZ)V", (void*) testSetUnpremul },
{ "nTestGetMinimumStride", "(JZZ)V", (void*) testGetMinimumStride },
{ "nTestDecode", "(JIZ" BITMAP ")V", (void*) testDecode },
{ "nTestDecodeStride", "(J)V", (void*) testDecodeStride },
{ "nTestSetTargetSize", "(J)V", (void*) testSetTargetSize },
{ "nTestComputeSampledSize", "(J" BITMAP "I)V", (void*) testComputeSampledSize },
{ "nTestDecodeScaled", "(J" BITMAP ")V", (void*) testDecodeScaled },
{ "nTestSetCrop", "(" ASSET_MANAGER STRING ")V", (void*) testSetCrop },
{ "nTestDecodeCrop", "(J" BITMAP "IIIIII)V", (void*) testDecodeCrop },
{ "nTestScalePlusUnpremul", "(J)V", (void*) testScalePlusUnpremul },
{ "nTestDecode", "(J" BITMAP "I)V", (void*) testDecodeSetDataSpace },
{ "nTestIsAnimated", "(JZ)V", (void*) testIsAnimated },
{ "nTestRepeatCount", "(JI)V", (void*) testRepeatCount },
};
int register_android_graphics_cts_AImageDecoderTest(JNIEnv* env) {
jclass clazz = env->FindClass("android/graphics/cts/AImageDecoderTest");
return env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(JNINativeMethod));
}