├── .github └── workflows │ └── fastlane.yml ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── kotlinc.xml ├── migrations.xml └── vcs.xml ├── LICENSE-MIT ├── README.md ├── app ├── .gitignore ├── CHANGELOG.md ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── logo.jxl │ ├── ic_launcher-playstore.png │ ├── java │ └── fr │ │ └── oupson │ │ └── jxlviewer │ │ ├── JxlApplication.java │ │ └── ViewerActivity.kt │ └── res │ ├── drawable │ ├── baseline_error_outline_24.xml │ └── ic_launcher_foreground.xml │ ├── layout │ └── activity_view.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ └── ic_launcher.webp │ ├── mipmap-mdpi │ └── ic_launcher.webp │ ├── mipmap-xhdpi │ └── ic_launcher.webp │ ├── mipmap-xxhdpi │ └── ic_launcher.webp │ ├── mipmap-xxxhdpi │ └── ic_launcher.webp │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 1.txt │ ├── 2.txt │ ├── 3.txt │ ├── 4.txt │ └── 5.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ └── 2.png │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── libjxl ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ ├── assets │ │ ├── android.png │ │ ├── didi.jxl │ │ ├── ferris.jxl │ │ └── logo.jxl │ └── java │ │ └── fr │ │ └── oupson │ │ └── libjxl │ │ └── JxlDecodeAndroidUnitTest.java │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ ├── includes │ │ ├── Decoder.h │ │ ├── Exception.h │ │ ├── FileDescriptorInputSource.h │ │ ├── ImageOutCallbackData.h │ │ ├── InputSource.h │ │ ├── JniInputStream.h │ │ ├── Options.h │ │ └── jxlviewer_consts.h │ ├── native-lib.cpp │ ├── src │ │ ├── Decoder.cpp │ │ └── Exception.cpp │ └── third_party │ │ ├── CMakeLists.txt │ │ └── skcms.cmake │ └── java │ └── fr │ └── oupson │ └── libjxl │ ├── JxlDecoder.java │ └── exceptions │ ├── ConfigException.java │ └── DecodeError.java ├── libjxl_microbenchmark ├── .gitignore ├── benchmark-proguard-rules.pro ├── build.gradle.kts └── src │ ├── androidTest │ ├── AndroidManifest.xml │ ├── assets │ │ ├── didi.jxl │ │ ├── ferris.jxl │ │ └── logo.jxl │ └── java │ │ └── fr │ │ └── oupson │ │ └── libjxl_microbenchmark │ │ └── JxlDecoderBenchmark.java │ └── main │ └── AndroidManifest.xml └── settings.gradle.kts /.github/workflows/fastlane.yml: -------------------------------------------------------------------------------- 1 | name: Fastlane Android 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | pull_request: 7 | jobs: 8 | fastlane-validate: 9 | # required to run on Linux because this is a docker container action 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libjxl/src/main/cpp/third_party/libjxl"] 2 | path = libjxl/src/main/cpp/third_party/libjxl 3 | url = https://github.com/libjxl/libjxl.git 4 | [submodule "libjxl/src/main/cpp/third_party/skcms"] 5 | path = libjxl/src/main/cpp/third_party/skcms 6 | url = https://skia.googlesource.com/skcms.git 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | 5 | deploymentTargetDropDown.xml 6 | androidTestResultsUserPreferences.xml 7 | 8 | /gradle.xml 9 | /misc.xml 10 | 11 | /sonarlint -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | JxlViewer -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 oupson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JxlViewer 2 | [![](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/fr.oupson.jxlviewer)](https://apt.izzysoft.de/fdroid/index/apk/fr.oupson.jxlviewer) 3 | [![GitHub release (latest by date)](https://img.shields.io/github/downloads/oupson/jxlviewer/latest/total)](https://github.com/oupson/jxlviewer/releases/latest) 4 | [![Support me](https://img.shields.io/liberapay/patrons/oupson.svg?logo=liberapay)](https://liberapay.com/oupson/) 5 | [![Maven Central Version](https://img.shields.io/maven-central/v/fr.oupson/libjxl)](https://central.sonatype.com/artifact/fr.oupson/libjxl/overview) 6 | 7 | A Jpeg XL viewer for android using [libjxl](https://github.com/libjxl/libjxl). 8 | 9 | ## Installation 10 | - Get it on [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/fr.oupson.jxlviewer) ! 11 | - Or install apk from [latest release](https://github.com/oupson/jxlviewer/releases/) (install universal if you don't know your abi). 12 | 13 | ## Changelog 14 | [See here](app/CHANGELOG.md) 15 | 16 | ## License 17 | Licensed under MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT). 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.4.0] - 2024-01-03 8 | ### Changed 9 | - Updated libjxl to v0.9.0 10 | - Improve multithreading decoding. 11 | 12 | ## [0.3.0] - 2023-01-22 13 | ### Added: 14 | - Error message when image failed to load. 15 | ### Changed 16 | - Updated libjxl to v0.8.0 17 | ### Fixed 18 | - App was registered to open non-jxl files on older devices. 19 | 20 | ## [0.2.2] - 2022-12-21 21 | ## Added: 22 | - Open image from URL. 23 | ## Fixed: 24 | - Load images with icc profiles. 25 | 26 | 27 | ## [0.2.1] - 2022-12-08 28 | ### Changed 29 | - Improve bitmap loading. 30 | ### Fixed 31 | - Fix issue with alpha. 32 | 33 | ## [0.2.0] - 2022-11-15 34 | ### Added 35 | - `x86`, `x86_64` and `armeabi-v7a` support. 36 | - Basic theme. 37 | 38 | ## [0.1.0] - 2022-10-28 39 | ### Added 40 | - Read jpeg xl images, animated or not for arm64 devices. 41 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "fr.oupson.jxlviewer" 8 | compileSdk = libs.versions.android.compileSdk.get().toInt() 9 | 10 | defaultConfig { 11 | applicationId = "fr.oupson.jxlviewer" 12 | minSdk = libs.versions.android.minSdk.get().toInt() 13 | targetSdk = libs.versions.android.compileSdk.get().toInt() 14 | versionCode = libs.versions.release.versionCode.get().toInt() 15 | versionName = libs.versions.release.version.get() 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | 19 | splits { 20 | abi { 21 | isEnable = true 22 | reset() 23 | include("x86", "x86_64", "armeabi-v7a", "arm64-v8a") 24 | isUniversalApk = true 25 | } 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | isMinifyEnabled = true 32 | isShrinkResources = true 33 | proguardFiles( 34 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 35 | ) 36 | } 37 | } 38 | 39 | compileOptions { 40 | sourceCompatibility = JavaVersion.VERSION_1_8 41 | targetCompatibility = JavaVersion.VERSION_1_8 42 | } 43 | 44 | kotlinOptions { 45 | jvmTarget = "1.8" 46 | } 47 | 48 | buildFeatures { 49 | viewBinding = true 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation(libs.androidx.appcompat) 55 | implementation(libs.androidx.constraintlayout) 56 | implementation(libs.androidx.core.ktx) 57 | implementation(libs.androidx.lifecycle.runtime.ktx) 58 | 59 | implementation(libs.google.material) 60 | 61 | implementation(libs.photoview) 62 | 63 | implementation(project(":libjxl")) 64 | 65 | testImplementation(libs.junit) 66 | androidTestImplementation(libs.androidx.test.junit) 67 | androidTestImplementation(libs.androidx.test.espresso) 68 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/assets/logo.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/assets/logo.jxl -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/fr/oupson/jxlviewer/JxlApplication.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.jxlviewer; 2 | 3 | import android.app.Application; 4 | 5 | import com.google.android.material.color.DynamicColors; 6 | 7 | // Apply Android 12+ dynamic colors. 8 | public class JxlApplication extends Application { 9 | @Override 10 | public void onCreate() { 11 | super.onCreate(); 12 | DynamicColors.applyToActivitiesIfAvailable(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/fr/oupson/jxlviewer/ViewerActivity.kt: -------------------------------------------------------------------------------- 1 | package fr.oupson.jxlviewer 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.drawable.AnimationDrawable 5 | import android.graphics.drawable.Drawable 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.os.ParcelFileDescriptor 9 | import android.util.Log 10 | import android.widget.Toast 11 | import androidx.activity.ComponentActivity 12 | import androidx.core.view.WindowCompat 13 | import androidx.core.view.WindowInsetsCompat 14 | import androidx.core.view.WindowInsetsControllerCompat 15 | import androidx.lifecycle.lifecycleScope 16 | import fr.oupson.jxlviewer.databinding.ActivityViewBinding 17 | import fr.oupson.libjxl.JxlDecoder 18 | import kotlinx.coroutines.Dispatchers 19 | import kotlinx.coroutines.launch 20 | import kotlinx.coroutines.withContext 21 | import java.io.InputStream 22 | import java.net.HttpURLConnection 23 | import java.net.URL 24 | 25 | class ViewerActivity : ComponentActivity() { 26 | companion object { 27 | private const val TAG = "ViewerActivity" 28 | } 29 | 30 | private lateinit var binding: ActivityViewBinding 31 | 32 | private val decoderConfig = JxlDecoder.Options().apply { 33 | format = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 34 | Bitmap.Config.RGBA_F16 35 | } else { 36 | Bitmap.Config.ARGB_8888 37 | } 38 | decodeMultipleFrames = true 39 | } 40 | 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | 45 | binding = ActivityViewBinding.inflate(layoutInflater) 46 | setContentView(binding.root) 47 | 48 | hideSystemBars() 49 | loadImage() 50 | } 51 | 52 | // Load image from intent if opened from file picker / other apps, or load default image. 53 | private fun loadImage() { 54 | lifecycleScope.launch(Dispatchers.IO) { 55 | try { 56 | 57 | val image = intent?.data?.let { 58 | when (it.scheme) { 59 | "https" -> { 60 | val conn = (URL(it.toString()).openConnection() as HttpURLConnection) 61 | conn.connect() 62 | 63 | val img = loadImage(conn.inputStream) 64 | conn.disconnect() 65 | 66 | img 67 | } 68 | 69 | else -> loadImage(contentResolver.openFileDescriptor(it, "r")!!) 70 | } 71 | } 72 | ?: loadImage(resources.assets.open("logo.jxl")) 73 | 74 | withContext(Dispatchers.Main) { 75 | binding.test.setImageDrawable(image) 76 | (image as? AnimationDrawable)?.start() 77 | } 78 | } catch (e: Exception) { 79 | Log.e(TAG, "Failed to load image", e) 80 | withContext(Dispatchers.Main) { 81 | binding.test.setImageResource(R.drawable.baseline_error_outline_24) 82 | Toast.makeText( 83 | this@ViewerActivity, 84 | R.string.image_load_failed, 85 | Toast.LENGTH_SHORT 86 | ).show() 87 | } 88 | } 89 | } 90 | } 91 | 92 | private fun loadImage(input: InputStream): Drawable? = input.use { 93 | JxlDecoder.loadJxl(it, decoderConfig) 94 | } 95 | 96 | private fun loadImage(fd: ParcelFileDescriptor): Drawable? = fd.use { 97 | JxlDecoder.loadJxl(it, decoderConfig) 98 | } 99 | 100 | // Enable immersive mode. 101 | private fun hideSystemBars() { 102 | val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView) 103 | // Configure the behavior of the hidden system bars 104 | windowInsetsController.systemBarsBehavior = 105 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 106 | // Hide both the status bar and the navigation bar 107 | windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_error_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #5fb4b1 5 | #006A68 6 | #FFFFFF 7 | #6FF7F3 8 | #00201F 9 | #4A6362 10 | #FFFFFF 11 | #CCE8E6 12 | #051F1F 13 | #4A607B 14 | #FFFFFF 15 | #D1E4FF 16 | #021D35 17 | #BA1A1A 18 | #FFDAD6 19 | #FFFFFF 20 | #410002 21 | #FAFDFC 22 | #191C1C 23 | #FAFDFC 24 | #191C1C 25 | #DAE5E3 26 | #3F4948 27 | #6F7978 28 | #EFF1F0 29 | #2D3131 30 | #4DDAD6 31 | #000000 32 | #006A68 33 | #BEC9C7 34 | #000000 35 | #4DDAD6 36 | #003736 37 | #00504E 38 | #6FF7F3 39 | #B0CCCA 40 | #1B3534 41 | #324B4A 42 | #CCE8E6 43 | #B1C8E8 44 | #1B324B 45 | #324862 46 | #D1E4FF 47 | #FFB4AB 48 | #93000A 49 | #690005 50 | #FFDAD6 51 | #191C1C 52 | #E0E3E2 53 | #191C1C 54 | #E0E3E2 55 | #3F4948 56 | #BEC9C7 57 | #889392 58 | #191C1C 59 | #E0E3E2 60 | #006A68 61 | #000000 62 | #4DDAD6 63 | #3F4948 64 | #000000 65 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0D1117 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Jpeg XL Viewer 3 | 4 | 5 | Failed to load image 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) apply false 3 | alias(libs.plugins.android.library) apply false 4 | alias(libs.plugins.kotlin.android) apply false 5 | alias(libs.plugins.androidx.benchmark) apply false 6 | alias(libs.plugins.vanniktech.maven.publish) apply false 7 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Initial release 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Changed: 2 | - Improve bitmap loading. 3 | Fixed: 4 | - Fix issue with alpha. 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Added: 2 | - Open image from URL. 3 | Fixed: 4 | - Load images with icc profiles. 5 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Added: 2 | - Error message when image failed to load. 3 | Changed: 4 | - Updated libjxl to v0.8.0 5 | Fixed: 6 | - App was registered to open non-jxl files on older devices. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Changed: 2 | - Updated libjxl to v0.9.0 3 | - Improve multithreading decoding. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | An application that allow you to view your JPEG XL files on your device ! 2 | 3 | It support animated and non-animated images. 4 | 5 | JPEG XL is a raster-graphics file format, it is designed to outperform existing raster formats. 6 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | View your jpeg-xl files. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | JxlViewer 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.defaults.buildfeatures.buildconfig=true 25 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | android-minSdk = "21" 3 | android-compileSdk = "35" 4 | release-version = "0.5.0-alpha2" 5 | release-versionCode = "6" 6 | 7 | agp = "8.7.2" 8 | 9 | kotlin = "1.9.22" 10 | 11 | junit = "4.13.2" 12 | 13 | androidx-appcompat = "1.7.0" 14 | androidx-benchmark = "1.3.3" 15 | androidx-constraintlayout = "2.2.0" 16 | androidx-core-ktx = "1.15.0" 17 | androidx-lifecycle = "2.8.7" 18 | androidx-test-espresso = "3.6.1" 19 | androidx-test-ext = "1.2.1" 20 | androidx-test-runner = "1.6.2" 21 | 22 | google-material = "1.12.0" 23 | 24 | photoview = "2.3.0" 25 | 26 | vanniktech-maven-publish = "0.30.0" 27 | 28 | [libraries] 29 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } 30 | androidx-benchmark-junit = { module = "androidx.benchmark:benchmark-junit4", version.ref = "androidx-benchmark" } 31 | androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" } 32 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" } 33 | androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } 34 | androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } 35 | androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext" } 36 | androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } 37 | 38 | google-material = { module = "com.google.android.material:material", version.ref = "google-material" } 39 | 40 | photoview = { module = "com.github.Baseflow:PhotoView", version.ref = "photoview" } 41 | 42 | junit = { module = "junit:junit", version.ref = "junit" } 43 | 44 | [plugins] 45 | android-application = { id = "com.android.application", version.ref = "agp" } 46 | android-library = { id = "com.android.library", version.ref = "agp" } 47 | androidx-benchmark = { id = "androidx.benchmark", version.ref = "androidx-benchmark" } 48 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 49 | vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech-maven-publish" } 50 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Oct 17 10:35:52 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | -------------------------------------------------------------------------------- /libjxl/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libjxl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.SonatypeHost 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | id("maven-publish") 6 | alias(libs.plugins.vanniktech.maven.publish) 7 | } 8 | 9 | android { 10 | namespace = "fr.oupson.libjxl" 11 | compileSdk = libs.versions.android.compileSdk.get().toInt() 12 | 13 | defaultConfig { 14 | minSdk = libs.versions.android.minSdk.get().toInt() 15 | 16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles("consumer-rules.pro") 18 | externalNativeBuild { 19 | cmake { 20 | cppFlags("-std=c++11") 21 | targets("jxlreader") 22 | arguments("-DANDROID_ARM_NEON=ON") 23 | } 24 | } 25 | 26 | splits { 27 | abi { 28 | isEnable = true 29 | reset() 30 | include("x86", "x86_64", "armeabi-v7a", "arm64-v8a") 31 | isUniversalApk = true 32 | } 33 | } 34 | } 35 | 36 | buildTypes { 37 | release { 38 | isMinifyEnabled = false 39 | proguardFiles( 40 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 41 | ) 42 | } 43 | } 44 | externalNativeBuild { 45 | cmake { 46 | path = file("src/main/cpp/CMakeLists.txt") 47 | } 48 | } 49 | compileOptions { 50 | sourceCompatibility = JavaVersion.VERSION_1_8 51 | targetCompatibility = JavaVersion.VERSION_1_8 52 | } 53 | publishing { 54 | multipleVariants { 55 | withSourcesJar() 56 | } 57 | } 58 | } 59 | 60 | dependencies { 61 | testImplementation(libs.junit) 62 | androidTestImplementation(libs.androidx.test.junit) 63 | androidTestImplementation(libs.androidx.test.espresso) 64 | } 65 | 66 | fun MavenPom.configure() { 67 | name.set("libjxl") 68 | description.set("A JPEG-XL decoding library for android.") 69 | url.set("https://github.com/oupson/jxlviewer/") 70 | version = libs.versions.release.version.get() 71 | licenses { 72 | license { 73 | name.set("MIT License") 74 | url.set("https://mit-license.org/") 75 | distribution.set("https://mit-license.org/") 76 | } 77 | } 78 | developers { 79 | developer { 80 | id.set("oupson") 81 | name.set("oupson") 82 | url.set("https://github.com/oupson/") 83 | } 84 | } 85 | scm { 86 | url.set("https://github.com/oupson/jxlviewer/") 87 | connection.set("scm:git:git://github.com/oupson/jxlviewer.git") 88 | developerConnection.set("scm:git:https://github.com/oupson/jxlviewer.git") 89 | } 90 | } 91 | 92 | mavenPublishing { 93 | publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) 94 | signAllPublications() 95 | coordinates("fr.oupson", "libjxl", libs.versions.release.version.get()) 96 | pom { 97 | this.configure() 98 | } 99 | } -------------------------------------------------------------------------------- /libjxl/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class fr.oupson.libjxl.exceptions.DecodeError { (int); } 2 | -keep class fr.oupson.libjxl.exceptions.DecodeError { (int,java.lang.String); } 3 | 4 | -keep class fr.oupson.libjxl.** { 5 | static native ; 6 | } -------------------------------------------------------------------------------- /libjxl/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /libjxl/src/androidTest/assets/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl/src/androidTest/assets/android.png -------------------------------------------------------------------------------- /libjxl/src/androidTest/assets/didi.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl/src/androidTest/assets/didi.jxl -------------------------------------------------------------------------------- /libjxl/src/androidTest/assets/ferris.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl/src/androidTest/assets/ferris.jxl -------------------------------------------------------------------------------- /libjxl/src/androidTest/assets/logo.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl/src/androidTest/assets/logo.jxl -------------------------------------------------------------------------------- /libjxl/src/androidTest/java/fr/oupson/libjxl/JxlDecodeAndroidUnitTest.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.libjxl; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.AnimationDrawable; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.net.Uri; 9 | import android.os.ParcelFileDescriptor; 10 | 11 | import androidx.test.core.app.ApplicationProvider; 12 | 13 | import org.junit.Assert; 14 | import org.junit.Test; 15 | 16 | import java.io.ByteArrayInputStream; 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.nio.file.Files; 21 | import java.nio.file.NoSuchFileException; 22 | import java.nio.file.Path; 23 | import java.util.Objects; 24 | 25 | import fr.oupson.libjxl.exceptions.ConfigException; 26 | import fr.oupson.libjxl.exceptions.DecodeError; 27 | 28 | public class JxlDecodeAndroidUnitTest { 29 | @Test 30 | public void decode_LogoShouldNotFail() throws IOException { 31 | Context context = ApplicationProvider.getApplicationContext(); 32 | InputStream input = context.getResources().getAssets().open("logo.jxl"); 33 | 34 | try { 35 | BitmapDrawable result = (BitmapDrawable) JxlDecoder.loadJxl(input, null); 36 | Assert.assertNotNull(result); 37 | Assert.assertEquals("Invalid image width", 1000, result.getBitmap().getWidth()); 38 | Assert.assertEquals("Invalid image height", 1000, result.getBitmap().getHeight()); 39 | } catch (Exception e) { 40 | Assert.fail("Failed to read image : " + e.getMessage()); 41 | } 42 | } 43 | 44 | @Test 45 | public void decode_DidiShouldNotFail() throws IOException { 46 | Context context = ApplicationProvider.getApplicationContext(); 47 | InputStream input = context.getResources().getAssets().open("didi.jxl"); 48 | 49 | try { 50 | BitmapDrawable result = (BitmapDrawable) JxlDecoder.loadJxl(input, null); 51 | Assert.assertNotNull(result); 52 | Assert.assertEquals("Invalid image width", 2048, result.getBitmap().getWidth()); 53 | Assert.assertEquals("Invalid image height", 1536, result.getBitmap().getHeight()); 54 | } catch (Exception e) { 55 | Assert.fail("Failed to read image : " + e.getMessage()); 56 | } 57 | } 58 | 59 | @Test 60 | public void decode_FerrisShouldNotFail() throws IOException { 61 | Context context = ApplicationProvider.getApplicationContext(); 62 | InputStream input = context.getResources().getAssets().open("ferris.jxl"); 63 | 64 | try { 65 | AnimationDrawable result = (AnimationDrawable) JxlDecoder.loadJxl(input, null); 66 | Assert.assertNotNull(result); 67 | Assert.assertEquals("Invalid number of frames", 27, result.getNumberOfFrames()); 68 | 69 | for (int i = 0; i < 27; i++) { 70 | BitmapDrawable frame = (BitmapDrawable) result.getFrame(i); 71 | Assert.assertEquals("Invalid frame width", 378, frame.getBitmap().getWidth()); 72 | Assert.assertEquals("Invalid frame height", 300, frame.getBitmap().getHeight()); 73 | } 74 | } catch (Exception e) { 75 | Assert.fail("Failed to read image : " + e.getMessage()); 76 | } 77 | } 78 | 79 | @Test 80 | public void decode_FerrisWithDecodeMultipleFramesFalseShouldDecodeOneFrame() throws IOException { 81 | Context context = ApplicationProvider.getApplicationContext(); 82 | InputStream input = context.getResources().getAssets().open("ferris.jxl"); 83 | 84 | try (JxlDecoder.Options options = new JxlDecoder.Options().setDecodeMultipleFrames(false)) { 85 | Drawable result = JxlDecoder.loadJxl(input, options); 86 | Assert.assertNotNull(result); 87 | Assert.assertNotEquals(AnimationDrawable.class, result.getClass()); 88 | } catch (Exception e) { 89 | Assert.fail("Failed to read image : " + e.getMessage()); 90 | } 91 | } 92 | 93 | @Test 94 | public void decode_FerrisWithDecodeMultipleFramesTrueShouldDecodeMultipleFrames() throws IOException { 95 | Context context = ApplicationProvider.getApplicationContext(); 96 | InputStream input = context.getResources().getAssets().open("ferris.jxl"); 97 | 98 | try (JxlDecoder.Options options = new JxlDecoder.Options().setDecodeMultipleFrames(true)) { 99 | Drawable result = JxlDecoder.loadJxl(input, options); 100 | Assert.assertNotNull(result); 101 | Assert.assertEquals(AnimationDrawable.class, result.getClass()); 102 | 103 | AnimationDrawable animation = (AnimationDrawable) result; 104 | 105 | Assert.assertEquals("Invalid number of frames", 27, animation.getNumberOfFrames()); 106 | 107 | for (int i = 0; i < 27; i++) { 108 | BitmapDrawable frame = (BitmapDrawable) animation.getFrame(i); 109 | Assert.assertEquals("Invalid frame width", 378, frame.getBitmap().getWidth()); 110 | Assert.assertEquals("Invalid frame height", 300, frame.getBitmap().getHeight()); 111 | } 112 | } catch (Exception e) { 113 | Assert.fail("Failed to read image : " + e.getMessage()); 114 | } 115 | } 116 | 117 | @Test 118 | public void decode_FerrisWithParcelFileDescriptorShouldNotFail() throws IOException { 119 | Context context = ApplicationProvider.getApplicationContext(); 120 | Path testFile = new File(context.getCacheDir(), "ferris.jxl").toPath(); 121 | if (!Files.exists(testFile)) { 122 | try (InputStream assetInputStream = context.getResources().getAssets().open("ferris.jxl")) { 123 | Files.copy(assetInputStream, testFile); 124 | } 125 | } 126 | 127 | Uri androidUri = Uri.fromFile(testFile.toFile()); 128 | 129 | try (ParcelFileDescriptor input = context.getContentResolver().openFileDescriptor(androidUri, "r")) { 130 | AnimationDrawable result = (AnimationDrawable) JxlDecoder.loadJxl(Objects.requireNonNull(input), null); 131 | Assert.assertNotNull(result); 132 | Assert.assertEquals("Invalid number of frames", 27, result.getNumberOfFrames()); 133 | 134 | for (int i = 0; i < 27; i++) { 135 | BitmapDrawable frame = (BitmapDrawable) result.getFrame(i); 136 | Assert.assertEquals("Invalid frame width", 378, frame.getBitmap().getWidth()); 137 | Assert.assertEquals("Invalid frame height", 300, frame.getBitmap().getHeight()); 138 | } 139 | } catch (Exception e) { 140 | Assert.fail("Failed to read image : " + e.getMessage()); 141 | } 142 | } 143 | 144 | @Test 145 | public void decode_WithoutEnoughInputShouldFail() throws IOException { 146 | Context context = ApplicationProvider.getApplicationContext(); 147 | InputStream input = context.getResources().getAssets().open("ferris.jxl"); 148 | 149 | byte[] content = new byte[100]; 150 | 151 | int size = input.read(content); 152 | input.close(); 153 | 154 | DecodeError error = Assert.assertThrows(DecodeError.class, () -> { 155 | Drawable result = JxlDecoder.loadJxl(new ByteArrayInputStream(content), null); 156 | }); 157 | 158 | Assert.assertEquals(DecodeError.DecodeErrorType.NeedMoreInputError, error.getErrorType()); 159 | } 160 | 161 | @Test 162 | public void decode_PngShouldFail() throws IOException { 163 | Context context = ApplicationProvider.getApplicationContext(); 164 | InputStream input = context.getResources().getAssets().open("android.png"); 165 | DecodeError error = Assert.assertThrows(DecodeError.class, () -> { 166 | Drawable result = JxlDecoder.loadJxl(input, null); 167 | }); 168 | Assert.assertEquals(DecodeError.DecodeErrorType.DecoderFailedError, error.getErrorType()); 169 | } 170 | 171 | @Test 172 | public void decode_WithFailingStreamShouldFail() throws IOException { 173 | Context context = ApplicationProvider.getApplicationContext(); 174 | 175 | class FailingStream extends InputStream { 176 | private final InputStream wrapped; 177 | private int count = 0; 178 | 179 | public FailingStream(InputStream stream) { 180 | this.wrapped = stream; 181 | } 182 | 183 | @Override 184 | public int read() { 185 | return -1; 186 | } 187 | 188 | @Override 189 | public int read(byte[] b, int off, int len) throws IOException { 190 | if (count == 0) { 191 | count += 1; 192 | return wrapped.read(b, off, len); 193 | } else { 194 | throw new NoSuchFileException("foo.jxl"); 195 | } 196 | } 197 | 198 | @Override 199 | public void close() throws IOException { 200 | this.wrapped.close(); 201 | super.close(); 202 | } 203 | } 204 | 205 | InputStream input = new FailingStream(context.getResources().getAssets().open("ferris.jxl")); 206 | 207 | // As input is marked finished, it is a decoder error and not an NeedMoreInputException 208 | Assert.assertThrows(NoSuchFileException.class, () -> { 209 | Drawable result = JxlDecoder.loadJxl(input, null); 210 | }); 211 | } 212 | 213 | @Test 214 | public void decodeThumbnails_shouldNotFail() throws IOException { 215 | Context context = ApplicationProvider.getApplicationContext(); 216 | 217 | String[] assetList = new String[]{"didi.jxl", "ferris.jxl", "logo.jxl"}; 218 | for (String asset : assetList) { 219 | InputStream inputStream = context.getAssets().open(asset); 220 | try { 221 | Bitmap result = JxlDecoder.loadThumbnail(inputStream); 222 | Assert.assertNotNull(result); 223 | } catch (Exception e) { 224 | Assert.fail("Failed to read image : " + e.getMessage()); 225 | } finally { 226 | inputStream.close(); 227 | } 228 | } 229 | } 230 | 231 | // TODO: find a way to test icc profile errors 232 | 233 | @Test 234 | public void decoderOptions_getBitmapConfigShouldReturnSetConfig() throws Exception { 235 | try (JxlDecoder.Options options = new JxlDecoder.Options()) { 236 | options.setFormat(Bitmap.Config.ARGB_8888); 237 | Assert.assertEquals(Bitmap.Config.ARGB_8888, options.getFormat()); 238 | 239 | options.setFormat(Bitmap.Config.RGBA_F16); 240 | Assert.assertEquals(Bitmap.Config.RGBA_F16, options.getFormat()); 241 | } 242 | } 243 | 244 | @Test 245 | public void decoderOptions_setBitmapConfigInvalidConfigShouldThrowException() throws Exception { 246 | try (JxlDecoder.Options options = new JxlDecoder.Options()) { 247 | Assert.assertThrows(ConfigException.class, () -> options.setFormat(Bitmap.Config.ALPHA_8)); 248 | Assert.assertThrows(ConfigException.class, () -> options.setFormat(Bitmap.Config.RGB_565)); 249 | Assert.assertThrows(ConfigException.class, () -> options.setFormat(Bitmap.Config.ARGB_4444)); 250 | Assert.assertThrows(ConfigException.class, () -> options.setFormat(Bitmap.Config.HARDWARE)); 251 | Assert.assertThrows(ConfigException.class, () -> options.setFormat(Bitmap.Config.RGBA_1010102)); 252 | } 253 | } 254 | 255 | @Test 256 | public void decoderOptions_getDecodeMultipleFramesShouldReturnSetConfig() throws Exception { 257 | try (JxlDecoder.Options options = new JxlDecoder.Options()) { 258 | options.setDecodeMultipleFrames(false); 259 | Assert.assertFalse(options.getDecodeMultipleFrames()); 260 | 261 | options.setDecodeMultipleFrames(true); 262 | Assert.assertTrue(options.getDecodeMultipleFrames()); 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /libjxl/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project("jxlreader") 4 | 5 | # load dependencies 6 | add_subdirectory(third_party) 7 | 8 | # add project headers 9 | include_directories(includes) 10 | 11 | # add jni library 12 | add_library( 13 | jxlreader 14 | 15 | # Sets the library as a shared library. 16 | SHARED 17 | 18 | native-lib.cpp 19 | src/Exception.cpp 20 | src/Decoder.cpp) 21 | 22 | 23 | # find android logger 24 | find_library(log-lib log) 25 | 26 | 27 | target_link_libraries( 28 | jxlreader 29 | 30 | android jnigraphics log z 31 | jxl_dec jxl_threads) 32 | 33 | link_skcms(jxlreader) 34 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/Decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef JXLVIEWER_DECODER_H 2 | #define JXLVIEWER_DECODER_H 3 | 4 | #include 5 | 6 | #include "InputSource.h" 7 | #include "Options.h" 8 | 9 | class Decoder { 10 | public: 11 | explicit Decoder(JNIEnv *env); 12 | 13 | ~Decoder(); 14 | 15 | jobject DecodeJxl(JNIEnv *env, InputSource &source, Options* options); 16 | 17 | jobject DecodeJxlThumbnail(JNIEnv *env, InputSource &source); 18 | 19 | private: 20 | JavaVM *vm; 21 | 22 | jclass drawableClass; 23 | jmethodID drawableMethodID; 24 | jmethodID addDrawableMethodID; 25 | 26 | jclass bitmapDrawableClass; 27 | jmethodID bitmapDrawableMethodID; 28 | 29 | jclass bitmapClass; 30 | jmethodID createBitmapMethodId; 31 | 32 | jobject bitmapConfigRgbaU8; 33 | jobject bitmapConfigRgbaF16; 34 | }; 35 | 36 | #endif //JXLVIEWER_DECODER_H 37 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/Exception.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupson on 15/01/2023. 3 | // 4 | 5 | #ifndef JXLVIEWER_EXCEPTION_H 6 | #define JXLVIEWER_EXCEPTION_H 7 | 8 | #include 9 | 10 | #define DECODER_FAILED_ERROR (0) 11 | #define ICC_PROFILE_ERROR (1) 12 | #define METHOD_CALL_FAILED_ERROR (2) 13 | #define NEED_MORE_INPUT_ERROR (3) 14 | #define OTHER_ERROR_TYPE (4) 15 | 16 | namespace jxlviewer { 17 | jint throwNewError(JNIEnv *env, int errorType); 18 | 19 | jint throwNewError(JNIEnv *env, int errorType, const char *message); 20 | 21 | inline jint throwNewError(JNIEnv *env, const char *className, const char *message) { 22 | jclass exClass = env->FindClass(className); 23 | return env->ThrowNew(exClass, message); 24 | } 25 | } 26 | 27 | #endif //JXLVIEWER_EXCEPTION_H 28 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/FileDescriptorInputSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupso on 04/01/2024. 3 | // 4 | 5 | #ifndef JXLVIEWER_FILEDESCRIPTORINPUTSOURCE_H 6 | #define JXLVIEWER_FILEDESCRIPTORINPUTSOURCE_H 7 | 8 | #include 9 | 10 | #include "InputSource.h" 11 | #include "Exception.h" 12 | 13 | inline int32_t readWithErrorHandling(JNIEnv *env, int fd, uint8_t *buffer, size_t size); 14 | 15 | class FileDescriptorInputSource : public InputSource { 16 | public: 17 | FileDescriptorInputSource(JNIEnv *env, int fd) : env(env), fd(fd) { 18 | 19 | } 20 | 21 | int32_t read(uint8_t *buffer, size_t size) override { 22 | return readWithErrorHandling(this->env, this->fd, buffer, size); 23 | } 24 | 25 | private: 26 | JNIEnv *env; 27 | int fd; 28 | }; 29 | 30 | inline int32_t readWithErrorHandling(JNIEnv *env, int fd, uint8_t *buffer, size_t size) { 31 | auto n = read(fd, buffer, size); 32 | if (n > 0) { 33 | return n; 34 | } else if (n == 0) { 35 | return -1; 36 | } else { 37 | auto error = strerror(errno); 38 | jxlviewer::throwNewError(env, "java/io/IOException", error); 39 | return INT32_MIN; 40 | } 41 | } 42 | 43 | #endif //JXLVIEWER_FILEDESCRIPTORINPUTSOURCE_H 44 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/ImageOutCallbackData.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupson on 07/02/2023. 3 | // 4 | 5 | #ifndef JXLVIEWER_IMAGEOUTCALLBACKDATA_H 6 | #define JXLVIEWER_IMAGEOUTCALLBACKDATA_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include "jxl/decode.h" 14 | #include 15 | #include "Exception.h" 16 | 17 | class ImageOutCallbackData { 18 | private: 19 | size_t width; 20 | size_t height; 21 | 22 | bool is_alpha_premultiplied; 23 | 24 | uint8_t *image_buffer; 25 | 26 | uint8_t *icc_buffer; 27 | skcms_ICCProfile icc = {}; 28 | 29 | skcms_PixelFormat sourcePixelFormat; 30 | skcms_PixelFormat outputPixelFormat; 31 | uint8_t sampleSize; 32 | 33 | public: 34 | explicit ImageOutCallbackData(BitmapConfig format) : ImageOutCallbackData(format, 35 | skcms_PixelFormat_RGBA_hhhh) { 36 | } 37 | 38 | ImageOutCallbackData(BitmapConfig format, skcms_PixelFormat sourcePixelFormat) : width(0), 39 | height(0), 40 | is_alpha_premultiplied( 41 | false), 42 | image_buffer( 43 | nullptr), 44 | icc_buffer( 45 | nullptr), 46 | sourcePixelFormat( 47 | sourcePixelFormat) { 48 | this->outputPixelFormat = (format == BitmapConfig::RGBA_8888) ? skcms_PixelFormat_RGBA_8888 49 | : skcms_PixelFormat_RGBA_hhhh; 50 | this->sampleSize = (format == BitmapConfig::RGBA_8888) ? 4 : 8; 51 | } 52 | 53 | ~ImageOutCallbackData() { 54 | if (icc_buffer != nullptr) { 55 | free(icc_buffer); 56 | icc_buffer = nullptr; 57 | } 58 | } 59 | 60 | void setSourcePixelFormat(skcms_PixelFormat pixelFormat) { 61 | this->sourcePixelFormat = pixelFormat; 62 | } 63 | 64 | size_t getWidth() const { 65 | return width; 66 | } 67 | 68 | size_t getHeight() const { 69 | return height; 70 | } 71 | 72 | void setSize(size_t image_width, size_t image_height) { 73 | this->width = image_width; 74 | this->height = image_height; 75 | } 76 | 77 | void setIsAlphaPremultiplied(bool alpha_premultiplied) { 78 | this->is_alpha_premultiplied = alpha_premultiplied; 79 | } 80 | 81 | uint8_t **getImageBufferPtr() { 82 | return &this->image_buffer; 83 | } 84 | 85 | bool parseICCProfile(JNIEnv *env, JxlDecoder *dec) noexcept { 86 | size_t icc_size; 87 | if (JXL_DEC_SUCCESS != 88 | JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) { 89 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderGetICCProfileSize"); 90 | return false; 91 | } 92 | 93 | this->icc_buffer = (uint8_t *) malloc(icc_size * sizeof(uint8_t)); 94 | if (this->icc_buffer == nullptr && icc_size != 0) { 95 | jxlviewer::throwNewError(env, "java/lang/OutOfMemoryError", 96 | "Failed to allocate memory for icc profile"); 97 | return false; 98 | } 99 | 100 | if (JXL_DEC_SUCCESS != 101 | JxlDecoderGetColorAsICCProfile(dec, JXL_COLOR_PROFILE_TARGET_DATA, this->icc_buffer, 102 | icc_size)) { 103 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, 104 | "JxlDecoderGetColorAsICCProfile"); 105 | return false; 106 | } 107 | 108 | if (!skcms_Parse(this->icc_buffer, icc_size, &icc)) { 109 | jxlviewer::throwNewError(env, ICC_PROFILE_ERROR, 110 | "Invalid ICC profile from JXL image decoder"); 111 | return false; 112 | } 113 | 114 | return true; 115 | } 116 | 117 | void imageDataFromCallback(const void *pixels, size_t x, size_t y, size_t num_pixels) { 118 | skcms_Transform(pixels, this->sourcePixelFormat, 119 | this->is_alpha_premultiplied ? skcms_AlphaFormat_PremulAsEncoded 120 | : skcms_AlphaFormat_Unpremul, &this->icc, 121 | this->image_buffer + ((y * this->width + x) * (this->sampleSize)), 122 | this->outputPixelFormat, 123 | skcms_AlphaFormat_PremulAsEncoded,// Android need images with alpha to be premultiplied, otherwise it produce strange results. 124 | skcms_sRGB_profile(), num_pixels); 125 | } 126 | }; 127 | 128 | void jxl_viewer_image_out_callback(void *opaque_data, size_t x, size_t y, size_t num_pixels, 129 | const void *pixels) { 130 | auto *data = (ImageOutCallbackData *) opaque_data; 131 | data->imageDataFromCallback(pixels, x, y, num_pixels); 132 | } 133 | 134 | 135 | #endif //JXLVIEWER_IMAGEOUTCALLBACKDATA_H 136 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/InputSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupso on 04/01/2024. 3 | // 4 | 5 | #ifndef JXLVIEWER_INPUTSOURCE_H 6 | #define JXLVIEWER_INPUTSOURCE_H 7 | 8 | #include 9 | 10 | /** 11 | * @class InputSource 12 | * @brief Abstraction over an input source. 13 | * 14 | * This represent an abstraction over a source, for example an Java InputStream or a file descriptor. 15 | */ 16 | class InputSource { 17 | public: 18 | /** 19 | * @brief Reads data from the source into the provided buffer. 20 | * @param buffer The buffer to read data into. 21 | * @param size The number of bytes to read. 22 | * @return The total number of bytes read, -1 if EOF or INT32_MIN if error with JNI error set correctly. 23 | * 24 | * This method reads data from the source into the specified buffer. 25 | */ 26 | virtual int32_t read(uint8_t *buffer, size_t size) = 0; 27 | }; 28 | 29 | #endif //JXLVIEWER_INPUTSOURCE_H 30 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/JniInputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupso on 04/01/2024. 3 | // 4 | 5 | #ifndef JXLVIEWER_JNIINPUTSTREAM_H 6 | #define JXLVIEWER_JNIINPUTSTREAM_H 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "InputSource.h" 13 | 14 | #define BUFFER_SIZE (4096) 15 | 16 | /** 17 | * @class JniInputStream 18 | * @brief An implementation of InputSource using Java Native Interface (JNI) to read from a Java InputStream. 19 | * 20 | * This class extends the InputSource class and provides functionality to read data from a Java InputStream 21 | * using the Java Native Interface (JNI). It overrides the read method to efficiently read data from the 22 | * underlying Java InputStream into a provided buffer. 23 | */ 24 | class JniInputStream : public InputSource { 25 | public: 26 | /** 27 | * @brief Constructor for JniInputStream. 28 | * @param env The JNI environment pointer. 29 | * @param inputStream The Java InputStream object. 30 | * 31 | * This constructor initializes the JniInputStream with the JNI environment pointer and the Java InputStream object. 32 | * It also sets up the JNI method ID for the read method and allocates a Java byte array for buffering. 33 | */ 34 | JniInputStream(JNIEnv *env, jobject inputStream) : env(env), inputStream(inputStream), 35 | sizeRead(0) { 36 | jclass inputStream_Clazz = env->FindClass("java/io/InputStream"); 37 | this->readMethodId = env->GetMethodID(inputStream_Clazz, "read", "([BII)I"); 38 | this->javaByteArray = env->NewByteArray(BUFFER_SIZE); 39 | } 40 | 41 | /** 42 | * @brief Destructor for JniInputStream. 43 | * 44 | * Cleans up resources, including deleting the allocated Java byte array. 45 | */ 46 | ~JniInputStream() { 47 | env->DeleteLocalRef(this->javaByteArray); 48 | } 49 | 50 | /** 51 | * @brief Reads data from the Java InputStream into the provided buffer. 52 | * @param buffer The buffer to read data into. 53 | * @param size The number of bytes to read. 54 | * @return The total number of bytes read, -1 if EOF or INT32_MIN if error with JNI error set correctly. 55 | * 56 | * This method reads data from the Java InputStream into the specified buffer. 57 | * It efficiently manages the reading process, handling buffering and JNI calls. 58 | */ 59 | int32_t read(uint8_t *buffer, size_t size) override { 60 | int32_t totalRead = (this->sizeRead > 0) ? readFromBuffer(buffer, (int32_t) size) 61 | : this->sizeRead; 62 | 63 | while (totalRead < size) { 64 | this->sizeRead = env->CallIntMethod(this->inputStream, this->readMethodId, 65 | javaByteArray, 0, BUFFER_SIZE); 66 | if (env->ExceptionCheck()) { 67 | return INT32_MIN; 68 | } else { 69 | this->offset = 0; 70 | if (this->sizeRead >= 0) { 71 | totalRead += readFromBuffer(buffer + totalRead, ((int32_t) size) - totalRead); 72 | } else { 73 | break; 74 | } 75 | } 76 | } 77 | 78 | return totalRead; 79 | } 80 | 81 | private: 82 | JNIEnv *env; 83 | jobject inputStream; 84 | jmethodID readMethodId; 85 | jbyteArray javaByteArray; 86 | int32_t sizeRead; 87 | int32_t offset; 88 | 89 | int32_t readFromBuffer(const uint8_t *buffer, int32_t size) { 90 | auto res = std::min(size, sizeRead - offset); 91 | env->GetByteArrayRegion(javaByteArray, offset, 92 | res, 93 | (jbyte *) buffer); 94 | if (res + offset == sizeRead) { 95 | sizeRead = 0; 96 | offset = 0; 97 | } else { 98 | offset += res; 99 | } 100 | return res; 101 | } 102 | }; 103 | 104 | #endif //JXLVIEWER_JNIINPUTSTREAM_H -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/Options.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupson on 29/11/2024. 3 | // 4 | 5 | #ifndef JXLVIEWER_OPTIONS_H 6 | #define JXLVIEWER_OPTIONS_H 7 | enum BitmapConfig { 8 | RGBA_8888 = 0, F16 = 1, 9 | }; 10 | 11 | class Options { 12 | public: 13 | BitmapConfig rgbaConfig = RGBA_8888; 14 | bool decodeMultipleFrames = true; 15 | }; 16 | 17 | #endif //JXLVIEWER_OPTIONS_H 18 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/includes/jxlviewer_consts.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupson on 21/12/2022. 3 | // 4 | 5 | #ifndef JXLVIEWER_CONST_H 6 | #define JXLVIEWER_CONST_H 7 | 8 | #define LOG_TAG "native-jxl" 9 | 10 | #endif //JXLVIEWER_CONST_H 11 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "InputSource.h" 4 | #include "JniInputStream.h" 5 | #include "FileDescriptorInputSource.h" 6 | #include "Decoder.h" 7 | #include "Options.h" 8 | 9 | extern "C" JNIEXPORT jobject JNICALL 10 | Java_fr_oupson_libjxl_JxlDecoder_loadJxlFromInputStream(JNIEnv *env, jclass /* clazz */, 11 | jlong native_decoder_ptr, 12 | jobject input_stream, jlong options_ptr) { 13 | auto *decoder = reinterpret_cast(native_decoder_ptr); 14 | auto *options = reinterpret_cast(options_ptr); 15 | auto jniInputStream = JniInputStream(env, input_stream); 16 | 17 | return decoder->DecodeJxl(env, jniInputStream, options); 18 | } 19 | 20 | extern "C" JNIEXPORT jobject JNICALL 21 | Java_fr_oupson_libjxl_JxlDecoder_loadJxlFromFd(JNIEnv *env, jclass /* clazz */, 22 | jlong native_decoder_ptr, jint fd, 23 | jlong options_ptr) { 24 | auto *decoder = reinterpret_cast(native_decoder_ptr); 25 | auto *options = reinterpret_cast(options_ptr); 26 | auto jniInputStream = FileDescriptorInputSource(env, fd); 27 | 28 | return decoder->DecodeJxl(env, jniInputStream, options); 29 | } 30 | 31 | extern "C" JNIEXPORT jlong JNICALL 32 | Java_fr_oupson_libjxl_JxlDecoder_getNativeDecoderPtr(JNIEnv *env, jclass /* clazz */) { 33 | auto *ptr = new Decoder(env); 34 | return reinterpret_cast(ptr); 35 | } 36 | 37 | extern "C" JNIEXPORT void JNICALL 38 | Java_fr_oupson_libjxl_JxlDecoder_freeNativeDecoderPtr(JNIEnv * /* env */, jclass /* clazz */, 39 | jlong native_decoder_ptr) { 40 | auto *decoder = reinterpret_cast(native_decoder_ptr); 41 | delete decoder; 42 | } 43 | 44 | extern "C" JNIEXPORT jobject JNICALL 45 | Java_fr_oupson_libjxl_JxlDecoder_loadThumbnailFromFd(JNIEnv *env, jclass /* clazz */, 46 | jlong native_decoder_ptr, jint fd) { 47 | auto *decoder = reinterpret_cast(native_decoder_ptr); 48 | auto jniInputStream = FileDescriptorInputSource(env, fd); 49 | 50 | return decoder->DecodeJxlThumbnail(env, jniInputStream); 51 | 52 | } 53 | 54 | extern "C" JNIEXPORT jobject JNICALL 55 | Java_fr_oupson_libjxl_JxlDecoder_loadThumbnailFromInputStream(JNIEnv *env, jclass /* clazz */, 56 | jlong native_decoder_ptr, 57 | jobject input_stream) { 58 | auto *decoder = reinterpret_cast(native_decoder_ptr); 59 | auto jniInputStream = JniInputStream(env, input_stream); 60 | 61 | return decoder->DecodeJxlThumbnail(env, jniInputStream); 62 | } 63 | 64 | jlong JNICALL 65 | decoderOptionsAlloc(JNIEnv * /* env */, jclass /* clazz */) { 66 | auto options = new Options(); 67 | return reinterpret_cast(options); 68 | } 69 | 70 | void JNICALL 71 | decoderOptionsFree(JNIEnv * /* env */, jclass /* clazz */, jlong ptr) { 72 | auto options = reinterpret_cast(ptr); 73 | delete options; 74 | } 75 | 76 | jint JNICALL 77 | decoderOptionsGetBitmapConfig(JNIEnv * /* env */, jclass /* clazz */, jlong ptr) { 78 | auto options = reinterpret_cast(ptr); 79 | return options->rgbaConfig; 80 | } 81 | 82 | void JNICALL 83 | decoderOptionsSetBitmapConfig(JNIEnv * /* env */, jclass /* clazz */, jlong ptr, jint format) { 84 | auto options = reinterpret_cast(ptr); 85 | options->rgbaConfig = static_cast(format); 86 | } 87 | 88 | 89 | jboolean JNICALL 90 | decoderOptionsGetDecodeMultipleFrames(JNIEnv * /* env */, jclass /* clazz */, jlong ptr) { 91 | auto options = reinterpret_cast(ptr); 92 | return (options->decodeMultipleFrames) ? JNI_TRUE : JNI_FALSE; 93 | } 94 | 95 | 96 | void JNICALL 97 | decoderOptionsSetDecodeMultipleFrames(JNIEnv * /* env */, jclass /* clazz */, jlong ptr, 98 | jboolean decodeMultipleFrames) { 99 | auto options = reinterpret_cast(ptr); 100 | options->decodeMultipleFrames = decodeMultipleFrames == JNI_TRUE; 101 | } 102 | 103 | 104 | jint registerDecoderOptions(JNIEnv *env) noexcept { 105 | jclass classOptions = env->FindClass("fr/oupson/libjxl/JxlDecoder$Options"); 106 | if (classOptions == nullptr) { 107 | return JNI_ERR; 108 | } 109 | 110 | static const JNINativeMethod methods[] = { 111 | {"alloc", "()J", reinterpret_cast(decoderOptionsAlloc)}, 112 | {"free", "(J)V", reinterpret_cast(decoderOptionsFree)}, 113 | {"setBitmapConfig", "(JI)V", reinterpret_cast(decoderOptionsSetBitmapConfig)}, 114 | {"getBitmapConfig", "(J)I", reinterpret_cast(decoderOptionsGetBitmapConfig)}, 115 | {"getDecodeMultipleFrames", "(J)Z", reinterpret_cast(decoderOptionsGetDecodeMultipleFrames)}, 116 | {"setDecodeMultipleFrames", "(JZ)V", reinterpret_cast(decoderOptionsSetDecodeMultipleFrames)}, 117 | }; 118 | 119 | return env->RegisterNatives(classOptions, methods, sizeof(methods) / sizeof(JNINativeMethod)); 120 | } 121 | 122 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) { 123 | JNIEnv *env; 124 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { 125 | return JNI_ERR; 126 | } 127 | 128 | if (registerDecoderOptions(env) != JNI_OK) { 129 | return JNI_ERR; 130 | } 131 | 132 | 133 | return JNI_VERSION_1_6; 134 | } 135 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/src/Decoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Decoder.h" 9 | #include "Exception.h" 10 | #include "JniInputStream.h" 11 | #include "ImageOutCallbackData.h" 12 | 13 | Decoder::Decoder(JNIEnv *env) : vm(nullptr) { 14 | env->GetJavaVM(&this->vm); 15 | this->drawableClass = reinterpret_cast(env->NewGlobalRef( 16 | env->FindClass("android/graphics/drawable/AnimationDrawable"))); 17 | this->drawableMethodID = env->GetMethodID(this->drawableClass, "", "()V"); 18 | this->addDrawableMethodID = env->GetMethodID(this->drawableClass, "addFrame", 19 | "(Landroid/graphics/drawable/Drawable;I)V"); 20 | 21 | this->bitmapDrawableClass = reinterpret_cast(env->NewGlobalRef( 22 | env->FindClass("android/graphics/drawable/BitmapDrawable"))); 23 | this->bitmapDrawableMethodID = env->GetMethodID(this->bitmapDrawableClass, "", 24 | "(Landroid/graphics/Bitmap;)V"); 25 | 26 | this->bitmapClass = reinterpret_cast(env->NewGlobalRef( 27 | env->FindClass("android/graphics/Bitmap"))); 28 | this->createBitmapMethodId = env->GetStaticMethodID(this->bitmapClass, "createBitmap", 29 | "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); 30 | 31 | // ARGB_8888 is stored as RGBA_8888 in memory 32 | jstring rgbaU8configName = env->NewStringUTF("ARGB_8888"); 33 | jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config"); 34 | jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", 35 | "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); 36 | 37 | this->bitmapConfigRgbaU8 = reinterpret_cast(env->NewGlobalRef( 38 | env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, 39 | rgbaU8configName))); 40 | 41 | if (android_get_device_api_level() >= 26) { 42 | jstring rgbaF16configName = env->NewStringUTF("RGBA_F16"); 43 | this->bitmapConfigRgbaF16 = reinterpret_cast(env->NewGlobalRef( 44 | env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, 45 | rgbaF16configName))); 46 | } 47 | } 48 | 49 | Decoder::~Decoder() { 50 | JNIEnv *env; 51 | 52 | bool needToDetach = false; 53 | jint res = this->vm->GetEnv((void **) &env, JNI_VERSION_1_6); 54 | if (JNI_EDETACHED == res) { 55 | res = this->vm->AttachCurrentThread(&env, nullptr); 56 | if (JNI_OK == res) { 57 | needToDetach = true; 58 | } else { 59 | return; 60 | } 61 | } else if (JNI_OK != res) { 62 | return; 63 | } 64 | 65 | env->DeleteGlobalRef(this->drawableClass); 66 | env->DeleteGlobalRef(this->bitmapDrawableClass); 67 | env->DeleteGlobalRef(this->bitmapClass); 68 | env->DeleteGlobalRef(this->bitmapConfigRgbaU8); 69 | env->DeleteGlobalRef(this->bitmapConfigRgbaF16); 70 | 71 | if (needToDetach) { 72 | this->vm->DetachCurrentThread(); 73 | } 74 | } 75 | 76 | jobject Decoder::DecodeJxl(JNIEnv *env, InputSource &source, Options *options) { 77 | jobject drawable = nullptr; 78 | BitmapConfig btmConfigNative = (options != nullptr) ? options->rgbaConfig 79 | : BitmapConfig::RGBA_8888; 80 | 81 | jobject bitmapConfig = (options != nullptr) ? ((options->rgbaConfig == 0) 82 | ? this->bitmapConfigRgbaU8 83 | : this->bitmapConfigRgbaF16) 84 | : this->bitmapConfigRgbaU8; 85 | 86 | // Multi-threaded parallel runner. 87 | auto runner = JxlResizableParallelRunnerMake(nullptr); 88 | 89 | auto dec = JxlDecoderMake(nullptr); 90 | if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), 91 | JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | 92 | JXL_DEC_FRAME | JXL_DEC_COLOR_ENCODING)) { 93 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSubscribeEvents"); 94 | return nullptr; 95 | } 96 | 97 | if (JXL_DEC_SUCCESS != 98 | JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { 99 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSetParallelRunner"); 100 | return nullptr; 101 | } 102 | 103 | JxlBasicInfo info; 104 | JxlFrameHeader frameHeader; 105 | JxlPixelFormat format = {4, JXL_TYPE_FLOAT16, JXL_NATIVE_ENDIAN, 0}; 106 | 107 | uint8_t buffer[BUFFER_SIZE]; 108 | 109 | jobject btm = nullptr; 110 | 111 | ImageOutCallbackData out_data(btmConfigNative); 112 | 113 | JxlDecoderStatus status = JXL_DEC_NEED_MORE_INPUT; 114 | 115 | for (;;) { 116 | if (status == JXL_DEC_ERROR) { 117 | jxlviewer::throwNewError(env, DECODER_FAILED_ERROR); 118 | return nullptr; 119 | } else if (status == JXL_DEC_NEED_MORE_INPUT) { 120 | auto remaining = JxlDecoderReleaseInput(dec.get()); // TODO REMAINING TEST 121 | auto readSize = source.read(buffer + remaining, sizeof(buffer) - remaining); 122 | if (readSize == -1) { 123 | jxlviewer::throwNewError(env, NEED_MORE_INPUT_ERROR); 124 | return nullptr; 125 | } else if (readSize == INT32_MIN) { 126 | return nullptr; 127 | } else { 128 | if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec.get(), buffer, readSize)) { 129 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSetInput"); 130 | return nullptr; 131 | } 132 | } 133 | } else if (status == JXL_DEC_BASIC_INFO) { 134 | if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { 135 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderGetBasicInfo"); 136 | return nullptr; 137 | } 138 | 139 | if (info.have_animation && (options == nullptr || options->decodeMultipleFrames)) { 140 | drawable = env->NewObject(drawableClass, drawableMethodID); 141 | } 142 | 143 | if (info.alpha_bits == 0) { 144 | if (btmConfigNative == BitmapConfig::RGBA_8888) { 145 | out_data.setSourcePixelFormat(skcms_PixelFormat_RGB_888); 146 | format = {3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 147 | } else { 148 | out_data.setSourcePixelFormat(skcms_PixelFormat_RGB_hhh); 149 | format = {3, JXL_TYPE_FLOAT16, JXL_NATIVE_ENDIAN, 0}; 150 | } 151 | } else { 152 | if (btmConfigNative == BitmapConfig::RGBA_8888) { 153 | out_data.setSourcePixelFormat(skcms_PixelFormat_RGBA_8888); 154 | format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 155 | } else { 156 | out_data.setSourcePixelFormat(skcms_PixelFormat_RGBA_hhhh); 157 | format = {4, JXL_TYPE_FLOAT16, JXL_NATIVE_ENDIAN, 0}; 158 | } 159 | } 160 | 161 | out_data.setSize(info.xsize, info.ysize); 162 | out_data.setIsAlphaPremultiplied(info.alpha_premultiplied); 163 | JxlResizableParallelRunnerSetThreads(runner.get(), 164 | JxlResizableParallelRunnerSuggestThreads( 165 | info.xsize, info.ysize)); 166 | } else if (status == JXL_DEC_COLOR_ENCODING) { 167 | if (!out_data.parseICCProfile(env, dec.get())) { 168 | return nullptr; 169 | } 170 | } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 171 | } else if (status == JXL_DEC_FULL_IMAGE) { 172 | AndroidBitmap_unlockPixels(env, btm); 173 | 174 | if (drawable != nullptr) { 175 | auto btmDrawable = env->NewObject(bitmapDrawableClass, bitmapDrawableMethodID, btm); 176 | uint32_t num = (info.animation.tps_numerator == 0) ? 1 177 | : info.animation.tps_numerator; 178 | env->CallVoidMethod(drawable, addDrawableMethodID, btmDrawable, 179 | (int) (frameHeader.duration * 1000 * 180 | info.animation.tps_denominator / num)); 181 | } 182 | } else if (status == JXL_DEC_SUCCESS) { 183 | if (drawable == nullptr) { 184 | auto btmDrawable = env->NewObject(bitmapDrawableClass, bitmapDrawableMethodID, btm); 185 | return btmDrawable; 186 | } else { 187 | return drawable; 188 | } 189 | } else if (status == JXL_DEC_FRAME) { 190 | if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &frameHeader)) { 191 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderGetFrameHeader"); 192 | return nullptr; 193 | } 194 | 195 | btm = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodId, 196 | (int) out_data.getWidth(), (int) out_data.getHeight(), 197 | bitmapConfig); 198 | 199 | AndroidBitmap_lockPixels(env, btm, 200 | reinterpret_cast(out_data.getImageBufferPtr())); 201 | 202 | if (JXL_DEC_SUCCESS != 203 | JxlDecoderSetImageOutCallback(dec.get(), &format, jxl_viewer_image_out_callback, 204 | &out_data)) { 205 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, 206 | "JxlDecoderSetImageOutCallback"); 207 | return nullptr; 208 | } 209 | } else { 210 | jxlviewer::throwNewError(env, OTHER_ERROR_TYPE, "Unknown decoder status"); 211 | return nullptr; 212 | } 213 | status = JxlDecoderProcessInput(dec.get()); 214 | } 215 | } 216 | 217 | // TODO: image preview when supported 218 | jobject Decoder::DecodeJxlThumbnail(JNIEnv *env, InputSource &source) { 219 | // Multi-threaded parallel runner. 220 | auto runner = JxlResizableParallelRunnerMake(nullptr); 221 | 222 | auto dec = JxlDecoderMake(nullptr); 223 | if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), 224 | JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | 225 | JXL_DEC_FRAME | JXL_DEC_COLOR_ENCODING | 226 | JXL_DEC_FRAME_PROGRESSION)) { 227 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSubscribeEvents"); 228 | return nullptr; 229 | } 230 | 231 | if (JXL_DEC_SUCCESS != 232 | JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { 233 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSetParallelRunner"); 234 | return nullptr; 235 | } 236 | 237 | if (JXL_DEC_SUCCESS != 238 | JxlDecoderSetProgressiveDetail(dec.get(), JxlProgressiveDetail::kLastPasses)) { 239 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSetProgressiveDetail"); 240 | return nullptr; 241 | } 242 | 243 | JxlBasicInfo info; 244 | JxlFrameHeader frameHeader; 245 | JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 246 | 247 | uint8_t buffer[BUFFER_SIZE]; 248 | 249 | jobject btm = nullptr; 250 | ImageOutCallbackData out_data(BitmapConfig::RGBA_8888); 251 | 252 | JxlDecoderStatus status = JXL_DEC_NEED_MORE_INPUT; 253 | 254 | for (;;) { 255 | if (status == JXL_DEC_ERROR) { 256 | jxlviewer::throwNewError(env, DECODER_FAILED_ERROR); 257 | return nullptr; 258 | } else if (status == JXL_DEC_NEED_MORE_INPUT) { 259 | auto remaining = JxlDecoderReleaseInput(dec.get()); // TODO REMAINING TEST 260 | auto readSize = source.read(buffer + remaining, sizeof(buffer) - remaining); 261 | if (readSize == -1) { 262 | jxlviewer::throwNewError(env, NEED_MORE_INPUT_ERROR); 263 | return nullptr; 264 | } else if (readSize == INT32_MIN) { 265 | return nullptr; 266 | } else { 267 | if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec.get(), buffer, readSize)) { 268 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderSetInput"); 269 | return nullptr; 270 | } 271 | } 272 | } else if (status == JXL_DEC_BASIC_INFO) { 273 | if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { 274 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderGetBasicInfo"); 275 | return nullptr; 276 | } 277 | if (info.alpha_bits == 0) { 278 | out_data = ImageOutCallbackData(BitmapConfig::RGBA_8888, skcms_PixelFormat_RGB_888); 279 | format = JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 280 | } 281 | out_data.setSize(info.xsize, info.ysize); 282 | out_data.setIsAlphaPremultiplied(info.alpha_premultiplied); 283 | JxlResizableParallelRunnerSetThreads(runner.get(), 284 | JxlResizableParallelRunnerSuggestThreads( 285 | info.xsize, info.ysize)); 286 | } else if (status == JXL_DEC_COLOR_ENCODING) { 287 | if (!out_data.parseICCProfile(env, dec.get())) { 288 | return nullptr; 289 | } 290 | } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 291 | } else if (status == JXL_DEC_FULL_IMAGE) { 292 | AndroidBitmap_unlockPixels(env, btm); 293 | 294 | JxlDecoderCloseInput(dec.get()); 295 | return btm; 296 | } else if (status == JXL_DEC_SUCCESS) { 297 | AndroidBitmap_unlockPixels(env, btm); 298 | return btm; 299 | } else if (status == JXL_DEC_FRAME) { 300 | if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &frameHeader)) { 301 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, "JxlDecoderGetFrameHeader"); 302 | return nullptr; 303 | } 304 | 305 | btm = env->CallStaticObjectMethod(bitmapClass, createBitmapMethodId, 306 | (int) out_data.getWidth(), (int) out_data.getHeight(), 307 | bitmapConfigRgbaU8); 308 | 309 | AndroidBitmap_lockPixels(env, btm, 310 | reinterpret_cast(out_data.getImageBufferPtr())); 311 | 312 | if (JXL_DEC_SUCCESS != 313 | JxlDecoderSetImageOutCallback(dec.get(), &format, jxl_viewer_image_out_callback, 314 | &out_data)) { 315 | jxlviewer::throwNewError(env, METHOD_CALL_FAILED_ERROR, 316 | "JxlDecoderSetImageOutCallback"); 317 | return nullptr; 318 | } 319 | JxlDecoderFlushImage(dec.get()); 320 | } else if (status == JXL_DEC_FRAME_PROGRESSION) { 321 | JxlDecoderFlushImage(dec.get()); 322 | JxlDecoderSkipCurrentFrame(dec.get()); 323 | } else { 324 | jxlviewer::throwNewError(env, OTHER_ERROR_TYPE, "Unknown decoder status"); 325 | return nullptr; 326 | } 327 | 328 | status = JxlDecoderProcessInput(dec.get()); 329 | } 330 | } -------------------------------------------------------------------------------- /libjxl/src/main/cpp/src/Exception.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by oupson on 15/01/2023. 3 | // 4 | 5 | #include "Exception.h" 6 | 7 | namespace jxlviewer { 8 | jint throwNewError(JNIEnv *env, int errorType) { 9 | jclass exClass = env->FindClass("fr/oupson/libjxl/exceptions/DecodeError"); 10 | if (env->ExceptionCheck()) { 11 | return -1; 12 | } 13 | 14 | jmethodID createClassMethod = env->GetMethodID(exClass, "", "(I)V"); 15 | 16 | jobject obj = env->NewObject(exClass, createClassMethod, errorType); 17 | return env->Throw((jthrowable) obj); 18 | } 19 | 20 | jint throwNewError(JNIEnv *env, int errorType, const char *message) { 21 | jclass exClass = env->FindClass("fr/oupson/libjxl/exceptions/DecodeError"); 22 | if (env->ExceptionCheck()) { 23 | return -1; 24 | } 25 | 26 | jmethodID createClassMethod = env->GetMethodID(exClass, "", "(ILjava/lang/String;)V"); 27 | 28 | jstring javaMessage = env->NewStringUTF(message); 29 | 30 | jobject obj = env->NewObject(exClass, createClassMethod, errorType, javaMessage); 31 | return env->Throw((jthrowable) obj); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # libjxl 2 | set(JPEGXL_STATIC CACHE BOOL ON) 3 | set(BUILD_SHARED_LIBS CACHE BOOL OFF) 4 | set(JPEGXL_ENABLE_SJPEG CACHE BOOL OFF) 5 | 6 | add_subdirectory(libjxl) 7 | 8 | include(skcms.cmake) 9 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/skcms/LICENSE" 10 | ${PROJECT_BINARY_DIR}/LICENSE.skcms COPYONLY) 11 | -------------------------------------------------------------------------------- /libjxl/src/main/cpp/third_party/skcms.cmake: -------------------------------------------------------------------------------- 1 | function(link_skcms TARGET_NAME) 2 | target_sources(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/third_party/skcms/skcms.cc") 3 | 4 | target_include_directories(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/third_party/skcms/") 5 | 6 | include(CheckCXXCompilerFlag) 7 | check_cxx_compiler_flag("-Wno-psabi" CXX_WPSABI_SUPPORTED) 8 | if (CXX_WPSABI_SUPPORTED) 9 | set_source_files_properties("${PROJECT_SOURCE_DIR}/third_party/skcms/skcms.cc" 10 | PROPERTIES COMPILE_OPTIONS "-Wno-psabi" 11 | TARGET_DIRECTORY ${TARGET_NAME}) 12 | endif () 13 | endfunction() 14 | -------------------------------------------------------------------------------- /libjxl/src/main/java/fr/oupson/libjxl/JxlDecoder.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.libjxl; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.drawable.Drawable; 5 | import android.os.Build; 6 | import android.os.ParcelFileDescriptor; 7 | 8 | import java.io.InputStream; 9 | 10 | import fr.oupson.libjxl.exceptions.ConfigException; 11 | import fr.oupson.libjxl.exceptions.DecodeError; 12 | 13 | public class JxlDecoder { 14 | private static JxlDecoder decoder = null; 15 | 16 | static { 17 | System.loadLibrary("jxlreader"); 18 | } 19 | 20 | private long nativeDecoderPtr; 21 | 22 | JxlDecoder() { 23 | this.nativeDecoderPtr = getNativeDecoderPtr(); 24 | } 25 | 26 | public static synchronized JxlDecoder getInstance() { 27 | if (decoder == null) { 28 | decoder = new JxlDecoder(); 29 | } 30 | 31 | return decoder; 32 | } 33 | 34 | public static Drawable loadJxl(InputStream inputStream, Options options) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 35 | return JxlDecoder.getInstance().decodeImage(inputStream, options); 36 | } 37 | 38 | public static Drawable loadJxl(ParcelFileDescriptor fileDescriptor, Options options) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 39 | return JxlDecoder.getInstance().decodeImage(fileDescriptor, options); 40 | } 41 | 42 | public static Bitmap loadThumbnail(InputStream inputStream) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 43 | return JxlDecoder.getInstance().decodeThumbnail(inputStream); 44 | } 45 | 46 | public static Bitmap loadThumbnail(ParcelFileDescriptor fileDescriptor) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 47 | return JxlDecoder.getInstance().decodeThumbnail(fileDescriptor); 48 | } 49 | 50 | private static native long getNativeDecoderPtr(); 51 | 52 | private static native void freeNativeDecoderPtr(long nativeDecoderPtr); 53 | 54 | private static native Drawable loadJxlFromInputStream(long nativeDecoderPtr, InputStream inputStream, long options) throws OutOfMemoryError, DecodeError, ClassNotFoundException; 55 | 56 | private static native Drawable loadJxlFromFd(long nativeDecoderPtr, int fd, long options) throws OutOfMemoryError, DecodeError, ClassNotFoundException; 57 | 58 | private static native Bitmap loadThumbnailFromInputStream(long nativeDecoderPtr, InputStream inputStream) throws OutOfMemoryError, DecodeError, ClassNotFoundException; 59 | 60 | private static native Bitmap loadThumbnailFromFd(long nativeDecoderPtr, int fd) throws OutOfMemoryError, DecodeError, ClassNotFoundException; 61 | 62 | @Override 63 | protected void finalize() throws Throwable { 64 | freeNativeDecoderPtr(this.nativeDecoderPtr); 65 | this.nativeDecoderPtr = 0L; 66 | super.finalize(); 67 | } 68 | 69 | public Drawable decodeImage(InputStream inputStream, Options options) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 70 | return loadJxlFromInputStream(this.nativeDecoderPtr, inputStream, (options == null) ? 0 : options.ptr); 71 | } 72 | 73 | public Drawable decodeImage(ParcelFileDescriptor fileDescriptor, Options options) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 74 | return loadJxlFromFd(this.nativeDecoderPtr, fileDescriptor.getFd(), (options == null) ? 0 : options.ptr); 75 | } 76 | 77 | public Bitmap decodeThumbnail(InputStream inputStream) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 78 | return loadThumbnailFromInputStream(this.nativeDecoderPtr, inputStream); 79 | } 80 | 81 | public Bitmap decodeThumbnail(ParcelFileDescriptor fileDescriptor) throws OutOfMemoryError, DecodeError, ClassNotFoundException { 82 | return loadThumbnailFromFd(this.nativeDecoderPtr, fileDescriptor.getFd()); 83 | } 84 | 85 | /** 86 | * Options used by {@link JxlDecoder}. 87 | */ 88 | public static class Options implements AutoCloseable { 89 | static { 90 | System.loadLibrary("jxlreader"); 91 | } 92 | 93 | private long ptr = alloc(); 94 | 95 | private static native long alloc(); 96 | 97 | private static native void free(long ptr); 98 | 99 | private static native int getBitmapConfig(long ptr); 100 | 101 | private static native void setBitmapConfig(long ptr, int format); 102 | 103 | private static native boolean getDecodeMultipleFrames(long ptr); 104 | 105 | private static native void setDecodeMultipleFrames(long ptr, boolean decodeMultipleFrames); 106 | 107 | @Override 108 | public void close() throws Exception { 109 | if (ptr != 0L) { 110 | free(this.ptr); 111 | ptr = 0L; 112 | } 113 | } 114 | 115 | @Override 116 | protected void finalize() throws Throwable { 117 | this.close(); 118 | super.finalize(); 119 | } 120 | 121 | /** 122 | * Get the bitmap config used by the decoder. 123 | * 124 | * @return The {@link Bitmap.Config} used by the decoder. 125 | * @throws ConfigException If the config return unsupported value (this should not happen). 126 | */ 127 | public Bitmap.Config getFormat() throws ConfigException { 128 | int config = getBitmapConfig(this.ptr); 129 | switch (config) { 130 | case 0: 131 | return Bitmap.Config.ARGB_8888; 132 | case 1: 133 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 134 | return Bitmap.Config.RGBA_F16; 135 | } else { 136 | throw new ConfigException("RGBA_F16 can't be used on Android < 8"); 137 | } 138 | default: 139 | throw new ConfigException(String.format("Unknown bitmap format : %d", config)); 140 | } 141 | } 142 | 143 | /** 144 | * Set the bitmap config for the output bitmaps. 145 | * Please note that only ARGB_8888 and RGBA_F16 are supported. 146 | * ARGB_8888 by default. 147 | * 148 | * @param config An ARGB_8888 or RGBA_F16 {@link Bitmap.Config}. 149 | * @throws ConfigException When the config is not supported. 150 | */ 151 | public JxlDecoder.Options setFormat(Bitmap.Config config) throws ConfigException { 152 | int format = 0; 153 | switch (config) { 154 | case ARGB_8888: 155 | break; 156 | case RGBA_F16: 157 | format = 1; 158 | break; 159 | default: 160 | throw new ConfigException("Only ARGB_8888 and RGBA_F16 are supported"); 161 | } 162 | setBitmapConfig(this.ptr, format); 163 | return this; 164 | } 165 | 166 | 167 | /** 168 | * Get if the decoder must decode multiple frames. 169 | * Is true by default. 170 | * 171 | * @return True if the decoder will try to decode multiple values, false otherwise. 172 | */ 173 | public boolean getDecodeMultipleFrames() { 174 | return getDecodeMultipleFrames(this.ptr); 175 | } 176 | 177 | 178 | /** 179 | * Set if the decoder must decode multiple frames. 180 | * 181 | * @param decodeMultipleFrames True if the decoder will try to decode multiple values, false otherwise. 182 | */ 183 | public JxlDecoder.Options setDecodeMultipleFrames(boolean decodeMultipleFrames) { 184 | setDecodeMultipleFrames(this.ptr, decodeMultipleFrames); 185 | return this; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /libjxl/src/main/java/fr/oupson/libjxl/exceptions/ConfigException.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.libjxl.exceptions; 2 | 3 | public class ConfigException extends Exception { 4 | public ConfigException() { 5 | super(); 6 | } 7 | 8 | public ConfigException(String message) { 9 | super(message); 10 | } 11 | 12 | public ConfigException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libjxl/src/main/java/fr/oupson/libjxl/exceptions/DecodeError.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.libjxl.exceptions; 2 | 3 | public class DecodeError extends Exception { 4 | public enum DecodeErrorType { 5 | DecoderFailedError, 6 | ICCProfileError, 7 | MethodCallFailedError, 8 | NeedMoreInputError, 9 | OtherError, 10 | UnknownError 11 | } 12 | 13 | private static DecodeErrorType errorTypeFromNative(int errorType) { 14 | switch (errorType) { 15 | case 0: 16 | return DecodeErrorType.DecoderFailedError; 17 | case 1: 18 | return DecodeErrorType.ICCProfileError; 19 | case 2: 20 | return DecodeErrorType.MethodCallFailedError; 21 | case 3: 22 | return DecodeErrorType.NeedMoreInputError; 23 | case 4: 24 | return DecodeErrorType.OtherError; 25 | default: 26 | return DecodeErrorType.UnknownError; 27 | } 28 | } 29 | 30 | private final DecodeErrorType errorType; 31 | private final String errorMessage; 32 | 33 | public DecodeError(DecodeErrorType errorType) { 34 | super(String.format("Failed to decode : %s", errorType)); 35 | this.errorType = errorType; 36 | this.errorMessage = null; 37 | } 38 | 39 | public DecodeError(DecodeErrorType errorType, String errorMessage) { 40 | super(String.format("Failed to decode : %s with %s", errorType, errorMessage)); 41 | this.errorType = errorType; 42 | this.errorMessage = errorMessage; 43 | } 44 | 45 | public DecodeError(int errorType) { 46 | this(errorTypeFromNative(errorType)); 47 | } 48 | 49 | public DecodeError(int errorType, String errorMessage) { 50 | this(errorTypeFromNative(errorType), errorMessage); 51 | } 52 | 53 | public DecodeErrorType getErrorType() { 54 | return errorType; 55 | } 56 | 57 | 58 | public String getErrorMessage() { 59 | return errorMessage; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /libjxl_microbenchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libjxl_microbenchmark/benchmark-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontobfuscate 24 | 25 | -ignorewarnings 26 | 27 | -keepattributes *Annotation* 28 | 29 | -dontnote junit.framework.** 30 | -dontnote junit.runner.** 31 | 32 | -dontwarn androidx.test.** 33 | -dontwarn org.junit.** 34 | -dontwarn org.hamcrest.** 35 | -dontwarn com.squareup.javawriter.JavaWriter 36 | 37 | -keepclasseswithmembers @org.junit.runner.RunWith public class * -------------------------------------------------------------------------------- /libjxl_microbenchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.androidx.benchmark) 4 | alias(libs.plugins.kotlin.android) 5 | } 6 | 7 | android { 8 | namespace = "fr.oupson.libjxl_microbenchmark" 9 | compileSdk = libs.versions.android.compileSdk.get().toInt() 10 | 11 | compileOptions { 12 | sourceCompatibility = JavaVersion.VERSION_1_8 13 | targetCompatibility = JavaVersion.VERSION_1_8 14 | } 15 | 16 | kotlinOptions { 17 | jvmTarget = "1.8" 18 | } 19 | 20 | defaultConfig { 21 | minSdk = libs.versions.android.minSdk.get().toInt() 22 | testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,LOW-BATTERY,UNLOCKED" 23 | testInstrumentationRunnerArguments["androidx.benchmark.profiling.mode"] = "StackSampling" 24 | } 25 | 26 | testBuildType = "release" 27 | 28 | buildTypes { 29 | getByName("release") { 30 | // The androidx.benchmark plugin configures release buildType with proper settings, such as: 31 | // - disables code coverage 32 | // - adds CPU clock locking task 33 | // - signs release buildType with debug signing config 34 | // - copies benchmark results into build/outputs/connected_android_test_additional_output folder 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | androidTestImplementation(libs.androidx.test.runner) 41 | androidTestImplementation(libs.androidx.test.junit) 42 | androidTestImplementation(libs.junit) 43 | androidTestImplementation(libs.androidx.benchmark.junit) 44 | 45 | androidTestImplementation(project(":libjxl")) 46 | } -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/androidTest/assets/didi.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl_microbenchmark/src/androidTest/assets/didi.jxl -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/androidTest/assets/ferris.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl_microbenchmark/src/androidTest/assets/ferris.jxl -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/androidTest/assets/logo.jxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oupson/jxlviewer/fac2fb255708fa1f3e8b9d2f3b1770f57f9b9d5e/libjxl_microbenchmark/src/androidTest/assets/logo.jxl -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/androidTest/java/fr/oupson/libjxl_microbenchmark/JxlDecoderBenchmark.java: -------------------------------------------------------------------------------- 1 | package fr.oupson.libjxl_microbenchmark; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.Drawable; 6 | 7 | import androidx.benchmark.BenchmarkState; 8 | import androidx.benchmark.BlackHole; 9 | import androidx.benchmark.junit4.BenchmarkRule; 10 | import androidx.test.core.app.ApplicationProvider; 11 | import androidx.test.ext.junit.runners.AndroidJUnit4; 12 | 13 | import org.junit.Assert; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | import java.io.ByteArrayInputStream; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | 22 | import fr.oupson.libjxl.JxlDecoder; 23 | 24 | @RunWith(AndroidJUnit4.class) 25 | public class JxlDecoderBenchmark { 26 | @Rule 27 | public BenchmarkRule benchmarkRule = new BenchmarkRule(); 28 | 29 | private void loadImage(byte[] content) { 30 | try (JxlDecoder.Options options = new JxlDecoder.Options().setDecodeMultipleFrames(true).setFormat(Bitmap.Config.ARGB_8888)) { 31 | Drawable animationDrawable = JxlDecoder.loadJxl(new ByteArrayInputStream(content), options); 32 | BlackHole.consume(animationDrawable); 33 | } catch (Exception e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | @Test 39 | public void benchmarkDecodeLogo() throws IOException { 40 | final BenchmarkState state = benchmarkRule.getState(); 41 | 42 | state.pauseTiming(); 43 | Context context = ApplicationProvider.getApplicationContext(); 44 | InputStream input = context.getResources().getAssets().open("logo.jxl"); 45 | 46 | byte[] content = new byte[input.available()]; 47 | 48 | int size = input.read(content); 49 | input.close(); 50 | 51 | Assert.assertEquals("Failed to read test file, invalid size", 117, size); 52 | state.resumeTiming(); 53 | 54 | while (state.keepRunning()) { 55 | loadImage(content); 56 | } 57 | } 58 | 59 | @Test 60 | public void benchmarkDecodeDidi() throws IOException { 61 | final BenchmarkState state = benchmarkRule.getState(); 62 | 63 | state.pauseTiming(); 64 | Context context = ApplicationProvider.getApplicationContext(); 65 | InputStream input = context.getResources().getAssets().open("didi.jxl"); 66 | 67 | byte[] content = new byte[input.available()]; 68 | 69 | int size = input.read(content); 70 | input.close(); 71 | 72 | Assert.assertEquals("Failed to read test file, invalid size", 529406, size); 73 | state.resumeTiming(); 74 | 75 | while (state.keepRunning()) { 76 | loadImage(content); 77 | } 78 | } 79 | 80 | @Test 81 | public void benchmarkDecodeFerris() throws IOException { 82 | final BenchmarkState state = benchmarkRule.getState(); 83 | 84 | state.pauseTiming(); 85 | Context context = ApplicationProvider.getApplicationContext(); 86 | InputStream input = context.getResources().getAssets().open("ferris.jxl"); 87 | 88 | byte[] content = new byte[input.available()]; 89 | 90 | int size = input.read(content); 91 | input.close(); 92 | 93 | Assert.assertEquals("Failed to read test file, invalid size", 404955, size); 94 | state.resumeTiming(); 95 | 96 | while (state.keepRunning()) { 97 | loadImage(content); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libjxl_microbenchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { setUrl("https://jitpack.io") } 14 | } 15 | } 16 | rootProject.name = "JxlViewer" 17 | include(":app") 18 | include(":libjxl") 19 | include(":libjxl_microbenchmark") 20 | --------------------------------------------------------------------------------