blob: 33a7d495d934c4a96e305b421f6351d3888e8189 [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <android/bitmap.h>
#include <android/log.h>
#include <cpu-features.h>
#include <jni.h>
#include "avif/avif.h"
#define LOG_TAG "avif_jni"
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#define FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \
JNIEXPORT RETURN_TYPE Java_org_aomedia_avif_android_AvifDecoder_##NAME( \
JNIEnv* env, jobject /*thiz*/, ##__VA_ARGS__); \
} \
JNIEXPORT RETURN_TYPE Java_org_aomedia_avif_android_AvifDecoder_##NAME( \
JNIEnv* env, jobject /*thiz*/, ##__VA_ARGS__)
namespace {
jfieldID global_info_width;
jfieldID global_info_height;
jfieldID global_info_depth;
// RAII wrapper class that properly frees the decoder related objects on
// destruction.
struct AvifDecoderWrapper {
public:
AvifDecoderWrapper() = default;
// Not copyable or movable.
AvifDecoderWrapper(const AvifDecoderWrapper&) = delete;
AvifDecoderWrapper& operator=(const AvifDecoderWrapper&) = delete;
~AvifDecoderWrapper() {
if (decoder != nullptr) {
avifDecoderDestroy(decoder);
}
}
avifDecoder* decoder = nullptr;
};
bool CreateDecoderAndParse(AvifDecoderWrapper* const decoder,
const uint8_t* const buffer, int length,
int threads) {
decoder->decoder = avifDecoderCreate();
if (decoder->decoder == nullptr) {
LOGE("Failed to create AVIF Decoder.");
return false;
}
decoder->decoder->maxThreads = threads;
decoder->decoder->ignoreXMP = AVIF_TRUE;
decoder->decoder->ignoreExif = AVIF_TRUE;
// Turn off 'clap' (clean aperture) property validation. The JNI wrapper
// ignores the 'clap' property.
decoder->decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
// Allow 'pixi' (pixel information) property to be missing. Older versions of
// libheif did not add the 'pixi' item property to AV1 image items (See
// crbug.com/1198455).
decoder->decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
avifResult res = avifDecoderSetIOMemory(decoder->decoder, buffer, length);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to set AVIF IO to a memory reader.");
return false;
}
res = avifDecoderParse(decoder->decoder);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to parse AVIF image: %s.", avifResultToString(res));
return false;
}
return true;
}
} // namespace
jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
const jclass info_class =
env->FindClass("org/aomedia/avif/android/AvifDecoder$Info");
global_info_width = env->GetFieldID(info_class, "width", "I");
global_info_height = env->GetFieldID(info_class, "height", "I");
global_info_depth = env->GetFieldID(info_class, "depth", "I");
return JNI_VERSION_1_6;
}
FUNC(jboolean, isAvifImage, jobject encoded, int length) {
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
const avifROData avif = {buffer, static_cast<size_t>(length)};
return avifPeekCompatibleFileType(&avif);
}
FUNC(jboolean, getInfo, jobject encoded, int length, jobject info) {
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
AvifDecoderWrapper decoder;
if (!CreateDecoderAndParse(&decoder, buffer, length, /*threads=*/ 1)) {
return false;
}
env->SetIntField(info, global_info_width, decoder.decoder->image->width);
env->SetIntField(info, global_info_height, decoder.decoder->image->height);
env->SetIntField(info, global_info_depth, decoder.decoder->image->depth);
return true;
}
FUNC(jboolean, decode, jobject encoded, int length, jobject bitmap,
jint threads) {
if (threads < 0) {
LOGE("Invalid value for threads (%d).", threads);
return false;
}
const uint8_t* const buffer =
static_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
AvifDecoderWrapper decoder;
if (!CreateDecoderAndParse(
&decoder, buffer, length,
(threads == 0) ? android_getCpuCount() : threads)) {
return false;
}
avifResult res = avifDecoderNextImage(decoder.decoder);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to decode AVIF image. Status: %d", res);
return false;
}
AndroidBitmapInfo bitmap_info;
if (AndroidBitmap_getInfo(env, bitmap, &bitmap_info) < 0) {
LOGE("AndroidBitmap_getInfo failed.");
return false;
}
// Ensure that the bitmap is large enough to store the decoded image.
if (bitmap_info.width < decoder.decoder->image->width ||
bitmap_info.height < decoder.decoder->image->height) {
LOGE(
"Bitmap is not large enough to fit the image. Bitmap %dx%d Image "
"%dx%d.",
bitmap_info.width, bitmap_info.height, decoder.decoder->image->width,
decoder.decoder->image->height);
return false;
}
// Ensure that the bitmap format is RGBA_8888, RGB_565 or RGBA_F16.
if (bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 &&
bitmap_info.format != ANDROID_BITMAP_FORMAT_RGB_565 &&
bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_F16) {
LOGE("Bitmap format (%d) is not supported.", bitmap_info.format);
return false;
}
void* bitmap_pixels = nullptr;
if (AndroidBitmap_lockPixels(env, bitmap, &bitmap_pixels) !=
ANDROID_BITMAP_RESULT_SUCCESS) {
LOGE("Failed to lock Bitmap.");
return false;
}
avifRGBImage rgb_image;
avifRGBImageSetDefaults(&rgb_image, decoder.decoder->image);
if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_F16) {
rgb_image.depth = 16;
rgb_image.isFloat = AVIF_TRUE;
} else if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
rgb_image.format = AVIF_RGB_FORMAT_RGB_565;
rgb_image.depth = 8;
} else {
rgb_image.depth = 8;
}
rgb_image.pixels = static_cast<uint8_t*>(bitmap_pixels);
rgb_image.rowBytes = bitmap_info.stride;
res = avifImageYUVToRGB(decoder.decoder->image, &rgb_image);
AndroidBitmap_unlockPixels(env, bitmap);
if (res != AVIF_RESULT_OK) {
LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res);
return false;
}
return true;
}