imageproc/drawing/
rect.rs

1use crate::definitions::Image;
2use crate::drawing::line::draw_line_segment_mut;
3use crate::drawing::Canvas;
4use crate::rect::Rect;
5use image::{GenericImage, ImageBuffer};
6use std::f32;
7
8/// Draws the outline of a rectangle on a new copy of an image.
9///
10/// Draws as much of the boundary of the rectangle as lies inside the image bounds.
11#[must_use = "the function does not modify the original image"]
12pub fn draw_hollow_rect<I>(image: &I, rect: Rect, color: I::Pixel) -> Image<I::Pixel>
13where
14    I: GenericImage,
15{
16    let mut out = ImageBuffer::new(image.width(), image.height());
17    out.copy_from(image, 0, 0).unwrap();
18    draw_hollow_rect_mut(&mut out, rect, color);
19    out
20}
21
22/// Draws the outline of a rectangle on an image in place.
23///
24/// Draws as much of the boundary of the rectangle as lies inside the image bounds.
25pub fn draw_hollow_rect_mut<C>(canvas: &mut C, rect: Rect, color: C::Pixel)
26where
27    C: Canvas,
28{
29    let left = rect.left() as f32;
30    let right = rect.right() as f32;
31    let top = rect.top() as f32;
32    let bottom = rect.bottom() as f32;
33
34    draw_line_segment_mut(canvas, (left, top), (right, top), color);
35    draw_line_segment_mut(canvas, (left, bottom), (right, bottom), color);
36    draw_line_segment_mut(canvas, (left, top), (left, bottom), color);
37    draw_line_segment_mut(canvas, (right, top), (right, bottom), color);
38}
39
40/// Draws a rectangle and its contents on a new copy of an image.
41///
42/// Draws as much of the rectangle and its contents as lies inside the image bounds.
43#[must_use = "the function does not modify the original image"]
44pub fn draw_filled_rect<I>(image: &I, rect: Rect, color: I::Pixel) -> Image<I::Pixel>
45where
46    I: GenericImage,
47{
48    let mut out = ImageBuffer::new(image.width(), image.height());
49    out.copy_from(image, 0, 0).unwrap();
50    draw_filled_rect_mut(&mut out, rect, color);
51    out
52}
53
54/// Draws a rectangle and its contents on an image in place.
55///
56/// Draws as much of the rectangle and its contents as lies inside the image bounds.
57pub fn draw_filled_rect_mut<C>(canvas: &mut C, rect: Rect, color: C::Pixel)
58where
59    C: Canvas,
60{
61    let canvas_bounds = Rect::at(0, 0).of_size(canvas.width(), canvas.height());
62    if let Some(intersection) = canvas_bounds.intersect(rect) {
63        for dy in 0..intersection.height() {
64            for dx in 0..intersection.width() {
65                let x = intersection.left() as u32 + dx;
66                let y = intersection.top() as u32 + dy;
67                canvas.draw_pixel(x, y, color);
68            }
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::drawing::Blend;
77    use crate::rect::Rect;
78    use image::{GrayImage, Luma, Pixel, Rgb, RgbImage, Rgba, RgbaImage};
79    use test::{black_box, Bencher};
80
81    #[bench]
82    fn bench_draw_filled_rect_mut_rgb(b: &mut Bencher) {
83        let mut image = RgbImage::new(200, 200);
84        let color = Rgb([120u8, 60u8, 47u8]);
85        let rect = Rect::at(50, 50).of_size(80, 90);
86        b.iter(|| {
87            draw_filled_rect_mut(&mut image, rect, color);
88            black_box(&image);
89        });
90    }
91
92    #[test]
93    fn test_draw_hollow_rect() {
94        let image = GrayImage::from_pixel(5, 5, Luma([1u8]));
95
96        let expected = gray_image!(
97            1, 1, 1, 1, 1;
98            1, 1, 1, 1, 1;
99            1, 1, 4, 4, 4;
100            1, 1, 4, 1, 4;
101            1, 1, 4, 4, 4);
102
103        let actual = draw_hollow_rect(&image, Rect::at(2, 2).of_size(3, 3), Luma([4u8]));
104        assert_pixels_eq!(actual, expected);
105    }
106
107    #[test]
108    fn test_draw_filled_rect() {
109        let image = GrayImage::from_pixel(5, 5, Luma([1u8]));
110
111        let expected = gray_image!(
112            1, 1, 1, 1, 1;
113            1, 4, 4, 4, 1;
114            1, 4, 4, 4, 1;
115            1, 4, 4, 4, 1;
116            1, 1, 1, 1, 1);
117
118        let actual = draw_filled_rect(&image, Rect::at(1, 1).of_size(3, 3), Luma([4u8]));
119        assert_pixels_eq!(actual, expected);
120    }
121
122    #[test]
123    fn test_draw_blended_filled_rect() {
124        // https://github.com/image-rs/imageproc/issues/261
125
126        let white = Rgba([255u8, 255u8, 255u8, 255u8]);
127        let blue = Rgba([0u8, 0u8, 255u8, 255u8]);
128        let semi_transparent_red = Rgba([255u8, 0u8, 0u8, 127u8]);
129
130        let mut image = Blend(RgbaImage::from_pixel(5, 5, white));
131
132        draw_filled_rect_mut(&mut image, Rect::at(1, 1).of_size(3, 3), blue);
133        draw_filled_rect_mut(
134            &mut image,
135            Rect::at(2, 2).of_size(1, 1),
136            semi_transparent_red,
137        );
138
139        // The central pixel should be blended
140        let mut blended = blue;
141        blended.blend(&semi_transparent_red);
142
143        #[rustfmt::skip]
144        let expected = vec![
145            white, white,   white, white, white,
146            white,  blue,    blue,  blue, white,
147            white,  blue, blended,  blue, white,
148            white,  blue,    blue,  blue, white,
149            white, white,   white, white, white
150        ];
151        let expected = RgbaImage::from_fn(5, 5, |x, y| expected[(y * 5 + x) as usize]);
152
153        assert_pixels_eq!(image.0, expected);
154
155        // Draw an opaque rectangle over the central pixel as a sanity check that
156        // we're blending in the correct direction only.
157        draw_filled_rect_mut(&mut image, Rect::at(2, 2).of_size(1, 1), blue);
158        assert_eq!(*image.0.get_pixel(2, 2), blue);
159    }
160}