Skip to content

Commit 7a7d75e

Browse files
committed
Lots of improvements, allow to choose preferred config format
1 parent a81aba9 commit 7a7d75e

30 files changed

+600
-587
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ Library provides simple interface to decode or encode ( create ) AVIF and HEIF i
44
Very fast and convinient to use AVIF in android apps with api version 24+
55
26+. Based on libheif, libde265, libx265, libyuv, libaom and libdav1d
66

7-
Correctly handles ICC, and color profiles.
7+
Correctly handles ICC, and color profiles and HDR images.
88
Fully supports HDR images, 10, 12 bit. Preprocess image in tile to increase speed.
9-
Extremly fast in decoding large HDR images or just large images
10-
The most features AVIF, HEIF library in android
9+
Extremly fast in decoding large HDR images or just large images.
10+
The most features AVIF, HEIF library in android.
11+
Supported decoding in all necessary pixel formats in Android and avoids android decoding bugs.
1112

1213
# Usage example
1314

1415
```kotlin
1516
// May decode AVIF(AV1) and HEIC (HEVC) images, all HDR images currently will be converted in 8bit
1617
val bitmap: Bitmap = HeifCoder().decode(buffer) // Decode avif from ByteArray
1718
val bytes: ByteArray = HeifCoder().encodeAvif(decodedBitmap) // Encode Bitmap to AVIF
18-
val bytes = HeifCoder().encodeHeic(bitmap) // Encode Bitmap to HEIC / Supports HDR in RGBA_F16
19+
val bytes = HeifCoder().encodeHeic(bitmap) // Encode Bitmap to HEIC / Supports HDR in RGBA_F16, RGBA_1010102, HARDWARE
1920
// Check if image is valid AVIF(AV1) image
2021
val isAvif = HeifCoder().isAvif(byteArray)
2122
// Check if image is valid HEIF(HEVC) image

app/src/main/java/com/radzivon/bartoshyk/avif/MainActivity.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,22 @@ class MainActivity : AppCompatActivity() {
3838

3939
// Example of a call to a native method
4040
//
41-
val buffer = this.assets.open("federico-beccari-hlg.avif").source().buffer().readByteArray()
41+
val buffer = this.assets.open("hato_custom_icc.avif").source().buffer().readByteArray()
4242
// assert(HeifCoder().isAvif(buffer))
4343
val size = HeifCoder().getSize(buffer)!!
44-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
44+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
4545
val time = measureTimeMillis {
46+
// val bitmap = HeifCoder().decodeSampled(
47+
// buffer,
48+
// size.width / 2,
49+
// size.height / 2,
50+
// PreferredColorConfig.HARDWARE
51+
// )
4652
val bitmap = HeifCoder().decodeSampled(
4753
buffer,
4854
size.width / 2,
4955
size.height / 2,
50-
PreferredColorConfig.RGBA_1010102
56+
PreferredColorConfig.HARDWARE
5157
)
5258
// val opts = BitmapFactory.Options()
5359
// opts.inMutable = true

avif-coder/src/main/cpp/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ add_library(coder SHARED coder.cpp JniException.cpp scaler.cpp
2020
imagebits/RgbaF16bitToNBitU16.cpp imagebits/RgbaF16bitNBitU8.cpp imagebits/Rgb1010102.cpp
2121
colorspace/PerceptualQuantinizer.cpp ThreadPool.hpp
2222
imagebits/CopyUnalignedRGBA.cpp colorspace/HLG.cpp JniDecoder.cpp imagebits/Rgba8ToF16.cpp
23-
imagebits/Rgb565.cpp JniBitmap.cpp ReformatBitmap.cpp Support.cpp IccRecognizer.cpp)
23+
imagebits/Rgb565.cpp JniBitmap.cpp ReformatBitmap.cpp Support.cpp IccRecognizer.cpp
24+
HardwareBuffersCompat.cpp)
2425

2526
add_library(libaom STATIC IMPORTED)
2627
add_library(libx265 STATIC IMPORTED)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// Created by Radzivon Bartoshyk on 16/09/2023.
3+
//
4+
5+
#include "HardwareBuffersCompat.h"
6+
7+
#include <mutex>
8+
#include <dlfcn.h>
9+
#include "JniException.h"
10+
#include "jni.h"
11+
12+
std::mutex dlMutex;
13+
14+
bool alreadyHdBuffersLoaded = false;
15+
16+
AHardwareBufferAllocateFunc AHardwareBuffer_allocate_compat = nullptr;
17+
AHardwareBufferIsSupportedFunc AHardwareBuffer_isSupported_compat = nullptr;
18+
AHardwareBufferUnlockFunc AHardwareBuffer_unlock_compat = nullptr;
19+
AHardwareBufferReleaseFunc AHardwareBuffer_release_compat = nullptr;
20+
AHardwareBufferLockFunc AHardwareBuffer_lock_compat = nullptr;
21+
AHardwareBufferToHardwareBufferFunc AHardwareBuffer_toHardwareBuffer_compat = nullptr;
22+
AHardwareBufferDescribeFunc AHardwareBuffer_describe_compat = nullptr;
23+
24+
bool loadAHardwareBuffersAPI() {
25+
// Bitmap doesn't support wrapping before 29 API
26+
if (androidOSVersion() < 29) {
27+
return false;
28+
}
29+
std::lock_guard guard(dlMutex);
30+
if (alreadyHdBuffersLoaded) {
31+
return AHardwareBuffer_allocate_compat != nullptr
32+
&& AHardwareBuffer_isSupported_compat != nullptr
33+
&& AHardwareBuffer_unlock_compat != nullptr
34+
&& AHardwareBuffer_release_compat != nullptr
35+
&& AHardwareBuffer_lock_compat != nullptr
36+
&& AHardwareBuffer_toHardwareBuffer_compat != nullptr
37+
&& AHardwareBuffer_describe_compat != nullptr;
38+
}
39+
alreadyHdBuffersLoaded = true;
40+
void *hhl = dlopen("libandroid.so", RTLD_NOW);
41+
if (!hhl) {
42+
return false;
43+
}
44+
45+
AHardwareBuffer_allocate_compat = (AHardwareBufferAllocateFunc) dlsym(hhl,
46+
"AHardwareBuffer_allocate");
47+
if (AHardwareBuffer_allocate_compat == nullptr) {
48+
return false;
49+
}
50+
51+
AHardwareBuffer_isSupported_compat = (AHardwareBufferIsSupportedFunc) dlsym(hhl,
52+
"AHardwareBuffer_isSupported");
53+
if (AHardwareBuffer_isSupported_compat == nullptr) {
54+
return false;
55+
}
56+
57+
AHardwareBuffer_unlock_compat = (AHardwareBufferUnlockFunc) dlsym(hhl,
58+
"AHardwareBuffer_unlock");
59+
if (AHardwareBuffer_unlock_compat == nullptr) {
60+
return false;
61+
}
62+
63+
AHardwareBuffer_release_compat = (AHardwareBufferReleaseFunc) dlsym(hhl,
64+
"AHardwareBuffer_release");
65+
if (AHardwareBuffer_release_compat == nullptr) {
66+
return false;
67+
}
68+
69+
AHardwareBuffer_lock_compat = (AHardwareBufferLockFunc) dlsym(hhl,
70+
"AHardwareBuffer_lock");
71+
if (AHardwareBuffer_lock_compat == nullptr) {
72+
return false;
73+
}
74+
75+
AHardwareBuffer_toHardwareBuffer_compat = (AHardwareBufferToHardwareBufferFunc) dlsym(hhl,
76+
"AHardwareBuffer_toHardwareBuffer");
77+
if (AHardwareBuffer_toHardwareBuffer_compat == nullptr) {
78+
return false;
79+
}
80+
81+
AHardwareBuffer_describe_compat = (AHardwareBufferDescribeFunc) dlsym(hhl,
82+
"AHardwareBuffer_describe");
83+
if (AHardwareBuffer_describe_compat == nullptr) {
84+
return false;
85+
}
86+
87+
return true;
88+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Created by Radzivon Bartoshyk on 16/09/2023.
3+
//
4+
5+
#ifndef AVIF_HARDWAREBUFFERSCOMPAT_H
6+
#define AVIF_HARDWAREBUFFERSCOMPAT_H
7+
8+
#include <android/bitmap.h>
9+
#include <android/hardware_buffer.h>
10+
#include <android/hardware_buffer_jni.h>
11+
12+
bool loadAHardwareBuffersAPI();
13+
14+
typedef int (*AHardwareBufferAllocateFunc)(
15+
const AHardwareBuffer_Desc *_Nonnull desc, AHardwareBuffer *_Nullable *_Nonnull outBuffer
16+
);
17+
18+
typedef int (*AHardwareBufferIsSupportedFunc)(
19+
const AHardwareBuffer_Desc *_Nonnull desc
20+
);
21+
22+
typedef int (*AHardwareBufferReleaseFunc)(
23+
AHardwareBuffer *_Nonnull buffer
24+
);
25+
26+
typedef int (*AHardwareBufferUnlockFunc)(
27+
AHardwareBuffer *_Nonnull buffer, int32_t *_Nullable fence
28+
);
29+
30+
typedef int (*AHardwareBufferLockFunc)(
31+
AHardwareBuffer *_Nonnull buffer, uint64_t usage, int32_t fence,
32+
const ARect *_Nullable rect, void *_Nullable *_Nonnull outVirtualAddress
33+
);
34+
35+
typedef void (*AHardwareBufferDescribeFunc)(
36+
const AHardwareBuffer *_Nonnull buffer, AHardwareBuffer_Desc *_Nonnull outDesc
37+
);
38+
39+
typedef jobject (*AHardwareBufferToHardwareBufferFunc)(
40+
JNIEnv *env, AHardwareBuffer *hardwareBuffer
41+
);
42+
43+
extern AHardwareBufferAllocateFunc AHardwareBuffer_allocate_compat;
44+
extern AHardwareBufferIsSupportedFunc AHardwareBuffer_isSupported_compat;
45+
extern AHardwareBufferUnlockFunc AHardwareBuffer_unlock_compat;
46+
extern AHardwareBufferReleaseFunc AHardwareBuffer_release_compat;
47+
extern AHardwareBufferLockFunc AHardwareBuffer_lock_compat;
48+
extern AHardwareBufferToHardwareBufferFunc AHardwareBuffer_toHardwareBuffer_compat;
49+
extern AHardwareBufferDescribeFunc AHardwareBuffer_describe_compat;
50+
51+
#endif //AVIF_HARDWAREBUFFERSCOMPAT_H

avif-coder/src/main/cpp/JniBitmap.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,16 @@
1111

1212
jobject
1313
createBitmap(JNIEnv *env, std::shared_ptr<uint8_t> &data, std::string &colorConfig, int stride,
14-
int imageWidth, int imageHeight, bool use16Floats) {
14+
int imageWidth, int imageHeight, bool use16Floats, jobject hwBuffer) {
15+
if (colorConfig == "HARDWARE") {
16+
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
17+
jmethodID createBitmapMethodID = env->GetStaticMethodID(bitmapClass, "wrapHardwareBuffer",
18+
"(Landroid/hardware/HardwareBuffer;Landroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;");
19+
jobject emptyObject = nullptr;
20+
jobject bitmapObj = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodID,
21+
hwBuffer, emptyObject);
22+
return bitmapObj;
23+
}
1524
jclass bitmapConfig = env->FindClass("android/graphics/Bitmap$Config");
1625
jfieldID rgba8888FieldID = env->GetStaticFieldID(bitmapConfig, colorConfig.c_str(),
1726
"Landroid/graphics/Bitmap$Config;");

avif-coder/src/main/cpp/JniBitmap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010

1111
jobject
1212
createBitmap(JNIEnv *env, std::shared_ptr<uint8_t> &data, std::string &colorConfig, int stride,
13-
int imageWidth, int imageHeight, bool use16Floats);
13+
int imageWidth, int imageHeight, bool use16Floats, jobject hwBuffer);
1414

1515
#endif //AVIF_JNIBITMAP_H

avif-coder/src/main/cpp/JniDecoder.cpp

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,25 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
3838
std::shared_ptr<heif_context> ctx(heif_context_alloc(),
3939
[](heif_context *c) { heif_context_free(c); });
4040
if (!ctx) {
41-
throwCoderCreationException(env);
41+
std::string exception = "Can't create HEIF/AVIF decoder due to unknown reason";
42+
throwException(env, exception);
4243
return static_cast<jobject>(nullptr);
4344
}
4445

4546
auto result = heif_context_read_from_memory_without_copy(ctx.get(), srcBuffer.data(),
4647
srcBuffer.size(),
4748
nullptr);
4849
if (result.code != heif_error_Ok) {
49-
throwCannotReadFileException(env);
50+
std::string exception = "Can't read heif file exception";
51+
throwException(env, exception);
5052
return static_cast<jobject>(nullptr);
5153
}
5254

5355
heif_image_handle *handle;
5456
result = heif_context_get_primary_image_handle(ctx.get(), &handle);
5557
if (result.code != heif_error_Ok) {
56-
throwCannotReadFileException(env);
58+
std::string exception = "Acquiring an image from file has failed";
59+
throwException(env, exception);
5760
return static_cast<jobject>(nullptr);
5861
}
5962

@@ -85,7 +88,8 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
8588

8689
if (result.code != heif_error_Ok) {
8790
heif_image_handle_release(handle);
88-
throwCantDecodeImageException(env);
91+
std::string exception = "Decoding an image has failed";
92+
throwException(env, exception);
8993
return static_cast<jobject>(nullptr);
9094
}
9195

@@ -133,15 +137,18 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
133137
if (!data) {
134138
heif_image_release(img);
135139
heif_image_handle_release(handle);
136-
throwCannotReadFileException(env);
140+
std::string exception = "Interleaving planed has failed";
141+
throwException(env, exception);
137142
return nullptr;
138143
}
139144
imageWidth = heif_image_get_width(img, heif_channel_interleaved);
140145
imageHeight = heif_image_get_height(img, heif_channel_interleaved);
141146
if (result.code != heif_error_Ok) {
142147
heif_image_release(img);
143148
heif_image_handle_release(handle);
144-
throwInvalidScale(env, result.message);
149+
std::string cp(result.message);
150+
std::string exception = "HEIF wasn't able to scale image due to " + cp;
151+
throwException(env, exception);
145152
return static_cast<jobject>(nullptr);
146153
}
147154

@@ -159,7 +166,9 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
159166
if (result.code != heif_error_Ok) {
160167
heif_image_release(img);
161168
heif_image_handle_release(handle);
162-
throwInvalidScale(env, result.message);
169+
std::string cp(result.message);
170+
std::string exception = "HEIF wasn't able to scale image due to " + cp;
171+
throwException(env, exception);
163172
return static_cast<jobject>(nullptr);
164173
}
165174

@@ -169,7 +178,8 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
169178
if (!data) {
170179
heif_image_release(scaledImg);
171180
heif_image_handle_release(handle);
172-
throwCannotReadFileException(env);
181+
std::string exception = "Interleaving planed has failed";
182+
throwException(env, exception);
173183
return nullptr;
174184
}
175185
imageWidth = heif_image_get_width(scaledImg, heif_channel_interleaved);
@@ -187,7 +197,8 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
187197
if (!data) {
188198
heif_image_release(img);
189199
heif_image_handle_release(handle);
190-
throwCannotReadFileException(env);
200+
std::string exception = "Interleaving planed has failed";
201+
throwException(env, exception);
191202
return nullptr;
192203
}
193204

@@ -303,11 +314,13 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
303314

304315
std::string imageConfig = useBitmapHalf16Floats ? "RGBA_F16" : "ARGB_8888";
305316

306-
ReformatColorConfig(dstARGB, imageConfig, preferredColorConfig, bitDepth, imageWidth,
307-
imageHeight, &stride, &useBitmapHalf16Floats);
317+
jobject hwBuffer = nullptr;
318+
319+
ReformatColorConfig(env, dstARGB, imageConfig, preferredColorConfig, bitDepth, imageWidth,
320+
imageHeight, &stride, &useBitmapHalf16Floats, &hwBuffer);
308321

309322
return createBitmap(env, dstARGB, imageConfig, stride, imageWidth, imageHeight,
310-
useBitmapHalf16Floats);
323+
useBitmapHalf16Floats, hwBuffer);
311324
}
312325

313326
extern "C"
@@ -322,4 +335,23 @@ Java_com_radzivon_bartoshyk_avif_coder_HeifCoder_decodeImpl(JNIEnv *env, jobject
322335
reinterpret_cast<jbyte *>(srcBuffer.data()));
323336
return decodeImplementationNative(env, thiz, srcBuffer, scaledWidth, scaledHeight,
324337
javaColorspace);
338+
}
339+
extern "C"
340+
JNIEXPORT jobject JNICALL
341+
Java_com_radzivon_bartoshyk_avif_coder_HeifCoder_decodeByteBufferImpl(JNIEnv *env, jobject thiz,
342+
jobject byteBuffer,
343+
jint scaledWidth,
344+
jint scaledHeight,
345+
jint clrConfig) {
346+
auto bufferAddress = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(byteBuffer));
347+
int length = (int) env->GetDirectBufferCapacity(byteBuffer);
348+
if (!bufferAddress || length <= 0) {
349+
std::string errorString = "Only direct byte buffers are supported";
350+
throwException(env, errorString);
351+
return nullptr;
352+
}
353+
std::vector<uint8_t> srcBuffer(length);
354+
std::copy(bufferAddress, bufferAddress + length, srcBuffer.begin());
355+
return decodeImplementationNative(env, thiz, srcBuffer, scaledWidth, scaledHeight,
356+
clrConfig);
325357
}

0 commit comments

Comments
 (0)