1use std::any::Any;
4use std::sync::Arc;
5
6use crate::budget::Budget;
7use crate::{
8 Color, GradientExtend, ImagePattern, ImagePixels, PixelSize, Point, RasterLimits, Rect,
9 Sampling, Transform, ValidationError,
10};
11
12mod evaluate;
13
14pub use evaluate::MeshPatch;
15
16#[cfg(test)]
17mod tests;
18
19pub const MAX_MESH_POINTS_PER_AXIS: u32 = 64;
21
22pub const MAX_MESH_SUBDIVISIONS: u32 = 32;
24
25#[derive(Clone, Debug, PartialEq)]
30pub struct MeshPaint {
31 pub mesh: MeshGradient,
33 pub size: PixelSize,
35 pub subdivisions: u32,
37}
38
39impl MeshPaint {
40 pub fn new(mesh: MeshGradient, size: PixelSize, subdivisions: u32) -> Self {
42 Self {
43 mesh,
44 size,
45 subdivisions,
46 }
47 }
48
49 pub fn validate(&self) -> Result<(), ValidationError> {
51 if !(1..=MAX_MESH_SUBDIVISIONS).contains(&self.subdivisions) {
52 return Err(ValidationError::limit(
53 LABEL,
54 "subdivisions",
55 "Subdivisions must be between 1 and 32",
56 ));
57 }
58 if self.size.width == 0 || self.size.height == 0 {
59 return Err(ValidationError::invalid(
60 LABEL,
61 "size",
62 "Mesh image must have positive dimensions",
63 ));
64 }
65 for row in 0..self.mesh.rows() - 1 {
66 for column in 0..self.mesh.columns() - 1 {
67 self.mesh.patch(column, row)?;
68 }
69 }
70 Ok(())
71 }
72
73 pub fn pattern(
75 &self,
76 bounds: Rect,
77 limits: RasterLimits,
78 ) -> Result<ImagePattern, ValidationError> {
79 self.mesh
80 .pattern(bounds, self.size, self.subdivisions, limits)
81 }
82}
83
84const EDGE_SLACK: f64 = 1e-9;
86
87const LABEL: &str = "mesh_gradient";
88
89#[derive(Clone, Copy, Debug, PartialEq)]
91pub struct MeshVertex {
92 pub position: Point,
94 pub color: Color,
96}
97
98#[derive(Clone, Debug, PartialEq)]
125pub struct MeshGradient {
126 columns: u32,
127 rows: u32,
128 vertices: Vec<MeshVertex>,
129}
130
131impl MeshGradient {
132 pub fn new(
137 columns: u32,
138 rows: u32,
139 vertices: Vec<MeshVertex>,
140 ) -> Result<Self, ValidationError> {
141 if !(2..=MAX_MESH_POINTS_PER_AXIS).contains(&columns)
142 || !(2..=MAX_MESH_POINTS_PER_AXIS).contains(&rows)
143 {
144 return Err(ValidationError::limit(
145 LABEL,
146 "size",
147 "Mesh needs between 2 and 64 control points per axis",
148 ));
149 }
150 if vertices.len() != columns as usize * rows as usize {
151 return Err(ValidationError::invalid(
152 LABEL,
153 "vertices",
154 "Vertex count must equal columns times rows",
155 ));
156 }
157 if vertices
158 .iter()
159 .any(|vertex| !vertex.position.x.is_finite() || !vertex.position.y.is_finite())
160 {
161 return Err(ValidationError::invalid(
162 LABEL,
163 "position",
164 "Mesh positions must be finite",
165 ));
166 }
167 Ok(Self {
168 columns,
169 rows,
170 vertices,
171 })
172 }
173
174 pub fn grid(columns: u32, rows: u32, colors: &[Color]) -> Result<Self, ValidationError> {
176 let vertices = colors
177 .iter()
178 .enumerate()
179 .map(|(index, color)| {
180 let column = index as u32 % columns.max(1);
181 let row = index as u32 / columns.max(1);
182 MeshVertex {
183 position: Point::new(
184 f64::from(column) / f64::from(columns.max(2) - 1),
185 f64::from(row) / f64::from(rows.max(2) - 1),
186 ),
187 color: *color,
188 }
189 })
190 .collect();
191 Self::new(columns, rows, vertices)
192 }
193
194 pub fn columns(&self) -> u32 {
196 self.columns
197 }
198
199 pub fn rows(&self) -> u32 {
201 self.rows
202 }
203
204 pub fn vertices(&self) -> &[MeshVertex] {
206 &self.vertices
207 }
208
209 pub fn patch(&self, column: u32, row: u32) -> Result<MeshPatch, ValidationError> {
213 if column >= self.columns - 1 || row >= self.rows - 1 {
214 return Err(ValidationError::invalid(
215 LABEL,
216 "patch",
217 "Patch index is outside the mesh",
218 ));
219 }
220 let at = |dx, dy| self.vertices[((row + dy) * self.columns + column + dx) as usize];
221 MeshPatch::new([at(0, 0), at(1, 0), at(0, 1), at(1, 1)])
222 }
223
224 pub fn rasterize(
229 &self,
230 size: PixelSize,
231 subdivisions: u32,
232 limits: RasterLimits,
233 ) -> Result<MeshImage, ValidationError> {
234 if !(1..=MAX_MESH_SUBDIVISIONS).contains(&subdivisions) {
235 return Err(ValidationError::limit(
236 LABEL,
237 "subdivisions",
238 "Subdivisions must be between 1 and 32",
239 ));
240 }
241 if size.width == 0 || size.height == 0 {
242 return Err(ValidationError::invalid(
243 LABEL,
244 "size",
245 "Mesh image must have positive dimensions",
246 ));
247 }
248 let mut budget = Budget::new(size, limits)?;
249 let texels = u64::from(size.width) * u64::from(size.height);
250 let lattice_columns = u64::from((self.columns - 1) * subdivisions + 1);
251 let lattice_rows = u64::from((self.rows - 1) * subdivisions + 1);
252 budget.bytes(texels * 16, LABEL)?;
253 budget.bytes(lattice_columns * lattice_rows * 48, LABEL)?;
254 budget.work(lattice_columns * lattice_rows, LABEL)?;
255 let mut lattice = Vec::new();
256 lattice
257 .try_reserve((lattice_columns * lattice_rows) as usize)
258 .map_err(|_| {
259 ValidationError::limit(LABEL, "max_bytes", "Could not reserve mesh lattice")
260 })?;
261 for row in 0..lattice_rows as u32 {
262 for column in 0..lattice_columns as u32 {
263 lattice.push(self.sample(column, row, subdivisions)?);
264 }
265 }
266 let mut pixels = Vec::new();
267 pixels.try_reserve(texels as usize).map_err(|_| {
268 ValidationError::limit(LABEL, "max_bytes", "Could not reserve mesh pixels")
269 })?;
270 pixels.resize(texels as usize, [0.0f32; 4]);
271 let stride = lattice_columns as usize;
272 for row in 0..lattice_rows as usize - 1 {
273 for column in 0..stride - 1 {
274 let corners = [
275 lattice[row * stride + column],
276 lattice[row * stride + column + 1],
277 lattice[(row + 1) * stride + column],
278 lattice[(row + 1) * stride + column + 1],
279 ];
280 for triangle in [[0, 1, 2], [1, 3, 2]] {
281 let corners = triangle.map(|index| corners[index]);
282 fill_triangle(&mut pixels, size, corners, &mut budget)?;
283 }
284 }
285 }
286 Ok(MeshImage {
287 width: size.width,
288 height: size.height,
289 pixels,
290 })
291 }
292
293 pub fn pattern(
295 &self,
296 bounds: Rect,
297 size: PixelSize,
298 subdivisions: u32,
299 limits: RasterLimits,
300 ) -> Result<ImagePattern, ValidationError> {
301 let image = self.rasterize(size, subdivisions, limits)?;
302 let scale_x = bounds.width() / f64::from(size.width);
303 let scale_y = bounds.height() / f64::from(size.height);
304 let transform = Transform::scale_non_uniform(scale_x, scale_y)
305 .then(Transform::translate(bounds.left, bounds.top));
306 let pattern = ImagePattern::from_pixels(Arc::new(image))
307 .transform(transform)
308 .extend(GradientExtend::Pad)
309 .sampling(Sampling::Bilinear);
310 pattern.validate()?;
311 Ok(pattern)
312 }
313
314 fn sample(&self, column: u32, row: u32, subdivisions: u32) -> Result<Lattice, ValidationError> {
315 let cell_column = (column / subdivisions).min(self.columns - 2);
316 let cell_row = (row / subdivisions).min(self.rows - 2);
317 let u = f64::from(column - cell_column * subdivisions) / f64::from(subdivisions);
318 let v = f64::from(row - cell_row * subdivisions) / f64::from(subdivisions);
319 let at = |dx: u32, dy: u32| {
320 self.vertices[((cell_row + dy) * self.columns + cell_column + dx) as usize]
321 };
322 let [a, b, c, d] = [at(0, 0), at(1, 0), at(0, 1), at(1, 1)];
323 let (su, sv) = (smooth(u), smooth(v));
324 let top = a.color.try_interpolate(&b.color, su)?;
325 let bottom = c.color.try_interpolate(&d.color, su)?;
326 let color = top.try_interpolate(&bottom, sv)?;
327 let mix = |p: fn(&MeshVertex) -> f64| {
328 let upper = p(&a) + (p(&b) - p(&a)) * u;
329 let lower = p(&c) + (p(&d) - p(&c)) * u;
330 upper + (lower - upper) * v
331 };
332 Ok(Lattice {
333 x: mix(|vertex| vertex.position.x),
334 y: mix(|vertex| vertex.position.y),
335 color: color.to_linear_premultiplied(),
336 })
337 }
338}
339
340#[derive(Clone, Debug, PartialEq)]
342pub struct MeshImage {
343 width: u32,
344 height: u32,
345 pixels: Vec<[f32; 4]>,
346}
347
348impl ImagePixels for MeshImage {
349 fn width(&self) -> u32 {
350 self.width
351 }
352
353 fn height(&self) -> u32 {
354 self.height
355 }
356
357 fn level_count(&self) -> usize {
358 1
359 }
360
361 fn level_size(&self, _level: usize) -> (u32, u32) {
362 (self.width, self.height)
363 }
364
365 fn texel(&self, _level: usize, column: usize, row: usize) -> [f32; 4] {
366 self.pixels[row * self.width as usize + column]
367 }
368
369 fn content_eq(&self, other: &dyn ImagePixels) -> bool {
370 other
371 .as_any()
372 .downcast_ref::<Self>()
373 .is_some_and(|other| self == other)
374 }
375
376 fn as_any(&self) -> &dyn Any {
377 self
378 }
379}
380
381#[derive(Clone, Copy)]
382struct Lattice {
383 x: f64,
384 y: f64,
385 color: [f64; 4],
386}
387
388fn smooth(value: f64) -> f64 {
389 value * value * (3.0 - 2.0 * value)
390}
391
392fn fill_triangle(
393 pixels: &mut [[f32; 4]],
394 size: PixelSize,
395 corners: [Lattice; 3],
396 budget: &mut Budget,
397) -> Result<(), ValidationError> {
398 let [a, b, c] = corners.map(|corner| {
399 (
400 corner.x * f64::from(size.width),
401 corner.y * f64::from(size.height),
402 )
403 });
404 let area = (b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0);
405 if area == 0.0 {
406 return Ok(());
407 }
408 let clamp = |low: f64, high: f64, limit: u32| {
409 (
410 (libm::floor(low - 0.5).max(0.0)) as u32,
411 (libm::ceil(high - 0.5).max(-1.0).min(f64::from(limit) - 1.0)) as i64,
412 )
413 };
414 let (first_column, last_column) =
415 clamp(a.0.min(b.0).min(c.0), a.0.max(b.0).max(c.0), size.width);
416 let (first_row, last_row) = clamp(a.1.min(b.1).min(c.1), a.1.max(b.1).max(c.1), size.height);
417 if last_column < i64::from(first_column) || last_row < i64::from(first_row) {
418 return Ok(());
419 }
420 let columns = last_column as u64 - u64::from(first_column) + 1;
421 let rows = last_row as u64 - u64::from(first_row) + 1;
422 budget.work(columns * rows, LABEL)?;
423 for row in first_row..=last_row as u32 {
424 for column in first_column..=last_column as u32 {
425 let point = (f64::from(column) + 0.5, f64::from(row) + 0.5);
426 let w1 = ((point.0 - a.0) * (c.1 - a.1) - (point.1 - a.1) * (c.0 - a.0)) / area;
427 let w2 = ((b.0 - a.0) * (point.1 - a.1) - (b.1 - a.1) * (point.0 - a.0)) / area;
428 let w0 = 1.0 - w1 - w2;
429 if w0 < -EDGE_SLACK || w1 < -EDGE_SLACK || w2 < -EDGE_SLACK {
430 continue;
431 }
432 let texel = &mut pixels[row as usize * size.width as usize + column as usize];
433 for (channel, slot) in texel.iter_mut().enumerate() {
434 *slot = (w0 * corners[0].color[channel]
435 + w1 * corners[1].color[channel]
436 + w2 * corners[2].color[channel]) as f32;
437 }
438 }
439 }
440 Ok(())
441}