diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4ed380c..0081a4a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,6 +19,16 @@ jobs:
- name: Clone repo
uses: actions/checkout@v4
+ # AVD needs a lot of disk space, so we need to free up some space
+ # See: https://github.com/ReactiveCircus/android-emulator-runner/issues/390#issuecomment-2323948660
+ - name: Free disk space
+ run: |
+ sudo apt purge -yq $(dpkg -l | grep '^ii' | awk '{ print $2 }' | grep -P '(aspnetcore|cabal-|dotnet-|ghc-|libmono|mongodb-|mysql-|php)') \
+ firefox google-chrome-stable google-cloud-cli microsoft-edge-stable mono-devel mono-runtime-common monodoc-manual powershell ruby
+ sudo apt autoremove -yq
+ sudo apt clean
+ sudo rm -fr /opt/ghc /opt/hostedtoolcache /usr/lib/node_modules /usr/local/share/boost /usr/share/dotnet /usr/share/swift
+
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v2
@@ -28,22 +38,85 @@ jobs:
java-version: 17
distribution: adopt
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
- name: Install native build dependencies
run: |
sudo apt update
sudo apt install \
build-essential \
- meson \
ninja-build \
nasm
+ python -m pip install --upgrade pip
+ python -m pip install meson
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- - name: Setup cmake
- uses: jwlawson/actions-setup-cmake@v1.14
+ - name: CMake Cache
+ uses: actions/cache@v4
+ id: cmake-cache
+ with:
+ path: |
+ library/.cxx/*
+ key: cmake-cache
+
+ - name: Build Library
+ run: ./gradlew :library:assemble
+
+ - name: Enable KVM group perms
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ - name: AVD cache
+ uses: actions/cache@v4
+ id: avd-cache
+ with:
+ path: |
+ ~/.android/avd/*
+ ~/.android/adb*
+ key: avd-29
+
+ - name: create AVD and generate snapshot for caching
+ if: steps.avd-cache.outputs.cache-hit != 'true'
+ uses: reactivecircus/android-emulator-runner@v2
with:
- cmake-version: '3.22.1'
+ api-level: 29
+ force-avd-creation: false
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: false
+ script: echo "Generated AVD snapshot for caching."
+
+ - name: Android Emulator Runner
+ uses: ReactiveCircus/android-emulator-runner@v2.33.0
+ with:
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
+ api-level: 29
+ ndk: 26.3.11579264
+ cmake: 3.31.1
+ script: |
+ ./gradlew :library:connectedAndroidTest
+
+
+ # Archive the generated AAR file
+ - name: Archive AAR
+ uses: actions/upload-artifact@v4
+ with:
+ path: library/build/outputs/aar/*.aar
+
+ - name: Check Library Stats
+ run: |
+ ls -lh library/build/intermediates/merged_native_libs/debug/*/out/lib/*/*
+ readelf -d library/build/intermediates/merged_native_libs/debug/*/out/lib/*/*
+
+
- - name: Build app
- run: ./gradlew assemble
diff --git a/.gitignore b/.gitignore
index 10cfdbf..112ef98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
.externalNativeBuild
.cxx
local.properties
+.cache
diff --git a/benchmark/.gitignore b/benchmark/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/benchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/benchmark/benchmark-proguard-rules.pro b/benchmark/benchmark-proguard-rules.pro
new file mode 100644
index 0000000..e4061d2
--- /dev/null
+++ b/benchmark/benchmark-proguard-rules.pro
@@ -0,0 +1,37 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-dontobfuscate
+
+-ignorewarnings
+
+-keepattributes *Annotation*
+
+-dontnote junit.framework.**
+-dontnote junit.runner.**
+
+-dontwarn androidx.test.**
+-dontwarn org.junit.**
+-dontwarn org.hamcrest.**
+-dontwarn com.squareup.javawriter.JavaWriter
+
+-keepclasseswithmembers @org.junit.runner.RunWith public class *
\ No newline at end of file
diff --git a/benchmark/build.gradle b/benchmark/build.gradle
new file mode 100644
index 0000000..ccfea22
--- /dev/null
+++ b/benchmark/build.gradle
@@ -0,0 +1,53 @@
+plugins {
+ id 'com.android.library'
+ id 'androidx.benchmark'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'dev.mihon.image.decoder.benchmark'
+ compileSdk 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+ defaultConfig {
+ minSdk 23
+ targetSdk 34
+
+ testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner'
+ }
+
+ testBuildType = "release"
+ buildTypes {
+ debug {
+ // Since debuggable can"t be modified by gradle for library modules,
+ // it must be done in a manifest - see src/androidTest/AndroidManifest.xml
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "benchmark-proguard-rules.pro"
+ }
+ release {
+ isDefault = true
+ }
+ }
+}
+
+dependencies {
+ implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
+ androidTestImplementation 'androidx.test:runner:1.6.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.2.1'
+ androidTestImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.2.4'
+ androidTestImplementation(project(":library"))
+ // Add your dependencies here. Note that you cannot benchmark code
+ // in an app module this way - you will need to move any code you
+ // want to benchmark to a library module:
+ // https://developer.android.com/studio/projects/android-library#Convert
+
+}
\ No newline at end of file
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..2713827
--- /dev/null
+++ b/benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/benchmark/src/androidTest/assets/image.avif b/benchmark/src/androidTest/assets/image.avif
new file mode 100644
index 0000000..b80acb5
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.avif differ
diff --git a/benchmark/src/androidTest/assets/image.heif b/benchmark/src/androidTest/assets/image.heif
new file mode 100644
index 0000000..f1d0596
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.heif differ
diff --git a/benchmark/src/androidTest/assets/image.jpg b/benchmark/src/androidTest/assets/image.jpg
new file mode 100644
index 0000000..1d8a4f3
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.jpg differ
diff --git a/benchmark/src/androidTest/assets/image.jxl b/benchmark/src/androidTest/assets/image.jxl
new file mode 100644
index 0000000..839af38
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.jxl differ
diff --git a/benchmark/src/androidTest/assets/image.png b/benchmark/src/androidTest/assets/image.png
new file mode 100644
index 0000000..d254b96
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.png differ
diff --git a/benchmark/src/androidTest/assets/image.webp b/benchmark/src/androidTest/assets/image.webp
new file mode 100644
index 0000000..e9a4df8
Binary files /dev/null and b/benchmark/src/androidTest/assets/image.webp differ
diff --git a/benchmark/src/androidTest/kotlin/dev/mihon/image/decoder/benchmark/ExampleBenchmark.kt b/benchmark/src/androidTest/kotlin/dev/mihon/image/decoder/benchmark/ExampleBenchmark.kt
new file mode 100644
index 0000000..962eaa0
--- /dev/null
+++ b/benchmark/src/androidTest/kotlin/dev/mihon/image/decoder/benchmark/ExampleBenchmark.kt
@@ -0,0 +1,108 @@
+package dev.mihon.image.decoder.benchmark
+
+import android.content.Context
+import android.graphics.Rect
+import dev.mihon.image.decoder.ImageDecoder
+import android.util.Log
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.random.Random
+
+/**
+ * Benchmark, which will execute on an Android device.
+ *
+ * The body of [BenchmarkRule.measureRepeated] is measured in a loop, and Studio will
+ * output the result. Modify your code to see how it affects performance.
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleBenchmark {
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ // Initialize context for asset loading
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun decodePng() {
+ benchmarkDecoder("image.png")
+ }
+
+ @Test
+ fun decodeJpg() {
+ benchmarkDecoder("image.jpg")
+ }
+
+ @Test
+ fun decodeHeif() {
+ benchmarkDecoder("image.heif")
+ }
+
+ @Test
+ fun decodeAvif() {
+ benchmarkDecoder("image.avif")
+ }
+
+ @Test
+ fun decodeJxl() {
+ benchmarkDecoder("image.jxl")
+ }
+
+ @Test
+ fun decodeWebp() {
+ benchmarkDecoder("image.webp")
+ }
+
+ private fun benchmarkDecoder(imageName: String) {
+
+ Log.i("Benchmark", "benchmarking $imageName")
+
+ // fix seed to make tests deterministic
+ val rng = Random(99)
+ val decoder = getDecoder(imageName)
+ benchmarkRule.measureRepeated {
+ randomDecode(decoder, rng)
+ }
+ }
+
+ private fun getDecoder(imageName: String): ImageDecoder {
+ return context.assets.open(imageName).use { stream ->
+ return@use ImageDecoder.newInstance(stream)!!
+ }
+ }
+
+ private fun randomDecode(decoder: ImageDecoder, rng: Random) {
+ var randomWidth = (0 until decoder.width).random(rng)
+ var randomHeight = (0 until decoder.height).random(rng)
+
+ // increase the sample size until the image have a reasonable size
+ var sampleSize = 1
+ while (randomWidth > 1000 || randomHeight > 1000) {
+ randomWidth /= 2
+ randomHeight /= 2
+ sampleSize *= 2
+ }
+
+ if (randomWidth == 0) randomWidth = 1
+ if (randomHeight == 0) randomHeight = 1
+
+ val randomX = (0 until decoder.width - randomWidth).random(rng)
+ val randomY = (0 until decoder.height - randomHeight).random(rng)
+
+ val region = Rect(randomX, randomY, randomX + randomWidth, randomY + randomHeight)
+
+ decoder.decode(region, sampleSize)!!
+ }
+}
diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 6c3f8b7..20cc9f5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,8 +8,9 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath "com.android.tools.build:gradle:7.3.1"
+ classpath "com.android.tools.build:gradle:8.7.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21"
+ classpath 'androidx.benchmark:benchmark-gradle-plugin:1.2.4'
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index aa991fc..bbbce66 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Sun Oct 20 20:31:18 BRT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/build.gradle b/library/build.gradle
index 790e371..9098651 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -2,39 +2,51 @@ plugins {
id 'com.android.library'
id 'kotlin-android'
id 'maven-publish'
+ id "com.dorongold.task-tree" version "4.0.0"
}
-group = 'tachiyomiorg'
+group = 'dev.mihon'
android {
+ namespace = 'dev.mihon.image.decoder'
+
compileSdkVersion 33
- ndkVersion "26.2.11394342"
+ ndkVersion "26.3.11579264"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
externalNativeBuild {
cmake {
+ targets 'ep_image-decoder'
}
}
+
+ ndk {
+ // abiFilters 'arm64-v8a'
+ }
}
buildTypes {
+ debug {
+ debuggable true
+ }
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
}
externalNativeBuild {
cmake {
@@ -45,6 +57,84 @@ android {
}
dependencies {
+ androidTestImplementation 'androidx.test:core:1.6.1'
+ androidTestImplementation 'androidx.test:core-ktx:1.6.1'
+ androidTestImplementation 'androidx.test.ext:junit:1.2.1'
+ androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.1'
+ androidTestImplementation 'junit:junit:4.13.2'
+ androidTestImplementation "androidx.test:runner:1.6.2"
+}
+
+task printTaskInputs {
+ doLast {
+ project.getTasks().each { task ->
+
+ if (!task.name.startsWith('buildCMakeDebug')) {
+ return
+ }
+
+ println "--------------------------------------------------------------------------------"
+ println " Task '${project.name}:${task.name}'"
+ println "--------------------------------------------------------------------------------"
+ println ""
+
+ println "File inputs:"
+ try {
+ task.inputs.files.each {
+ println " - ${it}"
+ }
+ } catch (e) {
+ println "Error ${e}"
+ }
+ println ""
+
+ println "Property inputs:"
+ try{
+ task.inputs.properties.each {
+ println " - ${it}"
+ }
+ } catch (e) {
+ println "Error ${e}"
+ }
+ println ""
+
+ println "File outputs:"
+ try {
+ task.outputs.files.each {
+ println " - ${it}"
+ }
+ task.outputs.properties.each {
+ println " - ${it}"
+ }
+ task.outputs.upToDateSpec.getSpecs().each {
+ println " * ${it}"
+ }
+ } catch (e) {
+ println "Error ${e}"
+ }
+ println ""
+
+ println "Dependencies"
+ try {
+ task.getDependsOn().each {
+ println "- ${it}"
+ }
+ } catch (e) {
+ println "Error ${e}"
+ }
+
+ println "--------------------------------------------------------------------------------"
+ println ""
+ }
+ }
+}
+
+tasks.configureEach { task ->
+ if (task.name.startsWith('buildCMakeDebug')) {
+ task.inputs.files(fileTree("src/main/cpp/"))
+ task.outputs.upToDateSpec = AndSpec.EMPTY
+ outputs.upToDateWhen { true }
+ }
}
afterEvaluate {
@@ -53,7 +143,7 @@ afterEvaluate {
// Creates a Maven publication called "release".
release(MavenPublication) {
from components.release
- groupId = 'tachiyomiorg'
+ groupId = 'dev.mihon'
artifactId = 'image-decoder'
version = '1.0'
}
diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro
index 7cfac34..1a0ec28 100644
--- a/library/consumer-rules.pro
+++ b/library/consumer-rules.pro
@@ -1,5 +1,5 @@
--keep class tachiyomi.decoder.ImageDecoder { *; }
--keep class tachiyomi.decoder.ImageDecoder$Companion { *; }
--keep class tachiyomi.decoder.ImageType { *; }
--keep class tachiyomi.decoder.Format { *; }
--keep class tachiyomi.decoder.Format$Companion { *; }
+-keep class dev.mihon.image.decoder.ImageDecoder { *; }
+-keep class dev.mihon.image.decoder.ImageDecoder$Companion { *; }
+-keep class dev.mihon.image.decoder.ImageType { *; }
+-keep class dev.mihon.image.decoder.Format { *; }
+-keep class dev.mihon.image.decoder.Format$Companion { *; }
diff --git a/library/src/androidTest/assets/image.avif b/library/src/androidTest/assets/image.avif
new file mode 100644
index 0000000..b80acb5
Binary files /dev/null and b/library/src/androidTest/assets/image.avif differ
diff --git a/library/src/androidTest/assets/image.heif b/library/src/androidTest/assets/image.heif
new file mode 100644
index 0000000..f1d0596
Binary files /dev/null and b/library/src/androidTest/assets/image.heif differ
diff --git a/library/src/androidTest/assets/image.jpg b/library/src/androidTest/assets/image.jpg
new file mode 100644
index 0000000..1d8a4f3
Binary files /dev/null and b/library/src/androidTest/assets/image.jpg differ
diff --git a/library/src/androidTest/assets/image.jxl b/library/src/androidTest/assets/image.jxl
new file mode 100644
index 0000000..839af38
Binary files /dev/null and b/library/src/androidTest/assets/image.jxl differ
diff --git a/library/src/androidTest/assets/image.png b/library/src/androidTest/assets/image.png
new file mode 100644
index 0000000..d254b96
Binary files /dev/null and b/library/src/androidTest/assets/image.png differ
diff --git a/library/src/androidTest/assets/image.webp b/library/src/androidTest/assets/image.webp
new file mode 100644
index 0000000..e9a4df8
Binary files /dev/null and b/library/src/androidTest/assets/image.webp differ
diff --git a/library/src/androidTest/kotlin/dev/mihon/image/decoder/ImageDecoderTest.kt b/library/src/androidTest/kotlin/dev/mihon/image/decoder/ImageDecoderTest.kt
new file mode 100644
index 0000000..d2a4734
--- /dev/null
+++ b/library/src/androidTest/kotlin/dev/mihon/image/decoder/ImageDecoderTest.kt
@@ -0,0 +1,240 @@
+package dev.mihon.image.decoder
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Rect
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.math.absoluteValue
+
+@RunWith(AndroidJUnit4::class)
+class ImageDecoderTest {
+
+ private lateinit var context: Context
+ private val imageFormats = listOf("image.png", "image.heif", "image.avif", "image.jpg", "image.webp", "image.jxl")
+ private val decoders = mutableListOf()
+
+ @Before
+ fun setUp() {
+ // Initialize context for asset loading
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @After
+ fun tearDown() {
+ // Ensure all decoders are recycled after tests
+ decoders.forEach { it?.recycle() }
+ }
+
+ @Test
+ fun testImageDecodingForSupportedFormats() {
+
+ // decode a png image using Android lib
+ val ref_bitmap = context.assets.open("image.png").use { stream ->
+ BitmapFactory.decodeStream(stream)
+ }
+
+ for (imageName in imageFormats) {
+ val decoder = context.assets.open(imageName).use { stream ->
+ return@use ImageDecoder.newInstance(stream)
+ }
+ assertNotNull("Failed to initialize decoder for $imageName", decoder)
+
+ // Verify decode operation
+ val bitmap = decoder?.decode(Rect(0, 0, decoder.width, decoder.height), 1)
+ assertNotNull("Bitmap decoding failed for $imageName", bitmap)
+ assertTrue("Bitmap width should be greater than 0", bitmap!!.width > 0)
+ assertTrue("Bitmap height should be greater than 0", bitmap.height > 0)
+
+ val diff =compareBitmap(ref_bitmap, bitmap)
+
+ Log.i("ImageDecoderTest", "testImageDecodingForSupportedFormats: diff is $diff")
+
+ assertTrue("Bitmap for $imageName, diff too big: $diff", diff < 0.1)
+
+ // Store the decoder for recycling in tearDown
+ decoders.add(decoder)
+ }
+ }
+
+ @Test
+ fun testImageDecodingForCropping() {
+ // decode a png image using Android lib
+ val refBitmap = context.assets.open("image.png").use { stream ->
+ BitmapFactory.decodeStream(stream)
+ }
+
+ // the image is 600x600
+ val crops = listOf(
+ Pair(1, Rect(0, 0, 300, 300)),
+ Pair(1, Rect(300, 300, 600, 600)),
+ Pair(1, Rect(300, 0, 600, 300)),
+ Pair(1, Rect(100, 100, 200, 200)),
+
+ Pair(2, Rect(100, 100, 200, 200)),
+ // test odd crop rect
+ Pair(2, Rect(100, 100, 201, 201)),
+
+ Pair(4, Rect(100, 100, 200, 200)),
+ // test non-multiple crop rect
+ Pair(4, Rect(100, 100, 202, 202)),
+ Pair(4, Rect(100, 100, 203, 203)),
+ )
+
+
+ // specify a crop rect
+ for (imageName in imageFormats) {
+ Log.i("ImageDecoderTest", "testing $imageName")
+ val decoder = context.assets.open(imageName).use { stream ->
+ return@use ImageDecoder.newInstance(stream)
+ }
+ assertNotNull("Failed to initialize decoder for $imageName", decoder)
+
+ for ((sampleSize, cropRect) in crops) {
+ Log.i("ImageDecoderTest", "testing $sampleSize $cropRect")
+ // crop the reference bitmap
+ var refCroppedBitmap = Bitmap.createBitmap(
+ refBitmap,
+ cropRect.left,
+ cropRect.top,
+ cropRect.width(),
+ cropRect.height()
+ )
+
+ if (sampleSize > 1) {
+ // downscale the reference bitmap
+ val scaledWidth = refCroppedBitmap.width / sampleSize
+ val scaledHeight = refCroppedBitmap.height / sampleSize
+ val scaledBitmap = Bitmap.createScaledBitmap(refCroppedBitmap, scaledWidth, scaledHeight, true)
+ refCroppedBitmap.recycle()
+ refCroppedBitmap = scaledBitmap
+ }
+
+ // Verify decode operation
+ val bitmap = decoder?.decode(cropRect, sampleSize)
+ assertNotNull("Bitmap decoding failed for $imageName", bitmap)
+ assertTrue("Bitmap width should be greater than 0", bitmap!!.width > 0)
+ assertTrue("Bitmap height should be greater than 0", bitmap.height > 0)
+ assertTrue("Bitmap width (${bitmap.width} should be equal to ref width (${refCroppedBitmap.width}", bitmap.width == refCroppedBitmap.width)
+ assertTrue("Bitmap height (${bitmap.height} should be equal to ref height (${refCroppedBitmap.height}", bitmap.height == refCroppedBitmap.height)
+
+ val diff = compareBitmap(refCroppedBitmap, bitmap)
+
+ Log.i("ImageDecoderTest", "testImageDecodingForSupportedFormats: diff is $diff")
+
+ assertTrue("Bitmap for $imageName, diff too big: $diff", diff < 0.2)
+ }
+
+ // Store the decoder for recycling in tearDown
+ decoders.add(decoder)
+ }
+
+ }
+
+
+ @Test
+ fun testImageCropBorders() {
+ // decode a png image using Android lib
+ var refBitmap = context.assets.open("image.png").use { stream ->
+ BitmapFactory.decodeStream(stream)
+ }
+
+ // crop the reference bitmap
+ refBitmap = Bitmap.createBitmap(refBitmap, 50, 50, 500, 500)
+
+ // the cropped image is 500x500
+ val crops = listOf(
+ Pair(1, Rect(0, 0, 300, 300)),
+ Pair(1, Rect(200, 200, 500, 500)),
+ Pair(1, Rect(300, 0, 500, 300)),
+ Pair(1, Rect(100, 100, 200, 200)),
+
+ Pair(2, Rect(100, 100, 200, 200)),
+ // test odd crop rect
+ Pair(2, Rect(100, 100, 201, 201)),
+
+ Pair(4, Rect(100, 100, 200, 200)),
+ // test non-multiple crop rect
+ Pair(4, Rect(100, 100, 202, 202)),
+ Pair(4, Rect(100, 100, 203, 203)),
+ )
+
+ // specify a crop rect
+ for (imageName in imageFormats) {
+ Log.i("ImageDecoderTest", "testing $imageName")
+ val decoder = context.assets.open(imageName).use { stream ->
+ return@use ImageDecoder.newInstance(stream, true)
+ }
+ assertNotNull("Failed to initialize decoder for $imageName", decoder)
+
+ for ((sampleSize, cropRect) in crops) {
+ Log.i("ImageDecoderTest", "testing $sampleSize $cropRect")
+ // crop the reference bitmap
+ var refCroppedBitmap = Bitmap.createBitmap(
+ refBitmap,
+ cropRect.left,
+ cropRect.top,
+ cropRect.width(),
+ cropRect.height()
+ )
+
+ if (sampleSize > 1) {
+ // downscale the reference bitmap
+ val scaledWidth = refCroppedBitmap.width / sampleSize
+ val scaledHeight = refCroppedBitmap.height / sampleSize
+ val scaledBitmap = Bitmap.createScaledBitmap(refCroppedBitmap, scaledWidth, scaledHeight, true)
+ refCroppedBitmap.recycle()
+ refCroppedBitmap = scaledBitmap
+ }
+
+ // Verify decode operation
+ val bitmap = decoder?.decode(cropRect, sampleSize)
+ assertNotNull("Bitmap decoding failed for $imageName", bitmap)
+ assertTrue("Bitmap width should be greater than 0", bitmap!!.width > 0)
+ assertTrue("Bitmap height should be greater than 0", bitmap.height > 0)
+ assertTrue("Bitmap width (${bitmap.width} should be equal to ref width (${refCroppedBitmap.width}", bitmap.width == refCroppedBitmap.width)
+ assertTrue("Bitmap height (${bitmap.height} should be equal to ref height (${refCroppedBitmap.height}", bitmap.height == refCroppedBitmap.height)
+
+ val diff = compareBitmap(refCroppedBitmap, bitmap)
+
+ Log.i("ImageDecoderTest", "testImageDecodingForSupportedFormats: diff is $diff")
+
+ assertTrue("Bitmap for $imageName, diff too big: $diff", diff < 0.2)
+ }
+
+ // Store the decoder for recycling in tearDown
+ decoders.add(decoder)
+ }
+
+ }
+
+ private fun compareBitmap(a: Bitmap, b: Bitmap): Double {
+ if (a.width != b.width || a.height != b.height) {
+ Log.e("ImageDecoderTest", "compareBitmap: image size mismatch: ${a.width}x${a.height} != ${b.width}x${b.height}")
+ return 1.0;
+ }
+
+ var accum = 0.0;
+ for (y in 0 until a.height) {
+ for (x in 0 until a.width) {
+ val c1 = a.getColor(x, y)
+ val c2 = b.getColor(x, y)
+ val dr = c1.red() - c2.red()
+ val dg = c1.green() - c2.green()
+ val db = c1.blue() - c2.blue()
+ accum += dr.absoluteValue + dg.absoluteValue + db.absoluteValue
+ }
+ }
+
+ accum /= a.width * a.height * 3.0;
+
+ return accum
+ }
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
index 1c7be0c..c96cb80 100644
--- a/library/src/main/AndroidManifest.xml
+++ b/library/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
+ package="dev.mihon.image.decoder">
diff --git a/library/src/main/cpp/CMakeCache.txt b/library/src/main/cpp/CMakeCache.txt
new file mode 100644
index 0000000..750e29c
--- /dev/null
+++ b/library/src/main/cpp/CMakeCache.txt
@@ -0,0 +1,468 @@
+# This is the CMakeCache file.
+# For build in directory: /home/rodrigodd/repos/image-decoder/library/src/main/cpp
+# It was generated by CMake: /usr/bin/cmake
+# You can edit this file to change values found and used by cmake.
+# If you do not want to change any of the values, simply exit the editor.
+# If you do want to change a value, simply edit, save, and exit the editor.
+# The syntax for the file is as follows:
+# KEY:TYPE=VALUE
+# KEY is the name of a variable in the cache.
+# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
+# VALUE is the current value for the KEY.
+
+########################
+# EXTERNAL cache entries
+########################
+
+//No help, variable specified on the command line.
+ANDROID_ABI:UNINITIALIZED=arm64-v8a
+
+//Path to a program.
+CCACHE_FOUND:FILEPATH=/usr/bin/ccache
+
+//Path to a program.
+CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line
+
+//Path to a program.
+CMAKE_AR:FILEPATH=/usr/bin/ar
+
+//ASM compiler
+CMAKE_ASM_COMPILER:FILEPATH=/usr/bin/cc
+
+//A wrapper around 'ar' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_ASM_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar
+
+//A wrapper around 'ranlib' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_ASM_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib
+
+//Flags used by the ASM compiler during all build types.
+CMAKE_ASM_FLAGS:STRING=
+
+//Flags used by the ASM compiler during DEBUG builds.
+CMAKE_ASM_FLAGS_DEBUG:STRING=-g
+
+//Flags used by the ASM compiler during MINSIZEREL builds.
+CMAKE_ASM_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
+
+//Flags used by the ASM compiler during RELEASE builds.
+CMAKE_ASM_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
+
+//Flags used by the ASM compiler during RELWITHDEBINFO builds.
+CMAKE_ASM_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
+
+//Choose the type of build, options are: None Debug Release RelWithDebInfo
+// MinSizeRel ...
+CMAKE_BUILD_TYPE:STRING=
+
+//Enable/Disable color output during build.
+CMAKE_COLOR_MAKEFILE:BOOL=ON
+
+//CXX compiler
+CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++
+
+//A wrapper around 'ar' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_CXX_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar
+
+//A wrapper around 'ranlib' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib
+
+//Flags used by the CXX compiler during all build types.
+CMAKE_CXX_FLAGS:STRING=
+
+//Flags used by the CXX compiler during DEBUG builds.
+CMAKE_CXX_FLAGS_DEBUG:STRING=-g
+
+//Flags used by the CXX compiler during MINSIZEREL builds.
+CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
+
+//Flags used by the CXX compiler during RELEASE builds.
+CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
+
+//Flags used by the CXX compiler during RELWITHDEBINFO builds.
+CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
+
+//C compiler
+CMAKE_C_COMPILER:FILEPATH=/usr/bin/cc
+
+//A wrapper around 'ar' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_C_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar
+
+//A wrapper around 'ranlib' adding the appropriate '--plugin' option
+// for the GCC compiler
+CMAKE_C_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib
+
+//Flags used by the C compiler during all build types.
+CMAKE_C_FLAGS:STRING=
+
+//Flags used by the C compiler during DEBUG builds.
+CMAKE_C_FLAGS_DEBUG:STRING=-g
+
+//Flags used by the C compiler during MINSIZEREL builds.
+CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
+
+//Flags used by the C compiler during RELEASE builds.
+CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
+
+//Flags used by the C compiler during RELWITHDEBINFO builds.
+CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
+
+//Path to a program.
+CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND
+
+//Flags used by the linker during all build types.
+CMAKE_EXE_LINKER_FLAGS:STRING=
+
+//Flags used by the linker during DEBUG builds.
+CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
+
+//Flags used by the linker during MINSIZEREL builds.
+CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
+
+//Flags used by the linker during RELEASE builds.
+CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
+
+//Flags used by the linker during RELWITHDEBINFO builds.
+CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
+
+//Enable/Disable output of compile commands during generation.
+CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
+
+//Value Computed by CMake.
+CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/home/rodrigodd/repos/image-decoder/library/src/main/cpp/CMakeFiles/pkgRedirects
+
+//No help, variable specified on the command line.
+CMAKE_FIND_ROOT_PATH:UNINITIALIZED=/home/rodrigodd/Android/Sdk/ndk/26.3.11579264/toolchains/llvm/prebuilt/linux-x86_64/sysroot/
+
+//Install path prefix, prepended onto install directories.
+CMAKE_INSTALL_PREFIX:PATH=/usr/local
+
+//Path to a program.
+CMAKE_LINKER:FILEPATH=/usr/bin/ld
+
+//Path to a program.
+CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/make
+
+//Flags used by the linker during the creation of modules during
+// all build types.
+CMAKE_MODULE_LINKER_FLAGS:STRING=
+
+//Flags used by the linker during the creation of modules during
+// DEBUG builds.
+CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
+
+//Flags used by the linker during the creation of modules during
+// MINSIZEREL builds.
+CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
+
+//Flags used by the linker during the creation of modules during
+// RELEASE builds.
+CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
+
+//Flags used by the linker during the creation of modules during
+// RELWITHDEBINFO builds.
+CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
+
+//Path to a program.
+CMAKE_NM:FILEPATH=/usr/bin/nm
+
+//Path to a program.
+CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy
+
+//Path to a program.
+CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump
+
+//Value Computed by CMake
+CMAKE_PROJECT_DESCRIPTION:STATIC=
+
+//Value Computed by CMake
+CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
+
+//Value Computed by CMake
+CMAKE_PROJECT_NAME:STATIC=imagedecoder
+
+//Path to a program.
+CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib
+
+//Path to a program.
+CMAKE_READELF:FILEPATH=/usr/bin/readelf
+
+//Flags used by the linker during the creation of shared libraries
+// during all build types.
+CMAKE_SHARED_LINKER_FLAGS:STRING=
+
+//Flags used by the linker during the creation of shared libraries
+// during DEBUG builds.
+CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
+
+//Flags used by the linker during the creation of shared libraries
+// during MINSIZEREL builds.
+CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
+
+//Flags used by the linker during the creation of shared libraries
+// during RELEASE builds.
+CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
+
+//Flags used by the linker during the creation of shared libraries
+// during RELWITHDEBINFO builds.
+CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
+
+//If set, runtime paths are not added when installing shared libraries,
+// but are added when building.
+CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
+
+//If set, runtime paths are not added when using shared libraries.
+CMAKE_SKIP_RPATH:BOOL=NO
+
+//Flags used by the linker during the creation of static libraries
+// during all build types.
+CMAKE_STATIC_LINKER_FLAGS:STRING=
+
+//Flags used by the linker during the creation of static libraries
+// during DEBUG builds.
+CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
+
+//Flags used by the linker during the creation of static libraries
+// during MINSIZEREL builds.
+CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
+
+//Flags used by the linker during the creation of static libraries
+// during RELEASE builds.
+CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
+
+//Flags used by the linker during the creation of static libraries
+// during RELWITHDEBINFO builds.
+CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
+
+//Path to a program.
+CMAKE_STRIP:FILEPATH=/usr/bin/strip
+
+//Path to a program.
+CMAKE_TAPI:FILEPATH=CMAKE_TAPI-NOTFOUND
+
+//If this value is on, makefiles will be generated without the
+// .SILENT directive, and all commands will be echoed to the console
+// during the make. This is useful for debugging only. With Visual
+// Studio IDE projects all commands are done without /nologo.
+CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
+
+//Git command line client
+GIT_EXECUTABLE:FILEPATH=/usr/bin/git
+
+//Path to a program.
+Make_EXECUTABLE:FILEPATH=/usr/bin/make
+
+//Path to a program.
+Meson_EXECUTABLE:FILEPATH=/usr/bin/meson
+
+//Path to a program.
+Ninja_EXECUTABLE:FILEPATH=/usr/bin/ninja
+
+//Include AVIF decoder
+WITH_AVIF:BOOL=ON
+
+//Include HEIF decoder
+WITH_HEIF:BOOL=ON
+
+//Include JPEG decoder
+WITH_JPEG:BOOL=ON
+
+//Include JXL decoder
+WITH_JXL:BOOL=ON
+
+//Include PNG decoder
+WITH_PNG:BOOL=ON
+
+//Include libvips and use it decoders
+WITH_VIPS:BOOL=ON
+
+//Include WebP decoder
+WITH_WEBP:BOOL=ON
+
+//Value Computed by CMake
+imagedecoder_BINARY_DIR:STATIC=/home/rodrigodd/repos/image-decoder/library/src/main/cpp
+
+//Value Computed by CMake
+imagedecoder_IS_TOP_LEVEL:STATIC=ON
+
+//Value Computed by CMake
+imagedecoder_SOURCE_DIR:STATIC=/home/rodrigodd/repos/image-decoder/library/src/main/cpp
+
+
+########################
+# INTERNAL cache entries
+########################
+
+//ADVANCED property for variable: CMAKE_ADDR2LINE
+CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_AR
+CMAKE_AR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_COMPILER
+CMAKE_ASM_COMPILER-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_COMPILER_AR
+CMAKE_ASM_COMPILER_AR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_COMPILER_RANLIB
+CMAKE_ASM_COMPILER_RANLIB-ADVANCED:INTERNAL=1
+CMAKE_ASM_COMPILER_WORKS:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_FLAGS
+CMAKE_ASM_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_FLAGS_DEBUG
+CMAKE_ASM_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_FLAGS_MINSIZEREL
+CMAKE_ASM_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_FLAGS_RELEASE
+CMAKE_ASM_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_ASM_FLAGS_RELWITHDEBINFO
+CMAKE_ASM_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//This is the directory where this CMakeCache.txt was created
+CMAKE_CACHEFILE_DIR:INTERNAL=/home/rodrigodd/repos/image-decoder/library/src/main/cpp
+//Major version of cmake used to create the current loaded cache
+CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
+//Minor version of cmake used to create the current loaded cache
+CMAKE_CACHE_MINOR_VERSION:INTERNAL=30
+//Patch version of cmake used to create the current loaded cache
+CMAKE_CACHE_PATCH_VERSION:INTERNAL=5
+//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE
+CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1
+//Path to CMake executable.
+CMAKE_COMMAND:INTERNAL=/usr/bin/cmake
+//Path to cpack program executable.
+CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack
+//Path to ctest program executable.
+CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest
+//ADVANCED property for variable: CMAKE_CXX_COMPILER
+CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR
+CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB
+CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_FLAGS
+CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG
+CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL
+CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE
+CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO
+CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_COMPILER
+CMAKE_C_COMPILER-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_COMPILER_AR
+CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB
+CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_FLAGS
+CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
+CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
+CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
+CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
+CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_DLLTOOL
+CMAKE_DLLTOOL-ADVANCED:INTERNAL=1
+//Path to cache edit program executable.
+CMAKE_EDIT_COMMAND:INTERNAL=/usr/bin/ccmake
+//Executable file format
+CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF
+//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
+CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
+CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
+CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
+CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
+CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
+CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
+//Name of external makefile project generator.
+CMAKE_EXTRA_GENERATOR:INTERNAL=
+//Name of generator.
+CMAKE_GENERATOR:INTERNAL=Unix Makefiles
+//Generator instance identifier.
+CMAKE_GENERATOR_INSTANCE:INTERNAL=
+//Name of generator platform.
+CMAKE_GENERATOR_PLATFORM:INTERNAL=
+//Name of generator toolset.
+CMAKE_GENERATOR_TOOLSET:INTERNAL=
+//Source directory with the top level CMakeLists.txt file for this
+// project
+CMAKE_HOME_DIRECTORY:INTERNAL=/home/rodrigodd/repos/image-decoder/library/src/main/cpp
+//Install .so files without execute permission.
+CMAKE_INSTALL_SO_NO_EXE:INTERNAL=0
+//ADVANCED property for variable: CMAKE_LINKER
+CMAKE_LINKER-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
+CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
+CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
+CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
+CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
+CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
+CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_NM
+CMAKE_NM-ADVANCED:INTERNAL=1
+//number of local generators
+CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
+//ADVANCED property for variable: CMAKE_OBJCOPY
+CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_OBJDUMP
+CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
+//Platform information initialized
+CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_RANLIB
+CMAKE_RANLIB-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_READELF
+CMAKE_READELF-ADVANCED:INTERNAL=1
+//Path to CMake installation.
+CMAKE_ROOT:INTERNAL=/usr/share/cmake
+//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
+CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
+CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
+CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
+CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
+CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
+CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_SKIP_RPATH
+CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
+CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
+CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
+CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
+CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
+CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_STRIP
+CMAKE_STRIP-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: CMAKE_TAPI
+CMAKE_TAPI-ADVANCED:INTERNAL=1
+//uname command
+CMAKE_UNAME:INTERNAL=/usr/bin/uname
+//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
+CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
+//ADVANCED property for variable: GIT_EXECUTABLE
+GIT_EXECUTABLE-ADVANCED:INTERNAL=1
+//linker supports push/pop state
+_CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED:INTERNAL=TRUE
+
diff --git a/library/src/main/cpp/CMakeLists.txt b/library/src/main/cpp/CMakeLists.txt
index 068db52..6935822 100644
--- a/library/src/main/cpp/CMakeLists.txt
+++ b/library/src/main/cpp/CMakeLists.txt
@@ -1,54 +1,188 @@
+find_program(Make_EXECUTABLE make)
+if(NOT Make_EXECUTABLE)
+ message(FATAL_ERROR "Make is required")
+endif()
+
+find_program(Ninja_EXECUTABLE ninja)
+if(NOT Ninja_EXECUTABLE)
+ message(FATAL_ERROR "Ninja is required")
+endif()
+
+find_program(Meson_EXECUTABLE meson)
+if(NOT Meson_EXECUTABLE)
+ message(FATAL_ERROR "Meson is required")
+endif()
+
cmake_minimum_required(VERSION 3.14)
-project(imagedecoder C CXX ASM)
-include(FetchContent)
-
-set(CMAKE_BUILD_TYPE Release)
-
-option(WITH_JPEG "Include JPEG decoder" ON)
-option(WITH_PNG "Include PNG decoder" ON)
-option(WITH_WEBP "Include WebP decoder" ON)
-option(WITH_HEIF "Include HEIF decoder" ON)
-option(WITH_AVIF "Include AVIF decoder" ON)
-option(WITH_JXL "Include JXL decoder" ON)
-
-add_library(imagedecoder SHARED
- java_stream.cpp
- java_wrapper.cpp
- java_objects.cpp
- borders.cpp
- row_convert.cpp
-)
+project(imagedecoder_super C CXX ASM)
-# Change the default path of fetch content to avoid downloading the same dependencies per architecture
-get_filename_component(deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
-set(FETCHCONTENT_BASE_DIR ${deps})
+# execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")
+#
+# get_cmake_property(_variableNames VARIABLES)
+# list (SORT _variableNames)
+# foreach (_variableName ${_variableNames})
+# message(STATUS "${_variableName}=${${_variableName}}")
+# endforeach()
+# message(FATAL_ERROR "STOP")
+#
+# set(CMAKE_BUILD_TYPE Release)
-add_subdirectory(lcms)
+set(THIRD_PARTY_LIB_PATH ${CMAKE_BINARY_DIR}/fakeroot)
+set(CMAKE_FIND_ROOT_PATH ${THIRD_PARTY_LIB_PATH};${CMAKE_FIND_ROOT_PATH})
+set_directory_properties(PROPERTIES EP_PREFIX ${THIRD_PARTY_LIB_PATH})
-if(WITH_JPEG)
- add_subdirectory(libjpeg-turbo)
- add_definitions(-DHAVE_LIBJPEG)
- target_sources(imagedecoder PRIVATE decoder_jpeg.cpp)
-endif()
-if(WITH_PNG)
- add_subdirectory(libpng)
- add_definitions(-DHAVE_LIBPNG)
- target_sources(imagedecoder PRIVATE decoder_png.cpp)
-endif()
-if(WITH_WEBP)
- add_subdirectory(libwebp)
- add_definitions(-DHAVE_LIBWEBP)
- target_sources(imagedecoder PRIVATE decoder_webp.cpp)
-endif()
-if(WITH_HEIF OR WITH_AVIF)
- add_subdirectory(libheif)
- add_definitions(-DHAVE_LIBHEIF)
- target_sources(imagedecoder PRIVATE decoder_heif.cpp)
-endif()
-if(WITH_JXL)
- add_subdirectory(libjxl)
- add_definitions(-DHAVE_LIBJXL)
- target_sources(imagedecoder PRIVATE decoder_jxl.cpp)
+# Prefer static libraries
+set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
+
+# Enable ccache if available
+find_program(CCACHE_FOUND ccache)
+if(CCACHE_FOUND)
+ set_property(GLOBAL PROPERTY CMAKE_C_COMPILER_LAUNCHER ccache)
+ set_property(GLOBAL PROPERTY CMAKE_C_LINKER_LAUNCHER ccache)
+endif(CCACHE_FOUND)
+
+# Set up variables for different architectures
+if(ANDROID_ABI STREQUAL "arm64-v8a")
+ set(ANDROID_CPU_FAMILY "aarch64")
+ set(ANDROID_CPU "aarch64")
+ set(ANDROID_CPU_ENDIANESS "little")
+ set(ANDROID_TARGET "aarch64-linux-android")
+elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
+ set(ANDROID_CPU_FAMILY "arm")
+ set(ANDROID_CPU "armv7-a")
+ set(ANDROID_CPU_ENDIANESS "little")
+ set(ANDROID_TARGET "armv7a-linux-androideabi")
+elseif(ANDROID_ABI STREQUAL "x86")
+ set(ANDROID_CPU_FAMILY "x86")
+ set(ANDROID_CPU "i686")
+ set(ANDROID_CPU_ENDIANESS "little")
+ set(ANDROID_TARGET "i686-linux-android")
+elseif(ANDROID_ABI STREQUAL "x86_64")
+ set(ANDROID_CPU_FAMILY "x86_64")
+ set(ANDROID_CPU "x86_64")
+ set(ANDROID_CPU_ENDIANESS "little")
+ set(ANDROID_TARGET "x86_64-linux-android")
+else()
+ message(FATAL_ERROR "Unsupported architecture: ${ANDROID_ABI}")
endif()
-target_link_libraries(imagedecoder android jnigraphics log)
+# Define the template for the cross-file
+set(CROSS_FILE_CONTENT "
+[host_machine]
+system = 'android'
+cpu_family = '${ANDROID_CPU_FAMILY}'
+cpu = '${ANDROID_CPU}'
+endian = '${ANDROID_CPU_ENDIANESS}'
+
+[binaries]
+c = ['${CMAKE_C_COMPILER}', '--target=${ANDROID_TARGET}${ANDROID_PLATFORM_LEVEL}']
+cpp = ['${CMAKE_CXX_COMPILER}', '--target=${ANDROID_TARGET}${ANDROID_PLATFORM_LEVEL}']
+ar = '${CMAKE_AR}'
+ld = '${CMAKE_LINKER}'
+strip = '${CMAKE_STRIP}'
+ranlib = '${CMAKE_RANLIB}'
+as = '${CMAKE_ASM_COMPILER}'
+# pkg-config = ['sh', '${CMAKE_CURRENT_LIST_DIR}/pkg-config-wrapper.sh', '${THIRD_PARTY_LIB_PATH}']
+pkg-config = 'pkg-config'
+
+[built-in options]
+c_args = ['-I${THIRD_PARTY_LIB_PATH}/include', '-Wno-error=format-nonliteral']
+cpp_args = ['-I${THIRD_PARTY_LIB_PATH}/include', '-Wno-error=format-nonliteral']
+c_link_args = ['-L${THIRD_PARTY_LIB_PATH}/lib']
+cpp_link_args = ['-L${THIRD_PARTY_LIB_PATH}/lib']
+
+[properties]
+# sys_root = '${CMAKE_SYSROOT}'
+cmake_toolchain_file = '${CMAKE_TOOLCHAIN_FILE}'
+pkg_config_libdir = '${THIRD_PARTY_LIB_PATH}/lib/pkgconfig'
+
+[cmake]
+CMAKE_BUILD_WITH_INSTALL_RPATH = 'ON'
+CMAKE_FIND_ROOT_PATH_MODE_PROGRAM = 'NEVER'
+CMAKE_FIND_ROOT_PATH_MODE_LIBRARY = 'ONLY'
+CMAKE_FIND_ROOT_PATH_MODE_INCLUDE = 'ONLY'
+CMAKE_FIND_ROOT_PATH_MODE_PACKAGE = 'ONLY'
+")
+
+# Generate the cross-file
+set(MESON_CROSS_FILE_PATH "${CMAKE_BINARY_DIR}/android-cross-file.txt")
+file(WRITE ${MESON_CROSS_FILE_PATH} "${CROSS_FILE_CONTENT}")
+
+# Common args to all Mason ExternalProjects
+set(EP_MESON_ARGS "--cross-file=${MESON_CROSS_FILE_PATH}"
+ "--prefix="
+ "--libdir=lib"
+ "--default-library=static"
+ "--buildtype=release"
+)
+
+# Escape ';'
+string(REPLACE ";" "$" CMAKE_FIND_ROOT_PATH_STR "${CMAKE_FIND_ROOT_PATH}")
+string(REPLACE ";" "$" CMAKE_FIND_LIBRARY_SUFFIXES_STR "${CMAKE_FIND_LIBRARY_SUFFIXES}")
+
+# Common args to all CMake ExternalProjects
+set(EP_CMAKE_ARGS "-DANDROID_ABI=${ANDROID_ABI}"
+ "-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
+ "-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF"
+ "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
+ "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/fakeroot"
+ "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
+ "-DCMAKE_SYSROOT=${CMAKE_SYSROOT}"
+ "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}"
+ "-DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH_STR}"
+ "-DCMAKE_FIND_LIBRARY_SUFFIXES=${CMAKE_FIND_LIBRARY_SUFFIXES_STR}"
+ "-DCMAKE_FIND_DEBUG_MODE=OFF"
+ "-DBUILD_SHARED_LIBS=OFF"
+ "-DBUILD_TESTING=OFF"
+ "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}"
+ "-DCMAKE_C_LINKER_LAUNCHER=${CMAKE_C_LINKER_LAUNCHER}")
+
+# Common args to all autotools ExternalProjects
+set(EP_AUTOTOOLS_ARGS "--host=${ANDROID_TARGET}${ANDROID_PLATFORM_LEVEL}"
+ "CC=${CMAKE_C_COMPILER} --target=${ANDROID_TARGET}${ANDROID_PLATFORM_LEVEL}"
+ "CXX=${CMAKE_CXX_COMPILER} --target=${ANDROID_TARGET}${ANDROID_PLATFORM_LEVEL}"
+ "AR=${CMAKE_AR}"
+ "AS=${CMAKE_ASM_COMPILER}"
+ "LD=${CMAKE_LINKER}"
+ "RANLIB=${CMAKE_RANLIB}"
+ "STRIP=${CMAKE_STRIP}"
+ "--prefix=${THIRD_PARTY_LIB_PATH}"
+ "--disable-shared"
+ "--enable-static"
+ "--with-pic")
+
+include("cmake/little-cms.cmake")
+include("cmake/jpeg-turbo.cmake")
+include("cmake/webp.cmake")
+include("cmake/zlib.cmake")
+include("cmake/dav1d.cmake")
+include("cmake/de265.cmake")
+include("cmake/heif.cmake")
+include("cmake/highway.cmake")
+include("cmake/brotli.cmake")
+include("cmake/jxl.cmake")
+include("cmake/tiff.cmake")
+include("cmake/ffi.cmake")
+include("cmake/iconv.cmake")
+include("cmake/glib.cmake")
+include("cmake/expat.cmake")
+include("cmake/spng.cmake")
+include("cmake/vips.cmake")
+
+include(ExternalProject)
+ExternalProject_Add(ep_image-decoder
+ DOWNLOAD_COMMAND ""
+ UPDATE_COMMAND ""
+ INSTALL_COMMAND ""
+ SOURCE_DIR ${PROJECT_SOURCE_DIR}/image-decoder
+ DEPENDS ep_vips
+ CMAKE_ARGS ${EP_CMAKE_ARGS}
+ "-DBUILD_SHARED_LIBS=ON"
+ "-DBUILD_STATIC_LIBS=OFF"
+ "-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH"
+ "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}"
+ "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
+ "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/brotli.cmake b/library/src/main/cpp/cmake/brotli.cmake
new file mode 100644
index 0000000..cccf491
--- /dev/null
+++ b/library/src/main/cpp/cmake/brotli.cmake
@@ -0,0 +1,10 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_brotli
+ GIT_REPOSITORY https://github.com/google/brotli
+ GIT_TAG v1.1.0
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/dav1d.cmake b/library/src/main/cpp/cmake/dav1d.cmake
new file mode 100644
index 0000000..97fafa2
--- /dev/null
+++ b/library/src/main/cpp/cmake/dav1d.cmake
@@ -0,0 +1,15 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_dav1d
+GIT_REPOSITORY https://code.videolan.org/videolan/dav1d.git
+ GIT_TAG 1.4.3
+ DEPENDS ep_zlib
+ CONFIGURE_COMMAND
+ ${Meson_EXECUTABLE} setup ${EP_MESON_ARGS}
+ BUILD_COMMAND
+ ${Ninja_EXECUTABLE} -C
+ INSTALL_COMMAND
+ ${Ninja_EXECUTABLE} -C install
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/de265.cmake b/library/src/main/cpp/cmake/de265.cmake
new file mode 100644
index 0000000..c9dfc41
--- /dev/null
+++ b/library/src/main/cpp/cmake/de265.cmake
@@ -0,0 +1,11 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_de265
+ GIT_REPOSITORY https://github.com/strukturag/libde265.git
+ GIT_TAG v1.0.15
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DENABLE_SDL=OFF
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/expat.cmake b/library/src/main/cpp/cmake/expat.cmake
new file mode 100644
index 0000000..20f3b63
--- /dev/null
+++ b/library/src/main/cpp/cmake/expat.cmake
@@ -0,0 +1,11 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_expat
+ GIT_REPOSITORY https://github.com/libexpat/libexpat.git
+ GIT_TAG R_2_6_2
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ SOURCE_SUBDIR expat
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/ffi.cmake b/library/src/main/cpp/cmake/ffi.cmake
new file mode 100644
index 0000000..3d00ea1
--- /dev/null
+++ b/library/src/main/cpp/cmake/ffi.cmake
@@ -0,0 +1,18 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_ffi
+ GIT_REPOSITORY https://github.com/libffi/libffi.git
+ GIT_TAG v3.4.6
+ BUILD_IN_SOURCE true
+ CONFIGURE_COMMAND
+ /autogen.sh && /configure ${EP_AUTOTOOLS_ARGS}
+ --disable-exec-static-tramp
+ --disable-multi-os-directory
+ --enable-pax_emutramp
+ BUILD_COMMAND
+ ${Make_EXECUTABLE} all
+ INSTALL_COMMAND
+ ${Make_EXECUTABLE} install
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/glib.cmake b/library/src/main/cpp/cmake/glib.cmake
new file mode 100644
index 0000000..37e4c07
--- /dev/null
+++ b/library/src/main/cpp/cmake/glib.cmake
@@ -0,0 +1,22 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_glib
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE
+ URL https://download.gnome.org/sources/glib/2.83/glib-2.83.0.tar.xz
+ URL_HASH SHA256=a07d9e1a57a4279c5ece71c26dc44eea12bd518ea9ff695d53e722997032b614
+ DEPENDS ep_zlib ep_ffi ep_iconv
+ CONFIGURE_COMMAND
+ ${Meson_EXECUTABLE} setup ${EP_MESON_ARGS}
+
+ -D selinux=disabled
+ -D glib_debug=disabled
+ -Dlibelf=disabled
+ -Dintrospection=disabled
+ -Dtests=false
+ BUILD_COMMAND
+ ${Meson_EXECUTABLE} compile -C
+ INSTALL_COMMAND
+ ${Meson_EXECUTABLE} install -C
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/heif.cmake b/library/src/main/cpp/cmake/heif.cmake
new file mode 100644
index 0000000..5f2c27e
--- /dev/null
+++ b/library/src/main/cpp/cmake/heif.cmake
@@ -0,0 +1,33 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_heif
+ GIT_REPOSITORY https://github.com/strukturag/libheif.git
+ GIT_TAG v1.19.2
+ DEPENDS ep_dav1d ep_de265 ep_webp ep_zlib
+ PATCH_COMMAND patch -p 1 < ${CMAKE_CURRENT_LIST_DIR}/patches/heif-require-webp.patch || true
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DWITH_GDK_PIXBUF=OFF
+ -DWITH_AOM_DECODER=OFF
+ -DWITH_AOM_ENCODER=OFF
+ -DWITH_SvtEnc=OFF
+ -DWITH_FFMPEG_DECODER=OFF
+ -DWITH_FFMPEG_ENCODER=OFF
+ -DWITH_JPEG_DECODER=OFF
+ -DWITH_JPEG_ENCODER=OFF
+ -DWITH_KVAZAAR=OFF
+ -DWITH_OpenJPEG_DECODER=OFF
+ -DWITH_OpenJPEG_ENCODER=OFF
+ -DWITH_LIBDE265_PLUGIN=OFF
+ -DWITH_EXAMPLES=OFF
+ -DWITH_DAV1D=ON
+ -DWITH_DAV1D_PLUGIN=OFF
+ -DWITH_LIBDE265=ON
+ -DWITH_LIBDE265_PLUGIN=OFF
+ -DWITH_X265=OFF
+ -DWITH_AOM=OFF
+ -DWITH_RAV1E=OFF
+ --preset=release
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/highway.cmake b/library/src/main/cpp/cmake/highway.cmake
new file mode 100644
index 0000000..084c75f
--- /dev/null
+++ b/library/src/main/cpp/cmake/highway.cmake
@@ -0,0 +1,16 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_highway
+ GIT_REPOSITORY https://github.com/google/highway.git
+ GIT_TAG 1.2.0
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DHWY_ENABLE_CONTRIB=OFF
+ -DHWY_ENABLE_EXAMPLES=OFF
+ -DHWY_ENABLE_TESTS=OFF
+ -DHWY_SYSTEM_GTEST=ON
+ -DHWY_FORCE_STATIC_LIBS=ON
+
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/iconv.cmake b/library/src/main/cpp/cmake/iconv.cmake
new file mode 100644
index 0000000..29b9db7
--- /dev/null
+++ b/library/src/main/cpp/cmake/iconv.cmake
@@ -0,0 +1,15 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_iconv
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE
+ URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz
+ CONFIGURE_COMMAND
+ /configure ${EP_AUTOTOOLS_ARGS}
+ --enable-extra-encodings
+ BUILD_COMMAND
+ ${Make_EXECUTABLE}
+ INSTALL_COMMAND
+ ${Make_EXECUTABLE} install
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/jpeg-turbo.cmake b/library/src/main/cpp/cmake/jpeg-turbo.cmake
new file mode 100644
index 0000000..e88e86c
--- /dev/null
+++ b/library/src/main/cpp/cmake/jpeg-turbo.cmake
@@ -0,0 +1,15 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_jpeg-turbo
+ GIT_REPOSITORY https://github.com/libjpeg-turbo/libjpeg-turbo
+ GIT_TAG 3.0.3
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DWITH_JPEG8=1
+ -DWITH_TURBOJPEG=0
+ -DENABLE_SHARED=0
+ -DENABLE_STATIC=1
+ -DREQUIRE_SIMD=1
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/jxl.cmake b/library/src/main/cpp/cmake/jxl.cmake
new file mode 100644
index 0000000..a8af5d3
--- /dev/null
+++ b/library/src/main/cpp/cmake/jxl.cmake
@@ -0,0 +1,34 @@
+include(ExternalProject)
+
+cmake_policy(SET CMP0097 NEW)
+
+ExternalProject_Add(ep_jxl
+ GIT_REPOSITORY https://github.com/libjxl/libjxl
+ GIT_TAG v0.11.0
+ GIT_SUBMODULES "third_party/skcms"
+ DEPENDS ep_highway ep_brotli ep_little-cms
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DJPEGXL_BUNDLE_LIBPNG=false
+ -DJPEGXL_ENABLE_DOXYGEN=OFF
+ -DJPEGXL_ENABLE_MANPAGES=OFF
+ -DJPEGXL_ENABLE_EXAMPLES=OFF
+ -DJPEGXL_ENABLE_SJPEG=OFF
+ -DJPEGXL_ENABLE_OPENEXR=OFF
+ -DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF
+ -DJPEGXL_ENABLE_TOOLS=OFF
+ -DJPEGXL_ENABLE_BENCHMARK=OFF
+ -DJPEGXL_ENABLE_JPEGLI=OFF
+ -DJPEGXL_ENABLE_DEVTOOLS=OFF
+ -DJPEGXL_FORCE_SYSTEM_HWY=ON
+ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON
+ -DJPEGXL_FORCE_SYSTEM_LCMS2=ON
+ -DJPEGXL_ENABLE_SKCMS=OFF
+ -DBUILD_SHARED_LIBS=OFF
+ # -DJPEGXL_ENABLE_JPEGLI=OFF
+ # -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=ON
+ # -DJPEGXL_INSTALL_JPEGLI_LIBJPEG=ON
+
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/little-cms.cmake b/library/src/main/cpp/cmake/little-cms.cmake
new file mode 100644
index 0000000..75197f1
--- /dev/null
+++ b/library/src/main/cpp/cmake/little-cms.cmake
@@ -0,0 +1,20 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_little-cms
+ GIT_REPOSITORY https://github.com/mm2/Little-CMS
+ GIT_TAG lcms2.16
+ # DOWNLOAD_COMMAND git clone https://github.com/mm2/Little-CMS --branch lcms2.16 --depth 1 ep_lcms || true
+ CONFIGURE_COMMAND
+ /autogen.sh ${EP_AUTOTOOLS_ARGS}
+ BUILD_COMMAND
+ ${Make_EXECUTABLE} all
+ INSTALL_COMMAND
+ ${Make_EXECUTABLE} install
+
+ # autogen.sh for example calls `aclocal -I m4` which only works in the source directory
+ BUILD_IN_SOURCE true
+
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+ GIT_SHALLOW true
+)
diff --git a/library/src/main/cpp/cmake/patches/heif-require-webp.patch b/library/src/main/cpp/cmake/patches/heif-require-webp.patch
new file mode 100644
index 0000000..dd23fb0
--- /dev/null
+++ b/library/src/main/cpp/cmake/patches/heif-require-webp.patch
@@ -0,0 +1,12 @@
+diff --git a/heifio/CMakeLists.txt b/heifio/CMakeLists.txt
+index 920eeca5..663b5fdb 100644
+--- a/heifio/CMakeLists.txt
++++ b/heifio/CMakeLists.txt
+@@ -25,6 +25,7 @@ target_compile_definitions(heifio
+ LIBHEIF_EXPORTS
+ HAVE_VISIBILITY)
+
++find_package(WebP)
+ find_package(TIFF)
+ if (TIFF_FOUND)
+ target_sources(heifio PRIVATE decoder_tiff.cc decoder_tiff.h encoder_tiff.h encoder_tiff.cc)
diff --git a/library/src/main/cpp/cmake/patches/tiff-0001-cmake--Replace-CMath--CMath-with-direct-link-to-avoid-export-of-target.patch b/library/src/main/cpp/cmake/patches/tiff-0001-cmake--Replace-CMath--CMath-with-direct-link-to-avoid-export-of-target.patch
new file mode 100644
index 0000000..e1be5f3
--- /dev/null
+++ b/library/src/main/cpp/cmake/patches/tiff-0001-cmake--Replace-CMath--CMath-with-direct-link-to-avoid-export-of-target.patch
@@ -0,0 +1,87 @@
+From 67f73084ca824e6c2465c47a5b67b16b5beca569 Mon Sep 17 00:00:00 2001
+From: Roger Leigh
+Date: Thu, 14 Dec 2023 18:30:31 +0000
+Subject: [PATCH] cmake: Replace CMath::CMath with direct link to avoid export
+ of target
+
+Link with CMATH_LIBRARIES instead of CMath::CMath. While this
+will still be exported, it will be available on the host system.
+---
+ cmake/FindCMath.cmake | 12 +++---------
+ contrib/dbs/CMakeLists.txt | 10 ++++++++--
+ libtiff/CMakeLists.txt | 6 +++---
+ tools/unsupported/CMakeLists.txt | 5 ++++-
+ 4 files changed, 18 insertions(+), 15 deletions(-)
+
+diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake
+index ad922180b..c4833af7e 100644
+--- a/cmake/FindCMath.cmake
++++ b/cmake/FindCMath.cmake
+@@ -55,18 +55,12 @@ if(CMath_FOUND)
+ set(CMath_INCLUDE_DIRS)
+ endif()
+ if(NOT CMath_LIBRARIES)
++ if(NOT CMath_LIBRARY)
++ unset(CMath_LIBRARY)
++ endif()
+ if (CMath_LIBRARY)
+ set(CMath_LIBRARIES ${CMath_LIBRARY})
+ endif()
+ endif()
+
+- if(NOT TARGET CMath::CMath)
+- if(CMath_LIBRARIES)
+- add_library(CMath::CMath UNKNOWN IMPORTED)
+- set_target_properties(CMath::CMath PROPERTIES
+- IMPORTED_LOCATION "${CMath_LIBRARY}")
+- else()
+- add_library(CMath::CMath INTERFACE IMPORTED)
+- endif()
+- endif()
+ endif()
+diff --git a/contrib/dbs/CMakeLists.txt b/contrib/dbs/CMakeLists.txt
+index 74b2a02a0..18b7ea8b4 100644
+--- a/contrib/dbs/CMakeLists.txt
++++ b/contrib/dbs/CMakeLists.txt
+@@ -26,13 +26,19 @@ add_executable(tiff-bi tiff-bi.c)
+ target_link_libraries(tiff-bi tiff tiff_port)
+
+ add_executable(tiff-grayscale tiff-grayscale.c)
+-target_link_libraries(tiff-grayscale tiff tiff_port CMath::CMath)
++target_link_libraries(tiff-grayscale PRIVATE tiff tiff_port)
++if(CMath_LIBRARIES)
++ target_link_libraries(tiff-grayscale PRIVATE ${CMath_LIBRARIES})
++endif()
+
+ add_executable(tiff-palette tiff-palette.c)
+ target_link_libraries(tiff-palette tiff tiff_port)
+
+ add_executable(tiff-rgb tiff-rgb.c)
+-target_link_libraries(tiff-rgb tiff tiff_port CMath::CMath)
++target_link_libraries(tiff-rgb PRIVATE tiff tiff_port)
++if(CMath_LIBRARIES)
++ target_link_libraries(tiff-rgb PRIVATE ${CMath_LIBRARIES})
++endif()
+
+ if(WEBP_SUPPORT AND EMSCRIPTEN)
+ # Emscripten is pretty finnicky about linker flags.
+diff --git a/libtiff/CMakeLists.txt b/libtiff/CMakeLists.txt
+index a8aa0c320..a65f3c230 100755
+--- a/libtiff/CMakeLists.txt
++++ b/libtiff/CMakeLists.txt
+@@ -184,9 +184,9 @@ if(WEBP_SUPPORT)
+ target_link_libraries(tiff PRIVATE WebP::webp)
+ string(APPEND tiff_requires_private " libwebp")
+ endif()
+-if(CMath_LIBRARY)
+- target_link_libraries(tiff PRIVATE CMath::CMath)
+- list(APPEND tiff_libs_private_list "${CMath_LIBRARY}")
++if(CMath_LIBRARIES)
++ target_link_libraries(tiff PRIVATE ${CMath_LIBRARIES})
++ list(APPEND tiff_libs_private_list "${CMath_LIBRARIES}")
+ endif()
+
+ set(tiff_libs_private_list "${tiff_libs_private_list}" PARENT_SCOPE)
+--
+GitLab
+
diff --git a/library/src/main/cpp/cmake/patches/vips-disable-unneded-targets.patch b/library/src/main/cpp/cmake/patches/vips-disable-unneded-targets.patch
new file mode 100644
index 0000000..0d3a351
--- /dev/null
+++ b/library/src/main/cpp/cmake/patches/vips-disable-unneded-targets.patch
@@ -0,0 +1,15 @@
+diff --git a/meson.build b/meson.build
+index d0c08e872..9ed8bfa7b 100644
+--- a/meson.build
++++ b/meson.build
+@@ -812,10 +812,3 @@ endif
+ if get_option('cplusplus')
+ subdir('cplusplus')
+ endif
+-
+-# these lines removed by a regexp for oss-fuzz builds, don't touch!
+-subdir('man')
+-subdir('po')
+-subdir('tools')
+-subdir('test')
+-subdir('fuzz')
diff --git a/library/src/main/cpp/cmake/spng.cmake b/library/src/main/cpp/cmake/spng.cmake
new file mode 100644
index 0000000..e848f41
--- /dev/null
+++ b/library/src/main/cpp/cmake/spng.cmake
@@ -0,0 +1,12 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_spng
+ GIT_REPOSITORY https://github.com/randy408/libspng.git
+ GIT_TAG v0.7.4
+ DEPENDS ep_zlib
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DZLIB_ROOT:STRING=${CMAKE_BINARY_DIR}/fakeroot
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/tiff.cmake b/library/src/main/cpp/cmake/tiff.cmake
new file mode 100644
index 0000000..b30ad1f
--- /dev/null
+++ b/library/src/main/cpp/cmake/tiff.cmake
@@ -0,0 +1,22 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_tiff
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE
+ URL http://download.osgeo.org/libtiff/tiff-4.7.0.tar.gz
+ DEPENDS ep_zlib ep_webp ep_jpeg-turbo
+ PATCH_COMMAND patch -p 1 < ${CMAKE_CURRENT_LIST_DIR}/patches/tiff-0001-cmake--Replace-CMath--CMath-with-direct-link-to-avoid-export-of-target.patch || true
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -Djbig=OFF
+ -Dlzma=OFF
+ -Dlerc=OFF
+ -Dlibdeflate=OFF
+ -Dcxx=OFF
+ -Djpeg=ON
+ -Dtiff-tools=OFF
+ -Dtiff-tests=OFF
+ -Dtiff-contrib=OFF
+ -Dtiff-docs=OFF
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/vips.cmake b/library/src/main/cpp/cmake/vips.cmake
new file mode 100644
index 0000000..9e43a07
--- /dev/null
+++ b/library/src/main/cpp/cmake/vips.cmake
@@ -0,0 +1,56 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_vips
+ GIT_REPOSITORY https://github.com/libvips/libvips.git
+ #GIT_TAG v8.15.3
+ GIT_TAG 50caa1922fa88e02c8cbfb3192fe8282d7da6eee
+ DEPENDS ep_expat ep_glib ep_heif ep_highway ep_jxl ep_spng ep_webp ep_tiff ep_jpeg-turbo ep_little-cms
+ PATCH_COMMAND patch -p 1 < ${CMAKE_CURRENT_LIST_DIR}/patches/vips-disable-unneded-targets.patch || true
+ CONFIGURE_COMMAND
+ ${Meson_EXECUTABLE} setup ${EP_MESON_ARGS}
+ -Ddeprecated=false
+ -Dexamples=false
+ -Dcplusplus=true
+ -Ddoxygen=false
+ -Dgtk_doc=false
+ -Dmodules=disabled
+ -Dintrospection=disabled
+ -Dvapi=false
+ -Dcfitsio=disabled
+ -Dcgif=enabled
+ -Dexif=disabled
+ -Dfftw=disabled
+ -Dfontconfig=disabled
+ -Darchive=disabled
+ -Dheif=enabled
+ -Dheif-module=disabled
+ -Dimagequant=disabled
+ -Djpeg=enabled
+ -Djpeg-xl=enabled
+ -Djpeg-xl-module=disabled
+ -Dlcms=enabled
+ -Dmagick=disabled
+ -Dmatio=disabled
+ -Dnifti=disabled
+ -Dopenexr=disabled
+ -Dopenjpeg=disabled
+ -Dopenslide=disabled
+ -Dhighway=enabled
+ -Dorc=disabled
+ -Dpangocairo=disabled
+ -Dpdfium=disabled
+ -Dpng=disabled
+ -Dpoppler=disabled
+ -Dquantizr=disabled
+ -Drsvg=disabled
+ -Dspng=enabled
+ -Dtiff=enabled
+ -Dwebp=enabled
+
+ BUILD_COMMAND
+ ${Ninja_EXECUTABLE} -C
+ INSTALL_COMMAND
+ ${Ninja_EXECUTABLE} -C install
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/webp.cmake b/library/src/main/cpp/cmake/webp.cmake
new file mode 100644
index 0000000..c86f8bc
--- /dev/null
+++ b/library/src/main/cpp/cmake/webp.cmake
@@ -0,0 +1,21 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_webp
+ GIT_REPOSITORY https://chromium.googlesource.com/webm/libwebp
+ GIT_TAG 1.4.0
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DWEBP_BUILD_ANIM_UTILS=OFF
+ -DWEBP_BUILD_CWEBP=OFF
+ -DWEBP_BUILD_DWEBP=OFF
+ -DWEBP_BUILD_GIF2WEBP=OFF
+ -DWEBP_BUILD_IMG2WEBP=OFF
+ -DWEBP_BUILD_VWEBP=OFF
+ -DWEBP_BUILD_WEBPINFO=OFF
+ -DWEBP_BUILD_WEBPMUX=OFF
+ -DWEBP_BUILD_EXTRAS=OFF
+ -DWEBP_BUILD_WEBP_JS=OFF
+ -DWEBP_ENABLE_SWAP_16BIT_CSP=ON
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/cmake/zlib.cmake b/library/src/main/cpp/cmake/zlib.cmake
new file mode 100644
index 0000000..2430a10
--- /dev/null
+++ b/library/src/main/cpp/cmake/zlib.cmake
@@ -0,0 +1,14 @@
+include(ExternalProject)
+
+ExternalProject_Add(ep_zlib
+ GIT_REPOSITORY https://github.com/zlib-ng/zlib-ng.git
+ GIT_TAG 2.2.1
+ UPDATE_DISCONNECTED True
+ CMAKE_ARGS
+ ${EP_CMAKE_ARGS}
+ -DINSTALL_PKGCONFIG_DIR=${THIRD_PARTY_LIB_PATH}/lib/pkgconfig
+ -DZLIB_COMPAT=ON
+ -DZLIB_ENABLE_TESTS=OFF
+ USES_TERMINAL_DOWNLOAD true
+ USES_TERMINAL_BUILD true
+)
diff --git a/library/src/main/cpp/decoder_base.h b/library/src/main/cpp/decoder_base.h
deleted file mode 100644
index ebf1c02..0000000
--- a/library/src/main/cpp/decoder_base.h
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// Created by len on 23/12/20.
-//
-
-#ifndef IMAGEDECODER_DECODER_BASE_H
-#define IMAGEDECODER_DECODER_BASE_H
-
-#include "borders.h"
-#include "java_stream.h"
-#include
-#include
-
-struct ImageInfo {
- uint32_t imageWidth;
- uint32_t imageHeight;
- bool isAnimated;
- Rect bounds;
-};
-
-class BaseDecoder {
-public:
- BaseDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile) {
- this->stream = std::move(stream);
- this->cropBorders = cropBorders;
- this->targetProfile = targetProfile;
- }
- virtual ~BaseDecoder() {
- if (transform) {
- cmsDeleteTransform(transform);
- }
- if (targetProfile) {
- cmsCloseProfile(targetProfile);
- }
- };
-
- virtual void decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) = 0;
-
-protected:
- std::shared_ptr stream;
-
-public:
- bool cropBorders;
- cmsHPROFILE targetProfile = nullptr;
- ImageInfo info;
- cmsHTRANSFORM transform = nullptr;
- bool useTransform = false;
- cmsUInt32Number inType;
-};
-
-#endif // IMAGEDECODER_DECODER_BASE_H
diff --git a/library/src/main/cpp/decoder_heif.cpp b/library/src/main/cpp/decoder_heif.cpp
deleted file mode 100644
index e3f6028..0000000
--- a/library/src/main/cpp/decoder_heif.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-//
-// Created by len on 27/5/21.
-//
-
-#include "decoder_heif.h"
-#include "row_convert.h"
-
-bool is_libheif_compatible(const uint8_t* bytes, uint32_t size) {
- //reject small invalid files that cause heif_check_filetype to return heif_filetype_maybe
- if (size < 12) {
- return false;
- }
-
- auto result = heif_check_filetype(bytes, size);
- return (result != heif_filetype_no) && (result != heif_filetype_yes_unsupported);
-}
-
-auto init_heif_context(Stream* stream) {
- auto ctx = heif::Context();
- ctx.read_from_memory_without_copy(stream->bytes, stream->size);
- return ctx;
-}
-
-HeifDecoder::HeifDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile)
- : BaseDecoder(std::move(stream), cropBorders, targetProfile) {
- this->info = parseInfo();
-}
-
-ImageInfo HeifDecoder::parseInfo() {
- auto ctx = init_heif_context(stream.get());
- auto handle = ctx.get_primary_image_handle();
-
- uint32_t imageWidth = handle.get_width();
- uint32_t imageHeight = handle.get_height();
- Rect bounds = {.x = 0, .y = 0, .width = imageWidth, .height = imageHeight};
- if (cropBorders) {
- try {
- auto img =
- handle.decode_image(heif_colorspace_YCbCr, heif_chroma_undefined);
- auto pixels = img.get_plane(heif_channel_Y, nullptr);
-
- bounds = findBorders(pixels, imageWidth, imageHeight);
- } catch (std::exception& ex) {
- LOGW("Couldn't crop borders on a HEIF/AVIF image of size %dx%d",
- imageWidth, imageHeight);
- } catch (heif::Error& error) {
- throw std::runtime_error(error.get_message());
- }
- }
-
- return ImageInfo{
- .imageWidth = imageWidth,
- .imageHeight = imageHeight,
- .isAnimated = false,
- .bounds = bounds,
- };
-}
-
-cmsHPROFILE HeifDecoder::getColorProfile(heif::ImageHandle handle) {
- auto im_handle = handle.get_raw_image_handle();
- size_t icc_size = heif_image_handle_get_raw_color_profile_size(im_handle);
- if (icc_size == 0) {
- return nullptr;
- }
- std::vector icc_profile(icc_size);
- heif_image_handle_get_raw_color_profile(im_handle, icc_profile.data());
-
- cmsHPROFILE src_profile = cmsOpenProfileFromMem(icc_profile.data(), icc_size);
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
- int chromabits = handle.get_chroma_bits_per_pixel();
-
- if (profileSpace != cmsSigRgbData && profileSpace != cmsSigGrayData) {
- cmsCloseProfile(src_profile);
- return nullptr;
- }
-
- return src_profile;
-}
-
-void HeifDecoder::decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) {
- // Decode full image (regions, subsamples or row by row are not supported
- // sadly)
- heif::Image img;
- try {
- auto ctx = init_heif_context(stream.get());
- auto handle = ctx.get_primary_image_handle();
-
- cmsHPROFILE src_profile = getColorProfile(handle);
- if (!src_profile) {
- src_profile = cmsCreate_sRGBProfile(); // assume sRGB
- }
-
- useTransform = true;
-
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
-
- if (profileSpace == cmsSigRgbData) {
- inType = TYPE_RGBA_8;
- } else {
- if (handle.has_alpha_channel()) {
- inType = TYPE_GRAYA_8;
- } else {
- inType = TYPE_GRAY_8;
- }
- }
-
- img =
- handle.decode_image(heif_colorspace_RGB, heif_chroma_interleaved_RGBA);
-
- transform =
- cmsCreateTransform(src_profile, inType, targetProfile, TYPE_RGBA_8,
- cmsGetHeaderRenderingIntent(src_profile),
- inType != TYPE_GRAY_8 ? cmsFLAGS_COPY_ALPHA : 0);
-
- cmsCloseProfile(src_profile);
- } catch (heif::Error& error) {
- throw std::runtime_error(error.get_message());
- }
-
- int stride;
- uint8_t* inPixels = img.get_plane(heif_channel_interleaved, &stride);
-
- // Calculate stride of the decoded image with the requested region
- uint32_t inStride = stride;
- uint32_t inStrideOffset = inRect.x * (inStride / info.imageWidth);
- auto inPixelsPos = inPixels + inStride * inRect.y;
-
- // Calculate output stride
- uint32_t outStride = outRect.width * 4;
- uint8_t* outPixelsPos = outPixels;
-
- if (sampleSize == 1) {
- for (uint32_t i = 0; i < outRect.height; i++) {
- // Apply row conversion function to the following row
- memcpy(outPixelsPos, inPixelsPos + inStrideOffset, outStride);
-
- // Shift row to read and write
- inPixelsPos += inStride;
- outPixelsPos += outStride;
- }
- } else {
- // Calculate the number of rows to discard
- uint32_t skipStart = (sampleSize - 2) / 2;
- uint32_t skipEnd = sampleSize - 2 - skipStart;
-
- for (uint32_t i = 0; i < outRect.height; ++i) {
- // Skip starting rows
- inPixelsPos += inStride * skipStart;
-
- // Apply row conversion function to the following two rows
- RGBA8888_to_RGBA8888_row(outPixelsPos, inPixelsPos + inStrideOffset,
- inPixelsPos + inStrideOffset + inStride,
- outRect.width, sampleSize);
-
- // Shift row to read to the next 2 rows (the ones we've just read) + the
- // skipped end rows
- inPixelsPos += inStride * (2 + skipEnd);
-
- // Shift row to write
- outPixelsPos += outStride;
- }
- }
-
- if (inType == TYPE_GRAY_8) {
- for (uint32_t i = 0; i < outRect.width * outRect.height; i++) {
- outPixels[i] = outPixels[i * 4];
- }
- }
-
- if (inType == TYPE_GRAYA_8) {
- for (uint32_t i = 0; i < outRect.width * outRect.height; i++) {
- outPixels[i * 2 + 0] = outPixels[i * 4 + 0];
- outPixels[i * 2 + 1] = outPixels[i * 4 + 3];
- }
- }
-}
diff --git a/library/src/main/cpp/decoder_heif.h b/library/src/main/cpp/decoder_heif.h
deleted file mode 100644
index 0a14d0c..0000000
--- a/library/src/main/cpp/decoder_heif.h
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// Created by len on 27/5/21.
-//
-
-#ifndef IMAGEDECODER_DECODER_HEIF_H
-#define IMAGEDECODER_DECODER_HEIF_H
-
-#include "decoder_base.h"
-#include
-#include
-
-bool is_libheif_compatible(const uint8_t* bytes, uint32_t size);
-
-class HeifDecoder : public BaseDecoder {
-public:
- HeifDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile);
-
- void decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize);
-
-private:
- ImageInfo parseInfo();
- cmsHPROFILE getColorProfile(heif::ImageHandle handle);
-};
-
-#endif // IMAGEDECODER_DECODER_HEIF_H
diff --git a/library/src/main/cpp/decoder_jpeg.cpp b/library/src/main/cpp/decoder_jpeg.cpp
deleted file mode 100644
index c08b12b..0000000
--- a/library/src/main/cpp/decoder_jpeg.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-//
-// Created by len on 23/12/20.
-//
-
-#include "decoder_jpeg.h"
-#include "cmyk.h"
-#include "log.h"
-#include "row_convert.h"
-#include
-
-JpegDecoder::JpegDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile)
- : BaseDecoder(std::move(stream), cropBorders, targetProfile) {
- this->info = parseInfo();
-}
-
-JpegDecodeSession::JpegDecodeSession()
- : jinfo(jpeg_decompress_struct{}), jerr(jpeg_error_mgr{}) {
- jinfo.err = jpeg_std_error(&jerr);
- jerr.error_exit = [](j_common_ptr info) {
- char jpegLastErrorMsg[JMSG_LENGTH_MAX];
- (*(info->err->format_message))(info, jpegLastErrorMsg);
- throw std::runtime_error(jpegLastErrorMsg);
- };
-}
-
-void JpegDecodeSession::init(Stream* stream) {
- jpeg_create_decompress(&jinfo);
- jpeg_mem_src(&jinfo, stream->bytes, stream->size);
- jpeg_save_markers(&jinfo, JPEG_APP0 + 2, 0xFFFF);
- jpeg_read_header(&jinfo, true);
-}
-
-JpegDecodeSession::~JpegDecodeSession() { jpeg_destroy_decompress(&jinfo); }
-
-std::unique_ptr JpegDecoder::initDecodeSession() {
- auto session = std::make_unique();
- session->init(stream.get());
- return session;
-}
-
-ImageInfo JpegDecoder::parseInfo() {
- auto session = initDecodeSession();
- auto jinfo = session->jinfo;
-
- uint32_t imageWidth = jinfo.image_width;
- uint32_t imageHeight = jinfo.image_height;
-
- Rect bounds = {.x = 0, .y = 0, .width = imageWidth, .height = imageHeight};
- if (cropBorders) {
- try {
- auto pixels = std::vector(imageWidth * imageHeight);
-
- jinfo.out_color_space = JCS_GRAYSCALE;
- jpeg_start_decompress(&jinfo);
-
- uint8_t* pixelsPtr = pixels.data();
- while (jinfo.output_scanline < jinfo.output_height) {
- uint8_t* offset = pixelsPtr + jinfo.output_scanline * imageWidth;
- jpeg_read_scanlines(&jinfo, &offset, 1);
- }
- jpeg_finish_decompress(&jinfo);
- bounds = findBorders(pixels.data(), imageWidth, imageHeight);
- } catch (std::exception& ex) {
- LOGW("Couldn't crop borders on a JPEG image of size %dx%d", imageWidth,
- imageHeight);
- }
- }
-
- return ImageInfo{
- .imageWidth = jinfo.image_width,
- .imageHeight = jinfo.image_height,
- .isAnimated = false,
- .bounds = bounds,
- };
-}
-
-cmsHPROFILE JpegDecoder::getColorProfile(jpeg_decompress_struct* jinfo) {
- JOCTET* icc_data;
- unsigned int icc_size;
- if (!jpeg_read_icc_profile(jinfo, &icc_data, &icc_size)) {
- return nullptr;
- }
- cmsHPROFILE src_profile = cmsOpenProfileFromMem(icc_data, icc_size);
- free(icc_data);
-
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
-
- auto colorspace = jinfo->jpeg_color_space;
-
- if ((colorspace == JCS_GRAYSCALE && profileSpace != cmsSigGrayData) ||
- (colorspace == JCS_CMYK && profileSpace != cmsSigCmykData) ||
- (colorspace == JCS_YCCK && profileSpace != cmsSigCmykData) ||
- (colorspace == JCS_RGB && profileSpace != cmsSigRgbData) ||
- (colorspace == JCS_YCbCr && profileSpace != cmsSigRgbData)) {
- cmsCloseProfile(src_profile);
- return nullptr;
- }
-
- return src_profile;
-}
-
-void JpegDecoder::decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) {
- auto session = initDecodeSession();
- auto* jinfo = &session->jinfo;
-
- if (jinfo->jpeg_color_space == JCS_CMYK ||
- jinfo->jpeg_color_space == JCS_YCCK) {
- // libjpeg can convert YCCK to CMYK
- inType = TYPE_CMYK_8;
- } else if (jinfo->jpeg_color_space == JCS_GRAYSCALE) {
- inType = TYPE_GRAY_8;
- } else {
- inType = TYPE_RGBA_8;
- }
-
- cmsHPROFILE src_profile = getColorProfile(jinfo);
- if (!src_profile) {
- if (inType == TYPE_CMYK_8) {
- src_profile = cmsOpenProfileFromMem(CMYK_USWebCoatedSWOP_icc,
- CMYK_USWebCoatedSWOP_icc_len);
- } else {
- inType = TYPE_RGBA_8;
- src_profile = cmsCreate_sRGBProfile();
- }
- }
-
- if (inType == TYPE_CMYK_8 && jinfo->saw_Adobe_marker) {
- inType = TYPE_CMYK_8_REV;
- }
-
- useTransform = true;
-
- transform =
- cmsCreateTransform(src_profile, inType, targetProfile, TYPE_RGBA_8,
- cmsGetHeaderRenderingIntent(src_profile),
- inType == TYPE_RGBA_8 ? cmsFLAGS_COPY_ALPHA : 0);
-
- cmsCloseProfile(src_profile);
-
- jinfo->out_color_space = inType == TYPE_GRAY_8 ? JCS_GRAYSCALE
- : inType == TYPE_CMYK_8 ? JCS_CMYK
- : inType == TYPE_CMYK_8_REV ? JCS_CMYK
- : JCS_EXT_RGBA;
-
- // 8 is the maximum supported scale by libjpeg, so we'll use custom logic for
- // further samples.
- jinfo->scale_denom = std::min(sampleSize, 8u);
- uint32_t customSample = sampleSize <= 8 ? 0 : sampleSize / 8;
- Rect decRect = customSample == 0 ? outRect : outRect.upsample(customSample);
-
- // libjpeg X axis need to be a multiple of the defined DCT. We need to know
- // these values in order to crop the unwanted region of the final image.
- uint32_t decX = decRect.x;
- uint32_t decWidth = decRect.width;
-
- // Init decoder and set regions.
- jpeg_start_decompress(jinfo);
- jpeg_crop_scanline(jinfo, &decX, &decWidth);
- jpeg_skip_scanlines(jinfo, decRect.y);
-
- // Now that we know the decoding width, we can calculate the row stride.
- uint32_t inComponents = jinfo->out_color_space == JCS_GRAYSCALE ? 1 : 4;
- uint32_t inStride = decWidth * inComponents;
- uint32_t inStrideOffset = (decRect.x - decX) * inComponents;
-
- // Allocate row and get pointers to the row and the row aligned for output
- // image.
- auto inRow = std::vector(inStride);
- uint8_t* inRowPtr = inRow.data();
- uint8_t* inRowPtrAligned = inRowPtr + inStrideOffset;
-
- // Save a pointer to the output image and calculate the output stride.
- uint8_t* outPixelsPos = outPixels;
- uint32_t outStride = outRect.width * inComponents;
-
- if (customSample == 0) {
- // No custom sampler needed, just decode and copy to destination.
- for (uint32_t i = 0; i < outRect.height; i++) {
- jpeg_read_scanlines(jinfo, &inRowPtr, 1);
-
- memcpy(outPixelsPos, inRowPtrAligned, outStride);
-
- outPixelsPos += outStride;
- }
- } else {
- // Custom sampler needed, we need to decode two rows and downsample them.
- // Allocate second row and get pointers to the row and the row aligned for
- // output image.
- auto inRow2 = std::vector(inStride);
- uint8_t* inRow2Ptr = inRow2.data();
- uint8_t* inRow2PtrAligned = inRow2Ptr + inStrideOffset;
-
- // We'll only decode the middle rows, so skip the other ones
- uint32_t skipStart = (customSample - 2) / 2;
- uint32_t skipEnd = customSample - 2 - skipStart;
-
- auto rowFn = jinfo->out_color_space == JCS_GRAYSCALE
- ? &GRAY8_to_GRAY8_row
- : &RGBA8888_to_RGBA8888_row;
-
- for (uint32_t i = 0; i < outRect.height; i++) {
- jpeg_skip_scanlines(jinfo, skipStart);
-
- jpeg_read_scanlines(jinfo, &inRowPtr, 1);
- jpeg_read_scanlines(jinfo, &inRow2Ptr, 1);
-
- rowFn(outPixelsPos, inRowPtrAligned, inRow2PtrAligned, outRect.width,
- customSample);
- outPixelsPos += outStride;
-
- jpeg_skip_scanlines(jinfo, skipEnd);
- }
- }
-
- // Call abort instead of finish to allow finishing before the last scanline.
- jpeg_abort_decompress(jinfo);
-}
diff --git a/library/src/main/cpp/decoder_jpeg.h b/library/src/main/cpp/decoder_jpeg.h
deleted file mode 100644
index eb484ef..0000000
--- a/library/src/main/cpp/decoder_jpeg.h
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Created by len on 23/12/20.
-//
-
-#ifndef IMAGEDECODER_DECODER_JPEG_H
-#define IMAGEDECODER_DECODER_JPEG_H
-
-#include "decoder_base.h"
-#include "jpeglib.h"
-#include "log.h"
-#include
-#include
-
-// Wrap the JPEG C API in this class to automatically manage memory
-class JpegDecodeSession {
-public:
- JpegDecodeSession();
- ~JpegDecodeSession();
-
- jpeg_decompress_struct jinfo;
- jpeg_error_mgr jerr;
-
- void init(Stream* stream);
-};
-
-class JpegDecoder : public BaseDecoder {
-public:
- JpegDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile);
-
- void decode(uint8_t* outPixels, Rect outRect, Rect srcRegion,
- uint32_t sampleSize);
-
-private:
- ImageInfo parseInfo();
- std::unique_ptr initDecodeSession();
- cmsHPROFILE getColorProfile(jpeg_decompress_struct* jinfo);
-};
-
-#endif // IMAGEDECODER_DECODER_JPEG_H
diff --git a/library/src/main/cpp/decoder_jxl.cpp b/library/src/main/cpp/decoder_jxl.cpp
deleted file mode 100644
index 169424a..0000000
--- a/library/src/main/cpp/decoder_jxl.cpp
+++ /dev/null
@@ -1,245 +0,0 @@
-//
-// Created by w on 02/07/21.
-//
-
-#include "decoder_jxl.h"
-#include "row_convert.h"
-
-JpegxlDecoder::JpegxlDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile)
- : BaseDecoder(std::move(stream), cropBorders, targetProfile),
- mSrcProfile(nullptr) {
- this->info = parseInfo();
-}
-
-void JpegxlDecoder::decode() {
- if (!pixels.empty()) {
- return;
- }
-
- auto runner = JxlResizableParallelRunnerMake(nullptr);
-
- auto dec = JxlDecoderMake(nullptr);
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
- JXL_DEC_COLOR_ENCODING |
- JXL_DEC_FULL_IMAGE)) {
- throw std::runtime_error("JxlDecoderSubscribeEvents failed");
- }
-
- if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
- JxlResizableParallelRunner,
- runner.get())) {
- throw std::runtime_error("JxlDecoderSetParallelRunner failed");
- }
-
- JxlDecoderSetInput(dec.get(), stream->bytes, stream->size);
- JxlDecoderCloseInput(dec.get());
-
- JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
- for (;;) {
- JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
-
- if (status == JXL_DEC_ERROR) {
- throw std::runtime_error("Decoder error");
- } else if (status == JXL_DEC_NEED_MORE_INPUT) {
- throw std::runtime_error("Error, already provided all input");
- } else if (status == JXL_DEC_BASIC_INFO) {
- if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &jxl_info)) {
- throw std::runtime_error("JxlDecoderGetBasicInfo failed");
- }
- JxlResizableParallelRunnerSetThreads(
- runner.get(), JxlResizableParallelRunnerSuggestThreads(
- jxl_info.xsize, jxl_info.ysize));
- } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
- size_t buffer_size;
- if (JXL_DEC_SUCCESS !=
- JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
- throw std::runtime_error("JxlDecoderImageOutBufferSize failed");
- }
-
- pixels.resize(buffer_size);
- if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
- pixels.data(),
- buffer_size)) {
- throw std::runtime_error("JxlDecoderSetImageOutBuffer failed");
- }
- } else if (status == JXL_DEC_COLOR_ENCODING) {
- // libjxl built-in color management, not fully available
- /*
- JxlDecoderSetCms(dec.get(), *JxlGetDefaultCms());
- cmsUInt32Number profileSize;
- cmsSaveProfileToMem(targetProfile, NULL, &profileSize);
- std::vector profile(profileSize);
- cmsSaveProfileToMem(targetProfile, profile.data(), &profileSize);
-
- if (JXL_DEC_SUCCESS != JxlDecoderSetOutputColorProfile(dec.get(), NULL,
- profile.data(),
- profileSize)) {
- throw std::runtime_error("JxlDecoderSetOutputColorProfile failed");
- }*/
-
- size_t size = 0;
- if (JXL_DEC_SUCCESS !=
- JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
- &size)) {
- throw std::runtime_error("JxlDecoderGetICCProfileSize failed");
- }
-
- std::vector icc_profile(size);
- if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
- dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
- icc_profile.data(), size)) {
- throw std::runtime_error("JxlDecoderGetColorAsICCProfile failed");
- }
-
- mSrcProfile = cmsOpenProfileFromMem(icc_profile.data(), size);
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(mSrcProfile);
-
- if (jxl_info.num_color_channels == 3 && profileSpace != cmsSigRgbData ||
- jxl_info.num_color_channels == 1 && profileSpace != cmsSigGrayData) {
- cmsCloseProfile(mSrcProfile);
- mSrcProfile = nullptr;
- }
- } else if (status == JXL_DEC_FULL_IMAGE) {
- break;
- } else if (status == JXL_DEC_SUCCESS) {
- break;
- } else {
- LOGW("Unexpected decoder status");
- break;
- }
- }
-}
-
-ImageInfo JpegxlDecoder::parseInfo() {
- decode();
-
- Rect bounds;
- if (cropBorders) {
- std::vector gray_pixels(jxl_info.xsize * jxl_info.ysize);
- uint8_t* gray_buffer = gray_pixels.data();
- if (jxl_info.num_color_channels == 3) {
- for (int x = 0; x < pixels.size(); x += 4) {
- uint8_t r = pixels[x + 0];
- uint8_t g = pixels[x + 1];
- uint8_t b = pixels[x + 2];
- int gray = (r * 0.2126 + g * 0.7152 + b * 0.0722) + 0.5;
- if (gray > 255) {
- gray = 255;
- }
- gray_buffer[x / 4] = (uint8_t)gray;
- }
- } else {
- for (int x = 0; x < pixels.size(); x += 4) {
- gray_buffer[x / 4] = pixels[x];
- }
- }
-
- bounds = findBorders(gray_buffer, jxl_info.xsize, jxl_info.ysize);
- } else {
- bounds = {
- .x = 0, .y = 0, .width = jxl_info.xsize, .height = jxl_info.ysize};
- }
-
- return ImageInfo{
- .imageWidth = jxl_info.xsize,
- .imageHeight = jxl_info.ysize,
- .isAnimated = false, // (bool)jxl_info.have_animation,
- .bounds = bounds,
- };
-}
-
-void JpegxlDecoder::decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) {
- decode();
-
- // Save transformed pixel data.
- if (!transformed) {
- uint8_t* buf = pixels.data();
-
- std::vector gray;
- if (jxl_info.num_color_channels == 3) {
- inType = TYPE_RGBA_8;
- } else if (mSrcProfile) {
- uint32_t num_pixels = jxl_info.xsize * jxl_info.ysize;
- if (jxl_info.alpha_bits > 0) {
- gray.resize(num_pixels * 2);
- for (int i = 0; i < num_pixels; i++) {
- gray[i * 2] = buf[i * 4];
- gray[i * 2 + 1] = buf[i * 4 + 3];
- }
- inType = TYPE_GRAYA_8;
- } else {
- gray.resize(num_pixels);
- for (int i = 0; i < num_pixels; i++) {
- gray[i] = buf[i * 4];
- }
- inType = TYPE_GRAY_8;
- }
- buf = gray.data();
- }
-
- if (!mSrcProfile) {
- inType = TYPE_RGBA_8;
- mSrcProfile = cmsCreate_sRGBProfile(); // assume sRGB, should never happen
- }
-
- transform =
- cmsCreateTransform(mSrcProfile, inType, targetProfile, TYPE_RGBA_8,
- cmsGetHeaderRenderingIntent(mSrcProfile),
- inType != TYPE_GRAY_8 ? cmsFLAGS_COPY_ALPHA : 0);
-
- cmsCloseProfile(mSrcProfile);
-
- cmsDoTransform(transform, buf, pixels.data(),
- jxl_info.xsize * jxl_info.ysize);
-
- cmsDeleteTransform(transform);
- transform = nullptr;
- transformed = true;
- }
-
- // Copied from decoder_heif.cpp
- uint32_t inStride = info.imageWidth * 4;
- uint32_t inStrideOffset = inRect.x * (inStride / info.imageWidth);
- auto inPixelsPos = (uint8_t*)pixels.data() + inStride * inRect.y;
-
- // Calculate output stride
- uint32_t outStride = outRect.width * 4;
- uint8_t* outPixelsPos = outPixels;
-
- // Set row conversion function
- auto rowFn = &RGBA8888_to_RGBA8888_row;
-
- if (sampleSize == 1) {
- for (uint32_t i = 0; i < outRect.height; i++) {
- // Apply row conversion function to the following row
- memcpy(outPixelsPos, inPixelsPos + inStrideOffset, outStride);
-
- // Shift row to read and write
- inPixelsPos += inStride;
- outPixelsPos += outStride;
- }
- } else {
- // Calculate the number of rows to discard
- uint32_t skipStart = (sampleSize - 2) / 2;
- uint32_t skipEnd = sampleSize - 2 - skipStart;
-
- for (uint32_t i = 0; i < outRect.height; ++i) {
- // Skip starting rows
- inPixelsPos += inStride * skipStart;
-
- // Apply row conversion function to the following two rows
- rowFn(outPixelsPos, inPixelsPos + inStrideOffset,
- inPixelsPos + inStrideOffset + inStride, outRect.width, sampleSize);
-
- // Shift row to read to the next 2 rows (the ones we've just read) + the
- // skipped end rows
- inPixelsPos += inStride * (2 + skipEnd);
-
- // Shift row to write
- outPixelsPos += outStride;
- }
- }
-}
diff --git a/library/src/main/cpp/decoder_jxl.h b/library/src/main/cpp/decoder_jxl.h
deleted file mode 100644
index 3d95485..0000000
--- a/library/src/main/cpp/decoder_jxl.h
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Created by w on 02/07/21.
-//
-
-#ifndef IMAGEDECODER_DECODER_JXL_H
-#define IMAGEDECODER_DECODER_JXL_H
-
-#include "decoder_base.h"
-#include
-#include
-#include
-#include
-
-class JpegxlDecoder : public BaseDecoder {
-public:
- JpegxlDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile);
-
- void decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize);
-
-private:
- void decode();
-
- ImageInfo parseInfo();
- std::vector pixels;
- JxlBasicInfo jxl_info;
- cmsHPROFILE mSrcProfile = nullptr;
- bool transformed = false;
-};
-
-#endif // IMAGEDECODER_DECODER_JXL_H
diff --git a/library/src/main/cpp/decoder_png.cpp b/library/src/main/cpp/decoder_png.cpp
deleted file mode 100644
index 4c64767..0000000
--- a/library/src/main/cpp/decoder_png.cpp
+++ /dev/null
@@ -1,301 +0,0 @@
-//
-// Created by len on 24/12/20.
-//
-
-#include "decoder_png.h"
-#include "row_convert.h"
-#include
-
-static void png_skip_rows(png_structrp png_ptr, png_uint_32 num_rows) {
- for (png_uint_32 i = 0; i < num_rows; ++i) {
- png_read_row(png_ptr, nullptr, nullptr);
- }
-}
-
-PngDecoder::PngDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile)
- : BaseDecoder(std::move(stream), cropBorders, targetProfile) {
- this->info = parseInfo();
-}
-
-PngDecodeSession::PngDecodeSession(Stream* stream)
- : png(nullptr), pinfo(nullptr),
- reader({.bytes = stream->bytes, .read = 0, .remain = stream->size}) {}
-
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "EndlessLoop"
-void PngDecodeSession::init() {
- auto errorFn = [](png_struct*, png_const_charp msg) {
- throw std::runtime_error(msg);
- };
- auto warnFn = [](png_struct*, png_const_charp msg) { LOGW("%s", msg); };
- png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorFn, warnFn);
- if (!png) {
- throw std::runtime_error("Failed to create png read struct");
- }
- pinfo = png_create_info_struct(png);
- if (!pinfo) {
- throw std::runtime_error("Failed to create png info struct");
- }
-
- auto readFn = [](png_struct* p, png_byte* data, png_size_t length) {
- auto* r = (PngReader*)png_get_io_ptr(p);
- uint32_t next = std::min(r->remain, (uint32_t)length);
- if (next > 0) {
- memcpy(data, r->bytes + r->read, next);
- r->read += next;
- r->remain -= next;
- }
- };
- png_set_read_fn(png, &reader, readFn);
- png_read_info(png, pinfo);
-}
-#pragma clang diagnostic pop
-
-PngDecodeSession::~PngDecodeSession() {
- png_destroy_read_struct(&png, &pinfo, nullptr);
-}
-
-std::unique_ptr PngDecoder::initDecodeSession() {
- auto session = std::make_unique(stream.get());
- session->init();
- return session;
-}
-
-ImageInfo PngDecoder::parseInfo() {
- auto session = initDecodeSession();
- auto png = session->png;
- auto pinfo = session->pinfo;
-
- uint32_t imageWidth = png_get_image_width(png, pinfo);
- uint32_t imageHeight = png_get_image_height(png, pinfo);
-
- Rect bounds = {.x = 0, .y = 0, .width = imageWidth, .height = imageHeight};
- if (cropBorders) {
- try {
- auto pixels = std::make_unique(imageWidth * imageHeight);
-
- uint8_t colorType = png_get_color_type(png, pinfo);
- uint8_t bitDepth = png_get_bit_depth(png, pinfo);
-
- png_set_expand(png);
- if (bitDepth == 16) {
- png_set_scale_16(png);
- }
- if (colorType & (uint8_t)PNG_COLOR_MASK_COLOR) {
- png_set_rgb_to_gray(png, 1, -1, -1);
- png_set_strip_alpha(png);
- } else if (colorType & (uint8_t)PNG_COLOR_MASK_ALPHA) {
- png_set_strip_alpha(png);
- }
-
- int32_t passes = png_set_interlace_handling(png);
-
- uint8_t* pixelsPos;
- while (--passes >= 0) {
- pixelsPos = pixels.get();
- for (uint32_t i = 0; i < imageHeight; ++i) {
- png_read_row(png, pixelsPos, nullptr);
- pixelsPos += imageWidth;
- }
- }
- bounds = findBorders(pixels.get(), imageWidth, imageHeight);
- } catch (std::bad_alloc& ex) {
- LOGW("Couldn't crop borders on a PNG image of size %dx%d", imageWidth,
- imageHeight);
- }
- }
-
- return ImageInfo{
- .imageWidth = imageWidth,
- .imageHeight = imageHeight,
- .isAnimated = false,
- .bounds = bounds,
- };
-}
-
-cmsHPROFILE PngDecoder::getColorProfile(png_struct* png, png_info* pinfo,
- uint8_t colorType) {
- if (!png_get_valid(png, pinfo, PNG_INFO_iCCP)) {
- return nullptr;
- }
-
- png_charp name;
- png_bytep icc_data;
- png_uint_32 icc_size;
- int comp_type;
- png_get_iCCP(png, pinfo, &name, &comp_type, &icc_data, &icc_size);
-
- cmsHPROFILE src_profile = cmsOpenProfileFromMem(icc_data, icc_size);
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
-
- bool rgb = colorType & PNG_COLOR_MASK_COLOR;
-
- if (rgb && profileSpace != cmsSigRgbData ||
- !rgb && profileSpace != cmsSigGrayData) {
- cmsCloseProfile(src_profile);
- return nullptr;
- }
-
- return src_profile;
-}
-
-void PngDecoder::decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) {
- auto session = initDecodeSession();
- auto png = session->png;
- auto pinfo = session->pinfo;
-
- uint8_t colorType = png_get_color_type(png, pinfo);
- uint8_t bitDepth = png_get_bit_depth(png, pinfo);
-
- png_set_expand(png);
-
- if (bitDepth == 16) {
- png_set_scale_16(png);
- }
-
- cmsHPROFILE src_profile = getColorProfile(png, pinfo, colorType);
- if (!src_profile) {
- src_profile = cmsCreate_sRGBProfile();
- inType = TYPE_RGBA_8;
-
- if (colorType == PNG_COLOR_TYPE_GRAY ||
- colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(png);
- }
- } else {
- if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA ||
- colorType == PNG_COLOR_TYPE_GRAY) {
- inType = TYPE_GRAYA_8;
- } else {
- inType = TYPE_RGBA_8;
- }
- }
-
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER);
- }
-
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
-
- useTransform = true;
-
- transform = cmsCreateTransform(
- src_profile, inType, targetProfile, TYPE_RGBA_8,
- cmsGetHeaderRenderingIntent(src_profile), cmsFLAGS_COPY_ALPHA);
-
- cmsCloseProfile(src_profile);
-
- int32_t passes = png_set_interlace_handling(png);
-
- png_read_update_info(png, pinfo);
-
- uint32_t inComponents = png_get_channels(png, pinfo);
- uint32_t inStride = info.imageWidth * inComponents;
- uint32_t inStrideOffset = inRect.x * inComponents;
-
- uint8_t* outPixelsPos = outPixels;
- uint32_t outStride = outRect.width * inComponents;
-
- auto rowFn = inComponents == 1 ? &GRAY8_to_GRAY8_row
- : inComponents == 2 ? &GRAYA88_to_GRAYA88_row
- : &RGBA8888_to_RGBA8888_row;
-
- if (sampleSize == 1) {
- uint32_t inRemainY = info.imageHeight - inRect.height - inRect.y;
-
- if (passes == 1) {
- auto inRow = std::vector(inStride);
- auto* inRowPtr = inRow.data();
- uint8_t* rowToWrite = inRowPtr + inStrideOffset;
-
- png_skip_rows(png, inRect.y);
- for (uint32_t i = 0; i < inRect.height; ++i) {
- png_read_row(png, inRowPtr, nullptr);
- memcpy(outPixelsPos, rowToWrite, outStride);
- outPixelsPos += outStride;
- }
- png_skip_rows(png, inRemainY);
- } else {
- // decode the image in full
- auto inPixels = std::vector(inStride * inRect.height);
- auto* inPixelsPos = inPixels.data();
-
- while (--passes >= 0) {
- png_skip_rows(png, inRect.y);
- for (uint32_t i = 0; i < inRect.height; ++i) {
- png_read_row(png, inPixelsPos, nullptr);
- inPixelsPos += inStride;
- }
- png_skip_rows(png, inRemainY);
- inPixelsPos = inPixels.data();
- }
-
- for (uint32_t i = 0; i < inRect.height; ++i) {
- memcpy(outPixelsPos, inPixelsPos + inStrideOffset, outStride);
- inPixelsPos += inStride;
- outPixelsPos += outStride;
- }
- }
- } else {
- uint32_t skipStart = (sampleSize - 2) / 2;
- uint32_t skipEnd = sampleSize - 2 - skipStart;
-
- if (passes == 1) {
- auto inRow1 = std::vector(inStride);
- auto inRow2 = std::vector(inStride);
-
- auto* inRow1Ptr = inRow1.data();
- auto* inRow2Ptr = inRow2.data();
- uint8_t* row1ToWrite = inRow1Ptr + inStrideOffset;
- uint8_t* row2ToWrite = inRow2Ptr + inStrideOffset;
-
- png_skip_rows(png, inRect.y);
-
- for (uint32_t i = 0; i < outRect.height; ++i) {
- png_skip_rows(png, skipStart);
- png_read_row(png, inRow1Ptr, nullptr);
- png_read_row(png, inRow2Ptr, nullptr);
-
- rowFn(outPixelsPos, row1ToWrite, row2ToWrite, outRect.width,
- sampleSize);
- png_skip_rows(png, skipEnd);
- outPixelsPos += outStride;
- }
- } else {
- auto tmpPixels = std::vector(inStride * outRect.height * 2);
-
- if (!useTransform) {
- tmpPixels.resize(outRect.width * outRect.height * 8);
- }
-
- auto* tmpPixelsPos = tmpPixels.data();
-
- uint32_t inHeightRounded = outRect.height * sampleSize;
- uint32_t inRemainY = info.imageHeight - inHeightRounded - inRect.y;
-
- while (--passes >= 0) {
- png_skip_rows(png, inRect.y);
- for (uint32_t i = 0; i < outRect.height; ++i) {
- png_skip_rows(png, skipStart);
- png_read_row(png, tmpPixelsPos, nullptr);
- tmpPixelsPos += inStride;
- png_read_row(png, tmpPixelsPos, nullptr);
- tmpPixelsPos += inStride;
- png_skip_rows(png, skipEnd);
- }
- png_skip_rows(png, inRemainY);
- tmpPixelsPos = tmpPixels.data();
- }
-
- for (uint32_t i = 0; i < outRect.height; ++i) {
- rowFn(outPixelsPos, tmpPixelsPos + inStrideOffset,
- tmpPixelsPos + inStride + inStrideOffset, outRect.width,
- sampleSize);
- outPixelsPos += outStride;
- tmpPixelsPos += inStride * 2;
- }
- }
- }
-}
diff --git a/library/src/main/cpp/decoder_png.h b/library/src/main/cpp/decoder_png.h
deleted file mode 100644
index 63e6356..0000000
--- a/library/src/main/cpp/decoder_png.h
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Created by len on 24/12/20.
-//
-
-#ifndef IMAGEDECODER_DECODER_PNG_H
-#define IMAGEDECODER_DECODER_PNG_H
-
-#include "decoder_base.h"
-#include "png.h"
-
-struct PngReader {
- uint8_t* bytes;
- uint32_t read;
- uint32_t remain;
-};
-
-// Wrap the PNG C API in this class to automatically manage memory
-class PngDecodeSession {
-public:
- PngDecodeSession(Stream* stream);
- ~PngDecodeSession();
-
- png_struct* png;
- png_info* pinfo;
- PngReader reader;
-
- void init();
-};
-
-class PngDecoder : public BaseDecoder {
-public:
- PngDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile);
-
- void decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize);
-
-private:
- ImageInfo parseInfo();
- std::unique_ptr initDecodeSession();
- cmsHPROFILE getColorProfile(png_struct* png, png_info* pinfo,
- uint8_t colorType);
-};
-
-#endif // IMAGEDECODER_DECODER_PNG_H
diff --git a/library/src/main/cpp/decoder_webp.cpp b/library/src/main/cpp/decoder_webp.cpp
deleted file mode 100644
index a47108a..0000000
--- a/library/src/main/cpp/decoder_webp.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-// Created by len on 30/12/20.
-//
-
-#include "decoder_webp.h"
-
-WebpDecoder::WebpDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile)
- : BaseDecoder(std::move(stream), cropBorders, targetProfile) {
- this->info = parseInfo();
-}
-
-ImageInfo WebpDecoder::parseInfo() {
- WebPBitstreamFeatures features;
- if (WebPGetFeatures(stream->bytes, 32, &features) != VP8_STATUS_OK) {
- throw std::runtime_error("Failed to parse webp");
- }
-
- uint32_t imageWidth = features.width;
- uint32_t imageHeight = features.height;
- bool isAnimated = features.has_animation;
-
- Rect bounds = {.x = 0, .y = 0, .width = imageWidth, .height = imageHeight};
- if (!isAnimated && cropBorders) {
- int iw = features.width;
- int ih = features.height;
- uint8_t *u, *v;
- int stride, uvStride;
- auto* luma = WebPDecodeYUV(stream->bytes, stream->size, &iw, &ih, &u, &v,
- &stride, &uvStride);
- if (luma != nullptr) {
- bounds = findBorders(luma, imageWidth, imageHeight);
- WebPFree(luma);
- } else {
- LOGW("Couldn't crop borders on a WebP image of size %dx%d", imageWidth,
- imageHeight);
- }
- }
-
- return ImageInfo{
- .imageWidth = imageWidth,
- .imageHeight = imageHeight,
- .isAnimated = isAnimated,
- .bounds = bounds,
- };
-}
-
-cmsHPROFILE WebpDecoder::getColorProfile() {
- WebPData data = {.bytes = stream->bytes, .size = stream->size};
- WebPDemuxer* const demux = WebPDemux(&data);
-
- if (!demux) {
- return nullptr;
- }
-
- uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
- WebPChunkIterator chunk_iter;
- cmsHPROFILE src_profile = nullptr;
-
- if ((flags & ICCP_FLAG) && WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter)) {
- src_profile =
- cmsOpenProfileFromMem(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
-
- WebPDemuxReleaseChunkIterator(&chunk_iter);
- }
-
- WebPDemuxDelete(demux);
-
- if (!src_profile) {
- return nullptr;
- }
-
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
-
- // WebP doesn't support gray-scale.
- if (profileSpace != cmsSigRgbData) {
- cmsCloseProfile(src_profile);
- return nullptr;
- }
-
- return src_profile;
-}
-
-void WebpDecoder::decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize) {
- WebPDecoderConfig config;
- WebPInitDecoderConfig(&config);
-
- cmsHPROFILE src_profile = getColorProfile();
- if (!src_profile) {
- src_profile = cmsCreate_sRGBProfile();
- }
-
- cmsColorSpaceSignature profileSpace = cmsGetColorSpace(src_profile);
- useTransform = true;
-
- inType = TYPE_RGBA_8;
-
- transform = cmsCreateTransform(
- src_profile, inType, targetProfile, TYPE_RGBA_8,
- cmsGetHeaderRenderingIntent(src_profile), cmsFLAGS_COPY_ALPHA);
-
- cmsCloseProfile(src_profile);
-
- // Set decode region
- config.options.use_cropping =
- inRect.width != info.imageWidth || inRect.height != info.imageHeight;
- config.options.crop_left = inRect.x;
- config.options.crop_top = inRect.y;
- config.options.crop_width = inRect.width;
- config.options.crop_height = inRect.height;
-
- // Set sample size
- config.options.use_scaling = sampleSize > 1;
- config.options.scaled_width = outRect.width;
- config.options.scaled_height = outRect.height;
-
- // Set colorspace and stride params
- uint32_t outStride = outRect.width * 4;
- config.output.colorspace = MODE_RGBA;
- config.output.u.RGBA.rgba = outPixels;
- config.output.u.RGBA.size = outStride * outRect.height;
- config.output.u.RGBA.stride = outStride;
- config.output.is_external_memory = 1;
-
- VP8StatusCode code;
- if (!info.isAnimated) {
- code = WebPDecode(stream->bytes, stream->size, &config);
- } else {
- WebPData data = {.bytes = stream->bytes, .size = stream->size};
- auto demuxer = std::unique_ptr{
- WebPDemux(&data), WebPDemuxDelete};
-
- WebPIterator iterator;
- if (!WebPDemuxGetFrame(demuxer.get(), 1, &iterator)) {
- throw std::runtime_error("Failed to init iterator");
- }
- code = WebPDecode(iterator.fragment.bytes, iterator.fragment.size, &config);
- WebPDemuxReleaseIterator(&iterator);
- }
- if (code != VP8_STATUS_OK) {
- throw std::runtime_error("Failed to decode image");
- }
-}
diff --git a/library/src/main/cpp/decoder_webp.h b/library/src/main/cpp/decoder_webp.h
deleted file mode 100644
index fb21920..0000000
--- a/library/src/main/cpp/decoder_webp.h
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// Created by len on 30/12/20.
-//
-
-#ifndef IMAGEDECODER_DECODER_WEBP_H
-#define IMAGEDECODER_DECODER_WEBP_H
-
-#include "decoder_base.h"
-#include
-#include
-
-class WebpDecoder : public BaseDecoder {
-public:
- WebpDecoder(std::shared_ptr&& stream, bool cropBorders,
- cmsHPROFILE targetProfile);
-
- void decode(uint8_t* outPixels, Rect outRect, Rect inRect,
- uint32_t sampleSize);
-
-private:
- ImageInfo parseInfo();
- cmsHPROFILE getColorProfile();
-};
-
-#endif // IMAGEDECODER_DECODER_WEBP_H
diff --git a/library/src/main/cpp/decoders.h b/library/src/main/cpp/decoders.h
deleted file mode 100644
index 4bdb7f7..0000000
--- a/library/src/main/cpp/decoders.h
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// Created by len on 30/5/21.
-//
-
-#ifndef IMAGEDECODER_DECODERS_H
-#define IMAGEDECODER_DECODERS_H
-
-#include "decoder_headers.h"
-#ifdef HAVE_LIBJPEG
-#include "decoder_jpeg.h"
-#endif
-#ifdef HAVE_LIBPNG
-#include "decoder_png.h"
-#endif
-#ifdef HAVE_LIBWEBP
-#include "decoder_webp.h"
-#endif
-#ifdef HAVE_LIBHEIF
-#include "decoder_heif.h"
-#endif
-#ifdef HAVE_LIBJXL
-#include "decoder_jxl.h"
-#endif
-
-#endif // IMAGEDECODER_DECODERS_H
diff --git a/library/src/main/cpp/image-decoder/CMakeLists.txt b/library/src/main/cpp/image-decoder/CMakeLists.txt
new file mode 100644
index 0000000..178623f
--- /dev/null
+++ b/library/src/main/cpp/image-decoder/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.14)
+project(imagedecoder C CXX ASM)
+
+add_library(imagedecoder SHARED
+ java_stream.cpp
+ java_wrapper.cpp
+ java_objects.cpp
+ borders.cpp
+ row_convert.cpp
+)
+
+target_link_libraries(imagedecoder android jnigraphics log lcms2)
+
+# set pkg-config libdir path
+set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
+message(STATUS "PKG_CONFIG_LIBDIR=${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
+
+find_package(PkgConfig REQUIRED)
+
+pkg_search_module(VIPS REQUIRED vips-cpp)
+target_include_directories(imagedecoder PRIVATE ${VIPS_STATIC_INCLUDE_DIRS})
+target_link_directories(imagedecoder PRIVATE ${VIPS_STATIC_LIBRARY_DIRS})
+
+# remove `spng` from VIPS_STATIC_LIBRARY_DIRS, and add `spng_static`
+list(REMOVE_ITEM VIPS_STATIC_LIBRARIES "spng")
+list(APPEND VIPS_STATIC_LIBRARIES "spng_static")
+
+list(REMOVE_ITEM VIPS_STATIC_LIBRARIES "c++")
+
+message(STATUS "VIPS_STATIC_INCLUDE_DIRS=${VIPS_STATIC_INCLUDE_DIRS}")
+message(STATUS "VIPS_STATIC_LIBRARY_DIRS=${VIPS_STATIC_LIBRARY_DIRS}")
+message(STATUS "VIPS_STATIC_LIBRARIES=${VIPS_STATIC_LIBRARIES}")
+
+add_definitions(-DHAVE_LIBVIPS)
+target_sources(imagedecoder PRIVATE decoder_vips.cpp)
+target_link_libraries(imagedecoder vips-cpp ${VIPS_STATIC_LIBRARIES} -static-libgcc -static-libstdc++)
diff --git a/library/src/main/cpp/borders.cpp b/library/src/main/cpp/image-decoder/borders.cpp
similarity index 100%
rename from library/src/main/cpp/borders.cpp
rename to library/src/main/cpp/image-decoder/borders.cpp
diff --git a/library/src/main/cpp/borders.h b/library/src/main/cpp/image-decoder/borders.h
similarity index 100%
rename from library/src/main/cpp/borders.h
rename to library/src/main/cpp/image-decoder/borders.h
diff --git a/library/src/main/cpp/cmyk.h b/library/src/main/cpp/image-decoder/cmyk.h
similarity index 100%
rename from library/src/main/cpp/cmyk.h
rename to library/src/main/cpp/image-decoder/cmyk.h
diff --git a/library/src/main/cpp/decoder_headers.h b/library/src/main/cpp/image-decoder/decoder_headers.h
similarity index 100%
rename from library/src/main/cpp/decoder_headers.h
rename to library/src/main/cpp/image-decoder/decoder_headers.h
diff --git a/library/src/main/cpp/image-decoder/decoder_vips.cpp b/library/src/main/cpp/image-decoder/decoder_vips.cpp
new file mode 100644
index 0000000..a512c5b
--- /dev/null
+++ b/library/src/main/cpp/image-decoder/decoder_vips.cpp
@@ -0,0 +1,115 @@
+#include "decoder_vips.h"
+
+#include "borders.h"
+#include "lcms2.h"
+#include "log.h"
+
+#include "stream.h"
+#include "vips/resample.h"
+#include
+
+#include
+
+using namespace vips;
+
+VipsDecoder* try_vips_decoder(std::shared_ptr& stream, bool cropBorders,
+ cmsHPROFILE targetProfile) {
+
+ try {
+ return new VipsDecoder(std::move(stream), cropBorders, targetProfile);
+ } catch (const std::exception& e) {
+ LOGW("Failed to initialize VipsDecoder: %s", e.what());
+ return nullptr;
+ }
+}
+
+VipsDecoder::VipsDecoder(std::shared_ptr&& stream, bool cropBorders,
+ cmsHPROFILE targetProfile)
+ : stream(std::move(stream)), targetProfile(targetProfile) {
+ // the VImage does not take ownership of the `stream`.
+ this->image = VImage::new_from_buffer(
+ this->stream->bytes, this->stream->size, nullptr,
+ // we need to decode multiple regions from the image, so access must be
+ // random.
+ VImage::option()->set("access", VIPS_ACCESS_RANDOM));
+
+ this->bounds = {.x = 0,
+ .y = 0,
+ .width = (uint32_t)image.width(),
+ .height = (uint32_t)image.height()};
+
+ // Crop the image if `cropBorders` is enabled
+ if (cropBorders) {
+ // convert image to gray
+ VImage gray_image =
+ image.colourspace(VIPS_INTERPRETATION_B_W).cast(VIPS_FORMAT_UCHAR);
+ this->bounds =
+ findBorders((uint8_t*)gray_image.data(), image.width(), image.height());
+ }
+}
+
+void VipsDecoder::decode(uint8_t* outPixels, const Rect outRect,
+ const uint32_t sampleSize) {
+
+ double scale = 1.0 / sampleSize;
+ VImage resized = image.resize(
+ scale, VImage::option()->set("kernel", VIPS_KERNEL_LANCZOS3));
+
+ cmsHTRANSFORM transform = nullptr;
+ cmsHPROFILE profile;
+ int bands = image.bands();
+
+ if (resized.get_typeof(VIPS_META_ICC_NAME) != 0) {
+ size_t size;
+ const void* data = resized.get_blob(VIPS_META_ICC_NAME, &size);
+
+ profile = cmsOpenProfileFromMem(data, size);
+ if (profile) {
+ cmsColorSpaceSignature colorspace = cmsGetColorSpace(profile);
+
+ if ((bands > 2) && (colorspace == cmsSigRgbData)) {
+ LOGI("RGB");
+ transform = cmsCreateTransform(
+ profile, TYPE_RGBA_8, targetProfile, TYPE_RGBA_8,
+ cmsGetHeaderRenderingIntent(profile), cmsFLAGS_COPY_ALPHA);
+ } else if ((bands == 4) && (colorspace == cmsSigCmykData)) {
+ LOGI("CMYK");
+ transform = cmsCreateTransform(
+ profile, TYPE_CMYK_8, targetProfile, TYPE_RGBA_8,
+ cmsGetHeaderRenderingIntent(profile), cmsFLAGS_COPY_ALPHA);
+ } else {
+ LOGI("n");
+ }
+
+ cmsCloseProfile(profile);
+ }
+ }
+
+ if (!transform) {
+ resized = resized.icc_transform("srgb");
+ cmsHPROFILE profile = cmsCreate_sRGBProfile();
+ transform = cmsCreateTransform(
+ profile, TYPE_RGBA_8, targetProfile, TYPE_RGBA_8,
+ cmsGetHeaderRenderingIntent(profile), cmsFLAGS_COPY_ALPHA);
+ cmsCloseProfile(profile);
+ }
+
+ // convert to RGBA8888
+ resized = resized.cast(VIPS_FORMAT_UCHAR);
+ if (!resized.has_alpha()) {
+ resized = resized.addalpha();
+ }
+
+ // Write the cropped data into the provided buffer
+ const VRegion region =
+ resized.region(outRect.x, outRect.y, outRect.width, outRect.height);
+ const uint8_t* outPixelsEnd = outPixels + outRect.width * outRect.height * 4;
+ uint8_t* outline = outPixels;
+ for (uint32_t y = outRect.y; y < outRect.y + outRect.height; y++) {
+ const uint8_t* line = region.addr(outRect.x, y);
+ cmsDoTransform(transform, line, outline, outRect.width);
+ outline += outRect.width * 4;
+ }
+ // ensure we didn't write past the end of the buffer
+ assert(outline <= outPixelsEnd);
+}
diff --git a/library/src/main/cpp/image-decoder/decoder_vips.h b/library/src/main/cpp/image-decoder/decoder_vips.h
new file mode 100644
index 0000000..0b4396b
--- /dev/null
+++ b/library/src/main/cpp/image-decoder/decoder_vips.h
@@ -0,0 +1,67 @@
+//
+// Created by len on 23/12/20.
+//
+
+#ifndef IMAGEDECODER_DECODER_VIPS_H
+#define IMAGEDECODER_DECODER_VIPS_H
+
+#include "lcms2.h"
+#include "rect.h"
+#include "stream.h"
+#include
+#include
+
+#include
+
+class VipsDecoder {
+public:
+ /**
+ * @brief Construct a new VipsDecoder object.
+ *
+ * @param stream The input stream for reading image data.
+ * @param cropBorders Indicates whether ImageInfo.bounds should exclude
+ * borders around the image.
+ * @param targetProfile The target color profile for the image.
+ */
+ VipsDecoder(std::shared_ptr&& stream, bool cropBorders,
+ cmsHPROFILE targetProfile);
+
+ ~VipsDecoder() {
+ if (targetProfile)
+ cmsCloseProfile(targetProfile);
+ }
+
+ /**
+ * @brief Decode a region of the image into the provided buffer.
+ *
+ * Decodes a region of the image specified by `srcRegion` into the given
+ * buffer `outPixels`. The buffer must be pre-allocated and expect pixel data
+ * in the RGBA8888 format, with a size of `outRect.width * outRect.height *
+ * 4`.
+ *
+ * @param[out] outPixels Pointer to the output buffer for decoded pixel data.
+ * @param[in] outRect Specifies the region of the resized image to decode.
+ * @param[in] sampleSize Downscaling factor for resizing the image.
+ */
+ void decode(uint8_t* outPixels, Rect outRect, uint32_t sampleSize);
+
+ // The bounds of the image. If `cropBorders` is true, the bounds exclude
+ // borders around the image.
+ Rect bounds;
+
+private:
+ // The input stream for reading image data.
+ std::shared_ptr stream;
+
+ // The target color profile for the decoded image.
+ cmsHPROFILE targetProfile;
+
+ // The VImage object. Have a reference to `stream`, so it must be declared
+ // after it, to ensure it is destroyed first.
+ vips::VImage image;
+};
+
+VipsDecoder* try_vips_decoder(std::shared_ptr& stream, bool cropBorders,
+ cmsHPROFILE targetProfile);
+
+#endif // IMAGEDECODER_DECODER_VIPS_H
diff --git a/library/src/main/cpp/java_objects.cpp b/library/src/main/cpp/image-decoder/java_objects.cpp
similarity index 90%
rename from library/src/main/cpp/java_objects.cpp
rename to library/src/main/cpp/image-decoder/java_objects.cpp
index c71d19c..7132890 100644
--- a/library/src/main/cpp/java_objects.cpp
+++ b/library/src/main/cpp/image-decoder/java_objects.cpp
@@ -13,11 +13,11 @@ static jmethodID createBitmapMethod;
void init_java_objects(JNIEnv* env) {
jclass tmpCls;
- tmpCls = env->FindClass("tachiyomi/decoder/ImageDecoder");
+ tmpCls = env->FindClass("dev/mihon/image/decoder/ImageDecoder");
imageDecoderCls = (jclass)env->NewGlobalRef(tmpCls);
imageDecoderCtor = env->GetMethodID(imageDecoderCls, "", "(JII)V");
- tmpCls = env->FindClass("tachiyomi/decoder/ImageType");
+ tmpCls = env->FindClass("dev/mihon/image/decoder/ImageType");
imageTypeCls = (jclass)env->NewGlobalRef(tmpCls);
imageTypeCtor = env->GetMethodID(imageTypeCls, "", "(IZ)V");
diff --git a/library/src/main/cpp/java_objects.h b/library/src/main/cpp/image-decoder/java_objects.h
similarity index 100%
rename from library/src/main/cpp/java_objects.h
rename to library/src/main/cpp/image-decoder/java_objects.h
diff --git a/library/src/main/cpp/java_stream.cpp b/library/src/main/cpp/image-decoder/java_stream.cpp
similarity index 100%
rename from library/src/main/cpp/java_stream.cpp
rename to library/src/main/cpp/image-decoder/java_stream.cpp
diff --git a/library/src/main/cpp/java_stream.h b/library/src/main/cpp/image-decoder/java_stream.h
similarity index 100%
rename from library/src/main/cpp/java_stream.h
rename to library/src/main/cpp/image-decoder/java_stream.h
diff --git a/library/src/main/cpp/java_wrapper.cpp b/library/src/main/cpp/image-decoder/java_wrapper.cpp
similarity index 52%
rename from library/src/main/cpp/java_wrapper.cpp
rename to library/src/main/cpp/image-decoder/java_wrapper.cpp
index e096c50..10e0096 100644
--- a/library/src/main/cpp/java_wrapper.cpp
+++ b/library/src/main/cpp/image-decoder/java_wrapper.cpp
@@ -2,14 +2,14 @@
// Created by len on 23/12/20.
//
-#include "borders.h"
-#include "decoders.h"
+#include "decode.h"
+#include "decoder_headers.h"
+#include "decoder_vips.h"
#include "java_objects.h"
#include "java_stream.h"
-#include "row_convert.h"
#include
-#include
#include
+#include
#include
jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -21,14 +21,19 @@ jint JNI_OnLoad(JavaVM* vm, void*) {
} else {
return JNI_ERR;
}
+
+ if (VIPS_INIT("VipsDecoder")) {
+ LOGE("Failed to initialize libvips.");
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_6;
}
extern "C" JNIEXPORT jobject JNICALL
-Java_tachiyomi_decoder_ImageDecoder_nativeNewInstance(JNIEnv* env, jclass,
- jobject jstream,
- jboolean cropBorders,
- jbyteArray icm_stream) {
+Java_dev_mihon_image_decoder_ImageDecoder_nativeNewInstance(
+ JNIEnv* env, jclass, jobject jstream, jboolean cropBorders,
+ jbyteArray icm_stream) {
auto stream = read_all_java_stream(env, jstream);
if (!stream) {
return nullptr;
@@ -50,60 +55,29 @@ Java_tachiyomi_decoder_ImageDecoder_nativeNewInstance(JNIEnv* env, jclass,
targetProfile = cmsCreate_sRGBProfile();
}
- BaseDecoder* decoder;
+ VipsDecoder* decoder;
try {
- if (false) {
- } // This should be optimized out by the compiler.
-#ifdef HAVE_LIBJPEG
- else if (is_jpeg(stream->bytes)) {
- decoder = new JpegDecoder(std::move(stream), cropBorders, targetProfile);
- }
-#endif
-#ifdef HAVE_LIBPNG
- else if (is_png(stream->bytes)) {
- decoder = new PngDecoder(std::move(stream), cropBorders, targetProfile);
- }
-#endif
-#ifdef HAVE_LIBWEBP
- else if (is_webp(stream->bytes)) {
- decoder = new WebpDecoder(std::move(stream), cropBorders, targetProfile);
- }
-#endif
-#ifdef HAVE_LIBHEIF
- else if (is_libheif_compatible(stream->bytes, stream->size)) {
- decoder = new HeifDecoder(std::move(stream), cropBorders, targetProfile);
- }
-#endif
-#ifdef HAVE_LIBJXL
- else if (is_jxl(stream->bytes)) {
- decoder =
- new JpegxlDecoder(std::move(stream), cropBorders, targetProfile);
- }
-#endif
- else {
- LOGE("No decoder found to handle this stream");
- return nullptr;
- }
+ decoder = new VipsDecoder(std::move(stream), cropBorders, targetProfile);
} catch (std::exception& ex) {
LOGE("%s", ex.what());
return nullptr;
}
- Rect bounds = decoder->info.bounds;
+ Rect bounds = decoder->bounds;
return create_image_decoder(env, (jlong)decoder, bounds.width, bounds.height);
}
extern "C" JNIEXPORT jobject JNICALL
-Java_tachiyomi_decoder_ImageDecoder_nativeDecode(JNIEnv* env, jobject,
- jlong decoderPtr,
- jint sampleSize, jint x,
- jint y, jint width,
- jint height) {
- auto* decoder = (BaseDecoder*)decoderPtr;
+Java_dev_mihon_image_decoder_ImageDecoder_nativeDecode(JNIEnv* env, jobject,
+ jlong decoderPtr,
+ jint sampleSize, jint x,
+ jint y, jint width,
+ jint height) {
+ auto* decoder = (VipsDecoder*)decoderPtr;
// Bounds of the image when crop borders is enabled, otherwise it matches the
// entire image.
- Rect bounds = decoder->info.bounds;
+ Rect bounds = decoder->bounds;
// Translated requested bounds to the original image.
Rect inRect = {x + bounds.x, y + bounds.y, (uint32_t)width, (uint32_t)height};
@@ -131,26 +105,7 @@ Java_tachiyomi_decoder_ImageDecoder_nativeDecode(JNIEnv* env, jobject,
}
try {
- std::vector out_buffer(outRect.width * outRect.height * 4);
- uint8_t* pout_buffer = out_buffer.data();
-
- decoder->decode(pout_buffer, outRect, inRect, sampleSize);
-
- if (decoder->useTransform) {
- cmsDoTransform(decoder->transform, pout_buffer, pixels,
- outRect.width * outRect.height);
-
- if (decoder->inType == TYPE_CMYK_8 ||
- decoder->inType == TYPE_CMYK_8_REV ||
- decoder->inType == TYPE_GRAY_8) {
- for (int i = 0; i < outRect.width * outRect.height; i++) {
- pixels[i * 4 + 3] = 255;
- }
- }
- } else {
- // out_buffer must be rgba.
- memcpy(pixels, out_buffer.data(), outRect.width * outRect.height * 4);
- }
+ decoder->decode(pixels, outRect, sampleSize);
} catch (std::exception& ex) {
LOGE("%s", ex.what());
AndroidBitmap_unlockPixels(env, bitmap);
@@ -162,15 +117,15 @@ Java_tachiyomi_decoder_ImageDecoder_nativeDecode(JNIEnv* env, jobject,
}
extern "C" JNIEXPORT void JNICALL
-Java_tachiyomi_decoder_ImageDecoder_nativeRecycle(JNIEnv*, jobject,
- jlong decoderPtr) {
- auto* decoder = (BaseDecoder*)decoderPtr;
+Java_dev_mihon_image_decoder_ImageDecoder_nativeRecycle(JNIEnv*, jobject,
+ jlong decoderPtr) {
+ auto* decoder = (VipsDecoder*)decoderPtr;
delete decoder;
}
extern "C" JNIEXPORT jobject JNICALL
-Java_tachiyomi_decoder_ImageDecoder_nativeFindType(JNIEnv* env, jclass,
- jbyteArray array) {
+Java_dev_mihon_image_decoder_ImageDecoder_nativeFindType(JNIEnv* env, jclass,
+ jbyteArray array) {
uint32_t toRead = 32;
uint32_t size = env->GetArrayLength(array);
@@ -189,13 +144,12 @@ Java_tachiyomi_decoder_ImageDecoder_nativeFindType(JNIEnv* env, jclass,
return create_image_type(env, 1, false);
} else if (is_webp(bytes)) {
try {
-#ifdef HAVE_LIBWEBP
- auto decoder = std::make_unique(
- std::make_shared(bytes, size), false, nullptr);
- return create_image_type(env, 2, decoder->info.isAnimated);
-#else
- throw std::runtime_error("WebP decoder not available");
-#endif
+ WebPBitstreamFeatures features;
+ if (WebPGetFeatures(bytes, 32, &features) != VP8_STATUS_OK) {
+ throw std::runtime_error("Failed to parse WebP header");
+ };
+
+ return create_image_type(env, 2, features.has_animation);
} catch (std::exception& ex) {
LOGW("Failed to parse WebP header. Falling back to non animated WebP");
return create_image_type(env, 2, false);
diff --git a/library/src/main/cpp/log.h b/library/src/main/cpp/image-decoder/log.h
similarity index 100%
rename from library/src/main/cpp/log.h
rename to library/src/main/cpp/image-decoder/log.h
diff --git a/library/src/main/cpp/rect.h b/library/src/main/cpp/image-decoder/rect.h
similarity index 100%
rename from library/src/main/cpp/rect.h
rename to library/src/main/cpp/image-decoder/rect.h
diff --git a/library/src/main/cpp/row_convert.cpp b/library/src/main/cpp/image-decoder/row_convert.cpp
similarity index 100%
rename from library/src/main/cpp/row_convert.cpp
rename to library/src/main/cpp/image-decoder/row_convert.cpp
diff --git a/library/src/main/cpp/row_convert.h b/library/src/main/cpp/image-decoder/row_convert.h
similarity index 100%
rename from library/src/main/cpp/row_convert.h
rename to library/src/main/cpp/image-decoder/row_convert.h
diff --git a/library/src/main/cpp/stream.h b/library/src/main/cpp/image-decoder/stream.h
similarity index 100%
rename from library/src/main/cpp/stream.h
rename to library/src/main/cpp/image-decoder/stream.h
diff --git a/library/src/main/cpp/lcms/CMakeLists.txt b/library/src/main/cpp/lcms/CMakeLists.txt
deleted file mode 100644
index 0d49d0d..0000000
--- a/library/src/main/cpp/lcms/CMakeLists.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-FetchContent_Declare(lcms2
- GIT_REPOSITORY https://github.com/mm2/Little-CMS
- GIT_TAG lcms2.16
- PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/lcms2.12.patch || true
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-FetchContent_MakeAvailable(lcms2)
-
-target_include_directories(imagedecoder PRIVATE ${lcms2_BINARY_DIR} ${lcms2_SOURCE_DIR})
-target_link_libraries(imagedecoder lcms2)
diff --git a/library/src/main/cpp/lcms/lcms2.12.patch b/library/src/main/cpp/lcms/lcms2.12.patch
deleted file mode 100644
index 2e590cc..0000000
--- a/library/src/main/cpp/lcms/lcms2.12.patch
+++ /dev/null
@@ -1,53 +0,0 @@
---- CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100
-+++ CMakeLists.txt 2021-07-09 19:13:01.393467300 +0100
-@@ -0,0 +1,48 @@
-+project(lcms2)
-+
-+cmake_minimum_required(VERSION 2.8)
-+
-+include_directories(include)
-+
-+set(HEADERS
-+ include/lcms2.h
-+ include/lcms2_plugin.h
-+)
-+
-+set(SOURCES
-+ src/cmsalpha.c
-+ src/cmscam02.c
-+ src/cmscgats.c
-+ src/cmscnvrt.c
-+ src/cmserr.c
-+ src/cmshalf.c
-+ src/cmsgamma.c
-+ src/cmsgmt.c
-+ src/cmsintrp.c
-+ src/cmsio0.c
-+ src/cmsio1.c
-+ src/cmslut.c
-+ src/cmsmd5.c
-+ src/cmsmtrx.c
-+ src/cmsnamed.c
-+ src/cmsopt.c
-+ src/cmspack.c
-+ src/cmspcs.c
-+ src/cmsplugin.c
-+ src/cmsps2.c
-+ src/cmssamp.c
-+ src/cmssm.c
-+ src/cmstypes.c
-+ src/cmsvirt.c
-+ src/cmswtpnt.c
-+ src/cmsxform.c
-+ src/lcms2_internal.h
-+)
-+
-+add_library(${PROJECT_NAME} STATIC ${HEADERS} ${SOURCES})
-+
-+set_target_properties(${PROJECT_NAME} PROPERTIES
-+ LIBRARY_OUTPUT_NAME "${PROJECT_NAME}"
-+ PUBLIC_HEADER "${HEADERS}"
-+)
-+
-+install(DIRECTORY include DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
-+install(TARGETS lcms2 DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
\ No newline at end of file
diff --git a/library/src/main/cpp/libheif/CMakeLists.txt b/library/src/main/cpp/libheif/CMakeLists.txt
deleted file mode 100644
index acaa5aa..0000000
--- a/library/src/main/cpp/libheif/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-FetchContent_Declare(libheif
- GIT_REPOSITORY https://github.com/strukturag/libheif
- GIT_TAG v1.13.0
- PATCH_COMMAND echo "" > gdk-pixbuf/CMakeLists.txt # Avoid building gdk-pixbuf, we don't need it
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-if(WITH_HEIF)
- include(libde265.cmake)
-endif()
-
-if(WITH_AVIF)
- include(dav1d.cmake)
-endif()
-
-option(BUILD_SHARED_LIBS "" OFF)
-option(WITH_EXAMPLES "" OFF)
-option(WITH_DAV1D "" OFF)
-option(WITH_LIBDE265 "" OFF)
-option(WITH_X265 "" OFF)
-option(WITH_AOM "" OFF)
-option(WITH_RAV1E "" OFF)
-
-FetchContent_MakeAvailable(libheif)
-
-target_include_directories(imagedecoder PRIVATE ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR})
-target_link_libraries(imagedecoder heif)
diff --git a/library/src/main/cpp/libheif/dav1d.cmake b/library/src/main/cpp/libheif/dav1d.cmake
deleted file mode 100644
index cba914c..0000000
--- a/library/src/main/cpp/libheif/dav1d.cmake
+++ /dev/null
@@ -1,66 +0,0 @@
-FetchContent_Declare(dav1d
- GIT_REPOSITORY https://github.com/videolan/dav1d.git
- GIT_TAG 1.4.0
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- BINARY_DIR dav1d-build
- SUBBUILD_DIR dav1d-subbuild
-)
-FetchContent_MakeAvailable(dav1d)
-
-set(DAV1D_FILENAME "${dav1d_BINARY_DIR}/src/libdav1d.a")
-
-if(NOT EXISTS "${DAV1D_FILENAME}")
- set(ENV{ANDROID_NDK} ${CMAKE_ANDROID_NDK})
- set(PREPARE_DAV1D "${CMAKE_CURRENT_LIST_DIR}/generate_dav1d_android_cross_compile.sh -a ${CMAKE_ANDROID_ARCH}")
- set(CONFIG_DAV1D "meson --buildtype release --default-library static --cross-file android_cross.txt -Denable_tools=false -Denable_tests=false ${dav1d_SOURCE_DIR}")
- set(BUILD_DAV1D "ninja")
-
- if(DEFINED ENV{JITPACK})
- # Why don't they have python 3 :(
- FetchContent_Declare(python
- URL https://github.com/kageiit/jitpack-python/releases/download/3.8/python-3.8-ubuntu-16.tar.gz
- BINARY_DIR python-build
- SUBBUILD_DIR python-subbuild
- )
- FetchContent_MakeAvailable(python)
- set(ENV{PATH} "${python_SOURCE_DIR}/bin:$ENV{PATH}")
-
- FetchContent_Declare(meson
- URL https://github.com/mesonbuild/meson/releases/download/0.58.0/meson-0.58.0.tar.gz
- BINARY_DIR meson-build
- SUBBUILD_DIR meson-subbuild
- )
- FetchContent_MakeAvailable(meson)
- file(RENAME ${meson_SOURCE_DIR}/meson.py ${meson_SOURCE_DIR}/meson)
- get_filename_component(ninja_PATH ${CMAKE_COMMAND} DIRECTORY)
- set(ENV{PATH} "${meson_SOURCE_DIR}:${ninja_PATH}:$ENV{PATH}")
-
- FetchContent_Declare(nasm
- URL http://mirrors.kernel.org/ubuntu/pool/universe/n/nasm/nasm_2.14.02-1_amd64.deb
- BINARY_DIR nasm-build
- SUBBUILD_DIR nasm-subbuild
- PATCH_COMMAND bash -c "tar xvf data.tar.xz || true"
- )
- FetchContent_MakeAvailable(nasm)
- set(ENV{PATH} "${nasm_SOURCE_DIR}/usr/bin:$ENV{PATH}")
- endif()
-
- execute_process(
- COMMAND bash -c "${PREPARE_DAV1D} && ${CONFIG_DAV1D} && ${BUILD_DAV1D}"
- WORKING_DIRECTORY ${dav1d_BINARY_DIR}
- )
-endif()
-
-if(NOT EXISTS "${DAV1D_FILENAME}")
- message(FATAL_ERROR "libavif: ${DAV1D_FILENAME} is missing")
-endif()
-
-set(DAV1D_FOUND 1)
-set(DAV1D_LIBRARIES "${DAV1D_FILENAME}")
-set(DAV1D_INCLUDE_DIR
- "${dav1d_BINARY_DIR}"
- "${dav1d_BINARY_DIR}/include"
- "${dav1d_BINARY_DIR}/include/dav1d"
- "${dav1d_SOURCE_DIR}/include"
-)
diff --git a/library/src/main/cpp/libheif/generate_dav1d_android_cross_compile.sh b/library/src/main/cpp/libheif/generate_dav1d_android_cross_compile.sh
deleted file mode 100755
index 402ebf7..0000000
--- a/library/src/main/cpp/libheif/generate_dav1d_android_cross_compile.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/bash
-
-while getopts "a:" opt; do
- case $opt in
- a)
- ARCH=$OPTARG ;;
- :)
- echo "Option -$OPTARG requires an argument." >&2
- exit 1
- ;;
- esac
-done
-
-if [[ -z "${ARCH}" ]] ; then
- echo 'You need to input arch with -a ARCH.'
- echo 'Supported archs are:'
- echo -e '\tarm arm64 x86 x86_64'
- exit 1
-elif [[ -z "${ANDROID_NDK}" ]] ; then
- echo "You need to provide the ANDROID_NDK environment variable"
- exit 1
-fi
-
-ANDROID_API=21
-
-case "${ARCH}" in
- 'arm')
- ARCH_TRIPLET='arm-linux-androideabi'
- ARCH_TRIPLET_VARIANT='armv7a-linux-androideabi'
- ABI='armeabi-v7a'
- CPU_FAMILY='arm'
- ARCH_CFLAGS='-march=armv7-a -mfpu=neon -mfloat-abi=softfp -mthumb'
- ARCH_LDFLAGS='-march=armv7-a -Wl,--fix-cortex-a8'
- B_ARCH='arm'
- B_ADDRESS_MODEL=32 ;;
- 'arm64')
- ARCH_TRIPLET='aarch64-linux-android'
- ARCH_TRIPLET_VARIANT=$ARCH_TRIPLET
- ABI='arm64-v8a'
- CPU_FAMILY='aarch64'
- B_ARCH='arm'
- B_ADDRESS_MODEL=64 ;;
- 'x86')
- ARCH_TRIPLET='i686-linux-android'
- ARCH_TRIPLET_VARIANT=$ARCH_TRIPLET
- ARCH_CFLAGS='-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32'
- ABI='x86'
- CPU_FAMILY='x86'
- B_ARCH='x86'
- B_ADDRESS_MODEL=32 ;;
- 'x86_64')
- ARCH_TRIPLET='x86_64-linux-android'
- ARCH_TRIPLET_VARIANT=$ARCH_TRIPLET
- ABI='x86_64'
- CPU_FAMILY='x86_64'
- ARCH_CFLAGS='-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel'
- B_ARCH='x86'
- B_ADDRESS_MODEL=64 ;;
- *)
- echo "Arch ${ARCH} is not supported."
- exit 1 ;;
-esac
-
-os=$(uname -s | tr '[:upper:]' '[:lower:]')
-CROSS_PREFIX="${ANDROID_NDK}"/toolchains/llvm/prebuilt/${os}-x86_64/bin
-
-set -eu
-
-echo "Generating toolchain description..."
-user_config=android_cross.txt
-rm -f $user_config
-cat < $user_config
-[binaries]
-name = 'android'
-c = '${CROSS_PREFIX}/${ARCH_TRIPLET_VARIANT}${ANDROID_API}-clang'
-cpp = '${CROSS_PREFIX}/${ARCH_TRIPLET_VARIANT}${ANDROID_API}-clang++'
-ar = '${CROSS_PREFIX}/llvm-ar'
-ld = '${CROSS_PREFIX}/llvm-ld'
-strip = '${CROSS_PREFIX}/llvm-strip'
-
-[properties]
-needs_exe_wrapper = true
-
-[host_machine]
-system = 'linux'
-cpu_family = '${CPU_FAMILY}'
-cpu = '${CPU_FAMILY}'
-endian = 'little'
-
-EOF
diff --git a/library/src/main/cpp/libheif/libde265.cmake b/library/src/main/cpp/libheif/libde265.cmake
deleted file mode 100644
index 6de9970..0000000
--- a/library/src/main/cpp/libheif/libde265.cmake
+++ /dev/null
@@ -1,21 +0,0 @@
-FetchContent_Declare(libde265
- GIT_REPOSITORY https://github.com/strukturag/libde265.git
- GIT_TAG v1.0.9
- BINARY_DIR libde265-build
- SUBBUILD_DIR libde265-subbuild
-)
-
-option(BUILD_SHARED_LIBS "" OFF)
-option(ENABLE_SDL "" OFF)
-if (${ANDROID} AND NOT ${ANDROID_ABI} STREQUAL "x86" AND NOT ${ANDROID_ABI} STREQUAL "x86_64")
- option(DISABLE_SSE "" ON)
-endif()
-
-FetchContent_MakeAvailable(libde265)
-
-set(LIBDE265_FOUND 1)
-set(LIBDE265_LIBRARIES libde265)
-set(LIBDE265_INCLUDE_DIR
- "${libde265_SOURCE_DIR}"
- "${libde265_BINARY_DIR}"
-)
diff --git a/library/src/main/cpp/libjpeg-turbo/CMakeLists.txt b/library/src/main/cpp/libjpeg-turbo/CMakeLists.txt
deleted file mode 100644
index abaa00f..0000000
--- a/library/src/main/cpp/libjpeg-turbo/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-FetchContent_Declare(libjpeg-turbo
- GIT_REPOSITORY https://github.com/libjpeg-turbo/libjpeg-turbo
- GIT_TAG 2.1.90
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-option(WITH_JPEG8 "" 1)
-option(WITH_TURBOJPEG "" 0)
-option(ENABLE_SHARED "" 0)
-option(REQUIRE_SIMD "" 1)
-
-FetchContent_MakeAvailable(libjpeg-turbo)
-
-target_include_directories(imagedecoder PRIVATE ${libjpeg-turbo_BINARY_DIR} ${libjpeg-turbo_SOURCE_DIR})
-target_link_libraries(imagedecoder jpeg-static)
diff --git a/library/src/main/cpp/libjxl/CMakeLists.txt b/library/src/main/cpp/libjxl/CMakeLists.txt
deleted file mode 100644
index 6c4724d..0000000
--- a/library/src/main/cpp/libjxl/CMakeLists.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-FetchContent_Declare(libjxl
- GIT_REPOSITORY https://github.com/libjxl/libjxl
- GIT_TAG v0.10.0
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-option(BUILD_TESTING "" OFF)
-option(JPEGXL_ENABLE_DOXYGEN "" OFF)
-option(JPEGXL_ENABLE_MANPAGES "" OFF)
-option(JPEGXL_ENABLE_EXAMPLES "" OFF)
-option(JPEGXL_ENABLE_SJPEG "" OFF)
-option(JPEGXL_ENABLE_OPENEXR "" OFF)
-option(JPEGXL_ENABLE_TRANSCODE_JPEG "" OFF)
-option(JPEGXL_ENABLE_TOOLS "" OFF)
-option(JPEGXL_ENABLE_BENCHMARK "" OFF)
-option(JPEGXL_ENABLE_JPEGLI "" OFF)
-option(JPEGXL_ENABLE_JNI "" OFF)
-option(JPEGXL_ENABLE_DEVTOOLS "" OFF)
-option(JPEGXL_ENABLE_BENCHMARK "" OFF)
-
-FetchContent_GetProperties(libjxl)
-
-if(NOT libjxl_POPULATED)
- FetchContent_Populate(libjxl)
- add_subdirectory(${libjxl_SOURCE_DIR} ${libjxl_BINARY_DIR} EXCLUDE_FROM_ALL)
-endif()
-
-target_include_directories(imagedecoder PRIVATE
- ${libjxl_BINARY_DIR}
- ${libjxl_SOURCE_DIR}
- $
-)
-
-target_link_libraries(imagedecoder
- jxl_dec
- jxl_threads
- brotlidec
- brotlienc
- brotlicommon
- hwy
- -Wl,--allow-multiple-definition
-)
diff --git a/library/src/main/cpp/libpng/CMakeLists.txt b/library/src/main/cpp/libpng/CMakeLists.txt
deleted file mode 100644
index 67316b5..0000000
--- a/library/src/main/cpp/libpng/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-FetchContent_Declare(libpng
- GIT_REPOSITORY https://github.com/glennrp/libpng
- GIT_TAG v1.6.42
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-option(PNG_SHARED "" OFF)
-option(PNG_TOOLS "" OFF)
-option(PNG_TESTS "" OFF)
-
-FetchContent_MakeAvailable(libpng)
-
-target_include_directories(imagedecoder PRIVATE ${libpng_BINARY_DIR} ${libpng_SOURCE_DIR})
-target_link_libraries(imagedecoder png_static z)
diff --git a/library/src/main/cpp/libwebp/CMakeLists.txt b/library/src/main/cpp/libwebp/CMakeLists.txt
deleted file mode 100644
index 0562db4..0000000
--- a/library/src/main/cpp/libwebp/CMakeLists.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-FetchContent_Declare(libwebp
- GIT_REPOSITORY https://chromium.googlesource.com/webm/libwebp
- GIT_TAG v1.3.2
- BINARY_DIR build
- SUBBUILD_DIR subbuild
-)
-
-option(WEBP_BUILD_ANIM_UTILS "" OFF)
-option(WEBP_BUILD_CWEBP "" OFF)
-option(WEBP_BUILD_DWEBP "" OFF)
-option(WEBP_BUILD_GIF2WEBP "" OFF)
-option(WEBP_BUILD_IMG2WEBP "" OFF)
-option(WEBP_BUILD_VWEBP "" OFF)
-option(WEBP_BUILD_WEBPINFO "" OFF)
-option(WEBP_BUILD_WEBPMUX "" OFF)
-option(WEBP_BUILD_EXTRAS "" OFF)
-option(WEBP_BUILD_WEBP_JS "" OFF)
-option(WEBP_ENABLE_SWAP_16BIT_CSP "" ON)
-
-FetchContent_MakeAvailable(libwebp)
-
-target_include_directories(imagedecoder PRIVATE ${libwebp_BINARY_DIR} ${libwebp_SOURCE_DIR})
-target_link_libraries(imagedecoder webpdecoder webpdemux)
diff --git a/library/src/main/java/tachiyomi/decoder/ImageDecoder.kt b/library/src/main/kotlin/dev/mihon/image/decoder/ImageDecoder.kt
similarity index 98%
rename from library/src/main/java/tachiyomi/decoder/ImageDecoder.kt
rename to library/src/main/kotlin/dev/mihon/image/decoder/ImageDecoder.kt
index c90cb2e..ebb6b4f 100644
--- a/library/src/main/java/tachiyomi/decoder/ImageDecoder.kt
+++ b/library/src/main/kotlin/dev/mihon/image/decoder/ImageDecoder.kt
@@ -1,4 +1,4 @@
-package tachiyomi.decoder
+package dev.mihon.image.decoder
import android.graphics.Bitmap
import android.graphics.Rect
diff --git a/library/src/main/java/tachiyomi/decoder/ImageType.kt b/library/src/main/kotlin/dev/mihon/image/decoder/ImageType.kt
similarity index 95%
rename from library/src/main/java/tachiyomi/decoder/ImageType.kt
rename to library/src/main/kotlin/dev/mihon/image/decoder/ImageType.kt
index 78194f8..e12dee3 100644
--- a/library/src/main/java/tachiyomi/decoder/ImageType.kt
+++ b/library/src/main/kotlin/dev/mihon/image/decoder/ImageType.kt
@@ -1,4 +1,4 @@
-package tachiyomi.decoder
+package dev.mihon.image.decoder
data class ImageType internal constructor(
val format: Format,
diff --git a/settings.gradle b/settings.gradle
index 9b6d090..111670b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
include ':library'
rootProject.name = "ImageDecoder"
+include ':benchmark'