space_filling/drawing/
mod.rs

1#![allow(non_snake_case)]
2use {
3  crate::{
4    solver::{
5      Argmax2D, adf::{ADF, quadtree::Quadtree}
6    },
7    geometry::{
8      self, BoundingBox, Shape,
9      PixelSpace, WorldSpace, DistPoint,
10      Translation, Rotation, Scale
11    },
12    sdf::SDF
13  },
14  euclid::{Box2D, Point2D, Size2D, Vector2D as V2},
15  image::{
16    ImageBuffer, Luma, Rgba, Pixel, RgbaImage
17  },
18  num_traits::{Float, AsPrimitive, Signed}
19};
20
21mod impl_draw_rgbaimage;
22#[cfg(test)] mod tests;
23
24pub trait Draw<Float, Backend>: Shape<Float> {
25  fn draw(&self, image: &mut Backend);
26}
27
28static MSG: &str = "Draw is only implemented for Texture";
29
30// Rust doesn't support trait upcasting yet
31impl <B, S, P> Draw<P, B> for Translation<S, P> where Translation<S, P>: Shape<P> {
32  fn draw(&self, _: &mut B) { unreachable!("{}", MSG) } }
33impl <B, S, P> Draw<P, B> for Rotation<S, P> where Rotation<S, P>: Shape<P> {
34  fn draw(&self, _: &mut B) { unreachable!("{}", MSG) } }
35impl <B, S, P> Draw<P, B> for Scale<S, P> where Scale<S, P>: Shape<P> {
36  fn draw(&self, _: &mut B) { unreachable!("{}", MSG) } }
37
38impl <B, P> Draw<P, B> for geometry::Line<P> where geometry::Line<P>: Shape<P> {
39  fn draw(&self, _: &mut B) { unreachable!("{}", MSG) } }
40impl <B, P, U> Draw<P, B> for geometry::Polygon<U> where P: Float, U: AsRef<[Point2D<P, WorldSpace>]> {
41  fn draw(&self, _: &mut B) { unreachable!("{}", MSG) } }
42
43#[derive(Debug, Copy, Clone)]
44pub struct Texture<S, T> {
45  pub shape: S,
46  pub texture: T
47}
48impl <P, S, T> SDF<P> for Texture<S, T> where S: SDF<P> {
49  fn sdf(&self, pixel: Point2D<P, WorldSpace>) -> P { self.shape.sdf(pixel) } }
50impl <P, S, T> BoundingBox<P> for Texture<S, T> where S: BoundingBox<P> {
51  fn bounding_box(&self) -> Box2D<P, WorldSpace> { self.shape.bounding_box() } }
52
53// try to fit world in the center of image, preserving aspect ratio
54fn rescale_bounding_box(
55  bounding_box: Box2D<f64, WorldSpace>,
56  resolution: Size2D<u32, PixelSpace>
57) -> (
58  Option<Box2D<u32, PixelSpace>>, // bounding_box,
59  V2<f64, PixelSpace>, // offset
60  f64 // min_side
61) {
62  let min_side = resolution.width.min(resolution.height) as f64;
63  let offset = (resolution.to_vector().to_f64() - V2::splat(min_side)) / 2.0;
64  let bounding_box = bounding_box
65    .scale(min_side, min_side).cast_unit()
66    .round_out()
67    .translate(offset)
68    .intersection(&Box2D::from_size(resolution.to_f64()))
69    .map(|x| x.cast::<u32>());
70  (bounding_box, offset, min_side)
71}
72
73/// Draw shapes, parallel.
74/// May cause undefined behaviour.
75pub fn draw_parallel<Float, Backend, Sh>(
76  framebuffer: &mut Backend,
77  shapes: impl rayon::iter::ParallelIterator<Item =Sh>
78) -> &mut Backend
79  where Backend: Sync + Send,
80        Sh: AsRef<dyn Draw<Float, Backend> + Send + Sync>
81{
82  let ptr = framebuffer as *mut _ as usize;
83  shapes.for_each(|shape|
84    shape.as_ref().draw(unsafe { &mut *(ptr as *mut Backend) })
85  );
86  framebuffer
87}
88
89pub fn display_sdf(sdf: impl Fn(Point2D<f64, WorldSpace>) -> f64, image: &mut RgbaImage, brightness: f64) {
90  let resolution = image.width();
91  let Δp = 1.0 / resolution as f64;
92
93  // distance scalar field
94  image.enumerate_pixels_mut()
95    .for_each(|(x, y, pixel)| {
96      let pixel_world = Point2D::new(x, y).to_f64() / resolution as f64;
97      let sdf = sdf(pixel_world);
98      let mut alpha = (Δp  - sdf.abs()).clamp(0.0, Δp) / Δp;
99      alpha *= (x > 0 && y > 0) as u8 as f64;
100      let mut color = Luma([
101        ((sdf * brightness).powf(1.0) * 255.0) as u8
102      ]).to_rgba();
103      color.blend(&Rgba([255, 0, 0, (alpha * 128.0) as u8]));
104      *pixel = color;
105    });
106}
107
108impl Argmax2D {
109  pub fn display_debug(&self) -> image::RgbImage {
110    let mut image = ImageBuffer::<image::Rgb<u8>, _>::new(
111      self.dist_map.resolution as u32,
112      self.dist_map.resolution as u32
113    );
114    let max_dist = self.find_max().distance;
115    self.dist_map.pixels().for_each(|DistPoint { distance, point }| {
116      let color = Luma::from([(distance / max_dist * 255.0) as u8]);
117      *image.get_pixel_mut(point.x as u32, point.y as u32) = color.to_rgb();
118    });
119    image
120  }
121}
122
123impl <Data, _Float: Float> Quadtree<Data, _Float> {
124  pub fn draw_layout(&self, image: &mut RgbaImage) -> &Self {
125    use geometry::Line;
126
127    let px = 1.0 / image.width() as f64;
128    self.traverse(&mut |node| {
129      if node.children.is_some() { return Ok(()) };
130
131      let rect = node.rect.cast();
132      let lines = [
133        [[0.0, 0.0], [rect.size.width, 0.0]],
134        [[rect.size.width, 0.0], rect.size.into()],
135        [rect.size.into(), [0.0, rect.size.height]],
136        [[0.0, rect.size.height], [0.0, 0.0]]
137      ];
138      let alpha = 1.0 - (node.depth as f64 / self.max_depth as f64);
139      lines.iter().for_each(|&[a, b]| {
140        Line { a: a.into(), b: b.into(), thickness: px }
141          .translate(rect.origin.to_vector())
142          .texture(Rgba([
143            ((1.0 - alpha).powi(2) * 255.0) as u8,
144            0,
145            128,
146            ((1.0 - alpha).powf(0.5) * 255.0) as u8])
147          )
148          .draw(image);
149      });
150      Ok(())
151    }).ok();
152    self
153  }
154
155  pub fn draw_bounding(&self, domain: euclid::Rect<_Float, WorldSpace>, image: &mut RgbaImage) -> &Self {
156    self.traverse(&mut |node| {
157      if node.children.is_none() && node.rect.intersects(&domain) {
158        let rect = node.rect.cast();
159        geometry::Rect {
160          size: rect.size.to_vector().to_point()
161        } .translate(rect.origin.to_vector() + rect.size.to_vector() * 0.5)
162          .texture(Rgba([0xFF, 0, 0, 0x7F]))
163          .draw(image)
164      }
165      Ok(())
166    }).ok();
167    self
168  }
169}
170
171impl <_Float: Float + Signed + AsPrimitive<f64>> ADF<_Float> {
172  pub fn display_sdf(&self, image: &mut RgbaImage, brightness: f64) -> &Self {
173    display_sdf(|p| self.sdf(p.cast()).to_f64().unwrap(), image, brightness);
174    self
175  }
176  pub fn draw_bucket_weights(&self, image: &mut RgbaImage) -> &Self {
177    self.tree.traverse(&mut |node| {
178      if node.children.is_none() {
179        let rect = node.rect;
180        let alpha = (((node.data.len() - 1) as f64 / 3.0).powf(1.75)
181          * 0.33 * 255.0) as u8;
182        geometry::Rect {
183          size: rect.size.to_vector().to_point()
184        } .translate(rect.origin.to_vector() + rect.size.to_vector() * _Float::from(0.5).unwrap())
185          .texture(Rgba([0x7F, 0xFF, 0, alpha]))
186          .draw(image)
187      }
188      Ok(())
189    }).ok();
190    self
191  }
192}