1use std::sync::Arc;
14
15use crate::color::Color;
16use agg_rust::trans_affine::TransAffine;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum FillRule {
21 NonZero,
23 EvenOdd,
25}
26
27impl Default for FillRule {
28 fn default() -> Self {
29 Self::NonZero
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum GradientSpread {
36 Pad,
38 Reflect,
40 Repeat,
42}
43
44impl Default for GradientSpread {
45 fn default() -> Self {
46 Self::Pad
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct GradientStop {
53 pub offset: f64,
54 pub color: Color,
55}
56
57#[derive(Clone, Debug, PartialEq)]
59pub struct LinearGradientPaint {
60 pub x1: f64,
61 pub y1: f64,
62 pub x2: f64,
63 pub y2: f64,
64 pub transform: TransAffine,
65 pub spread: GradientSpread,
66 pub stops: Vec<GradientStop>,
67}
68
69impl LinearGradientPaint {
70 pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
71 if self.stops.is_empty() {
72 return Color::transparent();
73 }
74
75 self.transform.inverse_transform(&mut x, &mut y);
76
77 let dx = self.x2 - self.x1;
78 let dy = self.y2 - self.y1;
79 let len2 = dx * dx + dy * dy;
80 let t = if len2 > f64::EPSILON {
81 ((x - self.x1) * dx + (y - self.y1) * dy) / len2
82 } else {
83 0.0
84 };
85 let t = apply_spread(t, self.spread);
86
87 sample_stops(&self.stops, t)
88 }
89}
90
91#[derive(Clone, Debug, PartialEq)]
93pub struct RadialGradientPaint {
94 pub cx: f64,
95 pub cy: f64,
96 pub r: f64,
97 pub fx: f64,
98 pub fy: f64,
99 pub transform: TransAffine,
100 pub spread: GradientSpread,
101 pub stops: Vec<GradientStop>,
102}
103
104impl RadialGradientPaint {
105 pub fn centered(cx: f64, cy: f64, r: f64, stops: &[(f64, Color)]) -> Self {
108 Self {
109 cx,
110 cy,
111 r,
112 fx: cx,
113 fy: cy,
114 transform: TransAffine::default(),
115 spread: GradientSpread::Pad,
116 stops: stops
117 .iter()
118 .map(|(offset, color)| GradientStop {
119 offset: *offset,
120 color: *color,
121 })
122 .collect(),
123 }
124 }
125
126 pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
127 if self.stops.is_empty() {
128 return Color::transparent();
129 }
130
131 self.transform.inverse_transform(&mut x, &mut y);
132
133 let dx = x - self.fx;
134 let dy = y - self.fy;
135 let fx = self.fx - self.cx;
136 let fy = self.fy - self.cy;
137 let a = dx * dx + dy * dy;
138 let t = if a <= f64::EPSILON || self.r <= f64::EPSILON {
139 0.0
140 } else {
141 let b = 2.0 * (fx * dx + fy * dy);
142 let c = fx * fx + fy * fy - self.r * self.r;
143 let disc = (b * b - 4.0 * a * c).max(0.0);
144 let k = (-b + disc.sqrt()) / (2.0 * a);
145 if k > f64::EPSILON {
146 1.0 / k
147 } else {
148 0.0
149 }
150 };
151 sample_stops(&self.stops, apply_spread(t, self.spread))
152 }
153}
154
155#[derive(Clone, Debug, PartialEq)]
157pub struct PatternPaint {
158 pub x: f64,
159 pub y: f64,
160 pub width: f64,
161 pub height: f64,
162 pub transform: TransAffine,
163 pub pixels: Arc<Vec<u8>>,
165 pub pixel_width: u32,
166 pub pixel_height: u32,
167}
168
169impl PatternPaint {
170 pub fn sample(&self, mut x: f64, mut y: f64) -> Color {
171 if self.width <= f64::EPSILON
172 || self.height <= f64::EPSILON
173 || self.pixel_width == 0
174 || self.pixel_height == 0
175 || self.pixels.is_empty()
176 {
177 return Color::transparent();
178 }
179
180 self.transform.inverse_transform(&mut x, &mut y);
181 let tx = (x - self.x).rem_euclid(self.width);
182 let ty_down = (y - self.y).rem_euclid(self.height);
183 let px = ((tx / self.width) * self.pixel_width as f64)
184 .floor()
185 .clamp(0.0, self.pixel_width.saturating_sub(1) as f64) as usize;
186 let py = (((self.height - ty_down) / self.height) * self.pixel_height as f64)
187 .floor()
188 .clamp(0.0, self.pixel_height.saturating_sub(1) as f64) as usize;
189 let i = (py * self.pixel_width as usize + px) * 4;
190 if i + 3 >= self.pixels.len() {
191 return Color::transparent();
192 }
193
194 Color::rgba(
195 self.pixels[i] as f32 / 255.0,
196 self.pixels[i + 1] as f32 / 255.0,
197 self.pixels[i + 2] as f32 / 255.0,
198 self.pixels[i + 3] as f32 / 255.0,
199 )
200 }
201}
202
203fn apply_spread(t: f64, spread: GradientSpread) -> f64 {
204 match spread {
205 GradientSpread::Pad => t.clamp(0.0, 1.0),
206 GradientSpread::Repeat => t - t.floor(),
207 GradientSpread::Reflect => {
208 let period = t.rem_euclid(2.0);
209 if period <= 1.0 {
210 period
211 } else {
212 2.0 - period
213 }
214 }
215 }
216}
217
218fn sample_stops(stops: &[GradientStop], t: f64) -> Color {
219 if t <= stops[0].offset {
220 return stops[0].color;
221 }
222 for pair in stops.windows(2) {
223 let a = pair[0];
224 let b = pair[1];
225 if t <= b.offset {
226 let span = (b.offset - a.offset).max(f64::EPSILON);
227 let u = ((t - a.offset) / span).clamp(0.0, 1.0) as f32;
228 return lerp_color(a.color, b.color, u);
229 }
230 }
231 stops[stops.len() - 1].color
232}
233
234fn lerp_color(a: Color, b: Color, t: f32) -> Color {
235 Color::rgba(
236 a.r + (b.r - a.r) * t,
237 a.g + (b.g - a.g) * t,
238 a.b + (b.b - a.b) * t,
239 a.a + (b.a - a.a) * t,
240 )
241}