From f8eea52262c841e0c12957f544263b559515a8c3 Mon Sep 17 00:00:00 2001
From: Kornel <kornel@geekhood.net>
Date: Sun, 11 Jun 2017 18:24:01 +0100
Subject: [PATCH] Minimal Stage object

---
 Cargo.toml       |   4 +-
 examples/tags.rs |   8 ++++
 src/eval.rs      |  11 +++++
 src/lib.rs       |   2 +
 src/pipeline.rs  |  19 ++++++++
 src/stage.rs     | 120 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 162 insertions(+), 2 deletions(-)
 create mode 100644 src/stage.rs

diff --git a/Cargo.toml b/Cargo.toml
index d231973..5605e1b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,8 +9,8 @@ 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.0.0"
+version = "4.1.0"
 
 [dependencies]
 foreign-types = "0.2.0"
-lcms2-sys = "2.3.0"
+lcms2-sys = "2.3.1"
diff --git a/examples/tags.rs b/examples/tags.rs
index 7ba22e6..0368dda 100644
--- a/examples/tags.rs
+++ b/examples/tags.rs
@@ -18,5 +18,13 @@ fn main() {
     for sig in profile.tag_signatures() {
         let tag = profile.read_tag(sig);
         println!("{:?} = {:?}", sig, tag);
+        match tag {
+            Tag::Pipeline(pipeline) => {
+                for stage in pipeline.stages() {
+                    println!(" └─ {:?}", stage);
+                }
+            }
+            _ => {}
+        }
     }
 }
diff --git a/src/eval.rs b/src/eval.rs
index a9f3424..3424fd8 100644
--- a/src/eval.rs
+++ b/src/eval.rs
@@ -3,6 +3,7 @@ use super::*;
 pub trait FloatOrU16: Sized + Copy {
     unsafe fn eval_tone_curve(self, handle: *const ffi::ToneCurve) -> Self;
     unsafe fn eval_pipeline(handle: *const ffi::Pipeline, input: &[Self], out: &mut [Self]);
+    unsafe fn stage_alloc_clut(contextid: ffi::Context, ngridpoints: u32, inputchan: u32, outputchan: u32, table: *const Self) -> *mut ffi::Stage;
 }
 
 impl FloatOrU16 for f32 {
@@ -15,6 +16,11 @@ impl FloatOrU16 for f32 {
     unsafe fn eval_pipeline(handle: *const ffi::Pipeline, input: &[Self], out: &mut [Self]) {
         ffi::cmsPipelineEvalFloat(input.as_ptr(), out.as_mut_ptr(), handle)
     }
+
+    #[inline]
+    unsafe fn stage_alloc_clut(contextid: ffi::Context, ngridpoints: u32, inputchan: u32, outputchan: u32, table: *const Self) -> *mut ffi::Stage {
+        ffi::cmsStageAllocCLutFloat(contextid, ngridpoints, inputchan, outputchan, table)
+    }
 }
 
 impl FloatOrU16 for u16 {
@@ -27,4 +33,9 @@ impl FloatOrU16 for u16 {
     unsafe fn eval_pipeline(handle: *const ffi::Pipeline, input: &[Self], out: &mut [Self]) {
         ffi::cmsPipelineEval16(input.as_ptr(), out.as_mut_ptr(), handle)
     }
+
+    #[inline]
+    unsafe fn stage_alloc_clut(contextid: ffi::Context, ngridpoints: u32, inputchan: u32, outputchan: u32, table: *const Self) -> *mut ffi::Stage {
+        ffi::cmsStageAllocCLut16bit(contextid, ngridpoints, inputchan, outputchan, table)
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 7378953..9f0f201 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,6 +15,7 @@ mod pipeline;
 mod eval;
 mod ext;
 mod locale;
+mod stage;
 mod transform;
 mod tonecurve;
 mod error;
@@ -28,6 +29,7 @@ pub use mlu::*;
 pub use ext::*;
 pub use locale::*;
 pub use pipeline::*;
+pub use stage::*;
 pub use transform::*;
 pub use tonecurve::*;
 pub use namedcolorlist::*;
diff --git a/src/pipeline.rs b/src/pipeline.rs
index 048df8b..d003306 100644
--- a/src/pipeline.rs
+++ b/src/pipeline.rs
@@ -1,5 +1,6 @@
 use super::*;
 use std::fmt;
+use stage::*;
 use std::ptr;
 use eval::FloatOrU16;
 use foreign_types::ForeignTypeRef;
@@ -42,6 +43,24 @@ impl PipelineRef {
         }
     }
 
+    pub fn first_stage(&self) -> Option<&StageRef> {
+        unsafe {
+            let f = ffi::cmsPipelineGetPtrToFirstStage(self.as_ptr());
+            if !f.is_null() {Some(ForeignTypeRef::from_ptr(f))} else {None}
+        }
+    }
+
+    pub fn last_stage(&self) -> Option<&StageRef> {
+        unsafe {
+            let f = ffi::cmsPipelineGetPtrToLastStage(self.as_ptr());
+            if !f.is_null() {Some(ForeignTypeRef::from_ptr(f))} else {None}
+        }
+    }
+
+    pub fn stages(&self) -> StagesIter {
+        StagesIter(self.first_stage())
+    }
+
     pub fn set_8bit(&mut self, on: bool) -> bool {
         unsafe {
             ffi::cmsPipelineSetSaveAs8bitsFlag(self.as_ptr(), on as i32) != 0
diff --git a/src/stage.rs b/src/stage.rs
new file mode 100644
index 0000000..87a530e
--- /dev/null
+++ b/src/stage.rs
@@ -0,0 +1,120 @@
+use super::*;
+use eval::FloatOrU16;
+use std::fmt;
+use std::ptr;
+use foreign_types::ForeignTypeRef;
+use context::Context;
+
+foreign_type! {
+    type CType = ffi::Stage;
+    fn drop = ffi::cmsStageFree;
+    /// This is an owned version of `Stage`.
+    pub struct Stage;
+    /// Stage functions
+    ///
+    /// Stages are single-step operations that can be chained to create pipelines.
+    /// Actual stage types does include matrices, tone curves, Look-up interpolation and user-defined.
+    /// There are functions to create new stage types and a plug-in type to allow stages to be saved in multi profile elements tag types.
+    /// See the plug-in API for further details.
+    pub struct StageRef;
+}
+
+impl Stage {
+    /// Creates an empty (identity) stage that does no operation.
+    ///
+    /// May be needed in order to save the pipeline as AToB/BToA tags in ICC profiles.
+    pub fn new_identity(channels: u32) -> Stage {
+        unsafe {Error::if_null(
+            ffi::cmsStageAllocIdentity(GlobalContext::new().as_ptr(), channels)
+        )}.unwrap()
+    }
+
+    /// Creates a stage that contains nChannels tone curves, one per channel.
+    pub fn new_tone_curves(curves: &[&ToneCurveRef]) -> LCMSResult<Stage> {
+        let ptrs: Vec<_> = curves.iter().map(|c| c.as_ptr() as *const _).collect();
+        unsafe {Error::if_null(
+            ffi::cmsStageAllocToneCurves(GlobalContext::new().as_ptr(), ptrs.len() as u32, ptrs.as_ptr())
+        )}
+    }
+
+    /// Creates a stage that contains a matrix plus an optional offset.
+    ///
+    /// Note that Matrix is specified in double precision, whilst CLUT has only float precision.
+    /// That is because an ICC profile can encode matrices with far more precision that CLUTS.
+    pub fn new_matrix(matrix2d: &[f64], rows: usize, cols: usize, offsets: Option<&[f64]>) -> LCMSResult<Self> {
+        if matrix2d.len() < rows*cols {
+            return Err(Error::MissingData);
+        }
+        if let Some(offsets) = offsets {
+            if offsets.len() < cols {
+                return Err(Error::MissingData);
+            }
+        }
+        unsafe {Error::if_null(
+            ffi::cmsStageAllocMatrix(GlobalContext::new().as_ptr(), rows as u32, cols as u32, matrix2d.as_ptr(),
+                offsets.map(|p|p.as_ptr()).unwrap_or(ptr::null()))
+        )}
+    }
+
+    /// Creates a stage that contains a float or 16 bits multidimensional lookup table (CLUT).
+    ///
+    /// Each dimension has same resolution. The CLUT can be initialized by specifying values in Table parameter.
+    /// The recommended way is to set Table to None and use sample_clut with a callback, because this way the implementation is independent of the selected number of grid points.
+    pub fn new_clut<Value: FloatOrU16>(grid_point_nodes: usize, input_channels: u32, output_channels: u32, table: Option<&[Value]>) -> LCMSResult<Self> {
+        if let Some(table) = table {
+            if table.len() < grid_point_nodes {
+                return Err(Error::MissingData)
+            }
+        }
+        unsafe {Error::if_null(
+            Value::stage_alloc_clut(GlobalContext::new().as_ptr(), grid_point_nodes as u32, input_channels, output_channels,
+                table.map(|p|p.as_ptr()).unwrap_or(ptr::null()))
+        )}
+    }
+}
+
+impl StageRef {
+    pub fn input_channels(&self) -> usize {
+        unsafe {
+            ffi::cmsStageInputChannels(self.as_ptr()) as usize
+        }
+    }
+
+    pub fn output_channels(&self) -> usize {
+        unsafe {
+            ffi::cmsStageOutputChannels(self.as_ptr()) as usize
+        }
+    }
+
+    pub fn stage_type(&self) -> ffi::StageSignature {
+        unsafe {
+            ffi::cmsStageType(self.as_ptr())
+        }
+    }
+}
+
+pub struct StagesIter<'a>(pub Option<&'a StageRef>);
+
+impl<'a> Iterator for StagesIter<'a> {
+    type Item = &'a StageRef;
+    fn next(&mut self) -> Option<Self::Item> {
+        let it = self.0;
+        if let Some(mpe) = it {
+            self.0 = unsafe {
+                let s = ffi::cmsStageNext(mpe.as_ptr());
+                if s.is_null() {
+                    None
+                } else {
+                    Some(ForeignTypeRef::from_ptr(s))
+                }
+            };
+        }
+        it
+    }
+}
+
+impl fmt::Debug for StageRef {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Stage({:?})", self.stage_type())
+    }
+}
-- 
GitLab