//! Shadings that aren't gradients, as gradient meshes: function-based ones (type 1) sampled on a //! grid, free-form and lattice triangle meshes (types 4 and 5) and Coons and tensor patch meshes //! (types 6 and 7), their vertices read from an array or packed in a string or file; and sampled //! functions (type 0) that shadings and tint transforms use. use std::sync::Arc; use vectorcraft_color::{Color, Paint}; use vectorcraft_doc::live::GradientMesh; use vectorcraft_doc::{Node, NodeId, NodeKind}; use vectorcraft_geom::{Affine, Point}; use super::graphics::Space; use super::interp::{Interp, matrix_of}; use super::obj::{DictRef, Key, Obj, PsError, Res, clamp_between, ps_err}; /// Most patches (or triangles) one shading makes: past it, it is left out with a warning. const MAX_PATCHES: usize = 4096; /// Rows and columns of the mesh a function-based shading is sampled on. const FUNCTION_GRID: u32 = 16; /// Most samples a sampled function has. const MAX_SAMPLES: usize = 1 << 24; /// Most outputs (colour components) a sampled function has. const MAX_OUTPUTS: usize = 32; /// Work one patch costs against the operation budget. const PATCH_OPS: u64 = 64; const TOO_MANY_PATCHES: &str = "shadings of more than 4096 patches or triangles were left out"; /// The numbers of a shading's vertices: from an array, or packed in bits ([`Packed`]). enum Vertices { Nums(Vec, usize), Packed(Packed), } /// Vertex data packed in bits: flags, coordinates and colour components, each its own width and /// mapped through `Decode`. struct Packed { bytes: Vec, /// The next bit. at: usize, flag: u32, coord: u32, comp: u32, decode: Vec, } impl Packed { fn bits(&mut self, n: u32) -> Option { if n == 0 || n > 32 || self.at.checked_add(n as usize)? > self.bytes.len() * 8 { return None; } let mut v = 0u64; for _ in 0..n { let byte = *self.bytes.get(self.at / 8)?; v = v << 1 | u64::from(byte >> (7 - self.at % 8) & 1); self.at += 1; } Some(v) } /// A value of `n` bits through decode range `i` (`[min max]` pairs). fn value(&mut self, n: u32, i: usize) -> Option { let raw = self.bits(n)? as f64; let max = ((1u64 << n) - 1) as f64; let (lo, hi) = (self.decode.get(2 * i).copied().unwrap_or(0.0), self.decode.get(2 * i + 1).copied().unwrap_or(1.0)); Some(lo + raw * (hi - lo) / max) } } impl Vertices { fn flag(&mut self) -> Option { match self { Self::Nums(v, at) => { let f = *v.get(*at)?; *at += 1; Some(f.max(0.0) as u32) } Self::Packed(p) => p.bits(p.flag).map(|f| f as u32), } } fn num(&mut self, comp: bool, i: usize) -> Option { match self { Self::Nums(v, at) => { let f = *v.get(*at)?; *at += 1; f.is_finite().then_some(f) } Self::Packed(p) => p.value(if comp { p.comp } else { p.coord }, i), } } fn point(&mut self) -> Option { Some(Point::new(self.num(false, 0)?, self.num(false, 1)?)) } /// A vertex colour's `n` values (components, or the function's input). fn color(&mut self, n: usize) -> Option> { (0..n).map(|i| self.num(true, 2 + i)).collect() } /// Packed vertices start at a byte (triangle meshes). fn align(&mut self) { if let Self::Packed(p) = self { p.at = p.at.div_ceil(8) * 8; } } } impl Interp<'_> { /// Paint shading `sh` (types 1, 4–7) as gradient meshes, `m` mapping shading space onto the /// document, under the current clip. pub fn mesh_fill(&mut self, sh: &DictRef, m: Affine) -> Res { let get = |k: &str| sh.borrow().get(&Key::name(k)).cloned(); let kind = get("ShadingType").and_then(|o| o.as_num()).unwrap_or(0.0); let space = self.space_of(&get("ColorSpace").ok_or(PsError::Ps("undefined", "ColorSpace".into()))?, 0)?; let function = get("Function"); let meshes = if kind == 1.0 { self.function_mesh(sh, &space, m)? } else { self.patch_mesh(sh, kind, &space, function.as_ref(), m)? }; let Some(meshes) = meshes else { self.out.warn(TOO_MANY_PATCHES); return Ok(()); }; let mut nodes: Vec = meshes.into_iter().map(|g| Node::new(NodeId(0), NodeKind::Mesh(g))).collect(); let node = match nodes.len() { 0 => return Ok(()), 1 => nodes.swap_remove(0), _ => { let children = nodes .into_iter() .map(|mut n| { n.id = self.out.doc.alloc_id(); Arc::new(n) }) .collect(); Node::group(NodeId(0), children) } }; let clips = self.g.clips.clone(); self.out.push(node, &clips); Ok(()) } /// The colour of a vertex's values `v`: through the shading's function when it has one. fn vertex_color(&mut self, space: &Space, function: Option<&Obj>, v: &[f64]) -> Res { let comps = match function { Some(f) => self.eval(f, v, 0)?, None => v.to_vec(), }; Ok(match self.paint_of(space, &comps)? { Paint::Solid { color, .. } => color, _ => Color::BLACK, }) } /// A function-based shading (type 1) sampled on a grid over its domain. fn function_mesh(&mut self, sh: &DictRef, space: &Space, m: Affine) -> Res>> { let get = |k: &str| sh.borrow().get(&Key::name(k)).cloned(); let domain: Vec = get("Domain").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default(); let [x0, x1, y0, y1] = <[f64; 4]>::try_from(domain).unwrap_or([0.0, 1.0, 0.0, 1.0]); let f = get("Function").ok_or(PsError::Ps("undefined", "Function".into()))?; let matrix = get("Matrix").and_then(|o| o.items().and_then(|i| matrix_of(&i.borrow()))).unwrap_or(Affine::IDENTITY); let n = FUNCTION_GRID as usize; let at = |i: usize, lo: f64, hi: f64| lo + (hi - lo) * i as f64 / n as f64; let mut colors = Vec::with_capacity((n + 1) * (n + 1)); for r in 0..=n { for c in 0..=n { self.spend(PATCH_OPS)?; colors.push(self.vertex_color(space, Some(&f), &[at(c, x0, x1), at(r, y0, y1)])?); } } let xf = m * matrix; let surface = |u: f64, v: f64| xf * Point::new(x0 + (x1 - x0) * u, y0 + (y1 - y0) * v); let color = |u: f64, v: f64| { let (c, r) = ((u * n as f64).round() as usize, (v * n as f64).round() as usize); colors.get(r * (n + 1) + c).copied().unwrap_or(Color::BLACK) }; Ok(Some(vec![GradientMesh::from_surface(FUNCTION_GRID, FUNCTION_GRID, &surface, &color)])) } /// The vertices of a mesh shading's `DataSource`. fn vertices(&mut self, sh: &DictRef, flags: bool) -> Res { let get = |k: &str| sh.borrow().get(&Key::name(k)).cloned(); let bytes = match get("DataSource").ok_or(PsError::Ps("undefined", "DataSource".into()))? { Obj::Array { items, .. } => { let v: Option> = items.borrow().iter().map(Obj::as_num).collect(); return Ok(Vertices::Nums(v.ok_or(PsError::Ps("typecheck", "DataSource".into()))?, 0)); } Obj::Str(s) => s.to_vec(), Obj::File { stream, .. } => self.peek_all(&stream)?, _ => return ps_err("typecheck", "DataSource"), }; let bits = |k: &str| get(k).and_then(|o| o.as_num()).filter(|v| (1.0..=32.0).contains(v)).map(|v| v as u32); let (Some(coord), Some(comp)) = (bits("BitsPerCoordinate"), bits("BitsPerComponent")) else { return ps_err("rangecheck", "BitsPerCoordinate"); }; let flag = if flags { bits("BitsPerFlag").ok_or(PsError::Ps("rangecheck", "BitsPerFlag".into()))? } else { 0 }; let decode: Vec = get("Decode").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default(); Ok(Vertices::Packed(Packed { bytes, at: 0, flag, coord, comp, decode })) } /// A triangle (types 4, 5) or patch (6, 7) mesh in the document (`m`); `None` past /// [`MAX_PATCHES`]. fn patch_mesh(&mut self, sh: &DictRef, kind: f64, space: &Space, function: Option<&Obj>, m: Affine) -> Res>> { let n = if function.is_some() { 1 } else { space.n() }; let mut v = self.vertices(sh, kind != 5.0)?; let mut corner = |it: &mut Self, v: &mut Vertices| -> Res> { match v.color(n) { Some(c) => it.vertex_color(space, function, &c).map(Some), None => Ok(None), } }; if kind == 6.0 || kind == 7.0 { return self.patches(&mut v, kind == 7.0, m, &mut corner); } let per_row = sh.borrow().get(&Key::name("VerticesPerRow")).and_then(Obj::as_num).filter(|r| (2.0..=1e6).contains(r)).map(|r| r as usize); let lattice = kind == 5.0; let mut pts: Vec<(Point, Color)> = vec![]; let mut tris: Vec<[usize; 3]> = vec![]; // Free-form: vertices still to come of the triangle being made. let mut left = 0; loop { let flag = if lattice { 0 } else { v.flag().unwrap_or(u32::MAX) }; let Some(p) = v.point().filter(|_| flag != u32::MAX) else { break }; let Some(color) = corner(self, &mut v)? else { break }; if !lattice { v.align(); } self.spend(PATCH_OPS)?; pts.push((m * p, color)); let i = pts.len() - 1; if lattice { // Each cell between two rows is two triangles. if let Some(w) = per_row && i >= w && !i.is_multiple_of(w) { tris.push([i - w - 1, i - w, i - 1]); tris.push([i - w, i - 1, i]); } } else if left > 0 { // A vertex ending a triangle begun with flag 0. left -= 1; if left == 0 && i >= 2 { tris.push([i - 2, i - 1, i]); } } else { // A triangle on the last one's edge (flag 1: its second and third vertices, 2: its // first and third), or the first vertex of a new one. match (flag, tris.last().copied()) { (1, Some([_, b, c])) => tris.push([b, c, i]), (2, Some([a, _, c])) => tris.push([a, c, i]), _ => left = 2, } } if tris.len() > MAX_PATCHES { return Ok(None); } } let at = |i: usize| pts.get(i).copied().unwrap_or((Point::ZERO, Color::BLACK)); Ok(Some(tris.iter().filter_map(|t| GradientMesh::triangle(t.map(at))).collect())) } /// Coons (`tensor`: tensor-product) patches: twelve boundary points (a tensor patch's four /// inner ones are read and left out), four corner colours; a patch on the last one's edge /// (flag 1–3) takes that edge's four points and two colours from it. fn patches( &mut self, v: &mut Vertices, tensor: bool, m: Affine, corner: &mut impl FnMut(&mut Self, &mut Vertices) -> Res>, ) -> Res>> { let mut out = vec![]; let mut prev: Option<([Point; 12], [Color; 4])> = None; while let Some(flag) = v.flag() { let shared = match (flag, &prev) { (0, _) => None, (1, Some((p, c))) => Some(([p[3], p[4], p[5], p[6]], [c[1], c[2]])), (2, Some((p, c))) => Some(([p[6], p[7], p[8], p[9]], [c[2], c[3]])), (3, Some((p, c))) => Some(([p[9], p[10], p[11], p[0]], [c[3], c[0]])), _ => return ps_err("rangecheck", "a patch flag"), }; let mut cp = [Point::ZERO; 12]; let mut colors = [Color::BLACK; 4]; let (first_point, first_color) = match shared { Some((pts, cs)) => { cp[..4].copy_from_slice(&pts); colors[..2].copy_from_slice(&cs); (4, 2) } None => (0, 0), }; for slot in cp.iter_mut().skip(first_point) { let Some(p) = v.point() else { return Ok(Some(out)) }; *slot = m * p; } if tensor && (0..4).any(|_| v.point().is_none()) { return Ok(Some(out)); } for slot in colors.iter_mut().skip(first_color) { let Some(c) = corner(self, v)? else { return Ok(Some(out)) }; *slot = c; } self.spend(PATCH_OPS)?; if out.len() >= MAX_PATCHES { return Ok(None); } out.extend(GradientMesh::coons(&cp, colors)); prev = Some((cp, colors)); } Ok(Some(out)) } /// A sampled function (type 0) at `x`: the samples around it, interpolated linearly for /// one input, the nearest one for more. pub(super) fn sampled(&mut self, f: &DictRef, x: &[f64]) -> Res> { let get = |k: &str| f.borrow().get(&Key::name(k)).cloned(); let nums = |k: &str| -> Vec { get(k).and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default() }; let (domain, range, size) = (nums("Domain"), nums("Range"), nums("Size")); let m = domain.len() / 2; let outs = range.len() / 2; if m == 0 || outs == 0 || outs > MAX_OUTPUTS || size.len() < m || size.iter().any(|s| !(1.0..=MAX_SAMPLES as f64).contains(s)) { return ps_err("rangecheck", "a sampled function"); } let bps = get("BitsPerSample").and_then(|o| o.as_num()).filter(|b| [1.0, 2.0, 4.0, 8.0, 12.0, 16.0, 24.0, 32.0].contains(b)).map(|b| b as usize); let bps = bps.ok_or(PsError::Ps("rangecheck", "BitsPerSample".into()))?; if size.iter().take(m).try_fold(1usize, |acc, s| acc.checked_mul(*s as usize)).is_none_or(|t| t > MAX_SAMPLES) { return ps_err("limitcheck", "Size"); } let bytes = match get("DataSource") { Some(Obj::Str(s)) => s.to_vec(), Some(Obj::File { stream, .. }) => self.peek_all(&stream)?, _ => return ps_err("typecheck", "DataSource"), }; let (encode, decode) = (nums("Encode"), nums("Decode")); let max = ((1u64 << bps) - 1) as f64; let sample = |index: usize, j: usize| -> f64 { let bit = (index as u64 * outs as u64 + j as u64) * bps as u64; let raw = (0..bps as u64).fold(0u64, |v, k| { let b = usize::try_from((bit + k) / 8).ok().and_then(|i| bytes.get(i)).copied().unwrap_or(0); v << 1 | u64::from(b >> (7 - (bit + k) % 8) & 1) }) as f64; let (d0, d1) = ( decode.get(2 * j).or(range.get(2 * j)).copied().unwrap_or(0.0), decode.get(2 * j + 1).or(range.get(2 * j + 1)).copied().unwrap_or(1.0), ); let (r0, r1) = (range.get(2 * j).copied().unwrap_or(0.0), range.get(2 * j + 1).copied().unwrap_or(1.0)); clamp_between(d0 + raw * (d1 - d0) / max, r0, r1) }; // Each input into sample space. let mut pos = Vec::with_capacity(m); for i in 0..m { let (a, b) = (domain.get(2 * i).copied().unwrap_or(0.0), domain.get(2 * i + 1).copied().unwrap_or(1.0)); let s = size.get(i).copied().unwrap_or(1.0); let (e0, e1) = (encode.get(2 * i).copied().unwrap_or(0.0), encode.get(2 * i + 1).copied().unwrap_or(s - 1.0)); let xi = clamp_between(x.get(i).copied().unwrap_or(a), a, b); let e = if b != a { e0 + (xi - a) * (e1 - e0) / (b - a) } else { e0 }; pos.push(clamp_between(e, 0.0, s - 1.0)); } if let ([p], Some(s)) = (pos.as_slice(), size.first()) { let (lo, hi) = (p.floor() as usize, (p.ceil() as usize).min(*s as usize - 1)); let t = p - p.floor(); return Ok((0..outs).map(|j| sample(lo, j) * (1.0 - t) + sample(hi, j) * t).collect()); } let mut index = 0; let mut stride = 1; for (i, p) in pos.iter().enumerate() { index += p.round() as usize * stride; stride *= size.get(i).copied().unwrap_or(1.0) as usize; } Ok((0..outs).map(|j| sample(index, j)).collect()) } }