pub const GIT_HASH: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); use vek::{Mat3, Rgb, Rgba, Vec3}; /// This is a fast approximation of powf. This should only be used when minor accuracy loss is acceptable. #[inline(always)] #[allow(unsafe_code)] fn approx_powf(b: f32, e: f32) -> f32 { unsafe { let b = b as f64; let e = e as f64; union Swagger { f: f64, a: [i32; 2], } let mut b = Swagger { f: b }; b.a[1] = (e * (b.a[1] as f64 - 1072632447.0) + 1072632447.0) as i32; b.a[0] = 0; b.f as f32 } } #[cfg(test)] mod approx_powf_tests { fn close_ei(a: f32, b: f32) -> bool { (a - b < 1.0 && a - b > 0.0) || (b - a < 1.0 && b - a > 0.0) } #[test] fn accuracy_1() { let test_values: Vec = vec![3.0, 2.5, 1.5, 2.2]; test_values.windows(2).for_each(|a| { assert!(close_ei(a[0].powf(a[1]), super::approx_powf(a[0], a[1]))); }); } } #[inline(always)] pub fn srgb_to_linear(col: Rgb) -> Rgb { #[inline(always)] fn to_linear(x: f32) -> f32 { if x <= 0.04045 { x / 12.92 } else { approx_powf((x + 0.055) / 1.055, 2.4) } } col.map(to_linear) } #[inline(always)] pub fn linear_to_srgb(col: Rgb) -> Rgb { #[inline(always)] fn to_srgb(x: f32) -> f32 { if x <= 0.0031308 { x * 12.92 } else { approx_powf(x, 1.0 / 2.4) * 1.055 - 0.055 } } col.map(to_srgb) } #[inline(always)] pub fn srgba_to_linear(col: Rgba) -> Rgba { Rgba::from_translucent(srgb_to_linear(Rgb::from(col)), col.a) } #[inline(always)] pub fn linear_to_srgba(col: Rgba) -> Rgba { Rgba::from_translucent(linear_to_srgb(Rgb::from(col)), col.a) } /// Convert rgb to hsv. Expects rgb to be [0, 1]. #[inline(always)] pub fn rgb_to_hsv(rgb: Rgb) -> Vec3 { let (r, g, b) = rgb.into_tuple(); let (max, min, diff, add) = { let (max, min, diff, add) = if r > g { (r, g, g - b, 0.0) } else { (g, r, b - r, 2.0) }; if b > max { (b, min, r - g, 4.0) } else { (max, b.min(min), diff, add) } }; let v = max; let h = if max == min { 0.0 } else { let mut h = 60.0 * (add + diff / (max - min)); if h < 0.0 { h += 360.0; } h }; let s = if max == 0.0 { 0.0 } else { (max - min) / max }; Vec3::new(h, s, v) } /// Convert hsv to rgb. Expects h [0, 360], s [0, 1], v [0, 1] #[inline(always)] pub fn hsv_to_rgb(hsv: Vec3) -> Rgb { let (h, s, v) = hsv.into_tuple(); let c = s * v; let h = h / 60.0; let x = c * (1.0 - (h % 2.0 - 1.0).abs()); let m = v - c; let (r, g, b) = if h >= 0.0 && h <= 1.0 { (c, x, 0.0) } else if h <= 2.0 { (x, c, 0.0) } else if h <= 3.0 { (0.0, c, x) } else if h <= 4.0 { (0.0, x, c) } else if h <= 5.0 { (x, 0.0, c) } else { (c, 0.0, x) }; Rgb::new(r + m, g + m, b + m) } /// Convert linear rgb to CIExyY #[inline(always)] pub fn rgb_to_xyy(rgb: Rgb) -> Vec3 { // XYZ let xyz = Mat3::new( 0.4124, 0.3576, 0.1805, 0.2126, 0.7152, 0.0722, 0.0193, 0.1192, 0.9504, ) * Vec3::from(rgb); let sum = xyz.sum(); Vec3::new(xyz.x / sum, xyz.y / sum, xyz.y) } /// Convert to CIExyY to linear rgb #[inline(always)] pub fn xyy_to_rgb(xyy: Vec3) -> Rgb { let xyz = Vec3::new( xyy.z / xyy.y * xyy.x, xyy.z, xyy.z / xyy.y * (1.0 - xyy.x - xyy.y), ); Rgb::from( Mat3::new( 3.2406, -1.5372, -0.4986, -0.9689, 1.8758, 0.0415, 0.0557, -0.2040, 1.0570, ) * xyz, ) } // TO-DO: speed this up #[inline(always)] pub fn saturate_srgb(col: Rgb, value: f32) -> Rgb { let mut hsv = rgb_to_hsv(srgb_to_linear(col)); hsv.y *= 1.0 + value; linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.min(1.0).max(0.0))) } /// Preserves the luma of one color while changing its chromaticty to match the other #[inline(always)] pub fn chromify_srgb(luma: Rgb, chroma: Rgb) -> Rgb { let l = rgb_to_xyy(srgb_to_linear(luma)).z; let mut xyy = rgb_to_xyy(srgb_to_linear(chroma)); xyy.z = l; linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.min(1.0).max(0.0))) }