incular_rendering/
gradients.rs1use crate::display_list::ImageSampling;
2use incular_core::finite_or_zero;
3use incular_core::{Color, Offset};
4use std::sync::{
5 Arc,
6 atomic::{AtomicU64, Ordering},
7};
8
9static NEXT_GRADIENT_ID: AtomicU64 = AtomicU64::new(1);
10
11fn next_gradient_id() -> GradientId {
12 GradientId(
13 NEXT_GRADIENT_ID
14 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
15 .expect("gradient identity space exhausted"),
16 )
17}
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
21pub struct GradientId(u64);
22
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct GradientStop {
25 pub offset: f32,
26 pub color: Color,
27}
28#[derive(Clone, Debug)]
29pub struct GradientStops {
30 id: GradientId,
31 stops: Arc<[GradientStop]>,
32}
33impl PartialEq for GradientStops {
34 fn eq(&self, other: &Self) -> bool {
35 self.stops == other.stops
36 }
37}
38
39#[must_use]
43pub fn sample_gradient_stops(stops: &GradientStops, t: f32) -> [f32; 4] {
44 let stops = stops.as_slice();
45 let t = finite_or_zero(t);
46 if t <= stops[0].offset {
47 return premultiplied(stops[0].color);
48 }
49 let last = stops.last().expect("normalized stops are non-empty");
50 if t >= last.offset {
51 return premultiplied(last.color);
52 }
53 let mut left = 0;
54 for index in 1..stops.len() {
55 if stops[index].offset <= t {
56 left = index;
57 } else {
58 let a = stops[left];
59 let b = stops[index];
60 let amount = ((t - a.offset) / (b.offset - a.offset)).clamp(0., 1.);
61 let a = premultiplied(a.color);
62 let b = premultiplied(b.color);
63 return [
64 a[0] + (b[0] - a[0]) * amount,
65 a[1] + (b[1] - a[1]) * amount,
66 a[2] + (b[2] - a[2]) * amount,
67 a[3] + (b[3] - a[3]) * amount,
68 ];
69 }
70 }
71 premultiplied(last.color)
72}
73fn premultiplied(color: Color) -> [f32; 4] {
74 let [r, g, b, a] = color.to_linear_rgba();
75 [r * a, g * a, b * a, a]
76}
77#[must_use]
80pub fn linear_gradient_t(start: Offset, end: Offset, point: Offset) -> f32 {
81 let dx = end.x - start.x;
82 let dy = end.y - start.y;
83 let length_squared = dx * dx + dy * dy;
84 if !length_squared.is_finite() || length_squared <= f32::EPSILON {
85 return 1.;
86 }
87 (((point.x - start.x) * dx + (point.y - start.y) * dy) / length_squared).clamp(0., 1.)
88}
89#[must_use]
92pub fn radial_gradient_t(center: Offset, radius: f32, point: Offset) -> f32 {
93 if !radius.is_finite() || radius <= 0. {
94 return 1.;
95 }
96 (((point.x - center.x).hypot(point.y - center.y)) / radius).clamp(0., 1.)
97}
98impl GradientStops {
99 #[must_use]
102 pub fn new(mut stops: Vec<GradientStop>) -> Self {
103 for s in &mut stops {
104 s.offset = if s.offset.is_finite() {
105 s.offset.clamp(0., 1.)
106 } else {
107 0.
108 };
109 }
110 stops.sort_by(|a, b| a.offset.total_cmp(&b.offset));
111 if stops.is_empty() {
112 stops.push(GradientStop {
113 offset: 0.,
114 color: Color::TRANSPARENT,
115 });
116 }
117 if stops.len() == 1 {
118 stops.push(GradientStop {
119 offset: 1.,
120 color: stops[0].color,
121 });
122 }
123 Self {
124 id: next_gradient_id(),
125 stops: stops.into(),
126 }
127 }
128 #[must_use]
129 pub fn as_slice(&self) -> &[GradientStop] {
130 &self.stops
131 }
132 #[must_use]
135 pub const fn id(&self) -> GradientId {
136 self.id
137 }
138}
139impl GradientId {
140 #[must_use]
141 pub const fn get(self) -> u64 {
142 self.0
143 }
144}
145#[derive(Clone, Debug, PartialEq)]
146pub struct LinearGradient {
147 pub start: Offset,
148 pub end: Offset,
149 pub stops: GradientStops,
150}
151#[derive(Clone, Debug, PartialEq)]
152pub struct RadialGradient {
153 pub center: Offset,
154 pub radius: f32,
155 pub stops: GradientStops,
156}
157#[derive(Clone, Debug, PartialEq)]
158pub struct SweepGradient {
159 pub center: Offset,
160 pub start_angle: f32,
161 pub stops: GradientStops,
162}
163#[derive(Clone, Debug, PartialEq)]
164pub enum Brush {
165 Solid(Color),
166 LinearGradient(LinearGradient),
167 RadialGradient(RadialGradient),
168 SweepGradient(SweepGradient),
169}
170
171pub type Shader = Brush;
177
178#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
185pub enum FilterQuality {
186 None,
187 #[default]
188 Low,
189 Medium,
190 High,
191}
192
193impl FilterQuality {
194 #[must_use]
195 pub const fn sampling(self) -> ImageSampling {
196 match self {
197 Self::None => ImageSampling::Nearest,
198 Self::Low | Self::Medium | Self::High => ImageSampling::Linear,
199 }
200 }
201}
202
203impl From<FilterQuality> for ImageSampling {
204 fn from(value: FilterQuality) -> Self {
205 value.sampling()
206 }
207}
208impl From<Color> for Brush {
209 fn from(color: Color) -> Self {
210 Self::Solid(color)
211 }
212}
213impl From<LinearGradient> for Brush {
214 fn from(gradient: LinearGradient) -> Self {
215 Self::LinearGradient(gradient)
216 }
217}
218impl From<RadialGradient> for Brush {
219 fn from(gradient: RadialGradient) -> Self {
220 Self::RadialGradient(gradient)
221 }
222}
223impl From<SweepGradient> for Brush {
224 fn from(gradient: SweepGradient) -> Self {
225 Self::SweepGradient(gradient)
226 }
227}
228#[must_use]
229pub fn sweep_gradient_t(center: Offset, start_angle: f32, point: Offset) -> f32 {
230 ((point.y - center.y).atan2(point.x - center.x) - start_angle).rem_euclid(std::f32::consts::TAU)
231 / std::f32::consts::TAU
232}