space_filling/drawing/
impl_draw_rgbaimage.rs

1#![allow(non_snake_case)]
2
3use num_traits::Float;
4use {
5  std::{sync::Arc, ops::Fn},
6  euclid::{Point2D, Rect, Size2D, Box2D},
7  image::{
8    DynamicImage, GenericImageView, Pixel, Rgba, RgbaImage,
9    imageops::FilterType
10  },
11  num_traits::{NumCast, AsPrimitive},
12  crate::{
13    drawing::{Draw, Shape, Texture, rescale_bounding_box},
14    geometry::{BoundingBox, PixelSpace, WorldSpace},
15    sdf::SDF
16  }
17};
18
19impl<Ty, P> SDF<P> for Ty where Ty: AsRef<dyn Draw<P, RgbaImage>> { fn sdf(&self, pixel: Point2D<P, WorldSpace>) -> P { self.as_ref().sdf(pixel) } }
20impl<Ty, P> BoundingBox<P> for Ty where Ty: AsRef<dyn Draw<P, RgbaImage>> { fn bounding_box(&self) -> Box2D<P, WorldSpace> { self.as_ref().bounding_box() } }
21
22impl <Cutie, P: Float> Draw<P, RgbaImage> for Texture<Cutie, Rgba<u8>>
23  where Cutie: Shape<P> + Clone,
24        P: NumCast + AsPrimitive<f64>
25{
26  fn draw(&self, image: &mut RgbaImage) {
27    self.shape.clone()
28      .texture(|_| self.texture)
29      .draw(image);
30  }
31}
32
33impl <'a, Cutie, P> Draw<P, RgbaImage> for Texture<Cutie, &'a DynamicImage>
34  where Cutie: Shape<P>,
35        P: Float + AsPrimitive<f64>
36{
37  fn draw(&self, image: &mut RgbaImage) {
38    let resolution: Size2D<_, PixelSpace> = image.dimensions().into();
39    let (bounding_box, offset, min_side) =
40      rescale_bounding_box(self.shape.bounding_box().to_f64(), resolution);
41    let bounding_box = match bounding_box {
42      Some(x) => x,
43      None => return
44    };
45    let Δp = 1.0 / min_side;
46    let tex = rescale_texture(self.texture, bounding_box.size().to_u32());
47
48    itertools::iproduct!(bounding_box.y_range(), bounding_box.x_range())
49      .map(|(y, x)| Point2D::<_, PixelSpace>::from([x, y]))
50      .for_each(|pixel| {
51        let pixel_world = ((pixel.to_f64() - offset).to_vector() / min_side)
52          .cast_unit().to_point();
53        let tex_px = pixel - bounding_box.min.to_vector();
54        let tex_px = tex.get_pixel(tex_px.x, tex_px.y);
55
56        let sdf = self.sdf(pixel_world.cast::<P>()).as_();
57        let pixel = image.get_pixel_mut(pixel.x, pixel.y);
58        *pixel = sdf_overlay_aa(sdf, Δp, *pixel, tex_px);
59      });
60  }
61}
62
63/// `F: Fn(v: Point2D) -> Rgba<u8>`
64/// where `v` is in normalized texture coordinates.
65impl <Cutie, F, P> Draw<P, RgbaImage> for Texture<Cutie, F>
66  where Cutie: Shape<P>,
67        F: Fn(Point2D<P, WorldSpace>) -> Rgba<u8>,
68        P: Float + AsPrimitive<f64>
69{
70  fn draw(&self, image: &mut RgbaImage) {
71    let resolution: Size2D<_, PixelSpace> = image.dimensions().into();
72    let (bounding_box, offset, min_side) =
73      rescale_bounding_box(self.bounding_box().to_f64(), resolution);
74    let bounding_box = match bounding_box {
75      Some(x) => x,
76      None => return // bounding box has no intersection with screen at all
77    };
78    let Δp = 1.0 / min_side;
79    let tex_scale = bounding_box.size().width.min(bounding_box.size().height) as f64;
80
81    itertools::iproduct!(bounding_box.y_range(), bounding_box.x_range())
82      .map(|(y, x)| Point2D::<_, PixelSpace>::from([x, y]))
83      .for_each(|pixel| {
84        let pixel_world = ((pixel.to_f64() - offset).to_vector() / min_side)
85          .cast_unit().to_point();
86        let sdf = self.sdf(pixel_world.cast::<P>()).as_();
87
88        let tex_px = ((pixel - bounding_box.min.to_vector()).to_f64() / tex_scale).cast_unit();
89        let tex_px = (self.texture)(tex_px.cast::<P>());
90
91        let pixel = image.get_pixel_mut(pixel.x, pixel.y);
92        *pixel = sdf_overlay_aa(sdf, Δp, *pixel, tex_px);
93      });
94  }
95}
96
97impl <Cutie, P> Draw<P, RgbaImage> for Texture<Cutie, DynamicImage>
98  where Cutie: Shape<P> + Clone,
99        P: Float + AsPrimitive<f64>
100{
101  fn draw(&self, image: &mut RgbaImage) {
102    Texture {
103      shape: self.shape.clone(),
104      texture: &self.texture
105    }.draw(image)
106  }
107}
108
109impl <Cutie, P> Draw<P, RgbaImage> for Texture<Cutie, Arc<DynamicImage>>
110  where Cutie: Shape<P> + Clone,
111        P: Float + AsPrimitive<f64>
112{
113  fn draw(&self, image: &mut RgbaImage) {
114    Texture {
115      shape: self.shape.clone(),
116      texture: self.texture.as_ref()
117    }.draw(image)
118  }
119}
120
121// resize the image to cover the entire container,
122// even if it has to cut off one of the edges
123fn rescale_texture(texture: &DynamicImage, size: Size2D<u32, PixelSpace>) -> DynamicImage {
124  let tex_size = Size2D::from(texture.dimensions()).to_f32();
125  let scaling_factor = tex_size.to_vector()
126    .component_div(size.to_f32().to_vector());
127  let scaling_factor = scaling_factor.x.min(scaling_factor.y);
128  let bound_inner = size.to_f32() * scaling_factor;
129  let bound_inner = Rect::new(
130    ((tex_size - bound_inner) / 2.0).to_vector().to_point(),
131    bound_inner
132  ).to_u32();
133  texture.crop_imm(
134    bound_inner.origin.x,
135    bound_inner.origin.y,
136    bound_inner.size.width,
137    bound_inner.size.height
138  ).resize_exact(size.width, size.height, FilterType::Triangle)
139}
140
141fn sdf_overlay_aa(sdf: f64, Δp: f64, mut col1: Rgba<u8>, mut col2: Rgba<u8>) -> Rgba<u8> {
142  let Δf = (0.5 * Δp - sdf) // antialias
143    .clamp(0.0, Δp);
144  let alpha = Δf / Δp;
145  // overlay blending with premultiplied alpha
146  col2.0[3] = ((col2.0[3] as f64) * alpha) as u8;
147  col1.blend(&col2);
148  col1
149}