1use super::ShadingKind;
21use crate::color::{ColorSpace, Rgb};
22use crate::function::{BitReader, Function};
23use kurbo::Point;
24use std::sync::Arc;
25
26pub const MAX_COMPONENTS: usize = 8;
28
29const VALID_COORD_BITS: [u32; 8] = [1, 2, 4, 8, 12, 16, 24, 32];
31
32const VALID_COMPONENT_BITS: [u32; 6] = [1, 2, 4, 8, 12, 16];
35
36const VALID_FLAG_BITS: [u32; 3] = [2, 4, 8];
38
39#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct Vertex {
42 pub point: Point,
44 pub color: Rgb,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct Triangle {
52 pub vertices: [Vertex; 3],
54}
55
56#[derive(Debug, Clone, PartialEq)]
59pub struct Patch {
60 pub points: Box<[Point]>,
62 pub colors: [Rgb; 4],
64}
65
66#[derive(Debug, Clone, PartialEq, Default)]
68pub struct Mesh {
69 pub triangles: Vec<Triangle>,
71 pub patches: Vec<Patch>,
73 pub component_range: [f32; 2],
84}
85
86impl Mesh {
87 #[must_use]
90 pub fn bounds(&self) -> Option<kurbo::Rect> {
91 let mut rect: Option<kurbo::Rect> = None;
92 let mut add = |p: Point| {
93 let r = kurbo::Rect::from_points(p, p);
94 rect = Some(match rect {
95 Some(existing) => existing.union(r),
96 None => r,
97 });
98 };
99 for t in &self.triangles {
100 for v in t.vertices {
101 add(v.point);
102 }
103 }
104 for p in &self.patches {
105 for point in &p.points {
106 add(*point);
107 }
108 }
109 rect
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct MeshParams {
116 pub coord_bits: u32,
118 pub component_bits: u32,
120 pub flag_bits: u32,
122 pub components: usize,
125 pub decode: Box<[f32]>,
127 pub coord_max: u32,
129 pub component_max: u32,
131}
132
133impl MeshParams {
134 #[must_use]
139 pub fn new(
140 coord_bits: u32,
141 component_bits: u32,
142 flag_bits: u32,
143 components: usize,
144 decode: &[f32],
145 kind: ShadingKind,
146 ) -> Option<Self> {
147 if !VALID_COORD_BITS.contains(&coord_bits)
148 || !VALID_COMPONENT_BITS.contains(&component_bits)
149 || (kind.reads_edge_flags() && !VALID_FLAG_BITS.contains(&flag_bits))
150 || components > MAX_COMPONENTS
151 {
152 return None;
153 }
154 if decode.len() != 4 + 2 * components {
156 return None;
157 }
158 Some(Self {
159 coord_bits,
160 component_bits,
161 flag_bits,
162 components,
163 decode: decode.into(),
164 coord_max: if coord_bits >= 32 {
165 u32::MAX
166 } else {
167 (1u32 << coord_bits) - 1
168 },
169 component_max: if component_bits >= 32 {
170 u32::MAX
171 } else {
172 (1u32 << component_bits) - 1
173 },
174 })
175 }
176
177 #[must_use]
183 pub fn component_range(&self) -> [f32; 2] {
184 [
185 self.decode.get(4).copied().unwrap_or(0.0),
186 self.decode.get(5).copied().unwrap_or(0.0),
187 ]
188 }
189}
190
191pub struct MeshReader<'a> {
193 bits: BitReader<'a>,
194 params: &'a MeshParams,
195 space: &'a ColorSpace,
196 functions: &'a [Arc<Function>],
197}
198
199impl<'a> MeshReader<'a> {
200 #[must_use]
202 pub fn new(
203 data: &'a [u8],
204 params: &'a MeshParams,
205 space: &'a ColorSpace,
206 functions: &'a [Arc<Function>],
207 ) -> Self {
208 Self {
209 bits: BitReader::new(data),
210 params,
211 space,
212 functions,
213 }
214 }
215
216 #[must_use]
218 pub fn can_read_flag(&self) -> bool {
219 self.bits.remaining() >= u64::from(self.params.flag_bits)
220 }
221
222 #[must_use]
227 pub fn can_read_coords(&self) -> bool {
228 self.bits.remaining() / 2 >= u64::from(self.params.coord_bits)
229 }
230
231 #[must_use]
239 pub fn can_read_color(&self) -> bool {
240 if self.params.component_bits == 0 {
241 return false;
242 }
243 self.bits.remaining() / u64::from(self.params.component_bits)
244 >= self.params.components as u64
245 }
246
247 pub fn read_flag(&mut self) -> u8 {
249 u8::try_from(self.bits.read(self.params.flag_bits) & 0x03).unwrap_or(0)
250 }
251
252 pub fn read_coords(&mut self) -> Point {
254 let decode = |raw: u32, min: f32, max: f32, max_raw: u32| -> f64 {
255 if self.params.coord_bits == 32 {
256 f64::from(min)
259 + f64::from(raw) * (f64::from(max) - f64::from(min)) / f64::from(max_raw)
260 } else {
261 #[expect(
262 clippy::cast_precision_loss,
263 reason = "below 32 bits the raw value is exact in f32, matching the C++"
264 )]
265 let v = min + (raw as f32) * (max - min) / (max_raw as f32);
266 f64::from(v)
267 }
268 };
269 let at = |i: usize| self.params.decode.get(i).copied().unwrap_or(0.0);
270 let raw_x = self.bits.read(self.params.coord_bits);
271 let raw_y = self.bits.read(self.params.coord_bits);
272 Point::new(
273 decode(raw_x, at(0), at(1), self.params.coord_max),
274 decode(raw_y, at(2), at(3), self.params.coord_max),
275 )
276 }
277
278 pub fn read_color(&mut self) -> Rgb {
283 let mut comps = [0.0f32; MAX_COMPONENTS];
284 for i in 0..self.params.components.min(MAX_COMPONENTS) {
285 let raw = self.bits.read(self.params.component_bits);
286 let min = self.params.decode.get(4 + i * 2).copied().unwrap_or(0.0);
287 let max = self
288 .params
289 .decode
290 .get(4 + i * 2 + 1)
291 .copied()
292 .unwrap_or(0.0);
293 #[expect(
294 clippy::cast_precision_loss,
295 reason = "component widths cap at 16 bits, exact in f32"
296 )]
297 let v = min + (raw as f32) * (max - min) / (self.params.component_max as f32);
298 if let Some(slot) = comps.get_mut(i) {
299 *slot = v;
300 }
301 }
302 if self.functions.is_empty() {
303 return self
304 .space
305 .try_to_rgb(comps.get(..self.params.components).unwrap_or(&[]))
306 .unwrap_or(Rgb::BLACK);
307 }
308 Rgb {
309 r: comps.first().copied().unwrap_or(0.0),
310 g: 0.0,
311 b: 0.0,
312 }
313 }
314
315 pub fn read_vertex(&mut self) -> Option<(u8, Vertex)> {
317 if !self.can_read_flag() {
318 return None;
319 }
320 let flag = self.read_flag();
321 if !self.can_read_coords() {
322 return None;
323 }
324 let point = self.read_coords();
325 if !self.can_read_color() {
326 return None;
327 }
328 let color = self.read_color();
329 self.bits.byte_align();
330 Some((flag, Vertex { point, color }))
331 }
332
333 pub fn read_vertex_row(&mut self, count: usize) -> Vec<Vertex> {
338 let mut row = Vec::with_capacity(count);
339 for _ in 0..count {
340 if !self.can_read_coords() {
341 return Vec::new();
342 }
343 let point = self.read_coords();
344 if !self.can_read_color() {
345 return Vec::new();
346 }
347 let color = self.read_color();
348 self.bits.byte_align();
349 row.push(Vertex { point, color });
350 }
351 row
352 }
353
354 #[must_use]
356 pub fn read_free_form(&mut self) -> Vec<Triangle> {
357 let mut out = Vec::new();
358 let mut previous: [Option<Vertex>; 3] = [None; 3];
359 loop {
360 let Some((flag, vertex)) = self.read_vertex() else {
361 return out;
362 };
363 if flag == 0 {
364 let (Some((_, b)), Some((_, c))) = (self.read_vertex(), self.read_vertex()) else {
367 return out;
368 };
369 previous = [Some(vertex), Some(b), Some(c)];
370 } else {
371 let (Some(p0), Some(p1), Some(p2)) = (previous[0], previous[1], previous[2]) else {
372 return out;
373 };
374 previous = if flag == 1 {
377 [Some(p1), Some(p2), Some(vertex)]
378 } else {
379 [Some(p0), Some(p2), Some(vertex)]
380 };
381 }
382 let (Some(a), Some(b), Some(c)) = (previous[0], previous[1], previous[2]) else {
383 return out;
384 };
385 out.push(Triangle {
386 vertices: [a, b, c],
387 });
388 }
389 }
390
391 #[must_use]
393 pub fn read_lattice(&mut self, per_row: usize) -> Vec<Triangle> {
394 if per_row < 2 {
396 return Vec::new();
397 }
398 let mut out = Vec::new();
399 let mut previous = self.read_vertex_row(per_row);
400 if previous.is_empty() {
401 return out;
402 }
403 loop {
404 let row = self.read_vertex_row(per_row);
405 if row.is_empty() {
406 return out;
407 }
408 for i in 0..per_row - 1 {
409 let (Some(a), Some(b), Some(c), Some(d)) = (
410 previous.get(i),
411 previous.get(i + 1),
412 row.get(i),
413 row.get(i + 1),
414 ) else {
415 continue;
416 };
417 out.push(Triangle {
418 vertices: [*a, *b, *c],
419 });
420 out.push(Triangle {
421 vertices: [*b, *d, *c],
422 });
423 }
424 previous = row;
425 }
426 }
427
428 #[must_use]
450 pub fn read_patches(&mut self, kind: ShadingKind) -> Vec<Patch> {
451 let point_count = if kind == ShadingKind::TensorMesh {
453 16
454 } else {
455 12
456 };
457 let mut out: Vec<Patch> = Vec::new();
458 let mut coords = vec![Point::ZERO; point_count];
459 let mut colors = [Rgb::BLACK; 4];
460 loop {
461 if !self.can_read_flag() {
462 return out;
463 }
464 let flag = self.read_flag();
465 let (start_point, start_color) = if flag == 0 { (0, 0) } else { (4, 2) };
468 if flag != 0 {
469 let Some(previous) = out.last() else {
470 return out;
471 };
472 for i in 0..4 {
473 let source = (usize::from(flag) * 3 + i) % 12;
476 if let (Some(slot), Some(p)) = (coords.get_mut(i), previous.points.get(source))
477 {
478 *slot = *p;
479 }
480 }
481 if let (Some(slot), Some(c)) =
482 (colors.first_mut(), previous.colors.get(usize::from(flag)))
483 {
484 *slot = *c;
485 }
486 let next = previous
487 .colors
488 .get((usize::from(flag) + 1) % 4)
489 .copied()
490 .unwrap_or(Rgb::BLACK);
491 if let Some(slot) = colors.get_mut(1) {
492 *slot = next;
493 }
494 }
495 for i in start_point..point_count {
499 if !self.can_read_coords() {
500 break;
501 }
502 let p = self.read_coords();
503 if let Some(slot) = coords.get_mut(i) {
504 *slot = p;
505 }
506 }
507 for i in start_color..4 {
508 if !self.can_read_color() {
509 break;
510 }
511 let c = self.read_color();
512 if let Some(slot) = colors.get_mut(i) {
513 *slot = c;
514 }
515 }
516 out.push(Patch {
517 points: coords.clone().into(),
518 colors,
519 });
520 }
523 }
524}
525
526#[must_use]
533pub fn coons_interior(boundary: &[Point]) -> [Point; 4] {
534 let p = |i: usize| boundary.get(i).copied().unwrap_or(Point::ZERO);
535 let (p00, p01, p02, p03) = (p(0), p(1), p(2), p(3));
537 let (p13, p23) = (p(4), p(5));
538 let (p33, p32, p31, p30) = (p(6), p(7), p(8), p(9));
539 let (p20, p10) = (p(10), p(11));
540 let blend = |a: Point, b: Point, c: Point, d: Point, e: Point, f: Point, g: Point, h: Point| {
541 Point::new(
542 (-4.0 * a.x + 6.0 * (b.x + c.x) - 2.0 * (d.x + e.x) + 3.0 * (f.x + g.x) - h.x) / 9.0,
543 (-4.0 * a.y + 6.0 * (b.y + c.y) - 2.0 * (d.y + e.y) + 3.0 * (f.y + g.y) - h.y) / 9.0,
544 )
545 };
546 [
547 blend(p00, p01, p10, p03, p30, p31, p13, p33),
548 blend(p03, p02, p13, p00, p33, p32, p10, p30),
549 blend(p30, p31, p20, p33, p00, p01, p23, p03),
550 blend(p33, p32, p23, p30, p03, p02, p20, p00),
551 ]
552}
553
554#[cfg(test)]
555mod tests {
556 #![allow(
560 clippy::unreadable_literal,
561 clippy::float_cmp,
562 clippy::indexing_slicing,
563 clippy::cast_precision_loss,
564 clippy::cast_possible_truncation,
565 reason = "test fixtures quote oracle vectors verbatim and compare exactly"
566 )]
567
568 use super::{MAX_COMPONENTS, MeshParams, MeshReader, ShadingKind};
569 use crate::color::ColorSpace;
570
571 fn params(components: usize, decode: &[f32]) -> Option<MeshParams> {
572 MeshParams::new(8, 8, 8, components, decode, ShadingKind::FreeFormMesh)
573 }
574
575 #[test]
576 fn bit_widths_are_validated_per_field() {
577 assert!(MeshParams::new(24, 8, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_some());
579 assert!(MeshParams::new(32, 8, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_some());
580 assert!(MeshParams::new(8, 24, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_none());
581 assert!(MeshParams::new(8, 32, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_none());
582 assert!(MeshParams::new(3, 8, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_none());
584 assert!(MeshParams::new(8, 3, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_none());
585 assert!(MeshParams::new(8, 8, 3, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_none());
587 assert!(MeshParams::new(8, 8, 2, 1, &[0.0; 6], ShadingKind::FreeFormMesh).is_some());
588 assert!(MeshParams::new(8, 8, 3, 1, &[0.0; 6], ShadingKind::LatticeMesh).is_some());
590 }
591
592 #[test]
593 fn the_decode_length_must_be_exact() {
594 assert!(params(1, &[0.0; 6]).is_some());
596 assert!(params(1, &[0.0; 5]).is_none());
597 assert!(params(1, &[0.0; 7]).is_none());
598 assert!(params(3, &[0.0; 10]).is_some());
600 assert!(params(3, &[0.0; 8]).is_none());
601 }
602
603 #[test]
604 fn more_than_eight_components_is_refused() {
605 assert!(params(MAX_COMPONENTS, &[0.0; 20]).is_some());
606 assert!(params(MAX_COMPONENTS + 1, &[0.0; 22]).is_none());
607 }
608
609 #[test]
610 fn flags_are_masked_to_two_bits() {
611 let p = MeshParams::new(8, 8, 8, 1, &[0.0; 6], ShadingKind::FreeFormMesh).expect("params");
612 let data = [0xFFu8; 8];
614 let space = ColorSpace::DeviceGray;
615 let mut reader = MeshReader::new(&data, &p, &space, &[]);
616 assert_eq!(reader.read_flag(), 3);
617 }
618
619 #[test]
620 fn a_lattice_row_shorter_than_two_yields_nothing() {
621 let p = MeshParams::new(
622 8,
623 8,
624 8,
625 1,
626 &[0.0f32, 1.0, 0.0, 1.0, 0.0, 1.0],
627 ShadingKind::LatticeMesh,
628 )
629 .expect("params");
630 let data = [0u8; 64];
631 let space = ColorSpace::DeviceGray;
632 let mut reader = MeshReader::new(&data, &p, &space, &[]);
633 assert!(reader.read_lattice(1).is_empty());
634 assert!(reader.read_lattice(0).is_empty());
635 }
636
637 #[test]
638 fn free_form_flag_three_behaves_as_flag_two() {
639 let p = MeshParams::new(
640 8,
641 8,
642 8,
643 1,
644 &[0.0f32, 255.0, 0.0, 255.0, 0.0, 1.0],
645 ShadingKind::FreeFormMesh,
646 )
647 .expect("params");
648 let space = ColorSpace::DeviceGray;
649 let mut data = Vec::new();
651 for (flag, x, y) in [(0u8, 0u8, 0u8), (0, 10, 0), (0, 0, 10)] {
652 data.extend_from_slice(&[flag, x, y, 128]);
653 }
654 data.extend_from_slice(&[2, 20, 20, 128]);
655 let mut reader = MeshReader::new(&data, &p, &space, &[]);
656 let with_two = reader.read_free_form();
657
658 let mut data3 = Vec::new();
659 for (flag, x, y) in [(0u8, 0u8, 0u8), (0, 10, 0), (0, 0, 10)] {
660 data3.extend_from_slice(&[flag, x, y, 128]);
661 }
662 data3.extend_from_slice(&[3, 20, 20, 128]);
663 let mut reader = MeshReader::new(&data3, &p, &space, &[]);
664 let with_three = reader.read_free_form();
665 assert_eq!(with_two, with_three);
666 assert_eq!(with_two.len(), 2);
667 }
668
669 #[test]
670 fn a_truncated_stream_stops_rather_than_reading_past_the_end() {
671 let p = MeshParams::new(
672 8,
673 8,
674 8,
675 1,
676 &[0.0f32, 255.0, 0.0, 255.0, 0.0, 1.0],
677 ShadingKind::FreeFormMesh,
678 )
679 .expect("params");
680 let space = ColorSpace::DeviceGray;
681 let data = [0u8, 5];
683 let mut reader = MeshReader::new(&data, &p, &space, &[]);
684 assert!(reader.read_free_form().is_empty());
685 }
686
687 #[test]
688 fn coons_interiors_are_derived_from_the_boundary() {
689 let boundary: Vec<kurbo::Point> = (0..12)
691 .map(|i| {
692 let t = f64::from(i) / 12.0;
693 kurbo::Point::new(t, t)
694 })
695 .collect();
696 let interior = super::coons_interior(&boundary);
697 for p in interior {
698 assert!(p.x.is_finite() && p.y.is_finite());
699 }
700 }
701}