use super::*;
#[must_use]
#[inline]
pub fn srgb_to_linear(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
#[must_use]
#[inline]
pub fn linear_to_srgb(c: f32) -> f32 {
if c <= 0.0031308 {
c * 12.92
} else {
1.055 * c.powf(1.0 / 2.4) - 0.055
}
}
#[must_use]
#[inline]
pub fn srgb_to_linear_vec3(color: Vec3) -> Vec3 {
Vec3::new(
srgb_to_linear(color.x),
srgb_to_linear(color.y),
srgb_to_linear(color.z),
)
}
#[must_use]
#[inline]
pub fn linear_to_srgb_vec3(color: Vec3) -> Vec3 {
Vec3::new(
linear_to_srgb(color.x),
linear_to_srgb(color.y),
linear_to_srgb(color.z),
)
}
#[must_use]
#[inline]
pub fn color_matrix_saturation(s: f32) -> Mat4 {
let lr = 0.2126 * (1.0 - s);
let lg = 0.7152 * (1.0 - s);
let lb = 0.0722 * (1.0 - s);
Mat4::from_cols_array_2d(&[
[lr + s, lr, lr, 0.0],
[lg, lg + s, lg, 0.0],
[lb, lb, lb + s, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
#[inline]
pub fn color_matrix_hue_rotate(angle: f32) -> Mat4 {
let cos = angle.cos();
let sin = angle.sin();
let r = Vec3::new(0.2126, 0.7152, 0.0722);
Mat4::from_cols_array_2d(&[
[
r.x + cos * (1.0 - r.x) + sin * (-r.x),
r.x + cos * (-r.x) + sin * 0.143,
r.x + cos * (-r.x) + sin * (-(1.0 - r.x)),
0.0,
],
[
r.y + cos * (-r.y) + sin * (-r.y),
r.y + cos * (1.0 - r.y) + sin * 0.140,
r.y + cos * (-r.y) + sin * r.y,
0.0,
],
[
r.z + cos * (-r.z) + sin * (1.0 - r.z),
r.z + cos * (-r.z) + sin * (-0.283),
r.z + cos * (1.0 - r.z) + sin * r.z,
0.0,
],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
#[inline]
#[allow(clippy::excessive_precision)]
pub fn linear_to_oklab(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
let l_ = l.cbrt();
let m_ = m.cbrt();
let s_ = s.cbrt();
let lab_l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
let lab_a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
let lab_b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
(lab_l, lab_a, lab_b)
}
#[must_use]
#[inline]
#[allow(clippy::excessive_precision)]
pub fn oklab_to_linear(lab_l: f32, lab_a: f32, lab_b: f32) -> (f32, f32, f32) {
let l_ = lab_l + 0.3963377774 * lab_a + 0.2158037573 * lab_b;
let m_ = lab_l - 0.1055613458 * lab_a - 0.0638541728 * lab_b;
let s_ = lab_l - 0.0894841775 * lab_a - 1.2914855480 * lab_b;
let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
let s = s_ * s_ * s_;
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
(r, g, b)
}
#[must_use]
#[inline]
pub fn linear_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let v = max;
let s = if max > crate::EPSILON_F32 {
delta / max
} else {
0.0
};
let h = if delta < crate::EPSILON_F32 {
0.0
} else if (max - r).abs() < crate::EPSILON_F32 {
std::f32::consts::FRAC_PI_3 * ((g - b) / delta).rem_euclid(6.0)
} else if (max - g).abs() < crate::EPSILON_F32 {
std::f32::consts::FRAC_PI_3 * ((b - r) / delta + 2.0)
} else {
std::f32::consts::FRAC_PI_3 * ((r - g) / delta + 4.0)
};
(h, s, v)
}
#[must_use]
#[inline]
pub fn hsv_to_linear(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
if s < crate::EPSILON_F32 {
return (v, v, v);
}
let h_deg = h / std::f32::consts::FRAC_PI_3; let sector = h_deg.floor() as i32;
let f = h_deg - sector as f32;
let p = v * (1.0 - s);
let q = v * (1.0 - s * f);
let t = v * (1.0 - s * (1.0 - f));
match sector.rem_euclid(6) {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q),
}
}
#[must_use]
#[inline]
pub fn linear_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let l = (max + min) * 0.5;
let s = if delta < crate::EPSILON_F32 {
0.0
} else {
delta / (1.0 - (2.0 * l - 1.0).abs())
};
let h = if delta < crate::EPSILON_F32 {
0.0
} else if (max - r).abs() < crate::EPSILON_F32 {
std::f32::consts::FRAC_PI_3 * ((g - b) / delta).rem_euclid(6.0)
} else if (max - g).abs() < crate::EPSILON_F32 {
std::f32::consts::FRAC_PI_3 * ((b - r) / delta + 2.0)
} else {
std::f32::consts::FRAC_PI_3 * ((r - g) / delta + 4.0)
};
(h, s, l)
}
#[must_use]
#[inline]
pub fn hsl_to_linear(h: f32, s: f32, l: f32) -> (f32, f32, f32) {
if s < crate::EPSILON_F32 {
return (l, l, l);
}
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let h_deg = h / std::f32::consts::FRAC_PI_3; let x = c * (1.0 - (h_deg.rem_euclid(2.0) - 1.0).abs());
let m = l - c * 0.5;
let sector = h_deg.floor() as i32;
let (r, g, b) = match sector.rem_euclid(6) {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(r + m, g + m, b + m)
}
#[must_use]
#[inline]
pub fn premultiply_alpha(r: f32, g: f32, b: f32, a: f32) -> (f32, f32, f32, f32) {
(r * a, g * a, b * a, a)
}
#[must_use]
#[inline]
pub fn unpremultiply_alpha(r: f32, g: f32, b: f32, a: f32) -> (f32, f32, f32, f32) {
if a < crate::EPSILON_F32 {
(0.0, 0.0, 0.0, 0.0)
} else {
let inv_a = 1.0 / a;
(r * inv_a, g * inv_a, b * inv_a, a)
}
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_src_over(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_sa = 1.0 - sa;
(
sr + dr * inv_sa,
sg + dg * inv_sa,
sb + db * inv_sa,
sa + da * inv_sa,
)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_dst_over(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_da = 1.0 - da;
(
dr + sr * inv_da,
dg + sg * inv_da,
db + sb * inv_da,
da + sa * inv_da,
)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_src_in(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
_dr: f32,
_dg: f32,
_db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
(sr * da, sg * da, sb * da, sa * da)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_dst_in(
_sr: f32,
_sg: f32,
_sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
(dr * sa, dg * sa, db * sa, da * sa)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_src_out(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
_dr: f32,
_dg: f32,
_db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_da = 1.0 - da;
(sr * inv_da, sg * inv_da, sb * inv_da, sa * inv_da)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_dst_out(
_sr: f32,
_sg: f32,
_sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_sa = 1.0 - sa;
(dr * inv_sa, dg * inv_sa, db * inv_sa, da * inv_sa)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_src_atop(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_sa = 1.0 - sa;
(
sr * da + dr * inv_sa,
sg * da + dg * inv_sa,
sb * da + db * inv_sa,
da,
)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_dst_atop(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_da = 1.0 - da;
(
dr * sa + sr * inv_da,
dg * sa + sg * inv_da,
db * sa + sb * inv_da,
sa,
)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_xor(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
let inv_sa = 1.0 - sa;
let inv_da = 1.0 - da;
(
sr * inv_da + dr * inv_sa,
sg * inv_da + dg * inv_sa,
sb * inv_da + db * inv_sa,
sa * inv_da + da * inv_sa,
)
}
#[must_use]
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn composite_plus(
sr: f32,
sg: f32,
sb: f32,
sa: f32,
dr: f32,
dg: f32,
db: f32,
da: f32,
) -> (f32, f32, f32, f32) {
(
(sr + dr).min(1.0),
(sg + dg).min(1.0),
(sb + db).min(1.0),
(sa + da).min(1.0),
)
}
#[must_use]
#[inline]
pub fn tonemap_reinhard(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
(r / (1.0 + r), g / (1.0 + g), b / (1.0 + b))
}
#[must_use]
#[inline]
pub fn tonemap_reinhard_extended(r: f32, g: f32, b: f32, white: f32) -> (f32, f32, f32) {
let w2 = white * white;
let map = |c: f32| c * (1.0 + c / w2) / (1.0 + c);
(map(r), map(g), map(b))
}
#[must_use]
#[inline]
pub fn tonemap_aces(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let map = |x: f32| {
let a = 2.51;
let b = 0.03;
let c = 2.43;
let d = 0.59;
let e = 0.14;
((x * (a * x + b)) / (x * (c * x + d) + e)).clamp(0.0, 1.0)
};
(map(r), map(g), map(b))
}
#[must_use]
#[inline]
pub fn linearize_depth(ndc_depth: f32, near: f32, far: f32) -> f32 {
near * far / (far - ndc_depth * (far - near))
}
#[must_use]
#[inline]
pub fn linearize_depth_reverse_z(ndc_depth: f32, near: f32) -> f32 {
near / ndc_depth
}
#[must_use]
#[inline]
pub fn lerp_srgb(a: (f32, f32, f32), b: (f32, f32, f32), t: f32) -> (f32, f32, f32) {
let al = (
srgb_to_linear(a.0),
srgb_to_linear(a.1),
srgb_to_linear(a.2),
);
let bl = (
srgb_to_linear(b.0),
srgb_to_linear(b.1),
srgb_to_linear(b.2),
);
let r = al.0 + (bl.0 - al.0) * t;
let g = al.1 + (bl.1 - al.1) * t;
let b_ch = al.2 + (bl.2 - al.2) * t;
(linear_to_srgb(r), linear_to_srgb(g), linear_to_srgb(b_ch))
}
#[must_use]
#[inline]
pub fn lerp_srgb_vec3(a: Vec3, b: Vec3, t: f32) -> Vec3 {
let al = srgb_to_linear_vec3(a);
let bl = srgb_to_linear_vec3(b);
let lerped = al + (bl - al) * t;
linear_to_srgb_vec3(lerped)
}
#[must_use]
#[inline]
pub fn ev100_to_luminance(ev: f32) -> f32 {
2.0_f32.powf(ev - 3.0) * 12.5 / std::f32::consts::PI
}
#[must_use]
#[inline]
pub fn luminance_to_ev100(luminance: f32) -> f32 {
(luminance * std::f32::consts::PI / 12.5).log2() + 3.0
}
#[must_use]
#[inline]
pub fn ev100_to_exposure(ev: f32) -> f32 {
1.0 / (1.2 * 2.0_f32.powf(ev))
}