refactor(mobile): move the native core decoders into a codecs crate (#32765)
* refactor(mobile): move the native core decoders into a codecs crate * make Format private * rename the decode export and options for what they hold * drop the unused ios and server groups and rename the target params (cherry picked from commit 11bf875313782b8a9e10269bb6f85226b7bdcd72)
This commit is contained in:
@@ -28,12 +28,12 @@ object NativeImage {
|
|||||||
external fun convert1010102(bitmap: Bitmap, outInfo: IntArray): Long
|
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
|
* [cancel], downscaled to cover [targetWidth] x [targetHeight] unless either is 0 and to fit
|
||||||
* within [maxPixelSize].
|
* within [maxPixelSize].
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun decodeJxl(
|
external fun decode(
|
||||||
address: Long,
|
address: Long,
|
||||||
length: Int,
|
length: Int,
|
||||||
targetWidth: Int,
|
targetWidth: Int,
|
||||||
@@ -43,7 +43,7 @@ object NativeImage {
|
|||||||
outInfo: IntArray
|
outInfo: IntArray
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
/** Stops the [decodeJxl] given this one-byte direct buffer. */
|
/** Stops the [decode] given this one-byte direct buffer. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun cancel(cancelled: ByteBuffer)
|
external fun cancel(cancelled: ByteBuffer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
// The fetch is done, so cancelling now stops the decode instead.
|
// The fetch is done, so cancelling now stops the decode instead.
|
||||||
val cancelled = ByteBuffer.allocateDirect(1)
|
val cancelled = ByteBuffer.allocateDirect(1)
|
||||||
signal.setOnCancelListener { NativeImage.cancel(cancelled) }
|
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.pointer,
|
||||||
buffer.offset,
|
buffer.offset,
|
||||||
width?.toInt() ?: 0,
|
width?.toInt() ?: 0,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ enum NativeCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static let coreThumbhash = symbol("native_core_thumbhash", as: NativeCoreThumbhashFn.self)
|
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)
|
private static let coreCancel = symbol("native_core_cancel", as: NativeCoreCancelFn.self)
|
||||||
|
|
||||||
static func thumbhash(_ hash: Data) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? {
|
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<Bool>) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? {
|
static func decodeImage(_ data: Data, targetWidth: Int64?, targetHeight: Int64?, maxPixelSize: Int, cancelled: UnsafeMutablePointer<Bool>) -> (width: Int, height: Int, pointer: UnsafeMutableRawPointer)? {
|
||||||
guard let coreJxlDecode else { return nil }
|
guard let coreDecodeImage else { return nil }
|
||||||
var info = NativeCoreImageInfo()
|
var info = NativeCoreImageInfo()
|
||||||
return data.withUnsafeBytes { bytes in
|
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 }
|
else { return nil }
|
||||||
return (Int(info.width), Int(info.height), UnsafeMutableRawPointer(pointer))
|
return (Int(info.width), Int(info.height), UnsafeMutableRawPointer(pointer))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
return request.completion(ImageProcessing.cancelledResult)
|
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)
|
registry.remove(requestId: request.id)
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
|
|
||||||
// The function pointer type comes from the declaration, so nothing is retyped here.
|
// The function pointer type comes from the declaration, so nothing is retyped here.
|
||||||
typedef __typeof__(&native_core_thumbhash) NativeCoreThumbhashFn;
|
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;
|
typedef __typeof__(&native_core_cancel) NativeCoreCancelFn;
|
||||||
|
|||||||
Generated
+13
-6
@@ -56,6 +56,18 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codecs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"jxl",
|
||||||
|
"libc",
|
||||||
|
"moxcms",
|
||||||
|
"pic-scale",
|
||||||
|
"quick_cache",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.8"
|
version = "4.6.8"
|
||||||
@@ -287,12 +299,7 @@ dependencies = [
|
|||||||
name = "native_core"
|
name = "native_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jxl",
|
"codecs",
|
||||||
"libc",
|
|
||||||
"moxcms",
|
|
||||||
"pic-scale",
|
|
||||||
"quick_cache",
|
|
||||||
"rayon",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/native_core", "crates/native_core_ffi"]
|
members = ["crates/codecs", "crates/native_core", "crates/native_core_ffi"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
+3
-2
@@ -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
|
in `mobile/` sets one up, otherwise install [rustup](https://rustup.rs) once and the build
|
||||||
fetches the pinned toolchain itself.
|
fetches the pinned toolchain itself.
|
||||||
|
|
||||||
- `crates/native_core`: the shared logic
|
- `crates/codecs`: the image decoders, a feature each, behind one `decode`
|
||||||
- `crates/native_core_ffi`: the C ABI, cbindgen writes `include/native_core.h`
|
- `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
|
- `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)
|
(`NOTICES` carries the licenses of the crates compiled into the core, update it with the dependencies)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -70,7 +70,7 @@ impl JxlParallelRunner for Cancellable<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Jxl<'a> {
|
impl<'a> Jxl<'a> {
|
||||||
pub fn open(mut input: &'a [u8], target: Target) -> Option<Self> {
|
pub fn open(mut input: &'a [u8], color_space: Target) -> Option<Self> {
|
||||||
let mut options = JxlDecoderOptions::default();
|
let mut options = JxlDecoderOptions::default();
|
||||||
// Colour transforms need straight alpha; premultiplying happens last.
|
// Colour transforms need straight alpha; premultiplying happens last.
|
||||||
options.alpha_mode = AlphaMode::Unpremultiplied;
|
options.alpha_mode = AlphaMode::Unpremultiplied;
|
||||||
@@ -85,7 +85,7 @@ impl<'a> Jxl<'a> {
|
|||||||
.ok()?;
|
.ok()?;
|
||||||
let profile = decoder.output_color_profile()?;
|
let profile = decoder.output_color_profile()?;
|
||||||
if let Some(cicp) = cicp(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) {
|
if color::is_hdr(cicp) {
|
||||||
decoder
|
decoder
|
||||||
@@ -121,11 +121,11 @@ impl<'a> Jxl<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes the first frame unless cancelled, downscaled to cover `target` pixels and to fit
|
/// Decodes the first frame unless cancelled, downscaled to cover `target_size` pixels and to
|
||||||
/// within `max_pixel_size`.
|
/// fit within `max_pixel_size`.
|
||||||
pub fn decode(
|
pub fn decode(
|
||||||
mut self,
|
mut self,
|
||||||
target: Option<(usize, usize)>,
|
target_size: Option<(usize, usize)>,
|
||||||
max_pixel_size: usize,
|
max_pixel_size: usize,
|
||||||
cancelled: &AtomicBool,
|
cancelled: &AtomicBool,
|
||||||
) -> Option<Image> {
|
) -> Option<Image> {
|
||||||
@@ -139,7 +139,7 @@ impl<'a> Jxl<'a> {
|
|||||||
let pixel_bytes =
|
let pixel_bytes =
|
||||||
format.color_type.samples_per_pixel() * format.color_data_format?.bytes_per_sample();
|
format.color_type.samples_per_pixel() * format.color_data_format?.bytes_per_sample();
|
||||||
let stride = w.checked_mul(pixel_bytes)?;
|
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 samples = if color::is_hdr(self.cicp) {
|
||||||
let mut rgba = Pixels::zeroed(w, h, pixel_bytes)?;
|
let mut rgba = Pixels::zeroed(w, h, pixel_bytes)?;
|
||||||
self.decode_frame(&mut rgba, stride, cancelled)?;
|
self.decode_frame(&mut rgba, stride, cancelled)?;
|
||||||
@@ -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<Format> {
|
||||||
|
#[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<Image> {
|
||||||
|
match probe(data)? {
|
||||||
|
#[cfg(feature = "jxl")]
|
||||||
|
Format::Jxl => jxl::Jxl::open(data, options.color_space)?.decode(
|
||||||
|
options.target_size,
|
||||||
|
options.max_pixel_size,
|
||||||
|
cancelled,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
/// The size of `size` scaled down to cover `target_size` and to fit within `max_pixel_size`, if
|
||||||
/// is smaller.
|
/// that is smaller.
|
||||||
pub(crate) fn cover(
|
pub(crate) fn cover(
|
||||||
(w, h): (usize, usize),
|
(w, h): (usize, usize),
|
||||||
target: Option<(usize, usize)>,
|
target_size: Option<(usize, usize)>,
|
||||||
max_pixel_size: usize,
|
max_pixel_size: usize,
|
||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
// Scales as side ratios, so the side that sets the scale lands exactly on its limit.
|
// 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 fit = (max_pixel_size, w.max(h));
|
||||||
let (num, den) = match target {
|
let (num, den) = match target_size {
|
||||||
Some((tw, th)) => {
|
Some((tw, th)) => {
|
||||||
let cover = if tw * h >= th * w { (tw, w) } else { (th, h) };
|
let cover = if tw * h >= th * w { (tw, w) } else { (th, h) };
|
||||||
if cover.0 * fit.1 < fit.0 * cover.1 {
|
if cover.0 * fit.1 < fit.0 * cover.1 {
|
||||||
Vendored
Vendored
Vendored
@@ -1,11 +1,19 @@
|
|||||||
|
#![cfg(feature = "jxl")]
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use native_core::color::{CicpColorPrimaries, CicpProfile, Target, TransferCharacteristics};
|
use codecs::Options;
|
||||||
use native_core::image::{Image, Pixels, Samples};
|
use codecs::color::{CicpColorPrimaries, CicpProfile, Target, TransferCharacteristics};
|
||||||
use native_core::jxl::Jxl;
|
use codecs::image::{Image, Pixels, Samples};
|
||||||
|
use codecs::jxl::Jxl;
|
||||||
|
|
||||||
fn decode(data: &[u8], target: Target) -> Option<Image> {
|
fn decode(data: &[u8], color_space: Target) -> Option<Image> {
|
||||||
Jxl::open(data, target)?.decode(None, 16384, &AtomicBool::new(false))
|
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<Pixels<u8>> {
|
fn decode_srgb(data: &[u8]) -> Option<Pixels<u8>> {
|
||||||
@@ -4,13 +4,16 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
convert = []
|
||||||
|
rotate = []
|
||||||
|
thumbhash = []
|
||||||
|
jxl = ["codecs/jxl"]
|
||||||
|
# One group per target.
|
||||||
|
flutter = ["convert", "rotate", "thumbhash", "jxl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
jxl = { workspace = true }
|
codecs = { path = "../codecs", default-features = false, optional = true }
|
||||||
libc = { workspace = true }
|
|
||||||
moxcms = { workspace = true }
|
|
||||||
pic-scale = { workspace = true }
|
|
||||||
quick_cache = { workspace = true }
|
|
||||||
rayon = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
pub mod color;
|
#[cfg(feature = "codecs")]
|
||||||
|
pub use codecs;
|
||||||
|
#[cfg(feature = "convert")]
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod image;
|
#[cfg(feature = "rotate")]
|
||||||
pub mod jxl;
|
|
||||||
mod resize;
|
|
||||||
pub mod rotate;
|
pub mod rotate;
|
||||||
|
#[cfg(feature = "thumbhash")]
|
||||||
pub mod thumbhash;
|
pub mod thumbhash;
|
||||||
|
|
||||||
pub fn version() -> &'static str {
|
pub fn version() -> &'static str {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "convert")]
|
||||||
|
|
||||||
use native_core::convert::convert_1010102;
|
use native_core::convert::convert_1010102;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "rotate")]
|
||||||
|
|
||||||
use native_core::rotate::{Orientation, rotate};
|
use native_core::rotate::{Orientation, rotate};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg(feature = "thumbhash")]
|
||||||
|
|
||||||
use native_core::thumbhash::decode;
|
use native_core::thumbhash::decode;
|
||||||
|
|
||||||
// Expected pixels come from the old Swift decoder patched to round like the Java did (it truncated),
|
// Expected pixels come from the old Swift decoder patched to round like the Java did (it truncated),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ name = "native_core_ffi"
|
|||||||
crate-type = ["cdylib", "staticlib"]
|
crate-type = ["cdylib", "staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
native_core = { path = "../native_core" }
|
native_core = { path = "../native_core", features = ["flutter"] }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
fn main() {
|
fn main() {
|
||||||
// Files that define exported items. A directory here would make Flutter rerun the hook every build.
|
// 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/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=src/thumbhash.rs");
|
||||||
println!("cargo:rerun-if-changed=cbindgen.toml");
|
println!("cargo:rerun-if-changed=cbindgen.toml");
|
||||||
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
|||||||
+17
-14
@@ -10,9 +10,9 @@ use jni::{
|
|||||||
objects::{JByteBuffer, JClass, JIntArray},
|
objects::{JByteBuffer, JClass, JIntArray},
|
||||||
sys::{jint, jlong},
|
sys::{jint, jlong},
|
||||||
};
|
};
|
||||||
use native_core::color::Target;
|
use native_core::codecs::color::Target;
|
||||||
use native_core::image::{Image, Samples};
|
use native_core::codecs::image::{Image, Samples};
|
||||||
use native_core::jxl::Jxl;
|
use native_core::codecs::{self, Options};
|
||||||
|
|
||||||
use super::guard;
|
use super::guard;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ pub struct NativeCoreImageInfo {
|
|||||||
pub transfer: u8,
|
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.
|
/// 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
|
/// 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
|
/// 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`.
|
/// `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`.
|
/// `info` must be null or a valid writable pointer, outside `data`.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn native_core_jxl_decode(
|
pub unsafe extern "C" fn native_core_decode_image(
|
||||||
data: *const u8,
|
data: *const u8,
|
||||||
len: size_t,
|
len: size_t,
|
||||||
keep_space: bool,
|
keep_space: bool,
|
||||||
@@ -60,16 +60,19 @@ pub unsafe extern "C" fn native_core_jxl_decode(
|
|||||||
let cancelled = unsafe { AtomicBool::from_ptr(cancelled) };
|
let cancelled = unsafe { AtomicBool::from_ptr(cancelled) };
|
||||||
// SAFETY: the caller provides a buffer readable for len bytes.
|
// SAFETY: the caller provides a buffer readable for len bytes.
|
||||||
let input = unsafe { slice::from_raw_parts(data, len) };
|
let input = unsafe { slice::from_raw_parts(data, len) };
|
||||||
let target = if keep_space {
|
let color_space = if keep_space {
|
||||||
Target::Source
|
Target::Source
|
||||||
} else {
|
} else {
|
||||||
Target::Srgb
|
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));
|
.then_some((target_width as usize, target_height as usize));
|
||||||
let Some(Image { cicp, samples }) = Jxl::open(input, target)
|
let options = Options {
|
||||||
.and_then(|image| image.decode(cover, max_pixel_size as usize, cancelled))
|
color_space,
|
||||||
else {
|
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();
|
return ptr::null_mut();
|
||||||
};
|
};
|
||||||
let (width, height, bit_depth, pixels) = match samples {
|
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.
|
/// Asks the decode given `flag` to stop early.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # 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)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn native_core_cancel(flag: *mut bool) {
|
pub unsafe extern "C" fn native_core_cancel(flag: *mut bool) {
|
||||||
if !flag.is_null() {
|
if !flag.is_null() {
|
||||||
@@ -104,7 +107,7 @@ pub unsafe extern "C" fn native_core_cancel(flag: *mut bool) {
|
|||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
#[unsafe(no_mangle)]
|
#[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>,
|
mut env: EnvUnowned<'caller>,
|
||||||
_class: JClass<'caller>,
|
_class: JClass<'caller>,
|
||||||
data: jlong,
|
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
|
// SAFETY: Kotlin passes a live native buffer of `length` bytes and a direct buffer it only
|
||||||
// changes through `cancel`, and the output is valid.
|
// changes through `cancel`, and the output is valid.
|
||||||
let rgba = unsafe {
|
let rgba = unsafe {
|
||||||
native_core_jxl_decode(
|
native_core_decode_image(
|
||||||
data as *const u8,
|
data as *const u8,
|
||||||
length as size_t,
|
length as size_t,
|
||||||
false,
|
false,
|
||||||
@@ -154,7 +157,7 @@ pub extern "system" fn Java_app_alextran_immich_NativeImage_cancel<'caller>(
|
|||||||
) {
|
) {
|
||||||
env.with_env(|env| -> jni::errors::Result<()> {
|
env.with_env(|env| -> jni::errors::Result<()> {
|
||||||
let flag = env.get_direct_buffer_address(&flag)?.cast::<bool>();
|
let flag = env.get_direct_buffer_address(&flag)?.cast::<bool>();
|
||||||
// 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) };
|
unsafe { native_core_cancel(flag) };
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -9,7 +9,7 @@ mod android;
|
|||||||
mod bitmap;
|
mod bitmap;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
mod convert;
|
mod convert;
|
||||||
mod jxl;
|
mod decode;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
mod rotate;
|
mod rotate;
|
||||||
mod thumbhash;
|
mod thumbhash;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const _manifests = [
|
|||||||
'../.cargo/config.toml',
|
'../.cargo/config.toml',
|
||||||
'../Cargo.toml',
|
'../Cargo.toml',
|
||||||
'../Cargo.lock',
|
'../Cargo.lock',
|
||||||
|
'../crates/codecs/Cargo.toml',
|
||||||
'../crates/native_core/Cargo.toml',
|
'../crates/native_core/Cargo.toml',
|
||||||
'$_crate/Cargo.toml',
|
'$_crate/Cargo.toml',
|
||||||
'$_crate/rust-toolchain.toml',
|
'$_crate/rust-toolchain.toml',
|
||||||
|
|||||||
Reference in New Issue
Block a user