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'