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