1use std::sync::Arc;
5
6use crate::color::{Color, PremultipliedColor};
7use crate::geometry::{Point, Transform};
8use crate::pixmap::Pixmap;
9
10use super::gradient::SpreadMode;
11use super::Shader;
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
15pub enum FilterQuality {
16 #[default]
18 Nearest,
19 Bilinear,
21}
22
23#[derive(Clone)]
26pub struct Pattern {
27 pixmap: Arc<Pixmap>,
28 spread: SpreadMode,
29 filter: FilterQuality,
30 opacity: f32,
32 inv_transform: Transform,
33}
34
35impl std::fmt::Debug for Pattern {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("Pattern")
38 .field("size", &(self.pixmap.width(), self.pixmap.height()))
39 .field("spread", &self.spread)
40 .field("filter", &self.filter)
41 .field("opacity", &self.opacity)
42 .finish()
43 }
44}
45
46impl Pattern {
47 #[allow(clippy::new_ret_no_self)] pub fn new(
52 pixmap: Arc<Pixmap>,
53 spread: SpreadMode,
54 filter: FilterQuality,
55 opacity: f32,
56 transform: Transform,
57 ) -> Option<Shader> {
58 let inv_transform = transform.invert()?;
59 Some(Shader::Pattern(Pattern {
60 pixmap,
61 spread,
62 filter,
63 opacity: opacity.clamp(0.0, 1.0),
64 inv_transform,
65 }))
66 }
67
68 #[inline]
71 fn texel(&self, ix: i32, iy: i32) -> PremultipliedColor {
72 let w = self.pixmap.width() as i32;
73 let h = self.pixmap.height() as i32;
74 let x = wrap_index(ix, w, self.spread);
75 let y = wrap_index(iy, h, self.spread);
76 let px = self.pixmap.pixel(x as u32, y as u32).unwrap();
77 PremultipliedColor {
78 r: px.red() as f32 / 255.0,
79 g: px.green() as f32 / 255.0,
80 b: px.blue() as f32 / 255.0,
81 a: px.alpha() as f32 / 255.0,
82 }
83 }
84
85 pub(super) fn color_at(&self, p: Point) -> Color {
86 if self.pixmap.width() == 0 || self.pixmap.height() == 0 {
87 return Color::TRANSPARENT;
88 }
89 self.color_for(self.inv_transform.map_point(p))
90 }
91
92 pub(super) fn shade_span(&self, x: usize, y: usize, len: usize, out: &mut Vec<Color>) {
97 if self.pixmap.width() == 0 || self.pixmap.height() == 0 {
98 out.extend(std::iter::repeat_n(Color::TRANSPARENT, len));
99 return;
100 }
101 let mut tp = self.inv_transform.map_point(Point::new(x as f32 + 0.5, y as f32 + 0.5));
102 let step = Point::new(self.inv_transform.sx, self.inv_transform.ky);
105 for _ in 0..len {
106 out.push(self.color_for(tp));
107 tp = tp + step;
108 }
109 }
110
111 fn color_for(&self, tp: Point) -> Color {
114 let premul = match self.filter {
115 FilterQuality::Nearest => self.texel(tp.x.floor() as i32, tp.y.floor() as i32),
116 FilterQuality::Bilinear => {
117 let fx = tp.x - 0.5;
119 let fy = tp.y - 0.5;
120 let x0 = fx.floor();
121 let y0 = fy.floor();
122 let tx = fx - x0;
123 let ty = fy - y0;
124 let (x0, y0) = (x0 as i32, y0 as i32);
125 let c00 = self.texel(x0, y0);
126 let c10 = self.texel(x0 + 1, y0);
127 let c01 = self.texel(x0, y0 + 1);
128 let c11 = self.texel(x0 + 1, y0 + 1);
129 let lerp = |a: f32, b: f32, t: f32| a + (b - a) * t;
130 let mix = |a: PremultipliedColor, b: PremultipliedColor, t: f32| PremultipliedColor {
131 r: lerp(a.r, b.r, t),
132 g: lerp(a.g, b.g, t),
133 b: lerp(a.b, b.b, t),
134 a: lerp(a.a, b.a, t),
135 };
136 let top = mix(c00, c10, tx);
137 let bot = mix(c01, c11, tx);
138 mix(top, bot, ty)
139 }
140 };
141 let a = premul.a;
144 if a <= 0.0 {
145 return Color::from_rgba(0.0, 0.0, 0.0, 0.0);
146 }
147 Color::from_rgba(premul.r / a, premul.g / a, premul.b / a, a * self.opacity)
148 }
149}
150
151#[inline]
153fn wrap_index(i: i32, size: i32, spread: SpreadMode) -> i32 {
154 if size <= 0 {
155 return 0;
156 }
157 match spread {
158 SpreadMode::Pad => i.clamp(0, size - 1),
159 SpreadMode::Repeat => i.rem_euclid(size),
160 SpreadMode::Reflect => {
161 let period = 2 * size;
162 let m = i.rem_euclid(period);
163 if m < size {
164 m
165 } else {
166 period - 1 - m
167 }
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::pixmap::Pixmap;
176
177 #[test]
178 fn pattern_samples_texture() {
179 let mut tex = Pixmap::new(2, 1).unwrap();
180 tex.fill(Color::from_rgba8(0, 0, 0, 0));
181 {
183 let d = tex.data_mut();
184 d[0..4].copy_from_slice(&[255, 0, 0, 255]);
185 d[4..8].copy_from_slice(&[0, 0, 255, 255]);
186 }
187 let shader = Pattern::new(
188 Arc::new(tex),
189 SpreadMode::Repeat,
190 FilterQuality::Nearest,
191 1.0,
192 Transform::identity(),
193 )
194 .unwrap();
195 let left = shader.color_at(0.5, 0.5);
196 let right = shader.color_at(1.5, 0.5);
197 assert!(left.red() > 0.9 && left.blue() < 0.1, "left={left:?}");
198 assert!(right.blue() > 0.9 && right.red() < 0.1, "right={right:?}");
199 let wrapped = shader.color_at(2.5, 0.5);
201 assert!(wrapped.red() > 0.9, "wrapped={wrapped:?}");
202 }
203
204 #[test]
207 fn shade_span_matches_color_at() {
208 let mut tex = Pixmap::new(4, 4).unwrap();
209 for (i, px) in tex.data_mut().chunks_exact_mut(4).enumerate() {
210 px.copy_from_slice(&[(i * 16) as u8, 32, 200, 255]);
211 }
212 let transform = Transform::from_translate(2.0, 1.0)
213 .pre_concat(Transform::from_rotate(15.0))
214 .pre_concat(Transform::from_scale(0.6, 0.9));
215 let shader = Pattern::new(
216 Arc::new(tex),
217 SpreadMode::Repeat,
218 FilterQuality::Bilinear,
219 0.75,
220 transform,
221 )
222 .unwrap();
223
224 let (x0, y, len) = (3usize, 6usize, 20usize);
225 let mut span = Vec::new();
226 shader.shade_span(x0, y, len, &mut span);
227 assert_eq!(span.len(), len);
228 for (i, c) in span.iter().enumerate() {
229 let want = shader.color_at((x0 + i) as f32 + 0.5, y as f32 + 0.5);
230 assert!((c.red() - want.red()).abs() < 1e-4, "red @{i}: {c:?} vs {want:?}");
231 assert!((c.green() - want.green()).abs() < 1e-4, "green @{i}");
232 assert!((c.blue() - want.blue()).abs() < 1e-4, "blue @{i}");
233 assert!((c.alpha() - want.alpha()).abs() < 1e-4, "alpha @{i}");
234 }
235 }
236}