//! The optimised decode / mask / placed-bake paths against the implementations they replaced //! (kept below verbatim as references), and counters for the work they no longer do. use super::counters::{self, INSIDE, QUAD, SCRATCH}; use super::placed::{Warp, square_to_quad}; use super::write::*; use super::*; // ------------------------------------------------------------------ the replaced code fn ref_unpredict(depth: u16, v: &mut [u8], w: usize, h: usize) -> usize { let mut allocs = 0; match depth { 16 => { for row in v.chunks_exact_mut(w * 2).take(h) { let mut prev = 0u16; for c in row.as_chunks_mut::<2>().0 { let x = u16::from_be_bytes([c[0], c[1]]).wrapping_add(prev); c.copy_from_slice(&x.to_be_bytes()); prev = x; } } } 32 => { for row in v.chunks_exact_mut(w * 4).take(h) { for i in 1..row.len() { row[i] = row[i].wrapping_add(row[i - 1]); } let planar = row.to_vec(); allocs += 1; for x in 0..w { for b in 0..4 { row[x * 4 + b] = planar[b * w + x]; } } } } _ => { for row in v.chunks_exact_mut(w).take(h) { for i in 1..row.len() { row[i] = row[i].wrapping_add(row[i - 1]); } } } } allocs } fn ref_decode_channel(p: &Psd, offset: usize, len: usize, w: usize, h: usize) -> Result> { let end = offset.checked_add(len).filter(|&e| e <= p.data.len()).ok_or(Error::Truncated)?; let mut r = Reader::at(&p.data[..end], offset); let comp = r.u16()?; decode_pixels(w, h)?; let rowb = p.row_bytes(w); let raw = match comp { 0 => r.bytes(rowb * h)?.to_vec(), 1 => { let mut counts = Vec::with_capacity(h.min(r.remaining() / 2)); for _ in 0..h { counts.push(if p.psb { r.u32()? as usize } else { r.u16()? as usize }); } let mut out = Vec::with_capacity(rowb * h); for c in counts { unpack_bits(r.bytes(c)?, rowb, &mut out)?; } out } 2 | 3 => { let rest = r.bytes(r.remaining())?; let mut v = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(rest, rowb * h).map_err(|e| Error::Invalid(format!("zip: {e:?}")))?; v.resize(rowb * h, 0); if comp == 3 { ref_unpredict(p.depth, &mut v, w, h); } v } c => return Err(Error::Unsupported(format!("compression {c}"))), }; Ok(p.ref_samples(&raw, w, h)) } fn ref_composite(p: &Psd) -> Result { let (w, h) = (p.width as usize, p.height as usize); decode_pixels(w, h)?; let mut r = Reader::at(&p.data, p.merged_offset); let comp = r.u16()?; let nch = p.channels as usize; let cc = p.mode.color_channels(); let want = (cc + usize::from(p.merged_alpha)).min(nch); let rowb = p.row_bytes(w); let mut planes: Vec>> = vec![None; nch]; match comp { 0 => { for pl in planes.iter_mut().take(want) { *pl = Some(p.ref_samples(r.bytes(rowb * h)?, w, h)); } } 1 => { let mut counts = Vec::with_capacity((nch * h).min(r.remaining() / 2)); for _ in 0..nch * h { counts.push(if p.psb { r.u32()? as usize } else { r.u16()? as usize }); } for (c, pl) in planes.iter_mut().enumerate().take(want) { let mut out = Vec::with_capacity(rowb * h); for row in 0..h { unpack_bits(r.bytes(counts[c * h + row])?, rowb, &mut out)?; } *pl = Some(p.ref_samples(&out, w, h)); } } 2 | 3 => { let rest = r.bytes(r.remaining())?; let v = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(rest, want * rowb * h).map_err(|e| Error::Invalid(format!("zip: {e:?}")))?; for (c, pl) in planes.iter_mut().enumerate().take(want) { let mut plane = v.get(c * rowb * h..(c + 1) * rowb * h).ok_or(Error::Truncated)?.to_vec(); if comp == 3 { ref_unpredict(p.depth, &mut plane, w, h); } *pl = Some(p.ref_samples(&plane, w, h)); } } c => return Err(Error::Unsupported(format!("compression {c}"))), } let alpha = if p.merged_alpha && nch > cc { planes[cc].take() } else { None }; let mut data = Vec::with_capacity(w * h); for i in 0..w * h { let [r, g, b] = p.ref_to_rgb(&planes, i); let a = alpha.as_ref().map_or(1.0, |a| a[i]); data.push([r, g, b, a]); } Ok(Pixels { width: w as u32, height: h as u32, data }) } fn ref_layer_pixels(p: &Psd, index: usize, canvas: bool) -> Result { let l = p.layers.get(index).ok_or_else(|| Error::Invalid(format!("no layer {index}")))?; let doc = Rect { top: 0, left: 0, bottom: p.height as i32, right: p.width as i32 }; let fill_rect = if l.fill.is_some() && !l.has_pixels() { l.mask.filter(|m| !m.rect.is_empty() && m.default_color == 0).map_or(doc, |m| m.rect) } else { l.rect }; let out_rect = if canvas { doc } else if fill_rect.is_empty() { Rect { top: 0, left: 0, bottom: 1, right: 1 } } else { fill_rect }; let (ow, oh) = (out_rect.width() as usize, out_rect.height() as usize); let mut out = vec![[0.0f32; 4]; decode_pixels(ow, oh)?]; let src = fill_rect; let (sw, sh) = (src.width() as usize, src.height() as usize); if sw > 0 && sh > 0 { let mut planes: Vec>> = vec![None; p.mode.color_channels()]; let mut alpha = None; if l.has_pixels() { for c in &l.channels { match c.id { -1 => alpha = Some(ref_decode_channel(p, c.offset, c.len, sw, sh)?), id if id >= 0 && (id as usize) < planes.len() => planes[id as usize] = Some(ref_decode_channel(p, c.offset, c.len, sw, sh)?), _ => {} } } } let fill = l.fill.map(|c| c.map(|v| v as f32)); let span = |s0: i32, n: usize, o0: i32, on: usize| { let off = i64::from(o0) - i64::from(s0); let lo = off.clamp(0, n as i64); let hi = (off + on as i64).clamp(lo, n as i64); (lo as usize..hi as usize, off) }; let (ys, oy) = span(src.top, sh, out_rect.top, oh); let (xs, ox) = span(src.left, sw, out_rect.left, ow); for y in ys { let dy = (y as i64 - oy) as usize; for x in xs.clone() { let dx = (x as i64 - ox) as usize; let i = y * sw + x; let (rgb, a) = match fill { Some(f) if !l.has_pixels() => (f, 1.0), _ => (p.ref_to_rgb(&planes, i), alpha.as_ref().map_or(1.0, |a| a[i])), }; out[dy * ow + dx] = [rgb[0], rgb[1], rgb[2], a]; } } } if let Some(m) = l.mask.filter(|m| !m.disabled) { let mc = l.channels.iter().find(|c| c.id == -2); let (mw, mh) = (m.rect.width() as usize, m.rect.height() as usize); let mask = match mc { Some(c) if mw > 0 && mh > 0 => Some(ref_decode_channel(p, c.offset, c.len, mw, mh)?), _ => None, }; let def = m.default_color as f32 / 255.0; for y in 0..oh { let py = out_rect.top + y as i32; for x in 0..ow { let px = out_rect.left + x as i32; let inside = py >= m.rect.top && py < m.rect.bottom && px >= m.rect.left && px < m.rect.right; let mut v = match (&mask, inside) { (Some(mk), true) => mk[(py - m.rect.top) as usize * mw + (px - m.rect.left) as usize], _ => def, }; if m.inverted { v = 1.0 - v; } out[y * ow + x][3] *= v; } } } Ok(Pixels { width: ow as u32, height: oh as u32, data: out }) } fn ref_map(so: &SmartObject, p: [f64; 2], w: f64, h: f64) -> [f64; 2] { let p = match &so.warp { Some(wp) => wp.apply(p, w, h), None => p, }; let m = square_to_quad(so.quad); let (u, v) = (p[0] / w.max(1e-9), p[1] / h.max(1e-9)); let z = m[2][0] * u + m[2][1] * v + m[2][2]; let z = if z.abs() < 1e-12 { 1e-12 } else { z }; [(m[0][0] * u + m[0][1] * v + m[0][2]) / z, (m[1][0] * u + m[1][1] * v + m[1][2]) / z] } fn ref_placed_bounds(so: &SmartObject, w: f64, h: f64) -> [f64; 4] { let n = 32; let mut b = [f64::MAX, f64::MAX, f64::MIN, f64::MIN]; for j in 0..=n { for i in 0..=n { let p = ref_map(so, [i as f64 / n as f64 * w, j as f64 / n as f64 * h], w, h); b = [b[0].min(p[0]), b[1].min(p[1]), b[2].max(p[0]), b[3].max(p[1])]; } } [b[0].floor(), b[1].floor(), b[2].ceil(), b[3].ceil()] } fn ref_placed_size(so: &SmartObject, w: f64, h: f64, k: f64) -> (u32, u32) { let bb = ref_placed_bounds(so, w, h); (((bb[2] - bb[0]) * k).ceil().clamp(1.0, 16384.0) as u32, ((bb[3] - bb[1]) * k).ceil().clamp(1.0, 16384.0) as u32) } fn ref_render_placed(so: &SmartObject, content: &Pixels, k: f64, size: Option<(u32, u32)>) -> Option<(Pixels, [f64; 2])> { let (cw, ch) = (content.width as f64, content.height as f64); if content.width == 0 || content.height == 0 { return None; } let bb = ref_placed_bounds(so, cw, ch); let (ow, oh) = match size { Some((w, h)) => (w.max(1), h.max(1)), None => ref_placed_size(so, cw, ch, k), }; let (ow, oh) = (ow as usize, oh as usize); let n = 48usize; let mut dst = Vec::with_capacity((n + 1) * (n + 1)); let mut src = Vec::with_capacity((n + 1) * (n + 1)); for j in 0..=n { for i in 0..=n { let s = [i as f64 / n as f64 * cw, j as f64 / n as f64 * ch]; let d = ref_map(so, s, cw, ch); dst.push([(d[0] - bb[0]) * k, (d[1] - bb[1]) * k]); src.push(s); } } let mut out = vec![[0.0f32; 4]; ow * oh]; let sample = |x: f64, y: f64| -> [f32; 4] { let (fx, fy) = ((x - 0.5).clamp(0.0, cw - 1.0), (y - 0.5).clamp(0.0, ch - 1.0)); let (x0, y0) = (fx.floor() as usize, fy.floor() as usize); let (x1, y1) = ((x0 + 1).min(content.width as usize - 1), (y0 + 1).min(content.height as usize - 1)); let (a, b) = ((fx - x0 as f64) as f32, (fy - y0 as f64) as f32); let at = |x: usize, y: usize| { let p = content.data[y * content.width as usize + x]; [p[0] * p[3], p[1] * p[3], p[2] * p[3], p[3]] }; let (p00, p10, p01, p11) = (at(x0, y0), at(x1, y0), at(x0, y1), at(x1, y1)); let mut o = [0.0; 4]; for c in 0..4 { o[c] = (p00[c] * (1.0 - a) + p10[c] * a) * (1.0 - b) + (p01[c] * (1.0 - a) + p11[c] * a) * b; } o }; let idx = |i: usize, j: usize| j * (n + 1) + i; for j in 0..n { for i in 0..n { for tri in [[idx(i, j), idx(i + 1, j), idx(i + 1, j + 1)], [idx(i, j), idx(i + 1, j + 1), idx(i, j + 1)]] { let [a, b, c] = tri.map(|t| dst[t]); let den = (b[1] - c[1]) * (a[0] - c[0]) + (c[0] - b[0]) * (a[1] - c[1]); if den.abs() < 1e-12 { continue; } let x0 = a[0].min(b[0]).min(c[0]).floor().max(0.0) as usize; let x1 = (a[0].max(b[0]).max(c[0]).ceil() as usize).min(ow); let y0 = a[1].min(b[1]).min(c[1]).floor().max(0.0) as usize; let y1 = (a[1].max(b[1]).max(c[1]).ceil() as usize).min(oh); for y in y0..y1 { for x in x0..x1 { let (px, py) = (x as f64 + 0.5, y as f64 + 0.5); let l1 = ((b[1] - c[1]) * (px - c[0]) + (c[0] - b[0]) * (py - c[1])) / den; let l2 = ((c[1] - a[1]) * (px - c[0]) + (a[0] - c[0]) * (py - c[1])) / den; let l3 = 1.0 - l1 - l2; if l1 < -1e-9 || l2 < -1e-9 || l3 < -1e-9 { continue; } let [sa, sb, sc] = tri.map(|t| src[t]); let sx = sa[0] * l1 + sb[0] * l2 + sc[0] * l3; let sy = sa[1] * l1 + sb[1] * l2 + sc[1] * l3; out[y * ow + x] = sample(sx, sy); } } } } } for p in &mut out { if p[3] > 0.0 { for c in 0..3 { p[c] /= p[3]; } } } Some((Pixels { width: ow as u32, height: oh as u32, data: out }, [bb[0], bb[1]])) } // ------------------------------------------------------------------ fixtures fn bits(p: &Pixels) -> Vec<[u32; 4]> { p.data.iter().map(|px| px.map(f32::to_bits)).collect() } /// Deterministic samples in 0..1 for a `w`×`h` plane, as file bytes at `depth`. fn synth_plane(depth: u16, w: usize, h: usize, seed: usize) -> Vec { let mut out = vec![]; for i in 0..w * h { let v = ((i * 7 + seed * 13) % 101) as f32 / 100.0; match depth { 8 => out.push((v * 255.0).round() as u8), 16 => out.extend(((v * 65535.0).round() as u16).to_be_bytes()), _ => out.extend(v.to_be_bytes()), } } out } /// The delta coding that `comp` 3 undoes (the inverse of `Psd::unpredict`). fn predict(raw: &[u8], w: usize, depth: u16) -> Vec { let mut out = raw.to_vec(); match depth { 16 => { for row in out.chunks_exact_mut(w * 2) { let mut prev = 0u16; for c in row.as_chunks_mut::<2>().0 { let x = u16::from_be_bytes([c[0], c[1]]); c.copy_from_slice(&x.wrapping_sub(prev).to_be_bytes()); prev = x; } } } 32 => { for row in out.chunks_exact_mut(w * 4) { let inter = row.to_vec(); for x in 0..w { for b in 0..4 { row[b * w + x] = inter[x * 4 + b]; } } for i in (1..row.len()).rev() { row[i] = row[i].wrapping_sub(row[i - 1]); } } } _ => { for row in out.chunks_exact_mut(w) { for i in (1..row.len()).rev() { row[i] = row[i].wrapping_sub(row[i - 1]); } } } } out } /// A pixel-data stream (compression field first) of consecutive `w`×`h` planes. fn stream(planes: &[Vec], w: usize, h: usize, depth: u16, comp: u16) -> Vec { let mut out = comp.to_be_bytes().to_vec(); let rowb = planes.first().map_or(0, |p| p.len() / h.max(1)); match comp { 0 => planes.iter().for_each(|p| out.extend(p)), 1 => { let rows: Vec> = planes.iter().flat_map(|p| p.chunks(rowb.max(1)).map(pack_bits)).collect(); rows.iter().for_each(|r| out.extend((r.len() as u16).to_be_bytes())); rows.iter().for_each(|r| out.extend(r)); } _ => { let all: Vec = planes.iter().flat_map(|p| if comp == 3 { predict(p, w, depth) } else { p.clone() }).collect(); out.extend(miniz_oxide::deflate::compress_to_vec_zlib(&all, 6)); } } out } /// A small RGB document with a background, a hidden box and a masked layer (8 bit, raw). fn base() -> Psd { let mut d = WDoc::new(40, 30); d.rle = false; let grad: Vec<[f32; 4]> = (0..1200).map(|i| [(i % 40) as f32 / 39.0, (i / 40) as f32 / 29.0, 0.25, 1.0]).collect(); let bg = WLayer::pixels("Background", Rect::new(0, 0, 40, 30), grad); let mut red = WLayer::solid("Red", Rect::new(5, 6, 10, 8), [1.0, 0.0, 0.0, 0.5]); red.hidden = true; let mut masked = WLayer::solid("Masked", Rect::new(20, 10, 12, 12), [0.0, 0.5, 1.0, 1.0]); let data = (0..144).map(|i| if i % 12 < 6 { 1.0 } else { 0.25 }).collect(); masked.mask = Some(WMask { rect: Rect::new(20, 10, 12, 12), data, default_color: 0, disabled: false }); d.layers = vec![bg, red, masked]; Psd::parse(write(&d)).unwrap() } /// `p` with every channel and the merged image re-encoded at `depth` with compression `comp`. fn reencode(p: &Psd, depth: u16, comp: u16) -> Psd { let mut z = p.clone(); z.depth = depth; let mut data = p.data.to_vec(); for (n, (l, zl)) in p.layers.iter().zip(&mut z.layers).enumerate() { for (c, zc) in l.channels.iter().zip(&mut zl.channels) { let r = if c.id == -2 { l.mask.map(|m| m.rect).unwrap_or(l.rect) } else { l.rect }; let (w, h) = (r.width() as usize, r.height() as usize); zc.offset = data.len(); data.extend(stream(&[synth_plane(depth, w, h, n * 5 + (c.id + 2) as usize)], w, h, depth, comp)); zc.len = data.len() - zc.offset; } } let (w, h) = (p.width as usize, p.height as usize); let planes: Vec> = (0..usize::from(p.channels)).map(|c| synth_plane(depth, w, h, 100 + c)).collect(); z.merged_offset = data.len(); data.extend(stream(&planes, w, h, depth, comp)); z.data = data.into(); z } fn variants() -> impl Iterator { [8u16, 16, 32].into_iter().flat_map(|d| [0u16, 1, 2, 3].into_iter().map(move |c| (d, c))) } // ------------------------------------------------------------------ decoding #[test] fn unpredict_matches_the_reference_and_allocates_one_scratch_row() { for depth in [8u16, 16, 32] { let p = Psd { depth, ..base() }; let (w, h) = (13usize, 9usize); let bps = usize::from(depth / 8); let mut a: Vec = (0..w * h * bps).map(|i| (i * 31 % 251) as u8).collect(); let mut b = a.clone(); counters::take(&SCRATCH); p.unpredict(&mut a, w, h); let scratch = counters::take(&SCRATCH); let old_allocs = ref_unpredict(depth, &mut b, w, h); assert_eq!(a, b, "{depth} bit"); // The reference copied every row; the new code uses one scratch row (32 bit only). assert_eq!((scratch, old_allocs), if depth == 32 { (1, h) } else { (0, 0) }, "{depth} bit"); } } #[test] fn raw_planes_are_borrowed_and_zip_planes_are_unpredicted_in_place() { let p = base(); // Raw: the sample bytes are a view into the file data. let q = reencode(&p, 8, 0); let c = q.layers[0].channels[0]; let mut r = Reader::at(&q.data, c.offset); let comp = r.u16().unwrap(); let plane = q.plane_bytes(comp, &mut r, 40, 30, &mut Budget::new(u64::MAX)).unwrap(); let Cow::Borrowed(b) = plane else { panic!("raw plane was copied") }; let file = q.data.as_ptr_range(); assert!(file.contains(&b.as_ptr()) && b.len() == 1200); // ZIP: the plane handed out is a sub-slice of the inflated buffer, not a copy. let mut v: Vec = (0..3 * 40 * 30).map(|i| (i % 255) as u8).collect(); let range = v.as_ptr_range(); for comp in [2, 3] { let got = q.zip_plane(comp, &mut v, 1, 40, 30).unwrap(); assert_eq!(got.len(), 1200); assert!(range.contains(&got.as_ptr()), "compression {comp}"); } assert!(q.zip_plane(2, &mut v, 3, 40, 30).is_err()); } #[test] fn decode_and_composite_match_the_reference() { let p = base(); for (depth, comp) in variants() { let q = reencode(&p, depth, comp); let what = format!("{depth} bit, compression {comp}"); assert_eq!(bits(&q.composite().unwrap()), bits(&ref_composite(&q).unwrap()), "composite {what}"); for i in 0..q.layers.len() { for canvas in [false, true] { assert_eq!(bits(&q.layer_pixels(i, canvas).unwrap()), bits(&ref_layer_pixels(&q, i, canvas).unwrap()), "layer {i} canvas {canvas}, {what}"); } } } } #[test] fn errors_match_the_reference_on_damaged_data() { let p = base(); for (depth, comp) in variants() { let mut q = reencode(&p, depth, comp); // Cut the file in the middle of the merged image / the last channel. let cut = q.data.len() - 7; q.data = q.data[..cut].into(); assert_eq!(q.composite().map(|p| bits(&p)), ref_composite(&q).map(|p| bits(&p)), "{depth} bit, compression {comp}"); assert_eq!(q.layer_pixels(2, true).map(|p| bits(&p)), ref_layer_pixels(&q, 2, true).map(|p| bits(&p)), "{depth} bit, compression {comp}"); } } // ------------------------------------------------------------------ the layer mask fn rect_at(left: i32, top: i32) -> Rect { Rect::new(left, top, 12, 12) } #[test] fn layer_mask_matches_the_reference_for_every_mask_shape() { let p = base(); for (depth, comp) in [(8, 1), (16, 2), (32, 3), (8, 0)] { let q = reencode(&p, depth, comp); // Moved so it overlaps the layer / canvas partly, wholly, or not at all (the data is 12×12). for (left, top) in [(20, 10), (14, 4), (-5, -3), (34, 24), (25, 12), (300, 300), (-300, 5)] { for default_color in [0u8, 255, 90] { for inverted in [false, true] { for disabled in [false, true] { for with_data in [true, false] { let mut r = q.clone(); r.layers[2].mask = Some(LayerMask { rect: rect_at(left, top), default_color, disabled, inverted }); if !with_data { r.layers[2].channels.retain(|c| c.id != -2); } for canvas in [false, true] { let what = format!( "{depth} bit comp {comp}, mask at ({left},{top}) default {default_color} inverted {inverted} disabled {disabled} data {with_data} canvas {canvas}" ); assert_eq!(bits(&r.layer_pixels(2, canvas).unwrap()), bits(&ref_layer_pixels(&r, 2, canvas).unwrap()), "{what}"); } } } } } } } } #[test] fn layer_mask_evaluates_inside_per_row_and_skips_no_ops() { let q = reencode(&base(), 8, 0); let with = |default_color, inverted, data: bool| { let mut r = q.clone(); r.layers[2].mask = Some(LayerMask { rect: rect_at(20, 10), default_color, disabled: false, inverted }); if !data { r.layers[2].channels.retain(|c| c.id != -2); } r }; // With mask data: one inside test per output row (the reference tested every pixel: 40 × 30). counters::take(&INSIDE); with(0, false, true).layer_pixels(2, true).unwrap(); assert_eq!(counters::take(&INSIDE), 30); // Own bounds: 12 rows. with(0, false, true).layer_pixels(2, false).unwrap(); assert_eq!(counters::take(&INSIDE), 12); // No mask data: no per-pixel or per-row test at all (a constant factor, skipped when 1.0). with(255, false, false).layer_pixels(2, true).unwrap(); with(0, true, false).layer_pixels(2, true).unwrap(); with(0, false, false).layer_pixels(2, true).unwrap(); assert_eq!(counters::take(&INSIDE), 0); // A no-op mask factor leaves the alpha untouched. let plain = with(255, false, false).layer_pixels(2, false).unwrap(); let mut none = q.clone(); none.layers[2].mask = None; assert_eq!(bits(&plain), bits(&none.layer_pixels(2, false).unwrap())); } // ------------------------------------------------------------------ placed layers fn warps() -> Vec> { let mut mesh = vec![]; for j in 0..4 { for i in 0..4 { mesh.push([i as f64 * 20.0 / 3.0 + if j == 1 || j == 2 { 2.0 } else { 0.0 }, j as f64 * 10.0 / 3.0]); } } vec![ None, Some(Warp { style: "warpArc".into(), bend: 40.0, bounds: [0.0, 0.0, 20.0, 10.0], ..Default::default() }), Some(Warp { style: "warpCustom".into(), bounds: [0.0, 0.0, 20.0, 10.0], cols: 1, rows: 1, mesh, ..Default::default() }), ] } fn content(w: u32, h: u32) -> Pixels { Pixels { width: w, height: h, data: (0..w * h).map(|i| [(i % w) as f32 / w as f32, (i / w) as f32 / h as f32, 0.5, if i % 7 == 0 { 0.4 } else { 1.0 }]).collect(), } } #[test] fn placed_bake_matches_the_reference() { let quads = [ [[10.0, 10.0], [50.0, 0.0], [50.0, 40.0], [10.0, 30.0]], [[0.0, 0.0], [20.0, 0.0], [20.0, 10.0], [0.0, 10.0]], [[3.0, 4.0], [33.0, 9.0], [28.0, 29.0], [-2.0, 21.0]], [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], ]; let c = content(20, 10); for quad in quads { for warp in warps() { let so = SmartObject { uuid: "x".into(), quad, size: None, warp }; let what = format!("{quad:?} {:?}", so.warp.as_ref().map(|w| &w.style)); assert_eq!(so.placed_bounds(20.0, 10.0), ref_placed_bounds(&so, 20.0, 10.0), "{what}"); for k in [0.5, 1.0, 2.0] { assert_eq!(so.placed_size(20.0, 10.0, k), ref_placed_size(&so, 20.0, 10.0, k), "{what} k {k}"); for size in [None, Some((30, 25)), Some((0, 0))] { let got = so.render_placed(&c, k, size); let want = ref_render_placed(&so, &c, k, size); assert_eq!( got.as_ref().map(|(p, o)| (p.width, p.height, bits(p), *o)), want.as_ref().map(|(p, o)| (p.width, p.height, bits(p), *o)), "{what} k {k} size {size:?}" ); } } for p in [[0.0, 0.0], [10.0, 5.0], [20.0, 10.0], [7.5, 2.25]] { assert_eq!(so.map(p, 20.0, 10.0), ref_map(&so, p, 20.0, 10.0), "{what} {p:?}"); } } } assert!(SmartObject { uuid: "x".into(), quad: [[0.0; 2]; 4], size: None, warp: None }.render_placed(&content(0, 0), 1.0, None).is_none()); } #[test] fn placed_bake_solves_the_quad_once() { let so = SmartObject { uuid: "x".into(), quad: [[10.0, 10.0], [50.0, 0.0], [50.0, 40.0], [10.0, 30.0]], size: None, warp: None }; counters::take(&QUAD); so.placed_bounds(20.0, 10.0); assert_eq!(counters::take(&QUAD), 1, "33×33 mapped points, one solve (was 1089)"); so.placed_size(20.0, 10.0, 1.0); assert_eq!(counters::take(&QUAD), 1); // The bounds, the size and the 49×49 grid of one bake share a single solve (was 1089 × 2 + 2401). so.render_placed(&content(20, 10), 1.0, None).unwrap(); assert_eq!(counters::take(&QUAD), 1); so.render_placed(&content(20, 10), 1.0, Some((40, 40))).unwrap(); assert_eq!(counters::take(&QUAD), 1); // A single `map` still solves for itself. so.map([1.0, 1.0], 20.0, 10.0); assert_eq!(counters::take(&QUAD), 1); }