Skip to content

Commit 268af20

Browse files
committed
Added Proper scaling
1 parent 7a7d75e commit 268af20

File tree

18 files changed

+395
-248
lines changed

18 files changed

+395
-248
lines changed

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

+10-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import coil.load
1616
import com.github.awxkee.avifcoil.HeifDecoder
1717
import com.radzivon.bartoshyk.avif.coder.HeifCoder
1818
import com.radzivon.bartoshyk.avif.coder.PreferredColorConfig
19+
import com.radzivon.bartoshyk.avif.coder.ScaleMode
1920
import com.radzivon.bartoshyk.avif.databinding.ActivityMainBinding
2021
import kotlinx.coroutines.Dispatchers
2122
import kotlinx.coroutines.launch
@@ -38,7 +39,7 @@ class MainActivity : AppCompatActivity() {
3839

3940
// Example of a call to a native method
4041
//
41-
val buffer = this.assets.open("hato_custom_icc.avif").source().buffer().readByteArray()
42+
val buffer = this.assets.open("federico-beccari-hlg.avif").source().buffer().readByteArray()
4243
// assert(HeifCoder().isAvif(buffer))
4344
val size = HeifCoder().getSize(buffer)!!
4445
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -51,9 +52,10 @@ class MainActivity : AppCompatActivity() {
5152
// )
5253
val bitmap = HeifCoder().decodeSampled(
5354
buffer,
54-
size.width / 2,
55-
size.height / 2,
56-
PreferredColorConfig.HARDWARE
55+
350,
56+
600,
57+
PreferredColorConfig.HARDWARE,
58+
ScaleMode.FIT
5759
)
5860
// val opts = BitmapFactory.Options()
5961
// opts.inMutable = true
@@ -68,7 +70,10 @@ class MainActivity : AppCompatActivity() {
6870
// binding.imageView.setImageBitmap(bitmap)
6971
}
7072

71-
// binding.imageView.load("https://wh.aimuse.online/creatives/IMUSE_03617fe2db82a584166_27/TT_a9d21ff1061d785347935fef/68f06252.avif",
73+
//https://wh.aimuse.online/creatives/IMUSE_03617fe2db82a584166_27/TT_a9d21ff1061d785347935fef/68f06252.avif
74+
//https://wh.aimuse.online/preset/federico-beccari.avif
75+
76+
// binding.imageView.load("https://wh.aimuse.online/preset/avif10bit.avif",
7277
// imageLoader = ImageLoader.Builder(this)
7378
// .components {
7479
// add(HeifDecoder.Factory())

app/src/main/res/layout/activity_main.xml

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
<ImageView
2020
android:id="@+id/imageView"
21-
android:layout_width="0dp"
22-
android:layout_height="0dp"
23-
android:scaleType="fitCenter"
21+
android:layout_width="240dp"
22+
android:layout_height="320dp"
23+
android:scaleType="centerCrop"
2424
app:layout_constraintBottom_toBottomOf="parent"
2525
app:layout_constraintEnd_toEndOf="parent"
2626
app:layout_constraintStart_toStartOf="parent"

avif-coder-coil/src/main/java/com/github/awxkee/avifcoil/HeifDecoder.kt

+41-69
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.graphics.Canvas
55
import android.graphics.Matrix
66
import android.graphics.Paint
77
import android.graphics.drawable.BitmapDrawable
8+
import android.os.Build
89
import android.util.Size
910
import coil.ImageLoader
1011
import coil.decode.DecodeResult
@@ -14,6 +15,8 @@ import coil.request.Options
1415
import coil.size.Scale
1516
import coil.size.pxOrElse
1617
import com.radzivon.bartoshyk.avif.coder.HeifCoder
18+
import com.radzivon.bartoshyk.avif.coder.PreferredColorConfig
19+
import com.radzivon.bartoshyk.avif.coder.ScaleMode
1720
import kotlinx.coroutines.runInterruptible
1821
import okio.ByteString.Companion.encodeUtf8
1922

@@ -24,91 +27,60 @@ class HeifDecoder(
2427
) : Decoder {
2528

2629
override suspend fun decode(): DecodeResult? = runInterruptible {
30+
// ColorSpace is preferred to be ignored due to lib is trying to handle all color profile by itself
2731
val sourceData = source.source.source().readByteArray()
28-
val originalSize = HeifCoder().getSize(sourceData) ?: return@runInterruptible null
29-
val dstWidth = options.size.width.pxOrElse { 0 }
30-
val dstHeight = options.size.height.pxOrElse { 0 }
31-
if (options.scale == Scale.FIT) {
32-
val scaledSize = aspectScale(originalSize, Size(dstWidth, dstHeight))
32+
33+
var mPreferredColorConfig: PreferredColorConfig = when (options.config) {
34+
Bitmap.Config.ALPHA_8 -> PreferredColorConfig.RGBA_8888
35+
Bitmap.Config.RGB_565 -> if (options.allowRgb565) PreferredColorConfig.RGB_565 else PreferredColorConfig.DEFAULT
36+
Bitmap.Config.ARGB_8888 -> PreferredColorConfig.RGBA_8888
37+
else -> PreferredColorConfig.DEFAULT
38+
}
39+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && options.config == Bitmap.Config.RGBA_F16) {
40+
mPreferredColorConfig = PreferredColorConfig.RGBA_F16
41+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.config == Bitmap.Config.HARDWARE) {
42+
mPreferredColorConfig = PreferredColorConfig.HARDWARE
43+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && options.config == Bitmap.Config.RGBA_1010102) {
44+
mPreferredColorConfig = PreferredColorConfig.RGBA_1010102
45+
}
46+
47+
if (options.size == coil.size.Size.ORIGINAL) {
3348
val originalImage =
34-
HeifCoder().decodeSampled(sourceData, scaledSize.width, scaledSize.height)
49+
HeifCoder().decode(
50+
sourceData,
51+
preferredColorConfig = mPreferredColorConfig
52+
)
3553
return@runInterruptible DecodeResult(
3654
BitmapDrawable(
3755
options.context.resources,
3856
originalImage
39-
), true
57+
), false
4058
)
4159
}
42-
val isHorizontal = originalSize.width > originalSize.height
43-
val square = originalSize.width * originalSize.height
44-
val maxSquare = 2480 * 1440
45-
val targetSizeNotAspect: Size =
46-
if (square > maxSquare) (if (isHorizontal) Size(
47-
2480,
48-
1440
49-
) else Size(
50-
1440,
51-
2480
52-
)) else originalSize
53-
val aspectSize = aspectScale(originalSize, targetSizeNotAspect)
60+
61+
val dstWidth = options.size.width.pxOrElse { 0 }
62+
val dstHeight = options.size.height.pxOrElse { 0 }
63+
val scaleMode = when (options.scale) {
64+
Scale.FILL -> ScaleMode.FILL
65+
Scale.FIT -> ScaleMode.FIT
66+
}
67+
5468
val originalImage =
55-
HeifCoder().decodeSampled(sourceData, aspectSize.width, aspectSize.height)
56-
val resizedBitmap = resizeAspectFill(originalImage, Size(dstWidth, dstHeight))
57-
originalImage.recycle()
69+
HeifCoder().decodeSampled(
70+
sourceData,
71+
dstWidth,
72+
dstHeight,
73+
preferredColorConfig = mPreferredColorConfig,
74+
scaleMode,
75+
)
5876
return@runInterruptible DecodeResult(
5977
BitmapDrawable(
6078
options.context.resources,
61-
resizedBitmap
79+
originalImage
6280
), true
6381
)
6482
}
6583

66-
private fun resizeAspectFill(sourceBitmap: Bitmap, dstSize: Size): Bitmap {
67-
val background = Bitmap.createBitmap(dstSize.width, dstSize.height, sourceBitmap.config)
68-
val originalWidth: Float = background.width.toFloat()
69-
val originalHeight: Float = background.height.toFloat()
70-
val canvas = Canvas(background)
71-
72-
val scale: Float =
73-
if (dstSize.height > dstSize.width) dstSize.height / originalHeight else dstSize.width / originalWidth
74-
75-
val xTranslation = (dstSize.width - originalWidth * scale) / 2.0f
76-
val yTranslation: Float = (dstSize.height - originalHeight * scale) / 2.0f
77-
78-
val transformation = Matrix()
79-
transformation.postTranslate(xTranslation, yTranslation)
80-
transformation.preScale(scale, scale)
81-
82-
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
83-
paint.isFilterBitmap = true
84-
85-
canvas.drawBitmap(sourceBitmap, transformation, paint)
86-
return background
87-
}
88-
89-
private fun aspectScale(
90-
sourceSize: Size,
91-
dstSize: Size
92-
): Size {
93-
val isHorizontal = sourceSize.width > sourceSize.height
94-
val targetSize =
95-
if (isHorizontal) dstSize else Size(dstSize.height, dstSize.width)
96-
if (targetSize.width > sourceSize.width && targetSize.height > sourceSize.height) {
97-
return sourceSize
98-
}
99-
val imageAspectRatio = sourceSize.width.toFloat() / sourceSize.height.toFloat()
100-
val canvasAspectRation = targetSize.width.toFloat() / targetSize.height.toFloat()
101-
val resizeFactor: Float = if (imageAspectRatio > canvasAspectRation) {
102-
targetSize.width.toFloat() / sourceSize.width.toFloat()
103-
} else {
104-
targetSize.height.toFloat() / sourceSize.height.toFloat()
105-
}
106-
return Size(
107-
(sourceSize.width.toFloat() * resizeFactor).toInt(),
108-
(sourceSize.height.toFloat() * resizeFactor).toInt()
109-
)
110-
}
111-
11284
class Factory : Decoder.Factory {
11385
override fun create(
11486
result: SourceResult,

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.22.1)
99

1010
project("coder")
1111

12-
add_library(coder SHARED coder.cpp JniException.cpp scaler.cpp
12+
add_library(coder SHARED coder.cpp JniException.cpp SizeScaler.cpp
1313
icc/cmsalpha.c icc/cmscam02.c
1414
icc/cmscgats.c icc/cmscnvrt.c icc/cmserr.c icc/cmsgamma.c
1515
icc/cmsgmt.c icc/cmshalf.c icc/cmsintrp.c icc/cmsio0.c
@@ -61,7 +61,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6161
if (CMAKE_BUILD_TYPE STREQUAL "Release")
6262
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -ffp-mode=fast")
6363
endif ()
64-
add_definitions(-DCMS_NO_REGISTER_KEYWORD)
64+
add_definitions(-DCMS_NO_REGISTER_KEYWORD -DSTB_IMAGE_RESIZE_IMPLEMENTATION)
6565

6666
set(CMAKE_ANDROID_API_MIN 24)
6767

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

+19-88
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include "libyuv/convert_argb.h"
1010
#include <vector>
1111
#include "JniException.h"
12-
#include "scaler.h"
12+
#include "SizeScaler.h"
1313
#include <android/log.h>
1414
#include <android/data_space.h>
1515
#include <sys/system_properties.h>
@@ -31,9 +31,13 @@
3131

3232
jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
3333
std::vector<uint8_t> &srcBuffer, jint scaledWidth,
34-
jint scaledHeight, jint javaColorspace) {
34+
jint scaledHeight, jint javaColorSpace, jint javaScaleMode) {
3535
PreferredColorConfig preferredColorConfig;
36-
checkDecodePreconditions(env, javaColorspace, &preferredColorConfig);
36+
ScaleMode scaleMode;
37+
if (!checkDecodePreconditions(env, javaColorSpace, &preferredColorConfig, javaScaleMode,
38+
&scaleMode)) {
39+
return static_cast<jobject>(nullptr);
40+
}
3741

3842
std::shared_ptr<heif_context> ctx(heif_context_alloc(),
3943
[](heif_context *c) { heif_context_free(c); });
@@ -131,87 +135,13 @@ jobject decodeImplementationNative(JNIEnv *env, jobject thiz,
131135
int stride;
132136
std::vector<uint8_t> initialData;
133137

134-
if (scaledHeight > 0 && scaledWidth > 0) {
135-
if (bitDepth == 8) {
136-
auto data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
137-
if (!data) {
138-
heif_image_release(img);
139-
heif_image_handle_release(handle);
140-
std::string exception = "Interleaving planed has failed";
141-
throwException(env, exception);
142-
return nullptr;
143-
}
144-
imageWidth = heif_image_get_width(img, heif_channel_interleaved);
145-
imageHeight = heif_image_get_height(img, heif_channel_interleaved);
146-
if (result.code != heif_error_Ok) {
147-
heif_image_release(img);
148-
heif_image_handle_release(handle);
149-
std::string cp(result.message);
150-
std::string exception = "HEIF wasn't able to scale image due to " + cp;
151-
throwException(env, exception);
152-
return static_cast<jobject>(nullptr);
153-
}
154-
155-
// For unknown reason some images doesn't scale properly by libheif in 8-bit
156-
initialData.resize(scaledWidth * 4 * scaledHeight);
157-
libyuv::ARGBScale(data, stride, imageWidth, imageHeight, initialData.data(),
158-
scaledWidth * 4, scaledWidth, scaledHeight, libyuv::kFilterBox);
159-
stride = scaledWidth * 4;
160-
imageHeight = scaledHeight;
161-
imageWidth = scaledWidth;
162-
heif_image_release(img);
163-
} else {
164-
heif_image *scaledImg;
165-
result = heif_image_scale_image(img, &scaledImg, scaledWidth, scaledHeight, nullptr);
166-
if (result.code != heif_error_Ok) {
167-
heif_image_release(img);
168-
heif_image_handle_release(handle);
169-
std::string cp(result.message);
170-
std::string exception = "HEIF wasn't able to scale image due to " + cp;
171-
throwException(env, exception);
172-
return static_cast<jobject>(nullptr);
173-
}
174-
175-
heif_image_release(img);
176-
177-
auto data = heif_image_get_plane_readonly(scaledImg, heif_channel_interleaved, &stride);
178-
if (!data) {
179-
heif_image_release(scaledImg);
180-
heif_image_handle_release(handle);
181-
std::string exception = "Interleaving planed has failed";
182-
throwException(env, exception);
183-
return nullptr;
184-
}
185-
imageWidth = heif_image_get_width(scaledImg, heif_channel_interleaved);
186-
imageHeight = heif_image_get_height(scaledImg, heif_channel_interleaved);
187-
initialData.resize(stride * imageHeight);
188-
coder::CopyUnalignedRGBA(reinterpret_cast<const uint8_t *>(data), stride,
189-
reinterpret_cast<uint8_t *>(initialData.data()), stride,
190-
imageWidth,
191-
imageHeight, useBitmapHalf16Floats ? 2 : 1);
192-
heif_image_release(scaledImg);
193-
}
194-
195-
} else {
196-
auto data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
197-
if (!data) {
198-
heif_image_release(img);
199-
heif_image_handle_release(handle);
200-
std::string exception = "Interleaving planed has failed";
201-
throwException(env, exception);
202-
return nullptr;
203-
}
204-
205-
imageWidth = heif_image_get_width(img, heif_channel_interleaved);
206-
imageHeight = heif_image_get_height(img, heif_channel_interleaved);
207-
initialData.resize(stride * imageHeight);
208-
209-
coder::CopyUnalignedRGBA(reinterpret_cast<const uint8_t *>(data), stride,
210-
reinterpret_cast<uint8_t *>(initialData.data()), stride,
211-
imageWidth,
212-
imageHeight, useBitmapHalf16Floats ? 2 : 1);
213-
214-
heif_image_release(img);
138+
imageWidth = heif_image_get_width(img, heif_channel_interleaved);
139+
imageHeight = heif_image_get_height(img, heif_channel_interleaved);
140+
auto scaleResult = RescaleImage(initialData, env, handle, img, &stride, useBitmapHalf16Floats,
141+
&imageWidth, &imageHeight, scaledWidth, scaledHeight,
142+
scaleMode);
143+
if (!scaleResult) {
144+
return static_cast<jobject >(nullptr);
215145
}
216146

217147
std::shared_ptr<uint8_t> dstARGB(static_cast<uint8_t *>(malloc(stride * imageHeight)),
@@ -328,21 +258,22 @@ JNIEXPORT jobject JNICALL
328258
Java_com_radzivon_bartoshyk_avif_coder_HeifCoder_decodeImpl(JNIEnv *env, jobject thiz,
329259
jbyteArray byte_array, jint scaledWidth,
330260
jint scaledHeight,
331-
jint javaColorspace) {
261+
jint javaColorspace, jint scaleMode) {
332262
auto totalLength = env->GetArrayLength(byte_array);
333263
std::vector<uint8_t> srcBuffer(totalLength);
334264
env->GetByteArrayRegion(byte_array, 0, totalLength,
335265
reinterpret_cast<jbyte *>(srcBuffer.data()));
336266
return decodeImplementationNative(env, thiz, srcBuffer, scaledWidth, scaledHeight,
337-
javaColorspace);
267+
javaColorspace, scaleMode);
338268
}
339269
extern "C"
340270
JNIEXPORT jobject JNICALL
341271
Java_com_radzivon_bartoshyk_avif_coder_HeifCoder_decodeByteBufferImpl(JNIEnv *env, jobject thiz,
342272
jobject byteBuffer,
343273
jint scaledWidth,
344274
jint scaledHeight,
345-
jint clrConfig) {
275+
jint clrConfig,
276+
jint scaleMode) {
346277
auto bufferAddress = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(byteBuffer));
347278
int length = (int) env->GetDirectBufferCapacity(byteBuffer);
348279
if (!bufferAddress || length <= 0) {
@@ -353,5 +284,5 @@ Java_com_radzivon_bartoshyk_avif_coder_HeifCoder_decodeByteBufferImpl(JNIEnv *en
353284
std::vector<uint8_t> srcBuffer(length);
354285
std::copy(bufferAddress, bufferAddress + length, srcBuffer.begin());
355286
return decodeImplementationNative(env, thiz, srcBuffer, scaledWidth, scaledHeight,
356-
clrConfig);
287+
clrConfig, scaleMode);
357288
}

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

-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@ jint throwHardwareBitmapException(JNIEnv *env) {
1616
return env->ThrowNew(exClass, "");
1717
}
1818

19-
jint throwCantEncodeImageException(JNIEnv *env, const char * msg) {
20-
jclass exClass;
21-
exClass = env->FindClass("com/radzivon/bartoshyk/avif/coder/CantEncodeImageException");
22-
return env->ThrowNew(exClass, msg);
23-
}
24-
2519
jint throwInvalidPixelsFormat(JNIEnv *env) {
2620
jclass exClass;
2721
exClass = env->FindClass("com/radzivon/bartoshyk/avif/coder/UnsupportedImageFormatException");

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

-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
jint throwBitDepthException(JNIEnv *env);
1212

13-
jint throwCantEncodeImageException(JNIEnv *env, const char *msg);
14-
1513
jint throwInvalidPixelsFormat(JNIEnv *env);
1614

1715
jint throwPixelsException(JNIEnv *env);

0 commit comments

Comments
 (0)