1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::context::Context;
use crate::eval::FloatOrU16;
use crate::{ffi, Error, GlobalContext, LCMSResult, ToneCurveRef};
use foreign_types::{foreign_type, ForeignTypeRef};
use std::fmt;
use std::ptr;

foreign_type! {
    /// 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.
    ///
    /// This is an owned version of `Stage`.
    pub unsafe type Stage {
        type CType = ffi::Stage;
        fn drop = ffi::cmsStageFree;
    }
}

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.
    #[must_use] 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().cast_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 {
    #[must_use]
    pub fn input_channels(&self) -> usize {
        unsafe { ffi::cmsStageInputChannels(self.as_ptr()) as usize }
    }

    #[must_use]
    pub fn output_channels(&self) -> usize {
        unsafe { ffi::cmsStageOutputChannels(self.as_ptr()) as usize }
    }

    #[must_use]
    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 {
    #[cold]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Stage({:?})", self.stage_type())
    }
}