use crate::{Crop, RGB};
const SKIN_COLOR: RGB = RGB {
r: 234,
g: 171,
b: 132,
};
const OUTSIDE_IMPORTANCE: f64 = -0.5;
const EDGE_RADIUS: f64 = 0.4;
const EDGE_WEIGHT: f64 = -20.0;
const RULE_OF_THIRDS: bool = true;
fn thirds(x: f64) -> f64 {
let x = ((x - (1.0 / 3.0) + 1.0) % 2.0 * 0.5 - 0.5) * 16.0;
f64::max(1.0 - x * x, 0.0)
}
pub fn bounds(l: f64) -> u8 {
l.clamp(0.0, 255.0).round() as u8
}
pub fn skin_col(c: RGB) -> f64 {
const K: f64 = 0.9420138987639984;
let skin_color: Vec<f64> = SKIN_COLOR.normalize().iter().map(|c| c / K).collect();
let [r_norm, g_norm, b_norm] = c.normalize();
let dr = r_norm - skin_color[0];
let dg = g_norm - skin_color[1];
let db = b_norm - skin_color[2];
let d = (dr.powi(2) + dg.powi(2) + db.powi(2)).sqrt();
1.0 - d.min(1.0)
}
pub fn importance(crop: &Crop, x: u32, y: u32) -> f64 {
if crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height {
return OUTSIDE_IMPORTANCE;
}
let xf = (x - crop.x) as f64 / (crop.width as f64);
let yf = (y - crop.y) as f64 / (crop.height as f64);
let px = (0.5 - xf).abs() * 2.0;
let py = (0.5 - yf).abs() * 2.0;
let dx = f64::max(px - 1.0 + EDGE_RADIUS, 0.0);
let dy = f64::max(py - 1.0 + EDGE_RADIUS, 0.0);
let d = (dx * dx + dy * dy) * EDGE_WEIGHT;
let mut s = 1.41 - (px * px + py * py).sqrt();
if RULE_OF_THIRDS {
s += (f64::max(0.0, s + d + 0.5) * 1.2) * (thirds(px) + thirds(py))
}
s + d
}
#[cfg(test)]
mod tests {
use crate::Crop;
use super::*;
use proptest::prelude::*;
use proptest::test_runner::Config;
fn gray(c: u8) -> RGB {
RGB::new(c, c, c)
}
#[test]
fn thirds_test() {
assert_eq!(0.0, thirds(0.0));
assert_eq!(0.0, thirds(0.5));
assert_eq!(0.0, thirds(1.0));
assert_eq!(1.0, thirds(1.0 / 3.0));
assert_eq!(0.9288888888888889, thirds(0.9 / 3.0));
assert_eq!(0.9288888888888884, thirds(1.1 / 3.0));
assert_eq!(0.7155555555555557, thirds(1.2 / 3.0));
assert_eq!(0.3599999999999989, thirds(1.3 / 3.0));
assert_eq!(0.0, thirds(1.4 / 3.0));
}
#[test]
fn bounds_test() {
assert_eq!(0, bounds(-1.0));
assert_eq!(0, bounds(0.0));
assert_eq!(10, bounds(10.0));
assert_eq!(255, bounds(255.0));
assert_eq!(255, bounds(255.1));
}
#[test]
fn cie_test() {
assert_eq!(0.0, gray(0).cie());
assert_eq!(331.49999999999994, gray(255).cie());
}
#[test]
fn skin_col_test() {
assert_eq!(0.7550795306611966, skin_col(gray(0)));
assert_eq!(0.7550795306611966, skin_col(gray(1)));
assert_eq!(0.7550795306611966, skin_col(gray(127)));
assert_eq!(0.7550795306611966, skin_col(gray(34)));
assert_eq!(0.7550795306611966, skin_col(gray(255)));
assert_eq!(0.5904611542890027, skin_col(RGB::new(134, 45, 23)));
assert_eq!(0.9384288009573658, skin_col(RGB::new(199, 145, 112)));
assert_eq!(0.9380840524535538, skin_col(RGB::new(100, 72, 56)));
assert_eq!(0.9384445374828501, skin_col(RGB::new(234, 171, 132)));
}
#[test]
fn importance_tests() {
assert_eq!(
-6.404213562373096,
importance(
&Crop {
x: 0,
y: 0,
width: 1,
height: 1
},
0,
0
)
);
}
fn color() -> impl Strategy<Value = RGB> {
(0..=255u8, 0..=255u8, 0..=255u8).prop_map(|(r, g, b)| RGB { r, g, b })
}
fn between_0_and_1() -> impl Strategy<Value = f64> {
(0u64..).prop_map(|i| i as f64 / u64::max_value() as f64)
}
proptest! {
#![proptest_config(Config::with_cases(10000))]
#[test]
fn skin_col_score_is_between_0_and_1(c in color()) {
let score = skin_col(c);
assert!(score >= 0.0 && score <= 0.94);
}
#[test]
fn thirds_result_is_within_defined_boundaries(input in between_0_and_1()) {
let result = thirds(input);
if input > 0.4583333333333332 || input <= 0.2083333333333334 {
assert_eq!(result, 0.0);
} else {
assert!(result > 0.0);
assert!(result <= 1.0);
}
}
}
}