diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt index 9d91f79a6..da1d4ca06 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt @@ -28,12 +28,12 @@ object NativeImage { external fun convert1010102(bitmap: Bitmap, outInfo: IntArray): Long /** - * Like [convert1010102], but decodes the JPEG XL image of [length] bytes at [address], stopped by + * Like [convert1010102], but decodes the image of [length] bytes at [address], stopped by * [cancel], downscaled to cover [targetWidth] x [targetHeight] unless either is 0 and to fit * within [maxPixelSize]. */ @JvmStatic - external fun decodeJxl( + external fun decode( address: Long, length: Int, targetWidth: Int, @@ -43,7 +43,7 @@ object NativeImage { outInfo: IntArray ): Long - /** Stops the [decodeJxl] given this one-byte direct buffer. */ + /** Stops the [decode] given this one-byte direct buffer. */ @JvmStatic external fun cancel(cancelled: ByteBuffer) } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt index 244d5d515..5de502734 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt @@ -106,7 +106,7 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi { // The fetch is done, so cancelling now stops the decode instead. val cancelled = ByteBuffer.allocateDirect(1) signal.setOnCancelListener { NativeImage.cancel(cancelled) } - val pointer = if (signal.isCanceled) 0L else NativeImage.decodeJxl( + val pointer = if (signal.isCanceled) 0L else NativeImage.decode( buffer.pointer, buffer.offset, width?.toInt() ?: 0, diff --git a/mobile/ios/Runner/Core/NativeCore.swift b/mobile/ios/Runner/Core/NativeCore.swift index 03eba1f95..3ebb3cd88 100644 --- a/mobile/ios/Runner/Core/NativeCore.swift +++ b/mobile/ios/Runner/Core/NativeCore.swift @@ -9,7 +9,7 @@ enum NativeCore { } private static let coreThumbhash = symbol("native_core_thumbhash", as: NativeCoreThumbhashFn.self) - private static let coreJxlDecode = symbol("native_core_jxl_decode", as: NativeCoreJxlDecodeFn.self) + private static let coreDecodeImage = symbol("native_core_decode_image", as: NativeCoreDecodeImageFn.self) private static let coreCancel = symbol("native_core_cancel", as: NativeCoreCancelFn.self) static func thumbhash(_ hash: Data) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? { @@ -23,11 +23,11 @@ enum NativeCore { } } - static func decodeJxl(_ data: Data, targetWidth: Int64?, targetHeight: Int64?, maxPixelSize: Int, cancelled: UnsafeMutablePointer) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? { - guard let coreJxlDecode else { return nil } + static func decodeImage(_ data: Data, targetWidth: Int64?, targetHeight: Int64?, maxPixelSize: Int, cancelled: UnsafeMutablePointer) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? { + guard let coreDecodeImage else { return nil } var info = NativeCoreImageInfo() return data.withUnsafeBytes { bytes in - guard let pointer = coreJxlDecode(bytes.bindMemory(to: UInt8.self).baseAddress, bytes.count, false, UInt32(clamping: targetWidth ?? 0), UInt32(clamping: targetHeight ?? 0), UInt32(clamping: maxPixelSize), cancelled, &info) + guard let pointer = coreDecodeImage(bytes.bindMemory(to: UInt8.self).baseAddress, bytes.count, false, UInt32(clamping: targetWidth ?? 0), UInt32(clamping: targetHeight ?? 0), UInt32(clamping: maxPixelSize), cancelled, &info) else { return nil } return (Int(info.width), Int(info.height), UnsafeMutableRawPointer(pointer)) } diff --git a/mobile/ios/Runner/Images/RemoteImagesImpl.swift b/mobile/ios/Runner/Images/RemoteImagesImpl.swift index ac405bf7e..c409b5d1e 100644 --- a/mobile/ios/Runner/Images/RemoteImagesImpl.swift +++ b/mobile/ios/Runner/Images/RemoteImagesImpl.swift @@ -80,7 +80,7 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi { return request.completion(ImageProcessing.cancelledResult) } - guard let (width, height, pointer) = NativeCore.decodeJxl(data, targetWidth: width, targetHeight: height, maxPixelSize: ImageProcessing.maxPixelSize, cancelled: request.decodeCancelled) else { + guard let (width, height, pointer) = NativeCore.decodeImage(data, targetWidth: width, targetHeight: height, maxPixelSize: ImageProcessing.maxPixelSize, cancelled: request.decodeCancelled) else { registry.remove(requestId: request.id) if request.isCancelled { return request.completion(ImageProcessing.cancelledResult) diff --git a/mobile/ios/Runner/Runner-Bridging-Header.h b/mobile/ios/Runner/Runner-Bridging-Header.h index d5b7f601c..d20ceca77 100644 --- a/mobile/ios/Runner/Runner-Bridging-Header.h +++ b/mobile/ios/Runner/Runner-Bridging-Header.h @@ -3,5 +3,5 @@ // The function pointer type comes from the declaration, so nothing is retyped here. typedef __typeof__(&native_core_thumbhash) NativeCoreThumbhashFn; -typedef __typeof__(&native_core_jxl_decode) NativeCoreJxlDecodeFn; +typedef __typeof__(&native_core_decode_image) NativeCoreDecodeImageFn; typedef __typeof__(&native_core_cancel) NativeCoreCancelFn; diff --git a/native/Cargo.lock b/native/Cargo.lock index e34bf8832..7df0927d5 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -56,6 +56,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "codecs" +version = "0.1.0" +dependencies = [ + "jxl", + "libc", + "moxcms", + "pic-scale", + "quick_cache", + "rayon", +] + [[package]] name = "combine" version = "4.6.8" @@ -287,12 +299,7 @@ dependencies = [ name = "native_core" version = "0.1.0" dependencies = [ - "jxl", - "libc", - "moxcms", - "pic-scale", - "quick_cache", - "rayon", + "codecs", ] [[package]] diff --git a/native/Cargo.toml b/native/Cargo.toml index 377057f54..5385b9fa6 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["crates/native_core", "crates/native_core_ffi"] +members = ["crates/codecs", "crates/native_core", "crates/native_core_ffi"] [workspace.package] version = "0.1.0" diff --git a/native/README.md b/native/README.md index 1d89d7389..dc7bfe948 100644 --- a/native/README.md +++ b/native/README.md @@ -5,8 +5,9 @@ Rust code that ships inside the mobile app. Flutter compiles it from source thro in `mobile/` sets one up, otherwise install [rustup](https://rustup.rs) once and the build fetches the pinned toolchain itself. -- `crates/native_core`: the shared logic -- `crates/native_core_ffi`: the C ABI, cbindgen writes `include/native_core.h` +- `crates/codecs`: the image decoders, a feature each, behind one `decode` +- `crates/native_core`: the shared logic, a feature per part and a group per target +- `crates/native_core_ffi`: the C ABI on the `flutter` group, cbindgen writes `include/native_core.h` - `native_core`: Flutter package with the build hook and the ffigen bindings (`NOTICES` carries the licenses of the crates compiled into the core, update it with the dependencies) diff --git a/native/crates/codecs/Cargo.toml b/native/crates/codecs/Cargo.toml new file mode 100644 index 000000000..ce9ac8c98 --- /dev/null +++ b/native/crates/codecs/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "codecs" +version.workspace = true +edition.workspace = true +license.workspace = true + +[features] +default = ["jxl"] +jxl = ["dep:jxl", "dep:rayon"] + +[dependencies] +jxl = { workspace = true, optional = true } +libc = { workspace = true } +moxcms = { workspace = true } +pic-scale = { workspace = true } +quick_cache = { workspace = true } +rayon = { workspace = true, optional = true } + +[lints] +workspace = true diff --git a/native/crates/native_core/src/color.rs b/native/crates/codecs/src/color.rs similarity index 100% rename from native/crates/native_core/src/color.rs rename to native/crates/codecs/src/color.rs diff --git a/native/crates/native_core/src/image.rs b/native/crates/codecs/src/image.rs similarity index 100% rename from native/crates/native_core/src/image.rs rename to native/crates/codecs/src/image.rs diff --git a/native/crates/native_core/src/jxl.rs b/native/crates/codecs/src/jxl.rs similarity index 96% rename from native/crates/native_core/src/jxl.rs rename to native/crates/codecs/src/jxl.rs index cc2384486..71abd8048 100644 --- a/native/crates/native_core/src/jxl.rs +++ b/native/crates/codecs/src/jxl.rs @@ -70,7 +70,7 @@ impl JxlParallelRunner for Cancellable<'_> { } impl<'a> Jxl<'a> { - pub fn open(mut input: &'a [u8], target: Target) -> Option { + pub fn open(mut input: &'a [u8], color_space: Target) -> Option { let mut options = JxlDecoderOptions::default(); // Colour transforms need straight alpha; premultiplying happens last. options.alpha_mode = AlphaMode::Unpremultiplied; @@ -85,7 +85,7 @@ impl<'a> Jxl<'a> { .ok()?; let profile = decoder.output_color_profile()?; if let Some(cicp) = cicp(profile) - && (target == Target::Source || color::is_srgb(cicp)) + && (color_space == Target::Source || color::is_srgb(cicp)) { if color::is_hdr(cicp) { decoder @@ -121,11 +121,11 @@ impl<'a> Jxl<'a> { }) } - /// Decodes the first frame unless cancelled, downscaled to cover `target` pixels and to fit - /// within `max_pixel_size`. + /// Decodes the first frame unless cancelled, downscaled to cover `target_size` pixels and to + /// fit within `max_pixel_size`. pub fn decode( mut self, - target: Option<(usize, usize)>, + target_size: Option<(usize, usize)>, max_pixel_size: usize, cancelled: &AtomicBool, ) -> Option { @@ -139,7 +139,7 @@ impl<'a> Jxl<'a> { let pixel_bytes = format.color_type.samples_per_pixel() * format.color_data_format?.bytes_per_sample(); let stride = w.checked_mul(pixel_bytes)?; - let size = resize::cover((w, h), target, max_pixel_size); + let size = resize::cover((w, h), target_size, max_pixel_size); let samples = if color::is_hdr(self.cicp) { let mut rgba = Pixels::zeroed(w, h, pixel_bytes)?; self.decode_frame(&mut rgba, stride, cancelled)?; diff --git a/native/crates/codecs/src/lib.rs b/native/crates/codecs/src/lib.rs new file mode 100644 index 000000000..0c1ad8e88 --- /dev/null +++ b/native/crates/codecs/src/lib.rs @@ -0,0 +1,49 @@ +use std::sync::atomic::AtomicBool; + +use color::Target; +use image::Image; + +pub mod color; +pub mod image; +#[cfg(feature = "jxl")] +pub mod jxl; +mod resize; + +enum Format { + #[cfg(feature = "jxl")] + Jxl, +} + +pub struct Options { + pub color_space: Target, + + /// Downscales to the smallest size that still fills this. + pub target_size: Option<(usize, usize)>, + + /// Downscales to fit within this many pixels per side. + pub max_pixel_size: usize, +} + +fn probe(data: &[u8]) -> Option { + #[cfg(feature = "jxl")] + if matches!( + ::jxl::api::check_signature(data), + ::jxl::api::JxlSignature::Codestream | ::jxl::api::JxlSignature::Container + ) { + return Some(Format::Jxl); + } + + None +} + +/// Decodes the first frame of `data` unless cancelled. +pub fn decode(data: &[u8], options: &Options, cancelled: &AtomicBool) -> Option { + match probe(data)? { + #[cfg(feature = "jxl")] + Format::Jxl => jxl::Jxl::open(data, options.color_space)?.decode( + options.target_size, + options.max_pixel_size, + cancelled, + ), + } +} diff --git a/native/crates/native_core/src/resize.rs b/native/crates/codecs/src/resize.rs similarity index 94% rename from native/crates/native_core/src/resize.rs rename to native/crates/codecs/src/resize.rs index 7ad83925c..3b8651bdd 100644 --- a/native/crates/native_core/src/resize.rs +++ b/native/crates/codecs/src/resize.rs @@ -47,16 +47,16 @@ impl Lanczos for u16 { } } -/// The size of `size` scaled down to cover `target` and to fit within `max_pixel_size`, if that -/// is smaller. +/// The size of `size` scaled down to cover `target_size` and to fit within `max_pixel_size`, if +/// that is smaller. pub(crate) fn cover( (w, h): (usize, usize), - target: Option<(usize, usize)>, + target_size: Option<(usize, usize)>, max_pixel_size: usize, ) -> Option<(usize, usize)> { // Scales as side ratios, so the side that sets the scale lands exactly on its limit. let fit = (max_pixel_size, w.max(h)); - let (num, den) = match target { + let (num, den) = match target_size { Some((tw, th)) => { let cover = if tw * h >= th * w { (tw, w) } else { (th, h) }; if cover.0 * fit.1 < fit.0 * cover.1 { diff --git a/native/crates/native_core/tests/fixtures/animated.jxl b/native/crates/codecs/tests/fixtures/animated.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/animated.jxl rename to native/crates/codecs/tests/fixtures/animated.jxl diff --git a/native/crates/native_core/tests/fixtures/gray.jxl b/native/crates/codecs/tests/fixtures/gray.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/gray.jxl rename to native/crates/codecs/tests/fixtures/gray.jxl diff --git a/native/crates/native_core/tests/fixtures/gray_icc.jxl b/native/crates/codecs/tests/fixtures/gray_icc.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/gray_icc.jxl rename to native/crates/codecs/tests/fixtures/gray_icc.jxl diff --git a/native/crates/native_core/tests/fixtures/jpeg_icc.jxl b/native/crates/codecs/tests/fixtures/jpeg_icc.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/jpeg_icc.jxl rename to native/crates/codecs/tests/fixtures/jpeg_icc.jxl diff --git a/native/crates/native_core/tests/fixtures/large.jxl b/native/crates/codecs/tests/fixtures/large.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/large.jxl rename to native/crates/codecs/tests/fixtures/large.jxl diff --git a/native/crates/native_core/tests/fixtures/p3.jxl b/native/crates/codecs/tests/fixtures/p3.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/p3.jxl rename to native/crates/codecs/tests/fixtures/p3.jxl diff --git a/native/crates/native_core/tests/fixtures/pq_alpha.jxl b/native/crates/codecs/tests/fixtures/pq_alpha.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/pq_alpha.jxl rename to native/crates/codecs/tests/fixtures/pq_alpha.jxl diff --git a/native/crates/native_core/tests/fixtures/pq_grays.jxl b/native/crates/codecs/tests/fixtures/pq_grays.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/pq_grays.jxl rename to native/crates/codecs/tests/fixtures/pq_grays.jxl diff --git a/native/crates/native_core/tests/fixtures/srgb_alpha.jxl b/native/crates/codecs/tests/fixtures/srgb_alpha.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/srgb_alpha.jxl rename to native/crates/codecs/tests/fixtures/srgb_alpha.jxl diff --git a/native/crates/native_core/tests/fixtures/xyb_icc.jxl b/native/crates/codecs/tests/fixtures/xyb_icc.jxl similarity index 100% rename from native/crates/native_core/tests/fixtures/xyb_icc.jxl rename to native/crates/codecs/tests/fixtures/xyb_icc.jxl diff --git a/native/crates/native_core/tests/jxl.rs b/native/crates/codecs/tests/jxl.rs similarity index 93% rename from native/crates/native_core/tests/jxl.rs rename to native/crates/codecs/tests/jxl.rs index ef261c5c3..d323cb438 100644 --- a/native/crates/native_core/tests/jxl.rs +++ b/native/crates/codecs/tests/jxl.rs @@ -1,11 +1,19 @@ +#![cfg(feature = "jxl")] + use std::sync::atomic::AtomicBool; -use native_core::color::{CicpColorPrimaries, CicpProfile, Target, TransferCharacteristics}; -use native_core::image::{Image, Pixels, Samples}; -use native_core::jxl::Jxl; +use codecs::Options; +use codecs::color::{CicpColorPrimaries, CicpProfile, Target, TransferCharacteristics}; +use codecs::image::{Image, Pixels, Samples}; +use codecs::jxl::Jxl; -fn decode(data: &[u8], target: Target) -> Option { - Jxl::open(data, target)?.decode(None, 16384, &AtomicBool::new(false)) +fn decode(data: &[u8], color_space: Target) -> Option { + let options = Options { + color_space, + target_size: None, + max_pixel_size: 16384, + }; + codecs::decode(data, &options, &AtomicBool::new(false)) } fn decode_srgb(data: &[u8]) -> Option> { diff --git a/native/crates/native_core/Cargo.toml b/native/crates/native_core/Cargo.toml index 8326eaa36..748f7a676 100644 --- a/native/crates/native_core/Cargo.toml +++ b/native/crates/native_core/Cargo.toml @@ -4,13 +4,16 @@ version.workspace = true edition.workspace = true license.workspace = true +[features] +convert = [] +rotate = [] +thumbhash = [] +jxl = ["codecs/jxl"] +# One group per target. +flutter = ["convert", "rotate", "thumbhash", "jxl"] + [dependencies] -jxl = { workspace = true } -libc = { workspace = true } -moxcms = { workspace = true } -pic-scale = { workspace = true } -quick_cache = { workspace = true } -rayon = { workspace = true } +codecs = { path = "../codecs", default-features = false, optional = true } [lints] workspace = true diff --git a/native/crates/native_core/src/lib.rs b/native/crates/native_core/src/lib.rs index f5c08829f..d41ea32c1 100644 --- a/native/crates/native_core/src/lib.rs +++ b/native/crates/native_core/src/lib.rs @@ -1,9 +1,10 @@ -pub mod color; +#[cfg(feature = "codecs")] +pub use codecs; +#[cfg(feature = "convert")] pub mod convert; -pub mod image; -pub mod jxl; -mod resize; +#[cfg(feature = "rotate")] pub mod rotate; +#[cfg(feature = "thumbhash")] pub mod thumbhash; pub fn version() -> &'static str { diff --git a/native/crates/native_core/tests/convert.rs b/native/crates/native_core/tests/convert.rs index d7a370ca9..b049b3007 100644 --- a/native/crates/native_core/tests/convert.rs +++ b/native/crates/native_core/tests/convert.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "convert")] + use native_core::convert::convert_1010102; #[test] diff --git a/native/crates/native_core/tests/rotate.rs b/native/crates/native_core/tests/rotate.rs index 4473fef72..2c3118bf4 100644 --- a/native/crates/native_core/tests/rotate.rs +++ b/native/crates/native_core/tests/rotate.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "rotate")] + use native_core::rotate::{Orientation, rotate}; #[test] diff --git a/native/crates/native_core/tests/thumbhash.rs b/native/crates/native_core/tests/thumbhash.rs index 1382c8cf6..842beb0ca 100644 --- a/native/crates/native_core/tests/thumbhash.rs +++ b/native/crates/native_core/tests/thumbhash.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "thumbhash")] + use native_core::thumbhash::decode; // Expected pixels come from the old Swift decoder patched to round like the Java did (it truncated), diff --git a/native/crates/native_core_ffi/Cargo.toml b/native/crates/native_core_ffi/Cargo.toml index 97f2a21d5..ea69608c1 100644 --- a/native/crates/native_core_ffi/Cargo.toml +++ b/native/crates/native_core_ffi/Cargo.toml @@ -10,7 +10,7 @@ name = "native_core_ffi" crate-type = ["cdylib", "staticlib"] [dependencies] -native_core = { path = "../native_core" } +native_core = { path = "../native_core", features = ["flutter"] } libc = { workspace = true } [target.'cfg(target_os = "android")'.dependencies] diff --git a/native/crates/native_core_ffi/build.rs b/native/crates/native_core_ffi/build.rs index 219b607a9..255ad93cc 100644 --- a/native/crates/native_core_ffi/build.rs +++ b/native/crates/native_core_ffi/build.rs @@ -3,7 +3,7 @@ use std::path::Path; fn main() { // Files that define exported items. A directory here would make Flutter rerun the hook every build. println!("cargo:rerun-if-changed=src/lib.rs"); - println!("cargo:rerun-if-changed=src/jxl.rs"); + println!("cargo:rerun-if-changed=src/decode.rs"); println!("cargo:rerun-if-changed=src/thumbhash.rs"); println!("cargo:rerun-if-changed=cbindgen.toml"); let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/native/crates/native_core_ffi/src/jxl.rs b/native/crates/native_core_ffi/src/decode.rs similarity index 87% rename from native/crates/native_core_ffi/src/jxl.rs rename to native/crates/native_core_ffi/src/decode.rs index f970a45d6..e09e6f3ad 100644 --- a/native/crates/native_core_ffi/src/jxl.rs +++ b/native/crates/native_core_ffi/src/decode.rs @@ -10,9 +10,9 @@ use jni::{ objects::{JByteBuffer, JClass, JIntArray}, sys::{jint, jlong}, }; -use native_core::color::Target; -use native_core::image::{Image, Samples}; -use native_core::jxl::Jxl; +use native_core::codecs::color::Target; +use native_core::codecs::image::{Image, Samples}; +use native_core::codecs::{self, Options}; use super::guard; @@ -30,7 +30,7 @@ pub struct NativeCoreImageInfo { pub transfer: u8, } -/// Decodes a JPEG XL image to premultiplied RGBA in sRGB, or with `keep_space`, in the image's own +/// Decodes an image to premultiplied RGBA in sRGB, or with `keep_space`, in the image's own /// space when it has BT.709, BT.2020 or Display P3 primaries with the sRGB, PQ or HLG transfer. /// Unless either is 0, it is downscaled to cover `target_width` x `target_height`, and it never /// exceeds `max_pixel_size` pixels per side. `info` gets the size and space. The caller frees the @@ -42,7 +42,7 @@ pub struct NativeCoreImageInfo { /// `cancelled` must be null or valid for the call and only changed through `native_core_cancel`. /// `info` must be null or a valid writable pointer, outside `data`. #[unsafe(no_mangle)] -pub unsafe extern "C" fn native_core_jxl_decode( +pub unsafe extern "C" fn native_core_decode_image( data: *const u8, len: size_t, keep_space: bool, @@ -60,16 +60,19 @@ pub unsafe extern "C" fn native_core_jxl_decode( let cancelled = unsafe { AtomicBool::from_ptr(cancelled) }; // SAFETY: the caller provides a buffer readable for len bytes. let input = unsafe { slice::from_raw_parts(data, len) }; - let target = if keep_space { + let color_space = if keep_space { Target::Source } else { Target::Srgb }; - let cover = (target_width > 0 && target_height > 0) + let target_size = (target_width > 0 && target_height > 0) .then_some((target_width as usize, target_height as usize)); - let Some(Image { cicp, samples }) = Jxl::open(input, target) - .and_then(|image| image.decode(cover, max_pixel_size as usize, cancelled)) - else { + let options = Options { + color_space, + target_size, + max_pixel_size: max_pixel_size as usize, + }; + let Some(Image { cicp, samples }) = codecs::decode(input, &options, cancelled) else { return ptr::null_mut(); }; let (width, height, bit_depth, pixels) = match samples { @@ -93,7 +96,7 @@ pub unsafe extern "C" fn native_core_jxl_decode( /// Asks the decode given `flag` to stop early. /// /// # Safety -/// `flag` must be null or valid and otherwise only read by `native_core_jxl_decode`. +/// `flag` must be null or valid and otherwise only read by `native_core_decode_image`. #[unsafe(no_mangle)] pub unsafe extern "C" fn native_core_cancel(flag: *mut bool) { if !flag.is_null() { @@ -104,7 +107,7 @@ pub unsafe extern "C" fn native_core_cancel(flag: *mut bool) { #[cfg(target_os = "android")] #[unsafe(no_mangle)] -pub extern "system" fn Java_app_alextran_immich_NativeImage_decodeJxl<'caller>( +pub extern "system" fn Java_app_alextran_immich_NativeImage_decode<'caller>( mut env: EnvUnowned<'caller>, _class: JClass<'caller>, data: jlong, @@ -121,7 +124,7 @@ pub extern "system" fn Java_app_alextran_immich_NativeImage_decodeJxl<'caller>( // SAFETY: Kotlin passes a live native buffer of `length` bytes and a direct buffer it only // changes through `cancel`, and the output is valid. let rgba = unsafe { - native_core_jxl_decode( + native_core_decode_image( data as *const u8, length as size_t, false, @@ -154,7 +157,7 @@ pub extern "system" fn Java_app_alextran_immich_NativeImage_cancel<'caller>( ) { env.with_env(|env| -> jni::errors::Result<()> { let flag = env.get_direct_buffer_address(&flag)?.cast::(); - // SAFETY: the buffer is the live one-byte flag Kotlin passed to `decodeJxl`. + // SAFETY: the buffer is the live one-byte flag Kotlin passed to `decode`. unsafe { native_core_cancel(flag) }; Ok(()) }) diff --git a/native/crates/native_core_ffi/src/lib.rs b/native/crates/native_core_ffi/src/lib.rs index 864736d96..45aa182bd 100644 --- a/native/crates/native_core_ffi/src/lib.rs +++ b/native/crates/native_core_ffi/src/lib.rs @@ -9,7 +9,7 @@ mod android; mod bitmap; #[cfg(target_os = "android")] mod convert; -mod jxl; +mod decode; #[cfg(target_os = "android")] mod rotate; mod thumbhash; diff --git a/native/native_core/hook/build.dart b/native/native_core/hook/build.dart index 476a9ad51..4df19fa2f 100644 --- a/native/native_core/hook/build.dart +++ b/native/native_core/hook/build.dart @@ -11,6 +11,7 @@ const _manifests = [ '../.cargo/config.toml', '../Cargo.toml', '../Cargo.lock', + '../crates/codecs/Cargo.toml', '../crates/native_core/Cargo.toml', '$_crate/Cargo.toml', '$_crate/rust-toolchain.toml',