diff --git a/.github/workflows/mirroring.yaml b/.github/workflows/mirroring.yaml index c074a8e..087d2ef 100644 --- a/.github/workflows/mirroring.yaml +++ b/.github/workflows/mirroring.yaml @@ -19,7 +19,7 @@ jobs: run: | git remote add gitlab https://oauth2:${{ secrets.GITLAB_TOKEN }}@gitlab.com/awxkee/jxl-coder.git git remote add codeberg https://awxkee:${{ secrets.CODEBERG_TOKEN }}@codeberg.org/awxkee/jxl-coder.git - git push gitlab --all --force - git push gitlab --tags --force - git push codeberg --all --force - git push codeberg --tags --force \ No newline at end of file + git push gitlab --all + git push gitlab --tags + git push codeberg --all + git push codeberg --tags \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8cf1e29..ef208a4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,17 +2,17 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") - id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.21" } android { namespace = "com.awxkee.jxlcoder" - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "com.awxkee.jxlcoder" minSdk = 24 - targetSdk = 35 + targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -35,8 +35,10 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" + kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } buildFeatures { compose = true diff --git a/app/src/main/java/com/awxkee/jxlcoder/MainActivity.kt b/app/src/main/java/com/awxkee/jxlcoder/MainActivity.kt index 09f4437..0bcb338 100644 --- a/app/src/main/java/com/awxkee/jxlcoder/MainActivity.kt +++ b/app/src/main/java/com/awxkee/jxlcoder/MainActivity.kt @@ -129,9 +129,9 @@ class MainActivity : ComponentActivity() { buffer4, width = largeImageSize.width / 3, height = largeImageSize.height / 3, - preferredColorConfig = PreferredColorConfig.RGBA_1010102, + preferredColorConfig = PreferredColorConfig.RGBA_8888, com.awxkee.jxlcoder.ScaleMode.FIT, - jxlResizeFilter = JxlResizeFilter.LANCZOS + jxlResizeFilter = JxlResizeFilter.MITCHELL_NETRAVALI ) lifecycleScope.launch { imagesArray.add(srcImage) diff --git a/build.gradle.kts b/build.gradle.kts index f704665..64808f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "9.0.0" apply false + id("com.android.application") version "9.2.1" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false - id("com.android.library") version "9.0.0" apply false + id("com.android.library") version "9.2.1" apply false id("com.google.devtools.ksp") version "2.3.2" apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9e5471d..b850118 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Aug 20 15:15:13 GET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jxlcoder/build.gradle.kts b/jxlcoder/build.gradle.kts index 636c943..5354983 100644 --- a/jxlcoder/build.gradle.kts +++ b/jxlcoder/build.gradle.kts @@ -1,27 +1,29 @@ -import com.android.build.gradle.internal.tasks.factory.dependsOn import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.DeploymentValidation +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") - id("signing") id("com.vanniktech.maven.publish") version "0.36.0" } mavenPublishing { if (System.getenv("PUBLISH_STATE") == "Release") { + publishToMavenCentral( + automaticRelease = true, + validateDeployment = DeploymentValidation.PUBLISHED + ) signAllPublications() } -} -mavenPublishing { configure( AndroidMultiVariantLibrary( - sourcesJar = true, - publishJavadocJar = true, - ) + JavadocJar.Javadoc(), + SourcesJar.Sources(), + ) ) coordinates("io.github.awxkee", "jxl-coder", System.getenv("VERSION_NAME") ?: "0.0.10") @@ -61,7 +63,7 @@ mavenPublishing { android { namespace = "io.github.awxkee.jxlcoder" - compileSdk = 35 + compileSdk = 36 defaultConfig { minSdk = 21 @@ -71,16 +73,16 @@ android { externalNativeBuild { cmake { ndkVersion = "26.1.10909125" - cppFlags.add ("-std=c++20") + cppFlags.add("-std=c++20") abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86_64", "x86") } } + } - publishing { - singleVariant("release") { - withSourcesJar() - withJavadocJar() - } + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() } } @@ -94,9 +96,9 @@ android { } } - sourceSets.named("main") { - this.jniLibs { - this.srcDir("src/main/cpp/lib") + sourceSets { + getByName("main") { + jniLibs.directories.add("src/main/cpp/lib") } } @@ -110,15 +112,17 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" + kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } } dependencies { - implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.core:core-ktx:1.18.0") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.3.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") diff --git a/jxlcoder/src/main/cpp/SizeScaler.cpp b/jxlcoder/src/main/cpp/SizeScaler.cpp index e2a5cbf..cc228db 100644 --- a/jxlcoder/src/main/cpp/SizeScaler.cpp +++ b/jxlcoder/src/main/cpp/SizeScaler.cpp @@ -49,47 +49,6 @@ bool RescaleImage(std::vector &rgbaData, uint32_t imageWidth = *imageWidthPtr; uint32_t imageHeight = *imageHeightPtr; if ((scaledHeight != 0 || scaledWidth != 0) && (scaledWidth != 0 && scaledHeight != 0)) { - - int xTranslation = 0, yTranslation = 0; - int canvasWidth = scaledWidth; - int canvasHeight = scaledHeight; - - if (scaleMode == Fit || scaleMode == Fill) { - std::pair currentSize(imageWidth, imageHeight); - if (scaledHeight > 0 && scaledWidth < 0) { - auto newBounds = ResizeAspectHeight(currentSize, scaledHeight, scaledHeight == -2); - scaledWidth = newBounds.first; - scaledHeight = newBounds.second; - } else if (scaledHeight < 0) { - auto newBounds = ResizeAspectWidth(currentSize, scaledWidth, scaledWidth == -2); - scaledWidth = newBounds.first; - scaledHeight = newBounds.second; - } else { - std::pair dstSize; - float scale = 1; - if (scaleMode == Fill) { - std::pair canvasSize(scaledWidth, scaledHeight); - dstSize = ResizeAspectFill(currentSize, canvasSize, &scale); - } else { - std::pair canvasSize(scaledWidth, scaledHeight); - dstSize = ResizeAspectFit(currentSize, canvasSize, &scale); - } - - xTranslation = std::max((int) (((float) dstSize.first - (float) canvasWidth) / 2.0f), 0); - yTranslation = std::max((int) (((float) dstSize.second - (float) canvasHeight) / 2.0f), 0); - - scaledWidth = dstSize.first; - scaledHeight = dstSize.second; - } - } - - uint32_t lineWidth = scaledWidth * static_cast(useFloats ? sizeof(uint16_t) : sizeof(uint8_t)) * 4; - uint32_t alignment = 64; - uint32_t padding = (alignment - (lineWidth % alignment)) % alignment; - uint32_t imdStride = lineWidth + padding; - - std::vector newImageData(imdStride * scaledHeight); - ScalingFunction sparkSampler = ScalingFunction::Bilinear; switch (sampler) { case bilinear: { @@ -134,135 +93,52 @@ bool RescaleImage(std::vector &rgbaData, break; } - if (useFloats) { - weave_scale_u16(reinterpret_cast(rgbaData.data()), - imageWidth * 4 * (int) sizeof(uint16_t), - imageWidth, imageHeight, - reinterpret_cast(newImageData.data()), - imdStride, - scaledWidth, scaledHeight, bitDepth, static_cast(sparkSampler), - doesOriginHasAlpha); - } else { - weave_scale_u8(reinterpret_cast(rgbaData.data()), - (int) imageWidth * 4 * (int) sizeof(uint8_t), - imageWidth, imageHeight, - reinterpret_cast(newImageData.data()), - imdStride, - scaledWidth, scaledHeight, static_cast(sparkSampler), - doesOriginHasAlpha); - + WeaveScaleMode mScaleMode = WeaveScaleMode::JustResize; + switch (scaleMode) { + case Fit:mScaleMode = WeaveScaleMode::ScaleToFit; + break; + case Fill:mScaleMode = WeaveScaleMode::ScaleToFill; + break; + case Resize:mScaleMode = WeaveScaleMode::JustResize; + break; } - imageWidth = scaledWidth; - imageHeight = scaledHeight; - - if (xTranslation > 0 || yTranslation > 0) { - int left = std::max(xTranslation, 0); - int right = xTranslation + canvasWidth; - int top = std::max(yTranslation, 0); - int bottom = yTranslation + canvasHeight; - - int croppedWidth = right - left; - int croppedHeight = bottom - top; - int newStride = - croppedWidth * 4 * (int) (useFloats ? sizeof(uint16_t) : sizeof(uint8_t)); - int srcStride = imdStride; - - std::vector croppedImage(newStride * croppedHeight); - - uint8_t *dstData = croppedImage.data(); - auto srcData = reinterpret_cast(newImageData.data()); - - for (int y = top, yc = 0; y < bottom; ++y, ++yc) { - int x = 0; - int xc = 0; - auto srcRow = reinterpret_cast(srcData + srcStride * y); - auto dstRow = reinterpret_cast(dstData + newStride * yc); - for (x = left, xc = 0; x < right; ++x, ++xc) { - if (useFloats) { - auto dst = reinterpret_cast(dstRow + xc * 4 * sizeof(uint16_t)); - auto src = reinterpret_cast(srcRow + - x * 4 * sizeof(uint16_t)); - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - } else { - auto dst = reinterpret_cast(dstRow); - auto src = reinterpret_cast(srcRow); - dst[xc] = src[x]; - } - } + if (useFloats) { + auto scalingResult = weave_scale_u16(reinterpret_cast(rgbaData.data()), + (int) imageWidth * 4 * (int) sizeof(uint16_t), + imageWidth, imageHeight, + scaledWidth, scaledHeight, + bitDepth, + static_cast(sparkSampler), + doesOriginHasAlpha, mScaleMode); + if (scalingResult.data == nullptr) { + return false; } - - imageWidth = croppedWidth; - imageHeight = croppedHeight; - - rgbaData = croppedImage; - *stride = newStride; - + *imageHeightPtr = scalingResult.height; + *imageWidthPtr = scalingResult.width; + *stride = scalingResult.stride * 2; + rgbaData.resize(scalingResult.length * 2); + memcpy(rgbaData.data(), scalingResult.data, scalingResult.length * 2); + weave_scaling_result16_free(scalingResult); + return true; } else { - rgbaData = newImageData; - *stride = imdStride; + auto scalingResult = weave_scale_u8(reinterpret_cast(rgbaData.data()), + (int) imageWidth * 4 * (int) sizeof(uint8_t), + imageWidth, imageHeight, + scaledWidth, scaledHeight, + static_cast(sparkSampler), + doesOriginHasAlpha, mScaleMode); + if (scalingResult.data == nullptr) { + return false; + } + *imageHeightPtr = scalingResult.height; + *imageWidthPtr = scalingResult.width; + *stride = scalingResult.stride; + rgbaData.resize(scalingResult.length); + memcpy(rgbaData.data(), scalingResult.data, scalingResult.length); + weave_scaling_result_free(scalingResult); + return true; } - - *imageWidthPtr = imageWidth; - *imageHeightPtr = imageHeight; - } return true; -} - -std::pair -ResizeAspectFit(std::pair sourceSize, std::pair dstSize, float *scale) { - int sourceWidth = sourceSize.first; - int sourceHeight = sourceSize.second; - float xFactor = (float) dstSize.first / (float) sourceSize.first; - float yFactor = (float) dstSize.second / (float) sourceSize.second; - float resizeFactor = std::min(xFactor, yFactor); - *scale = resizeFactor; - std::pair resultSize((int) ((float) sourceWidth * resizeFactor), - (int) ((float) sourceHeight * resizeFactor)); - return resultSize; -} - -std::pair -ResizeAspectFill(std::pair sourceSize, std::pair dstSize, float *scale) { - int sourceWidth = sourceSize.first; - int sourceHeight = sourceSize.second; - float xFactor = (float) dstSize.first / (float) sourceSize.first; - float yFactor = (float) dstSize.second / (float) sourceSize.second; - float resizeFactor = std::max(xFactor, yFactor); - *scale = resizeFactor; - std::pair resultSize((int) ((float) sourceWidth * resizeFactor), - (int) ((float) sourceHeight * resizeFactor)); - return resultSize; -} - -std::pair -ResizeAspectHeight(std::pair sourceSize, int maxHeight, bool multipleBy2) { - int sourceWidth = sourceSize.first; - int sourceHeight = sourceSize.second; - float scaleFactor = (float) maxHeight / (float) sourceSize.second; - std::pair resultSize((int) ((float) sourceWidth * scaleFactor), - (int) ((float) sourceHeight * scaleFactor)); - if (multipleBy2) { - resultSize.first = (resultSize.first / 2) * 2; - resultSize.second = (resultSize.second / 2) * 2; - } - return resultSize; -} - -std::pair -ResizeAspectWidth(std::pair sourceSize, int maxWidth, bool multipleBy2) { - int sourceWidth = sourceSize.first; - int sourceHeight = sourceSize.second; - float scaleFactor = (float) maxWidth / (float) sourceSize.first; - std::pair resultSize((int) ((float) sourceWidth * scaleFactor), - (int) ((float) sourceHeight * scaleFactor)); - if (multipleBy2) { - resultSize.first = (resultSize.first / 2) * 2; - resultSize.second = (resultSize.second / 2) * 2; - } - return resultSize; } \ No newline at end of file diff --git a/jxlcoder/src/main/cpp/SizeScaler.h b/jxlcoder/src/main/cpp/SizeScaler.h index 55e2e69..e2e9f9a 100644 --- a/jxlcoder/src/main/cpp/SizeScaler.h +++ b/jxlcoder/src/main/cpp/SizeScaler.h @@ -51,16 +51,4 @@ bool RescaleImage(std::vector &rgbaData, XSampler sampler, bool doesOriginHasAlpha); -std::pair -ResizeAspectFit(std::pair sourceSize, std::pair dstSize, float *scale); - -std::pair -ResizeAspectFill(std::pair sourceSize, std::pair dstSize, float *scale); - -std::pair -ResizeAspectHeight(std::pair sourceSize, int maxHeight, bool multipleBy2); - -std::pair -ResizeAspectWidth(std::pair sourceSize, int maxWidth, bool multipleBy2); - #endif //AVIF_SIZESCALER_H diff --git a/jxlcoder/src/main/cpp/lib/arm64-v8a/libweaver.a b/jxlcoder/src/main/cpp/lib/arm64-v8a/libweaver.a index 1d57151..8dfedde 100644 Binary files a/jxlcoder/src/main/cpp/lib/arm64-v8a/libweaver.a and b/jxlcoder/src/main/cpp/lib/arm64-v8a/libweaver.a differ diff --git a/jxlcoder/src/main/cpp/lib/armeabi-v7a/libweaver.a b/jxlcoder/src/main/cpp/lib/armeabi-v7a/libweaver.a index 861c6f8..74ef924 100644 Binary files a/jxlcoder/src/main/cpp/lib/armeabi-v7a/libweaver.a and b/jxlcoder/src/main/cpp/lib/armeabi-v7a/libweaver.a differ diff --git a/jxlcoder/src/main/cpp/lib/x86/libweaver.a b/jxlcoder/src/main/cpp/lib/x86/libweaver.a index 6b79b2c..2081084 100644 Binary files a/jxlcoder/src/main/cpp/lib/x86/libweaver.a and b/jxlcoder/src/main/cpp/lib/x86/libweaver.a differ diff --git a/jxlcoder/src/main/cpp/lib/x86_64/libweaver.a b/jxlcoder/src/main/cpp/lib/x86_64/libweaver.a index 3a95de0..6aa220d 100644 Binary files a/jxlcoder/src/main/cpp/lib/x86_64/libweaver.a and b/jxlcoder/src/main/cpp/lib/x86_64/libweaver.a differ diff --git a/jxlcoder/src/main/cpp/weaver.h b/jxlcoder/src/main/cpp/weaver.h index db1bbea..d01e775 100644 --- a/jxlcoder/src/main/cpp/weaver.h +++ b/jxlcoder/src/main/cpp/weaver.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -17,29 +19,55 @@ enum class ScalingFunction { Box = 10, }; +enum class WeaveScaleMode { + JustResize, + ScaleToFill, + ScaleToFit, +}; + +struct ScalingResultU8 { + uint8_t *data; + uintptr_t width; + uintptr_t height; + uintptr_t stride; + uintptr_t length; + uintptr_t capacity; +}; + +struct ScalingResultU16 { + uint16_t *data; + uintptr_t width; + uintptr_t height; + uintptr_t stride; + uintptr_t length; + uintptr_t capacity; +}; + extern "C" { -void weave_scale_u8(const uint8_t *src, - uint32_t src_stride, - uint32_t width, - uint32_t height, - const uint8_t *dst, - uint32_t dst_stride, - uint32_t new_width, - uint32_t new_height, - ScalingFunction scaling_function, - bool premultiply_alpha); - -void weave_scale_u16(const uint16_t *src, - uintptr_t src_stride, - uint32_t width, - uint32_t height, - uint16_t *dst, - uintptr_t dst_stride, - uint32_t new_width, - uint32_t new_height, - uintptr_t bit_depth, - ScalingFunction scaling_function, - bool premultiply_alpha); +void weave_scaling_result_free(ScalingResultU8 result); + +void weave_scaling_result16_free(ScalingResultU16 result); + +ScalingResultU8 weave_scale_u8(const uint8_t *src, + uint32_t src_stride, + uint32_t width, + uint32_t height, + int32_t new_width, + int32_t new_height, + ScalingFunction scaling_function, + bool premultiply_alpha, + WeaveScaleMode scale_mode); + +ScalingResultU16 weave_scale_u16(const uint16_t *src, + uintptr_t src_stride, + uint32_t width, + uint32_t height, + int32_t new_width, + int32_t new_height, + uintptr_t bit_depth, + ScalingFunction scaling_function, + bool premultiply_alpha, + WeaveScaleMode scale_mode); } // extern "C" diff --git a/settings.gradle.kts b/settings.gradle.kts index 58f5c3e..f238b61 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,7 +10,6 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { setUrl("https://jitpack.io") } } } diff --git a/weaver/Cargo.lock b/weaver/Cargo.lock index e4853d3..7df8c5d 100644 --- a/weaver/Cargo.lock +++ b/weaver/Cargo.lock @@ -130,14 +130,13 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colorutils-rs" -version = "0.7.6" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2fc25857fa523662de5cae84225b0e7bfb24a2a3f9ed8802fecf03df7252b1" +checksum = "69abc9a8ed011e2b7946769f460b9e76e8b659ece9ef4001b9d8bba3489f796d" dependencies = [ "erydanos", "half", "num-traits", - "rayon", ] [[package]] @@ -338,9 +337,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "novtb" -version = "0.1.7" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbd44661994c55bfba67f2ca983accdc37d2b4cb24d52a4a6f0a13a2649a892" +checksum = "75c0ea7569466bbce2394c0e5654b1e283c28ef7c70799e6f07b32029cca6d74" dependencies = [ "core_affinity", ] @@ -378,9 +377,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "pic-scale" -version = "0.6.15" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff95ed1aee2a9952d6e292e3408024d212c4a2443174413f60480f2991a99727" +checksum = "cba4401bb9a940ca9067dfd64f87a164ad52522a5a9dd7fe864f011a5f143144" dependencies = [ "novtb", "num-traits", @@ -804,18 +803,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/weaver/Cargo.toml b/weaver/Cargo.toml index d4976aa..3ff3a49 100644 --- a/weaver/Cargo.toml +++ b/weaver/Cargo.toml @@ -4,15 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -colorutils-rs = "0.7.6" +colorutils-rs = "0.8.0" num-traits = "0.2.19" -pic-scale = { version = "0.6.15", default-features = false} +pic-scale = { version = "0.7.6", default-features = false} [features] -arm_set = ["pic-scale/nightly_i8mm", "pic-scale/rdm"] +arm_set = ["pic-scale/nightly_i8mm", "pic-scale/rdm", "pic-scale/sve"] [build-dependencies] -cbindgen = "0.29.0" +cbindgen = "0.29.2" [lib] crate-type = ["staticlib"] diff --git a/weaver/build.rs b/weaver/build.rs index b18a55c..e9486af 100644 --- a/weaver/build.rs +++ b/weaver/build.rs @@ -36,6 +36,7 @@ fn main() { cbindgen::Builder::new() .with_crate(crate_dir) + .with_pragma_once(true) .generate() .expect("Unable to generate bindings") .write_to_file("include/weaver.h"); diff --git a/weaver/include/weaver.h b/weaver/include/weaver.h index 7357beb..d01e775 100644 --- a/weaver/include/weaver.h +++ b/weaver/include/weaver.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -17,29 +19,55 @@ enum class ScalingFunction { Box = 10, }; +enum class WeaveScaleMode { + JustResize, + ScaleToFill, + ScaleToFit, +}; + +struct ScalingResultU8 { + uint8_t *data; + uintptr_t width; + uintptr_t height; + uintptr_t stride; + uintptr_t length; + uintptr_t capacity; +}; + +struct ScalingResultU16 { + uint16_t *data; + uintptr_t width; + uintptr_t height; + uintptr_t stride; + uintptr_t length; + uintptr_t capacity; +}; + extern "C" { -uintptr_t weave_scale_u8(const uint8_t *src, - uint32_t src_stride, - uint32_t width, - uint32_t height, - uint8_t *dst, - uint32_t dst_stride, - uint32_t new_width, - uint32_t new_height, - ScalingFunction scaling_function, - bool premultiply_alpha); - -uintptr_t weave_scale_u16(const uint16_t *src, - uintptr_t src_stride, - uint32_t width, - uint32_t height, - uint16_t *dst, - uintptr_t dst_stride, - uint32_t new_width, - uint32_t new_height, - uintptr_t bit_depth, - ScalingFunction scaling_function, - bool premultiply_alpha); +void weave_scaling_result_free(ScalingResultU8 result); + +void weave_scaling_result16_free(ScalingResultU16 result); + +ScalingResultU8 weave_scale_u8(const uint8_t *src, + uint32_t src_stride, + uint32_t width, + uint32_t height, + int32_t new_width, + int32_t new_height, + ScalingFunction scaling_function, + bool premultiply_alpha, + WeaveScaleMode scale_mode); + +ScalingResultU16 weave_scale_u16(const uint16_t *src, + uintptr_t src_stride, + uint32_t width, + uint32_t height, + int32_t new_width, + int32_t new_height, + uintptr_t bit_depth, + ScalingFunction scaling_function, + bool premultiply_alpha, + WeaveScaleMode scale_mode); } // extern "C" diff --git a/weaver/src/lib.rs b/weaver/src/lib.rs index f234a21..05177c1 100644 --- a/weaver/src/lib.rs +++ b/weaver/src/lib.rs @@ -31,6 +31,8 @@ mod scale; mod scaling_function; pub use colorutils_rs::TransferFunction; -pub use scale::weave_scale_u16; -pub use scale::weave_scale_u8; +pub use scale::{ + weave_scale_u16, weave_scale_u8, weave_scaling_result16_free, weave_scaling_result_free, + ScalingResultU16, ScalingResultU8, WeaveScaleMode, +}; pub use scaling_function::ScalingFunction; diff --git a/weaver/src/scale.rs b/weaver/src/scale.rs index 6b2ba24..2dd622c 100644 --- a/weaver/src/scale.rs +++ b/weaver/src/scale.rs @@ -29,17 +29,103 @@ use crate::scaling_function::ScalingFunction; use num_traits::FromPrimitive; use pic_scale::{ - BufferStore, ImageStore, ImageStoreMut, ImageStoreScaling, PicScaleError, ScalingOptions, - ThreadingPolicy, + BufferStore, ImageStore, ImageStoreMut, ImageStoreScaling, ScalingOptions, ThreadingPolicy, }; use std::fmt::Debug; use std::slice; -#[inline] -fn map_error_code(error: Result<(), PicScaleError>) -> usize { - match error { - Ok(_) => 0, - Err(err) => err.code(), +#[repr(C)] +struct ScalingResultGen { + data: *mut T, + width: usize, + height: usize, + stride: usize, + length: usize, + capacity: usize, +} + +#[repr(C)] +pub struct ScalingResultU8 { + data: *mut u8, + width: usize, + height: usize, + stride: usize, + length: usize, + capacity: usize, +} + +#[repr(C)] +pub struct ScalingResultU16 { + data: *mut u16, + width: usize, + height: usize, + stride: usize, + length: usize, + capacity: usize, +} + +#[repr(C)] +pub enum WeaveScaleMode { + JustResize, + ScaleToFill, + ScaleToFit, +} + +#[no_mangle] +pub extern "C" fn weave_scaling_result_free(result: ScalingResultU8) { + if result.data.is_null() { + return; + } + unsafe { + _ = Vec::from_raw_parts(result.data, result.length, result.capacity); + } +} + +#[no_mangle] +pub extern "C" fn weave_scaling_result16_free(result: ScalingResultU16) { + if result.data.is_null() { + return; + } + unsafe { + _ = Vec::from_raw_parts(result.data, result.length, result.capacity); + } +} + +fn resolve_dimensions( + src_width: u32, + src_height: u32, + new_width: i32, + new_height: i32, +) -> (usize, usize) { + match (new_width, new_height) { + (w, -1) if w > 0 => { + // auto height + let scale = w as f64 / src_width as f64; + let h = (src_height as f64 * scale).round() as usize; + (w as usize, h.max(1)) + } + (w, -2) if w > 0 => { + // auto height divisible by 2 + let scale = w as f64 / src_width as f64; + let h = (src_height as f64 * scale).round() as usize; + (w as usize, (h.max(1) + 1) & !1) + } + (-1, h) if h > 0 => { + // auto width + let scale = h as f64 / src_height as f64; + let w = (src_width as f64 * scale).round() as usize; + (w.max(1), h as usize) + } + (-2, h) if h > 0 => { + // auto width divisible by 2 + let scale = h as f64 / src_height as f64; + let w = (src_width as f64 * scale).round() as usize; + ((w.max(1) + 1) & !1, h as usize) + } + (w, h) => { + // both explicit + (w.max(1) as usize, h.max(1) as usize) + } } } @@ -52,108 +138,156 @@ fn pic_scale_scale_generic< src_stride: usize, width: u32, height: u32, - dst: *mut T, - dst_stride: usize, - new_width: u32, - new_height: u32, + new_width: i32, + new_height: i32, bit_depth: u32, resizing_filter: ScalingFunction, premultiply_alpha: bool, -) -> usize + scale_mode: WeaveScaleMode, +) -> ScalingResultGen where ImageStore<'a, T, N>: ImageStoreScaling<'a, T, N>, { - unsafe { - let source_image: std::borrow::Cow<[T]>; + let source_image: std::borrow::Cow<[T]>; - let required_align_of_t: usize = align_of::(); - let size_of_t: usize = size_of::(); + let required_align_of_t: usize = align_of::(); + let size_of_t: usize = size_of::(); - let mut j_src_stride = src_stride / size_of_t; + let mut j_src_stride = src_stride / size_of_t; - if src as usize % required_align_of_t != 0 || src_stride % size_of_t != 0 { - let mut _src_slice = vec![T::default(); width as usize * height as usize * N]; - let j = slice::from_raw_parts(src as *const u8, src_stride * height as usize); + if src as usize % required_align_of_t != 0 || src_stride % size_of_t != 0 { + let mut _src_slice = vec![T::default(); width as usize * height as usize * N]; + let inner_source = + unsafe { slice::from_raw_parts(src as *const u8, src_stride * height as usize) }; + let t_size = size_of::(); - for (dst, src) in _src_slice - .chunks_exact_mut(width as usize * N) - .zip(j.chunks_exact(src_stride)) - { - for (dst, src) in dst.iter_mut().zip(src.chunks_exact(N)) { - let src_pixel = src.as_ptr() as *const T; + for (dst, src) in _src_slice + .chunks_exact_mut(width as usize * N) + .zip(inner_source.chunks_exact(src_stride)) + { + for (dst, src) in dst.iter_mut().zip(src.chunks_exact(t_size)) { + let src_pixel = src.as_ptr() as *const T; + unsafe { *dst = src_pixel.read_unaligned(); } } - source_image = std::borrow::Cow::Owned(_src_slice); - j_src_stride = width as usize * N; - } else { + } + source_image = std::borrow::Cow::Owned(_src_slice); + j_src_stride = width as usize * N; + } else { + unsafe { source_image = std::borrow::Cow::Borrowed(slice::from_raw_parts( src, src_stride / size_of_t * height as usize, )); } + } + + let source_store = ImageStore:: { + buffer: source_image, + channels: N, + width: width as usize, + height: height as usize, + stride: j_src_stride, + bit_depth: bit_depth as usize, + }; + + let mut options = ScalingOptions::default(); + options.premultiply_alpha = premultiply_alpha; + options.threading_policy = ThreadingPolicy::Single; + options.resampling_function = resizing_filter.to_resampling_function(); + + let (new_width, new_height) = resolve_dimensions(width, height, new_width, new_height); + + let (scale_w, scale_h, crop_x, crop_y, crop_w, crop_h) = match scale_mode { + WeaveScaleMode::ScaleToFill => { + // ScaleToFill: scale up to cover, then center crop + let x_factor = new_width as f64 / width as f64; + let y_factor = new_height as f64 / height as f64; + let scale = x_factor.max(y_factor); + let sw = ((width as f64 * scale).round() as usize).max(1); + let sh = ((height as f64 * scale).round() as usize).max(1); + let cx = ((sw as i64 - new_width as i64) / 2).max(0) as usize; + let cy = ((sh as i64 - new_height as i64) / 2).max(0) as usize; + // guard: crop window can't exceed scaled size due to rounding + let cw = new_width.min(sw); + let ch = new_height.min(sh); + (sw, sh, cx, cy, cw, ch) + } + WeaveScaleMode::ScaleToFit => { + // ScaleToFit: scale to fit within bounds, crop excess (will be 0 on one axis) + let x_factor = new_width as f64 / width as f64; + let y_factor = new_height as f64 / height as f64; + let scale = x_factor.min(y_factor); + let sw = ((width as f64 * scale).round() as usize).max(1); + let sh = ((height as f64 * scale).round() as usize).max(1); + let cx = ((sw as i64 - new_width as i64) / 2).max(0) as usize; + let cy = ((sh as i64 - new_height as i64) / 2).max(0) as usize; + let cw = sw.min(new_width); + let ch = sh.min(new_height); + (sw, sh, cx, cy, cw, ch) + } + WeaveScaleMode::JustResize => { + // JustResize + (new_width, new_height, 0, 0, new_width, new_height) + } + }; - let source_store = ImageStore:: { - buffer: source_image, - channels: N, - width: width as usize, - height: height as usize, - stride: j_src_stride, - bit_depth: bit_depth as usize, + let Ok(mut scaled_store) = + ImageStoreMut::try_alloc_with_depth(scale_w, scale_h, bit_depth as usize) + else { + return ScalingResultGen { + data: std::ptr::null_mut(), + width: 0, + height: 0, + stride: 0, + capacity: 0, + length: 0, }; + }; - let mut options = ScalingOptions::default(); - options.premultiply_alpha = premultiply_alpha; - options.threading_policy = ThreadingPolicy::Single; - options.resampling_function = resizing_filter.to_resampling_function(); - - if dst as usize % required_align_of_t != 0 && dst_stride % size_of_t != 0 { - let mut dst_store = ImageStoreMut::alloc_with_depth( - new_width as usize, - new_height as usize, - bit_depth as usize, - ); - - let result = source_store.scale(&mut dst_store, options); - let result_code = map_error_code(result); - if result_code != 0 { - return result_code; - } + _ = source_store.scale(&mut scaled_store, options); - let dst_slice = - slice::from_raw_parts_mut(dst as *mut u8, new_width as usize * dst_stride); - - for (src, dst) in dst_store - .as_bytes() - .chunks_exact(dst_store.stride()) - .zip(dst_slice.chunks_exact_mut(dst_stride)) - { - for (src, dst) in src.iter().zip(dst.chunks_exact_mut(N)) { - let dst_ptr = dst.as_mut_ptr() as *mut T; - dst_ptr.write_unaligned(*src); + let final_store = if crop_x > 0 || crop_y > 0 || crop_w != scale_w || crop_h != scale_h { + match scaled_store.crop_with_copy(crop_x, crop_y, crop_w, crop_h) { + Ok(v) => v, + Err(_) => { + return ScalingResultGen { + data: std::ptr::null_mut(), + width: 0, + stride: 0, + height: 0, + capacity: 0, + length: 0, } } - 0 - } else { - let dst_slice = - slice::from_raw_parts_mut(dst, new_height as usize * (dst_stride / size_of_t)); - let buffer = BufferStore::Borrowed(dst_slice); - let mut dst_store = ImageStoreMut:: { - buffer, - width: new_width as usize, - height: new_height as usize, - bit_depth: bit_depth as usize, - channels: N, - stride: dst_stride / size_of_t, - }; - - let result = source_store.scale(&mut dst_store, options); - let result_code = map_error_code(result); - if result_code != 0 { - return result_code; - } - 0 } + } else { + scaled_store + }; + + let final_width = final_store.width; + let final_height = final_store.height; + + let stride = final_store.stride(); + + let mut final_buffer = match final_store.buffer { + BufferStore::Borrowed(v) => v.to_vec(), + BufferStore::Owned(v) => v, + }; + + let data = final_buffer.as_mut_ptr(); + let capacity = final_buffer.capacity(); + let length = final_buffer.len(); + std::mem::forget(final_buffer); + + ScalingResultGen { + data, + capacity, + length, + stride, + width: final_width, + height: final_height, } } @@ -163,26 +297,32 @@ pub unsafe extern "C" fn weave_scale_u8( src_stride: u32, width: u32, height: u32, - dst: *mut u8, - dst_stride: u32, - new_width: u32, - new_height: u32, + new_width: i32, + new_height: i32, scaling_function: ScalingFunction, premultiply_alpha: bool, -) -> usize { - pic_scale_scale_generic::( + scale_mode: WeaveScaleMode, +) -> ScalingResultU8 { + let q = pic_scale_scale_generic::( src, src_stride as usize, width, height, - dst, - dst_stride as usize, new_width, new_height, 8, scaling_function, premultiply_alpha, - ) + scale_mode, + ); + ScalingResultU8 { + data: q.data, + stride: q.stride, + width: q.width, + height: q.height, + length: q.length, + capacity: q.capacity, + } } #[no_mangle] @@ -191,25 +331,31 @@ pub unsafe extern "C" fn weave_scale_u16( src_stride: usize, width: u32, height: u32, - dst: *mut u16, - dst_stride: usize, - new_width: u32, - new_height: u32, + new_width: i32, + new_height: i32, bit_depth: usize, scaling_function: ScalingFunction, premultiply_alpha: bool, -) -> usize { - pic_scale_scale_generic::( + scale_mode: WeaveScaleMode, +) -> ScalingResultU16 { + let q = pic_scale_scale_generic::( src, src_stride, width, height, - dst, - dst_stride, new_width, new_height, bit_depth as u32, scaling_function, premultiply_alpha, - ) + scale_mode, + ); + ScalingResultU16 { + data: q.data, + stride: q.stride, + width: q.width, + height: q.height, + length: q.length, + capacity: q.capacity, + } }