From e0bd98cdd1b1269848bfde92ec098d22fc0a8a32 Mon Sep 17 00:00:00 2001 From: Kornel <kornel@geekhood.net> Date: Fri, 15 Sep 2017 17:49:14 +0100 Subject: [PATCH] No-cache flags for Sync compatibility --- Cargo.toml | 2 +- README.md | 4 +- examples/thread.rs | 9 ++++ src/flags.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/profile.rs | 4 +- src/transform.rs | 68 +++++++++++++++++--------- tests/transform.rs | 4 +- 8 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 src/flags.rs diff --git a/Cargo.toml b/Cargo.toml index 3ecee13..b6fa4b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" documentation = "https://pornel.github.io/rust-lcms2/lcms2/" repository = "https://github.com/pornel/rust-lcms2.git" categories = ["multimedia::images", "api-bindings"] -version = "4.2.1" +version = "5.0.0" [dependencies] foreign-types = "0.3.0" diff --git a/README.md b/README.md index acc858a..8b6d1ea 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ if b"ICC_PROFILE\0" == &app2_marker_data[0..12] { There's more in the `examples` directory. -This crate requires Rust 1.18 or later. It's up to date with LCMS 2.8 (should work with 2.6 to 2.9). +This crate requires Rust 1.20 or later. It's up to date with LCMS 2.8 (should work with 2.6 to 2.9). ## Threads In LCMS all functions are in 2 flavors: global and `*THR()` functions. In this crate this is represented by having functions with `GlobalContext` and `ThreadContext`. Create profiles, transforms, etc. using `*_context()` constructors to give them their private coontext, which makes them sendable between threads (i.e. they're `Send`). -`Transform` does not implement `Sync`, because LCMS2 has a thread-unsafe cache in the transform. +By default `Transform` does not implement `Sync`, because LCMS2 has a thread-unsafe cache in the transform. You can set `Flags::NO_CACHE` to make it safe (this is checked at compile time). diff --git a/examples/thread.rs b/examples/thread.rs index cee12f0..bf81f92 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -3,6 +3,8 @@ use lcms2::*; use std::thread; fn main() { + // Send // + thread::spawn(|| { // For each thread create its own context let context = ThreadContext::new(); @@ -23,4 +25,11 @@ fn main() { let out = [0u8; 3]; tr.transform_pixels(&[[1u8,2,3]], &mut [out]); }).join().unwrap(); + + // Sync // + + let sync = Transform::new_flags_context(ThreadContext::new(), &profile, PixelFormat::RGB_8, &profile, PixelFormat::RGB_8, Intent::Saturation, Flags::NO_CACHE).unwrap(); + let out = [0u8; 3]; + sync.transform_pixels(&[[1u8,2,3]], &mut [out]); + let _: Box<Sync> = Box::new(sync); } diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000..429fbe8 --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,118 @@ +use ffi; +use std::ops; + +#[derive(Debug, Copy, Clone)] +/// Flags for creating `Transform`. Can be OR-ed together with `|`. +/// +/// There's a special `NO_CACHE` flag that enables sharing transform between threads. +pub struct Flags<T: CacheFlag = AllowCache>(pub u32, T); + +impl Flags { + /// Inhibit 1-pixel cache. This is required to make `Transform` implement `Sync` + pub const NO_CACHE: Flags<DisallowCache> = Flags(ffi::FLAGS_NOCACHE, DisallowCache); + /// Inhibit optimizations + pub const NO_OPTIMIZE: Flags = Flags(ffi::FLAGS_NOOPTIMIZE, AllowCache); + /// Don't transform anyway + pub const NULL_TRANSFORM: Flags = Flags(ffi::FLAGS_NULLTRANSFORM, AllowCache); + + /// Proofing flags + /// Out of Gamut alarm + pub const GAMUT_CHECK: Flags = Flags(ffi::FLAGS_GAMUTCHECK, AllowCache); + /// Do softproofing + pub const SOFT_PROOFING: Flags = Flags(ffi::FLAGS_SOFTPROOFING, AllowCache); + + // Misc + pub const BLACKPOINT_COMPENSATION: Flags = Flags(ffi::FLAGS_BLACKPOINTCOMPENSATION, AllowCache); + /// Don't fix scum dot + pub const NO_WHITE_ON_WHITE_FIXUP: Flags = Flags(ffi::FLAGS_NOWHITEONWHITEFIXUP, AllowCache); + /// Use more memory to give better accurancy + pub const HIGHRES_PRECALC: Flags = Flags(ffi::FLAGS_HIGHRESPRECALC, AllowCache); + /// Use less memory to minimize resources + pub const LOWRES_PRECALC: Flags = Flags(ffi::FLAGS_LOWRESPRECALC, AllowCache); + + /// For devicelink creation + /// Create 8 bits devicelinks + pub const DEVICELINK_8BITS: Flags = Flags(ffi::FLAGS_8BITS_DEVICELINK, AllowCache); + /// Guess device class (for transform2devicelink) + pub const GUESS_DEVICE_CLASS: Flags = Flags(ffi::FLAGS_GUESSDEVICECLASS, AllowCache); + /// Keep profile sequence for devicelink creation + pub const KEEP_SEQUENCE: Flags = Flags(ffi::FLAGS_KEEP_SEQUENCE, AllowCache); + + /// Specific to a particular optimizations + /// Force CLUT optimization + pub const FORCE_CLUT: Flags = Flags(ffi::FLAGS_FORCE_CLUT, AllowCache); + /// create postlinearization tables if possible + pub const CLUT_POST_LINEARIZATION: Flags = Flags(ffi::FLAGS_CLUT_POST_LINEARIZATION, AllowCache); + /// create prelinearization tables if possible + pub const CLUT_PRE_LINEARIZATION: Flags = Flags(ffi::FLAGS_CLUT_PRE_LINEARIZATION, AllowCache); + + /// Specific to unbounded mode + /// Prevent negative numbers in floating point transforms + pub const NO_NEGATIVES: Flags = Flags(ffi::FLAGS_NONEGATIVES, AllowCache); + + /// Alpha channels are copied on cmsDoTransform() + pub const COPY_ALPHA: Flags = Flags(ffi::FLAGS_COPY_ALPHA, AllowCache); + + /// CRD special + pub const NO_DEFAULT_RESOURCE_DEF: Flags = Flags(ffi::FLAGS_NODEFAULTRESOURCEDEF, AllowCache); +} + +impl<T: CacheFlag> ops::BitOr<Flags<T>> for Flags<DisallowCache> { + type Output = Flags<DisallowCache>; + fn bitor(self, other: Flags<T>) -> Flags<DisallowCache> { + Flags(self.0 | other.0, DisallowCache) + } +} + +impl<T: CacheFlag> ops::BitOr<Flags<T>> for Flags<AllowCache> { + type Output = Flags<T>; + fn bitor(self, other: Flags<T>) -> Flags<T> { + Flags(self.0 | other.0, other.1) + } +} + +impl<T: CacheFlag> Flags<T> { + pub(crate) fn bits(&self) -> u32 { + self.0 + } + + pub(crate) fn allow_cache(&self) -> Flags { + Flags(self.0, AllowCache) + } + + pub fn has<F: CacheFlag>(&mut self, flag: Flags<F>) -> bool { + 0 != (self.0 & flag.0) + } +} + +impl Default for Flags { + /// Default flags + /// + /// By default allows non-thread-safe cache, which improves performance, but limits transforms to use by one thread only. + fn default() -> Self { + Flags(0, AllowCache) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct DisallowCache; +#[derive(Copy, Clone, Debug)] +pub struct AllowCache; + +pub trait CacheFlag: Sized {} +impl CacheFlag for AllowCache {} +impl CacheFlag for DisallowCache {} + +#[test] +fn flags() { + let _ = Flags::default(); + let mut t = Flags::COPY_ALPHA | Flags::NO_OPTIMIZE; + t = t | Flags::CLUT_PRE_LINEARIZATION; + assert!(t.has(Flags::CLUT_PRE_LINEARIZATION)); + assert!(t.has(Flags::COPY_ALPHA)); + assert!(t.has(Flags::NO_OPTIMIZE)); + assert!(!t.has(Flags::DEVICELINK_8BITS)); + assert!(!t.has(Flags::GAMUT_CHECK)); + let _ = Flags::default() | Flags::NO_CACHE; + let _ = Flags::NO_CACHE | Flags::CLUT_PRE_LINEARIZATION; +} diff --git a/src/lib.rs b/src/lib.rs index d312d1c..860846a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod namedcolorlist; mod pipeline; mod eval; mod ext; +mod flags; mod locale; mod stage; mod transform; @@ -30,6 +31,7 @@ pub use ciecam::*; pub use context::{GlobalContext, ThreadContext}; pub use mlu::*; pub use ext::*; +pub use flags::*; pub use locale::*; pub use pipeline::*; pub use stage::*; diff --git a/src/profile.rs b/src/profile.rs index 455f174..e585d7e 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -89,8 +89,8 @@ impl Profile<GlobalContext> { /// Generates a device-link profile from a given color transform. This profile can then be used by any other function accepting profile handle. /// Depending on the specified version number, the implementation of the devicelink may vary. Accepted versions are in range 1.0…4.3 - pub fn new_device_link<F, T>(transform: &Transform<F, T>, version: f64, flags: u32) -> LCMSResult<Self> { - Self::new_handle(unsafe { ffi::cmsTransform2DeviceLink(transform.handle, version, flags) }) + pub fn new_device_link<F, T>(transform: &Transform<F, T>, version: f64, flags: Flags) -> LCMSResult<Self> { + Self::new_handle(unsafe { ffi::cmsTransform2DeviceLink(transform.handle, version, flags.bits()) }) } } diff --git a/src/transform.rs b/src/transform.rs index 67195fd..329ff6f 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -17,23 +17,35 @@ use std::marker::PhantomData; /// then don't worry! Write some code that calls `transform_pixels()`, /// because this is the function that makes the type of the transform clear. /// +/// In case you need to store the transform in a struct or return from a function, the full type of `Transform` is: +/// +/// ```rust,ignore +/// Transform<InputPixelFormat, OutputPixelFormat, Context = GlobalContext, Flags = AllowCache> +/// ``` +/// /// * `InputPixelFormat` — e.g. `(u8,u8,u8)` or struct `RGB<u8>`, etc. /// The type must have appropriate number of bytes per pixel (i.e. you can't just use `[u8]` for everything). /// * `OutputPixelFormat` — similar to `InputPixelFormat`. If both are the same, then `transform_in_place()` function works. /// * `Context` — it's `GlobalContext` for the default non-thread-safe version, or `ThreadContext` for thread-safe version. +/// * `Flags` — `AllowCache` or `DisallowCache`. If you disallow cache, then the transform will be accessible from multiple threads. +/// /// Thread-safety: /// /// * Transform is `Send` if you create it with `ThreadContext` (use `new_*_context()` functions). -pub struct Transform<InputPixelFormat, OutputPixelFormat, Context = GlobalContext> { +/// * Transform is `Sync` if you create it without cache. Set flags to `Flags::NO_CACHE`. +/// +pub struct Transform<InputPixelFormat, OutputPixelFormat, Context = GlobalContext, Flags = AllowCache> { pub(crate) handle: ffi::HTRANSFORM, _from: PhantomData<InputPixelFormat>, _to: PhantomData<OutputPixelFormat>, _context_ref: PhantomData<Context>, + _flags_ref: PhantomData<Flags>, } -unsafe impl<'a, F, T, C: Send> Send for Transform<F, T, C> {} +unsafe impl<'a, F, T, C: Send, Z> Send for Transform<F, T, C, Z> {} +unsafe impl<'a, F, T, C: Send> Sync for Transform<F, T, C, DisallowCache> {} -impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform<InputPixelFormat, OutputPixelFormat, GlobalContext> { +impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform<InputPixelFormat, OutputPixelFormat, GlobalContext, AllowCache> { /// Creates a color transform for translating bitmaps. /// /// Basic, non-tread-safe version. @@ -50,17 +62,18 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform< output: &Profile, out_format: PixelFormat, intent: Intent) -> LCMSResult<Self> { - Self::new_flags(input, in_format, output, out_format, intent, 0) + Self::new_flags(input, in_format, output, out_format, intent, Flags::default()) } - pub fn new_flags(input: &Profile, + /// Non-thread-safe + pub fn new_flags<Fl: CacheFlag>(input: &Profile, in_format: PixelFormat, output: &Profile, out_format: PixelFormat, intent: Intent, - flags: u32) + flags: Flags<Fl>) -> LCMSResult<Self> { - Self::new_flags_context(GlobalContext::new(), input, in_format, output, out_format, intent, flags) + Self::new_flags_context(GlobalContext::new(), input, in_format, output, out_format, intent, flags.allow_cache()) } /// A proofing transform does emulate the colors that would appear as the image were rendered on a specific device. @@ -75,7 +88,7 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform< pub fn new_proofing(input: &Profile, in_format: PixelFormat, output: &Profile, out_format: PixelFormat, proofing: &Profile, intent: Intent, proofng_intent: Intent, - flags: u32) + flags: Flags) -> LCMSResult<Self> { Self::new_proofing_context(GlobalContext::new(), input, in_format, output, out_format, proofing, intent, proofng_intent, flags) } @@ -84,12 +97,12 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform< /// /// User passes in an array of handles to open profiles. The returned color transform do "smelt" all profiles in a single devicelink. /// Color spaces must be paired with the exception of Lab/XYZ, which can be interchanged. - pub fn new_multiprofile(profiles: &[&Profile], in_format: PixelFormat, out_format: PixelFormat, intent: Intent, flags: u32) -> LCMSResult<Self> { + pub fn new_multiprofile(profiles: &[&Profile], in_format: PixelFormat, out_format: PixelFormat, intent: Intent, flags: Flags) -> LCMSResult<Self> { Self::new_multiprofile_context(GlobalContext::new(), profiles, in_format, out_format, intent, flags) } } -impl<PixelFormat: Copy + Clone, Ctx: Context> Transform<PixelFormat, PixelFormat, Ctx> { +impl<PixelFormat: Copy + Clone, Ctx: Context, C> Transform<PixelFormat, PixelFormat, Ctx, C> { pub fn transform_in_place(&self, srcdst: &mut [PixelFormat]) { let size = srcdst.len(); assert!(size < std::u32::MAX as usize); @@ -102,7 +115,17 @@ impl<PixelFormat: Copy + Clone, Ctx: Context> Transform<PixelFormat, PixelFormat } } -impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Context> Transform<InputPixelFormat, OutputPixelFormat, Ctx> { +impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Context> Transform<InputPixelFormat, OutputPixelFormat, Ctx, AllowCache> { + // Same as `new()`, but allows specifying thread-safe context (enables `Send`) + // + // For `Sync`, see `new_flags_context` and `Flags::NO_CACHE` + pub fn new_context(context: Ctx, input: &Profile<Ctx>, in_format: PixelFormat, + output: &Profile<Ctx>, out_format: PixelFormat, intent: Intent) -> LCMSResult<Self> { + Self::new_flags_context(context, input, in_format, output, out_format, intent, Flags::default()) + } +} + +impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Context, Fl: CacheFlag> Transform<InputPixelFormat, OutputPixelFormat, Ctx, Fl> { fn new_handle(handle: ffi::HTRANSFORM, in_format: PixelFormat, out_format: PixelFormat) -> LCMSResult<Self> { if handle.is_null() { Err(Error::ObjectCreationError) @@ -112,6 +135,7 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Conte _from: Self::check_format::<InputPixelFormat>(in_format, true), _to: Self::check_format::<OutputPixelFormat>(out_format, false), _context_ref: PhantomData, + _flags_ref: PhantomData, }) } } @@ -149,20 +173,15 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Conte unsafe { ffi::cmsGetTransformOutputFormat(self.handle) as PixelFormat } } - pub fn new_context(context: Ctx, input: &Profile<Ctx>, in_format: PixelFormat, - output: &Profile<Ctx>, out_format: PixelFormat, intent: Intent) -> LCMSResult<Self> { - Self::new_flags_context(context, input, in_format, output, out_format, intent, 0) - } - pub fn new_flags_context(context: Ctx, input: &Profile<Ctx>, in_format: PixelFormat, output: &Profile<Ctx>, out_format: PixelFormat, - intent: Intent, flags: u32) + intent: Intent, flags: Flags<Fl>) -> LCMSResult<Self> { Self::new_handle(unsafe { ffi::cmsCreateTransformTHR(context.as_ptr(), input.handle, in_format, output.handle, out_format, - intent, flags) + intent, flags.bits()) }, in_format, out_format) } @@ -170,22 +189,22 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Conte pub fn new_proofing_context(context: Ctx, input: &Profile<Ctx>, in_format: PixelFormat, output: &Profile<Ctx>, out_format: PixelFormat, proofing: &Profile<Ctx>, intent: Intent, proofng_intent: Intent, - flags: u32) + flags: Flags<Fl>) -> LCMSResult<Self> { Self::new_handle(unsafe { ffi::cmsCreateProofingTransformTHR(context.as_ptr(), input.handle, in_format, output.handle, out_format, - proofing.handle, intent, proofng_intent, flags) + proofing.handle, intent, proofng_intent, flags.bits()) }, in_format, out_format) } fn new_multiprofile_context(context: Ctx, profiles: &[&Profile], - in_format: PixelFormat, out_format: PixelFormat, intent: Intent, flags: u32) -> LCMSResult<Self> { + in_format: PixelFormat, out_format: PixelFormat, intent: Intent, flags: Flags<Fl>) -> LCMSResult<Self> { let mut handles: Vec<_> = profiles.iter().map(|p| p.handle).collect(); unsafe { Self::new_handle( - ffi::cmsCreateMultiprofileTransformTHR(context.as_ptr(), handles.as_mut_ptr(), handles.len() as u32, in_format, out_format, intent, flags), + ffi::cmsCreateMultiprofileTransformTHR(context.as_ptr(), handles.as_mut_ptr(), handles.len() as u32, in_format, out_format, intent, flags.bits()), in_format, out_format, ) @@ -193,7 +212,7 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, Ctx: Conte } } -impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform<InputPixelFormat, OutputPixelFormat, GlobalContext> { +impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone, C> Transform<InputPixelFormat, OutputPixelFormat, GlobalContext, C> { /// Adaptation state for absolute colorimetric intent, on all but cmsCreateExtendedTransform. /// /// See `ThreadContext::adaptation_state()` @@ -235,10 +254,11 @@ impl<InputPixelFormat: Copy + Clone, OutputPixelFormat: Copy + Clone> Transform< } } -impl<F, T, C> Drop for Transform<F, T, C> { +impl<F, T, C, L> Drop for Transform<F, T, C, L> { fn drop(&mut self) { unsafe { ffi::cmsDeleteTransform(self.handle); } } } + diff --git a/tests/transform.rs b/tests/transform.rs index 4b51046..abcea48 100644 --- a/tests/transform.rs +++ b/tests/transform.rs @@ -84,7 +84,7 @@ fn transform() { let tiny2 = tiny.icc().unwrap(); let tiny2 = Profile::new_icc(&tiny2).unwrap(); - let tr = Transform::new_flags(&tiny, PixelFormat::RGBA_8, &tiny2, PixelFormat::RGB_16, Intent::Perceptual, 0).unwrap(); + let tr = Transform::new_flags(&tiny, PixelFormat::RGBA_8, &tiny2, PixelFormat::RGB_16, Intent::Perceptual, Flags::default()).unwrap(); let src = vec![0xFFFFFFFFu32,0,0x7F7F7F7F,0x10101010]; let mut dst = vec![RGB16{r:0,g:1,b:2}; 4]; tr.transform_pixels(&src, &mut dst); @@ -114,7 +114,7 @@ fn context() { let t = Transform::new_proofing_context(&c, &in_p, PixelFormat::RGB_8, &out_p, PixelFormat::RGB_8, - &proof, Intent::Perceptual, Intent::Perceptual, 0).unwrap(); + &proof, Intent::Perceptual, Intent::Perceptual, Flags::default()).unwrap(); let tmp = (0u8,0u8,0u8); t.transform_in_place(&mut [tmp]); } -- GitLab