Skip to content

Commit

Permalink
Added alpha compression, and different compression quality
Browse files Browse the repository at this point in the history
  • Loading branch information
awxkee committed Aug 20, 2023
1 parent 575efbb commit af16495
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ repositories {
```

```groovy
implementation 'com.github.awxkee:jxlcoder:1.0.4' // or any version above picker from release tags
implementation 'com.github.awxkee:jxlcoder:1.0.5' // or any version above picker from release tags
```
Binary file added app/src/main/assets/alpha_jxl.jxl
Binary file not shown.
10 changes: 8 additions & 2 deletions app/src/main/java/com/awxkee/jxlcoder/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ class MainActivity : ComponentActivity() {

val buffer1 = this.assets.open("first_jxl.jxl").source().buffer().readByteArray()
val buffer2 = this.assets.open("second_jxl.jxl").source().buffer().readByteArray()
val image = JxlCoder().decode(buffer2)
val compressedBuffer = JxlCoder().encode(image)
val buffer3 = this.assets.open("alpha_jxl.jxl").source().buffer().readByteArray()
val image = JxlCoder().decode(buffer3)
val compressedBuffer = JxlCoder().encode(
image,
colorSpace = JxlColorSpace.RGBA,
compressionOption = JxlCompressionOption.LOOSY,
loosyLevel = 5.0f
)
val decompressedImage = JxlCoder().decode(compressedBuffer)

setContent {
Expand Down
112 changes: 97 additions & 15 deletions jxlcoder/src/main/cpp/jxlcoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
#include <jxl/encode_cxx.h>
#include "android/bitmap.h"

enum jxl_colorspace {
rgb = 1,
rgba = 2
};

enum jxl_compression_option {
loseless = 1,
loosy = 2
};

/**
* Compresses the provided pixels.
*
Expand All @@ -22,7 +32,9 @@
* @param compressed will be populated with the compressed bytes
*/
bool EncodeJxlOneshot(const std::vector<uint8_t> &pixels, const uint32_t xsize,
const uint32_t ysize, std::vector<uint8_t> *compressed) {
const uint32_t ysize, std::vector<uint8_t> *compressed,
jxl_colorspace colorspace, jxl_compression_option compression_option,
float compression_distance) {
auto enc = JxlEncoderMake(/*memory_manager=*/nullptr);
auto runner = JxlThreadParallelRunnerMake(
/*memory_manager=*/nullptr,
Expand All @@ -34,25 +46,53 @@ bool EncodeJxlOneshot(const std::vector<uint8_t> &pixels, const uint32_t xsize,
return false;
}

JxlPixelFormat pixel_format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
JxlPixelFormat pixel_format;
switch (colorspace) {
case rgb:
pixel_format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
break;
case rgba:
pixel_format = {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
break;
}

JxlBasicInfo basic_info;
JxlEncoderInitBasicInfo(&basic_info);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.bits_per_sample = 32;
basic_info.exponent_bits_per_sample = 8;
basic_info.uses_original_profile = JXL_FALSE;
basic_info.uses_original_profile = compression_option == loosy ? JXL_FALSE : JXL_TRUE;
basic_info.num_color_channels = 3;
basic_info.alpha_bits = 0;

if (colorspace == rgba) {
basic_info.num_extra_channels = 1;
basic_info.alpha_bits = 8;
}

if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) {
fprintf(stderr, "JxlEncoderSetBasicInfo failed\n");
return false;
}

switch (colorspace) {
case rgb:
basic_info.num_color_channels = 3;
break;
case rgba:
basic_info.num_color_channels = 4;
JxlExtraChannelInfo channelInfo;
JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &channelInfo);
channelInfo.bits_per_sample = 8;
channelInfo.alpha_premultiplied = false;
if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(enc.get(), 0, &channelInfo)) {
return false;
}
break;
}

JxlColorEncoding color_encoding = {};
JxlColorEncodingSetToSRGB(&color_encoding,
/*is_gray=*/pixel_format.num_channels < 3);
JxlColorEncodingSetToSRGB(&color_encoding, pixel_format.num_channels < 3);
if (JXL_ENC_SUCCESS !=
JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) {
fprintf(stderr, "JxlEncoderSetColorEncoding failed\n");
Expand All @@ -69,6 +109,16 @@ bool EncodeJxlOneshot(const std::vector<uint8_t> &pixels, const uint32_t xsize,
fprintf(stderr, "JxlEncoderAddImageFrame failed\n");
return false;
}

if (compression_option == loseless &&
JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, JXL_TRUE)) {
return false;
} else if (compression_option == loosy &&
JXL_ENC_SUCCESS !=
JxlEncoderSetFrameDistance(frame_settings, compression_distance)) {
return false;
}

JxlEncoderCloseInput(enc.get());

compressed->resize(64);
Expand Down Expand Up @@ -223,6 +273,18 @@ jint throwCantCompressImage(JNIEnv *env) {
return env->ThrowNew(exClass, "");
}

jint throwInvalidColorSpaceException(JNIEnv *env) {
jclass exClass;
exClass = env->FindClass("com/awxkee/jxlcoder/InvalidColorSpaceException");
return env->ThrowNew(exClass, "");
}

jint throwInvalidCompressionOptionException(JNIEnv *env) {
jclass exClass;
exClass = env->FindClass("com/awxkee/jxlcoder/InvalidCompressionOptionException");
return env->ThrowNew(exClass, "");
}

extern "C"
JNIEXPORT jobject JNICALL
Java_com_awxkee_jxlcoder_JxlCoder_decodeImpl(JNIEnv *env, jobject thiz, jbyteArray byte_array) {
Expand Down Expand Up @@ -273,7 +335,19 @@ Java_com_awxkee_jxlcoder_JxlCoder_decodeImpl(JNIEnv *env, jobject thiz, jbyteArr
}
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_awxkee_jxlcoder_JxlCoder_encodeImpl(JNIEnv *env, jobject thiz, jobject bitmap) {
Java_com_awxkee_jxlcoder_JxlCoder_encodeImpl(JNIEnv *env, jobject thiz, jobject bitmap,
jint javaColorSpace, jint javaCompressionOption,
jfloat compression_level) {
auto colorspace = static_cast<jxl_colorspace>(javaColorSpace);
if (!colorspace) {
throwInvalidColorSpaceException(env);
return static_cast<jbyteArray>(nullptr);
}
auto compression_option = static_cast<jxl_compression_option>(javaCompressionOption);
if (!compression_option) {
throwInvalidCompressionOptionException(env);
return static_cast<jbyteArray>(nullptr);
}
AndroidBitmapInfo info;
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
throwPixelsException(env);
Expand Down Expand Up @@ -302,23 +376,31 @@ Java_com_awxkee_jxlcoder_JxlCoder_encodeImpl(JNIEnv *env, jobject thiz, jobject
AndroidBitmap_unlockPixels(env, bitmap);

std::vector<uint8_t> rgbPixels;
rgbPixels.resize(info.width * info.height * 3);
libyuv::ARGBToRGB24(rgbaPixels.data(), static_cast<int>(info.stride), rgbPixels.data(),
static_cast<int>(info.width * 3), static_cast<int>(info.width),
static_cast<int>(info.height));
switch (colorspace) {
case rgb:
rgbPixels.resize(info.width * info.height * 3);
libyuv::ARGBToRGB24(rgbaPixels.data(), static_cast<int>(info.stride), rgbPixels.data(),
static_cast<int>(info.width * 3), static_cast<int>(info.width),
static_cast<int>(info.height));
break;
case rgba:
rgbPixels.resize(info.stride * info.height);
libyuv::ARGBToABGR(rgbaPixels.data(), static_cast<int>(info.stride), rgbPixels.data(),
static_cast<int>(info.stride), static_cast<int>(info.width),
static_cast<int>(info.height));
break;
}
rgbaPixels.clear();
rgbaPixels.resize(1);

std::vector<uint8_t> compressedVector;

if (!EncodeJxlOneshot(rgbPixels, info.width, info.height, &compressedVector)) {
if (!EncodeJxlOneshot(rgbPixels, info.width, info.height, &compressedVector, colorspace,
compression_option, compression_level)) {
throwCantCompressImage(env);
return static_cast<jbyteArray>(nullptr);
}

rgbaPixels.clear();
rgbaPixels.resize(1);

jbyteArray byteArray = env->NewByteArray((jsize) compressedVector.size());
char *memBuf = (char *) ((void *) compressedVector.data());
env->SetByteArrayRegion(byteArray, 0, (jint) compressedVector.size(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.awxkee.jxlcoder

import androidx.annotation.Keep

@Keep
class InvalidColorSpaceException: Exception("Invalid color space was provided")
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.awxkee.jxlcoder

import androidx.annotation.Keep

@Keep
class InvalidCompressionOptionException: Exception("Invalid compression option was provided") {
}
28 changes: 24 additions & 4 deletions jxlcoder/src/main/java/com/awxkee/jxlcoder/JxlCoder.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.awxkee.jxlcoder

import android.graphics.Bitmap
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.annotation.Keep

@Keep
Expand All @@ -10,15 +12,33 @@ class JxlCoder {
return decodeImpl(byteArray)
}

fun encode(bitmap: Bitmap): ByteArray {
return encodeImpl(bitmap)
/**
* @param loosyLevel Sets the distance level for lossy compression: target max butteraugli
* * distance, lower = higher quality. Range: 0 .. 15.
* * 0.0 = mathematically lossless (however, use JxlEncoderSetFrameLossless
* * instead to use true lossless, as setting distance to 0 alone is not the only
* * requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default
* * value: 1.0.
*/
fun encode(
bitmap: Bitmap,
colorSpace: JxlColorSpace = JxlColorSpace.RGB,
compressionOption: JxlCompressionOption = JxlCompressionOption.LOOSY,
@FloatRange(from = 0.0, to = 15.0) loosyLevel: Float = 1.0f,
): ByteArray {
return encodeImpl(bitmap, colorSpace.cValue, compressionOption.cValue, loosyLevel)
}

private external fun decodeImpl(
byteArray: ByteArray,
byteArray: ByteArray
): Bitmap

private external fun encodeImpl(bitmap: Bitmap): ByteArray
private external fun encodeImpl(
bitmap: Bitmap,
colorSpace: Int,
compressionOption: Int,
loosyLevel: Float,
): ByteArray

companion object {
// Used to load the 'jxlcoder' library on application startup.
Expand Down
5 changes: 5 additions & 0 deletions jxlcoder/src/main/java/com/awxkee/jxlcoder/JxlColorSpace.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.awxkee.jxlcoder

enum class JxlColorSpace(internal val cValue: Int) {
RGB(1), RGBA(2)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.awxkee.jxlcoder

enum class JxlCompressionOption(internal val cValue: Int) {
LOSELESS(1), LOOSY(2)
}

0 comments on commit af16495

Please sign in to comment.