use crate::opbasics::*;
use std::cmp;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OpBaseCurve {
pub exposure: f32,
pub points: Vec<(f32, f32)>,
}
impl OpBaseCurve {
pub fn new(img: &ImageSource) -> OpBaseCurve {
match img {
ImageSource::Raw(_) => {
OpBaseCurve{
exposure: 0.0,
points: vec![(0.50, 0.60)],
}
},
ImageSource::Other(_) => {
OpBaseCurve{
exposure: 0.0,
points: vec![],
}
}
}
}
}
impl<'a> ImageOp<'a> for OpBaseCurve {
fn name(&self) -> &str {"basecurve"}
fn run(&self, _pipeline: &PipelineGlobals, buf: Arc<OpBuffer>) -> Arc<OpBuffer> {
if self.points.len() == 0 && self.exposure.abs() < 0.001 {
return buf
}
let mut final_points = self.points.clone();
for (_, to) in final_points.iter_mut() {
*to = *to * self.exposure.exp2();
}
let func = SplineFunc::new(&final_points);
Arc::new(buf.mutate_lines_copying(&(|line: &mut [f32], _| {
for pix in line.chunks_exact_mut(3) {
pix[0] = func.interpolate(pix[0]);
}
})))
}
}
impl OpBaseCurve {
pub fn get_spline(&self) -> SplineFunc {
SplineFunc::new(&self.points)
}
}
pub struct SplineFunc {
points: Vec<(f32,f32)>,
c1s: Vec<f32>,
c2s: Vec<f32>,
c3s: Vec<f32>,
}
impl SplineFunc {
pub fn new(p: &[(f32,f32)]) -> SplineFunc {
let mut points = Vec::new();
if p.len() == 0 || (p[0].0 > 0.0 && p[0].1 > 0.0) {
points.push((0.0, 0.0));
}
points.extend_from_slice(p);
if p.len() == 0 || (p[p.len()-1].0 < 1.0 && p[p.len()-1].1 < 1.0) {
points.push((1.0, 1.0));
}
let mut dxs = Vec::new();
let mut dys = Vec::new();
let mut slopes = Vec::new();
for i in 0..(points.len()-1) {
let dx = points[i+1].0 - points[i].0;
let dy = points[i+1].1 - points[i].1;
dxs.push(dx);
dys.push(dy);
slopes.push(dy/dx);
}
let mut c1s = vec![slopes[0]];
for i in 0..(dxs.len()-1) {
let m = slopes[i];
let next = slopes[i+1];
if m*next <= 0.0 {
c1s.push(0.0);
} else {
let dx = dxs[i];
let dxnext = dxs[i+1];
let common = dx + dxnext;
c1s.push(3.0*common/((common+dxnext)/m + (common + dx)/next));
}
}
c1s.push(slopes[slopes.len()-1]);
let mut c2s = Vec::new();
let mut c3s = Vec::new();
for i in 0..(c1s.len()-1) {
let c1 = c1s[i];
let slope = slopes[i];
let invdx = 1.0 / dxs[i];
let common = c1+c1s[i+1] - slope - slope;
c2s.push((slope-c1-common)*invdx);
c3s.push(common*invdx*invdx);
}
SplineFunc {
points: points,
c1s: c1s,
c2s: c2s,
c3s: c3s,
}
}
pub fn interpolate(&self, val: f32) -> f32 {
let end = self.points[self.points.len()-1].0;
if val >= end {
return self.points[self.points.len()-1].1;
}
let first = self.points[0].0;
if val <= first {
return self.points[0].1;
}
let mut low: isize = 0;
let mut mid: isize;
let mut high: isize = (self.c3s.len() - 1) as isize;
while low <= high {
mid = (low+high)/2;
let xhere = self.points[mid as usize].0;
if xhere < val { low = mid + 1; }
else if xhere > val { high = mid - 1; }
else { return self.points[mid as usize].1 }
}
let i = cmp::max(0, high) as usize;
let diff = val - self.points[i].0;
self.points[i].1 + self.c1s[i]*diff + self.c2s[i]*diff*diff + self.c3s[i]*diff*diff*diff
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extremes() {
let spline = SplineFunc::new(&[]);
assert_eq!(spline.interpolate(0.0), 0.0);
assert_eq!(spline.interpolate(1.0), 1.0);
}
#[test]
fn saturates() {
let spline = SplineFunc::new(&[]);
assert_eq!(spline.interpolate(1.5), 1.0);
assert_eq!(spline.interpolate(-0.2), 0.0);
}
#[test]
fn high_blackpoint() {
let spline = SplineFunc::new(&[(0.0,0.2)]);
assert_eq!(spline.interpolate(0.0), 0.2);
}
#[test]
fn low_whitepoint() {
let spline = SplineFunc::new(&[(1.0,0.8)]);
assert_eq!(spline.interpolate(1.0), 0.8);
}
}