use super::curves::{exact_srgb_to_linear, linear_to_srgb};
use super::kernels::mix4;
use crate::MAX_GENERATOR_STOPS;
pub(crate) const KIND_NONE: u32 = 0;
pub(crate) const KIND_SOLID: u32 = 1;
pub(crate) const KIND_LINEAR: u32 = 2;
pub(crate) const KIND_RADIAL: u32 = 3;
pub(crate) const KIND_CONIC: u32 = 4;
#[derive(Clone, Debug)]
pub(crate) struct Paint {
kind: u32,
a: [f32; 2],
b: [f32; 2],
spread: u32,
count: usize,
offsets: [f32; MAX_GENERATOR_STOPS],
colors: [[f32; 4]; MAX_GENERATOR_STOPS],
}
impl Paint {
pub(crate) fn new(
kind: u32,
a: [f32; 2],
b: [f32; 2],
spread: u32,
count: usize,
offsets: [f32; MAX_GENERATOR_STOPS],
colors: [[f32; 4]; MAX_GENERATOR_STOPS],
) -> Self {
Self {
kind,
a,
b,
spread,
count: count.min(MAX_GENERATOR_STOPS),
offsets,
colors: colors.map(stop_color),
}
}
pub(crate) const fn is_none(&self) -> bool {
self.kind == KIND_NONE || self.count == 0
}
pub(crate) fn constant(&self) -> Option<[f32; 4]> {
if self.is_none() {
return Some([0.0; 4]);
}
(self.kind == KIND_SOLID || self.count == 1).then_some(self.colors[0])
}
#[inline]
pub(crate) fn at(&self, uv: [f32; 2], res: [f32; 2]) -> [f32; 4] {
if let Some(color) = self.constant() {
return color;
}
let t = self.spread_position(self.position(uv, res));
let offsets = &self.offsets;
if t <= offsets[0] {
return self.colors[0];
}
for stop in 1..self.count {
if t <= offsets[stop] {
let span = offsets[stop] - offsets[stop - 1];
let f = if span <= 0.0 {
1.0
} else {
(t - offsets[stop - 1]) / span
};
return mix4(self.colors[stop - 1], self.colors[stop], f);
}
}
self.colors[self.count - 1]
}
#[inline]
fn position(&self, uv: [f32; 2], res: [f32; 2]) -> f32 {
let p = [uv[0] * res[0], uv[1] * res[1]];
let a = [self.a[0] * res[0], self.a[1] * res[1]];
if self.kind == KIND_LINEAR {
let direction = [self.b[0] * res[0] - a[0], self.b[1] * res[1] - a[1]];
let along = (p[0] - a[0]) * direction[0] + (p[1] - a[1]) * direction[1];
let squared = direction[0] * direction[0] + direction[1] * direction[1];
return along / squared.max(0.000_001);
}
let offset = [p[0] - a[0], p[1] - a[1]];
let squared = offset[0] * offset[0] + offset[1] * offset[1];
if self.kind == KIND_RADIAL {
return squared.sqrt() / (self.b[0] * res[0].min(res[1])).max(0.000_001);
}
if squared <= 0.0 {
return 0.0;
}
let angle = libm::atan2f(offset[0], -offset[1]) - self.b[0];
let turn = angle / std::f32::consts::TAU;
turn - turn.floor()
}
#[inline]
fn spread_position(&self, t: f32) -> f32 {
if self.kind == KIND_CONIC {
return t;
}
match self.spread {
1 => t - t.floor(),
2 => {
let m = t - 2.0 * (t * 0.5).floor();
if m > 1.0 { 2.0 - m } else { m }
}
_ => t.clamp(0.0, 1.0),
}
}
}
pub(crate) fn stop_color(color: [f32; 4]) -> [f32; 4] {
let alpha = color[3].clamp(0.0, 1.0);
let linear = |value: f32| exact_srgb_to_linear(value.clamp(0.0, 1.0));
[
linear(color[0]) * alpha,
linear(color[1]) * alpha,
linear(color[2]) * alpha,
alpha,
]
}
#[inline]
pub(crate) fn encode(color: [f32; 4]) -> [f32; 4] {
let alpha = color[3];
if alpha <= 0.0 {
return [0.0; 4];
}
let encoded = |value: f32| linear_to_srgb((value / alpha).clamp(0.0, 1.0)) * alpha;
[
encoded(color[0]),
encoded(color[1]),
encoded(color[2]),
alpha,
]
}