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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
use crate::eval::FloatOrU16;
use crate::{ffi, Error, LCMSResult};
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use std::fmt;
use std::ptr;
foreign_type! {
/// Tone curves are powerful constructs that can contain curves specified in diverse ways.
///
/// The curve is stored in segments, where each segment can be sampled or specified by parameters. A 16.bit simplification of the *whole* curve is kept for optimization purposes. For float operation, each segment is evaluated separately. Plug-ins may be used to define new parametric schemes.
///
/// Owned version of `ToneCurveRef`
pub unsafe type ToneCurve {
type CType = ffi::ToneCurve;
fn drop = ffi::cmsFreeToneCurve;
fn clone = ffi::cmsDupToneCurve;
}
}
impl ToneCurve {
/// Simplified wrapper to `new_parametric`. Builds a parametric curve of type 1.
#[must_use]
pub fn new(gamma: f64) -> Self {
unsafe { Self::from_ptr(ffi::cmsBuildGamma(ptr::null_mut(), gamma)) }
}
/// Builds a tone curve based on a table of 16-bit values. Tone curves built with this function are restricted to 0…1.0 domain.
#[track_caller]
#[inline]
#[must_use]
pub fn new_tabulated(values: &[u16]) -> Self {
assert!(values.len() < std::i32::MAX as usize);
unsafe {
Self::new_handle(ffi::cmsBuildTabulatedToneCurve16(
ptr::null_mut(),
values.len() as _,
values.as_ptr(),
))
}
}
/// Builds a tone curve based on a table of floating point values. Tone curves built with this function are **not** restricted to 0…1.0 domain.
#[track_caller]
#[inline]
#[must_use]
pub fn new_tabulated_float(values: &[f32]) -> Self {
assert!(values.len() < std::i32::MAX as usize);
unsafe {
Self::new_handle(ffi::cmsBuildTabulatedToneCurveFloat(
ptr::null_mut(),
values.len() as u32,
values.as_ptr(),
))
}
}
/// See Table 52 in LCMS documentation for descriptino of the types.
///
/// 1. Exponential
/// 2. CIE 122-1966
/// 3. IEC 61966-3
/// 4. IEC 61966-2.1 (sRGB)
/// 5. See PDF
/// 6. Identical to 5, unbounded.
/// 7. See PDF
/// 8. See PDF
/// 108. (108) S-Shaped sigmoidal
///
/// Always use 10-parameter slice for plug-in types.
///
/// If `curve_type` is negative, then the curve is analytically inverted.
pub fn new_parametric(curve_type: i16, params: &[f64]) -> LCMSResult<Self> {
let params_min_len = match curve_type.abs() {
1 => 1,
2 => 3,
3 => 4,
4 => 5,
5 => 7,
6 => 4,
7 => 5,
8 => 6,
108 => 1,
_ => 10,
};
if params.len() < params_min_len {
return Err(Error::MissingData);
}
unsafe {
Error::if_null(ffi::cmsBuildParametricToneCurve(ptr::null_mut(), curve_type.into(), params.as_ptr()))
}
}
#[track_caller]
#[inline]
unsafe fn new_handle(handle: *mut ffi::ToneCurve) -> Self {
assert!(!handle.is_null());
Self::from_ptr(handle)
}
}
impl ToneCurveRef {
/// Creates a tone curve that is the inverse of given tone curve.
#[inline]
#[must_use]
pub fn reversed(&self) -> ToneCurve {
unsafe { ToneCurve::from_ptr(ffi::cmsReverseToneCurve(self.as_ptr())) }
}
/// Creates a tone curve that is the inverse of given tone curve. In the case it couldn’t be analytically reversed, a tablulated curve of `nResultSamples` is created.
#[inline]
#[must_use]
pub fn reversed_samples(&self, samples: usize) -> ToneCurve {
unsafe { ToneCurve::from_ptr(ffi::cmsReverseToneCurveEx(samples as _, self.as_ptr())) }
}
/// Composites two tone curves in the form Y^-1(X(t))
/// (self is X, the argument is Y)
#[must_use]
pub fn join(&self, y: &ToneCurveRef, points: usize) -> ToneCurve {
unsafe {
ToneCurve::from_ptr(ffi::cmsJoinToneCurve(
ptr::null_mut(),
self.as_ptr(),
y.as_ptr(),
points as u32,
))
}
}
/// Returns TRUE if the tone curve contains more than one segment, FALSE if it has only one segment.
#[inline]
#[must_use]
pub fn is_multisegment(&self) -> bool {
unsafe { ffi::cmsIsToneCurveMultisegment(self.as_ptr()) != 0 }
}
/// Returns an estimation of cube being an identity (1:1) in the [0..1] domain. Does not take unbounded parts into account. This is just a coarse approximation, with no mathematical validity.
#[inline]
#[must_use]
pub fn is_linear(&self) -> bool {
unsafe { ffi::cmsIsToneCurveLinear(self.as_ptr()) != 0 }
}
/// Returns an estimation of monotonicity of curve in the [0..1] domain. Does not take unbounded parts into account. This is just a coarse approximation, with no mathematical validity.
#[inline]
#[must_use]
pub fn is_monotonic(&self) -> bool {
unsafe { ffi::cmsIsToneCurveMonotonic(self.as_ptr()) != 0 }
}
/// Does not take unbounded parts into account.
#[inline]
#[must_use]
pub fn is_descending(&self) -> bool {
unsafe { ffi::cmsIsToneCurveDescending(self.as_ptr()) != 0 }
}
#[inline]
#[must_use]
pub fn parametric_type(&self) -> i32 {
unsafe { ffi::cmsGetToneCurveParametricType(self.as_ptr()) }
}
/// Estimates the apparent gamma of the tone curve by using least squares fitting.
/// Precision: The maximum standard deviation allowed on the residuals, 0.01 is a fair value, set it to a big number to fit any curve, mo matter how good is the fit.
#[inline]
#[must_use]
pub fn estimated_gamma(&self, precision: f64) -> Option<f64> {
let g = unsafe { ffi::cmsEstimateGamma(self.as_ptr(), precision) };
if g <= -1.0 { None } else { Some(g) }
}
/// Smoothes tone curve according to the lambda parameter. From: Eilers, P.H.C. (1994) Smoothing and interpolation with finite differences. in: Graphic Gems IV, Heckbert, P.S. (ed.), Academic press.
#[inline]
pub fn smooth(&mut self, lambda: f64) -> bool {
unsafe { ffi::cmsSmoothToneCurve((self as *mut Self).cast(), lambda) != 0 }
}
/// Tone curves do maintain a shadow low-resolution tabulated representation of the curve. This function returns a pointer to this table.
#[must_use]
pub fn estimated_entries(&self) -> &[u16] {
unsafe {
let len = ffi::cmsGetToneCurveEstimatedTableEntries(self.as_ptr()) as usize;
let data = ffi::cmsGetToneCurveEstimatedTable(self.as_ptr());
std::slice::from_raw_parts(data, len)
}
}
/// Evaluates the given number (u16 or f32) across the given tone curve.
///
/// This function is significantly faster for u16, since it uses a pre-computed 16-bit lookup table.
#[inline]
pub fn eval<ToneCurveValue: FloatOrU16>(&self, v: ToneCurveValue) -> ToneCurveValue {
unsafe { v.eval_tone_curve(self.as_ptr()) }
}
}
impl fmt::Debug for ToneCurveRef {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ToneCurve({} entries, gamma ~{:.1})", self.estimated_entries().len(), self.estimated_gamma(1.).unwrap_or(0.))
}
}
#[test]
fn tones() {
let _ = ToneCurve::new(0.);
let _ = ToneCurve::new(1230.);
let _ = ToneCurve::new(-10.);
let g = ToneCurve::new(1. / 2.2);
let r: &ToneCurveRef = &g;
let mut z: ToneCurve = r.to_owned();
assert!(g.estimated_gamma(0.1).is_some());
assert_eq!(1., g.eval(1.));
assert_eq!(0, g.eval(0u16));
assert!(!z.is_linear());
assert!(z.is_monotonic());
assert!(!z.is_descending());
assert!(z.reversed().is_monotonic());
assert!(z.smooth(0.5));
assert_eq!(0, g.estimated_entries()[0]);
assert_eq!(std::u16::MAX, *g.estimated_entries().last().unwrap());
assert!(ToneCurve::new_parametric(7, &[0.]).is_err());
}
|