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