Skip to main content

dessin/shapes/
image.rs

1use super::{BoundingBox, ShapeBoundingBox, UnParticular};
2use crate::shapes::{Shape, ShapeOp};
3use image::DynamicImage;
4use nalgebra::{Point2, Scale2, Transform2, Vector2};
5
6#[derive(Debug, Clone, PartialEq)]
7///
8pub struct ImagePosition<'a> {
9	///
10	pub top_left: Point2<f32>,
11	///
12	pub top_right: Point2<f32>,
13	///
14	pub bottom_right: Point2<f32>,
15	///
16	pub bottom_left: Point2<f32>,
17	///
18	pub center: Point2<f32>,
19
20	///
21	pub width: f32,
22	///
23	pub height: f32,
24
25	///
26	pub rotation: f32,
27
28	///
29	pub image: &'a DynamicImage,
30}
31
32#[derive(Default, Debug, Clone, PartialEq)]
33///
34pub struct Image {
35	///
36	pub image: DynamicImage,
37	///
38	pub local_transform: Transform2<f32>,
39}
40impl Image {
41	#[inline]
42	///
43	pub fn image_size_pixel(&self) -> (u32, u32) {
44		(self.image.width(), self.image.height())
45	}
46
47	#[inline]
48	///
49	pub fn aspect_ratio(&self) -> f32 {
50		let (w, h) = self.image_size_pixel();
51		w as f32 / h as f32
52	}
53
54	///
55	pub fn image(&mut self, image: DynamicImage) -> &mut Self {
56		self.image = image;
57		self
58	}
59
60	#[inline]
61	///
62	pub fn with_image(mut self, image: DynamicImage) -> Self {
63		self.image(image);
64		self
65	}
66
67	///
68	pub fn keep_aspect_ratio(&mut self) -> &mut Self {
69		self.scale(Scale2::new(self.aspect_ratio(), 1.));
70		self
71	}
72
73	#[inline]
74	///
75	pub fn with_keep_aspect_ratio(mut self) -> Self {
76		self.keep_aspect_ratio();
77		self
78	}
79
80	///
81	pub fn position<'a>(&'a self, parent_transform: &Transform2<f32>) -> ImagePosition<'a> {
82		let transform = self.global_transform(parent_transform);
83
84		let top_left = transform * Point2::new(-0.5, 0.5);
85		let top_right = transform * Point2::new(0.5, 0.5);
86		let bottom_right = transform * Point2::new(0.5, -0.5);
87		let bottom_left = transform * Point2::new(-0.5, -0.5);
88		let center = transform * Point2::origin();
89
90		let rot_dir = transform * Vector2::x();
91		let rotation = rot_dir.y.atan2(rot_dir.x);
92
93		ImagePosition {
94			center,
95			top_left,
96			top_right,
97			bottom_right,
98			bottom_left,
99			width: (top_right - top_left).magnitude(),
100			height: (top_right - bottom_right).magnitude(),
101			rotation,
102			image: &self.image,
103		}
104	}
105}
106
107impl From<Image> for Shape {
108	#[inline]
109	fn from(v: Image) -> Self {
110		Shape::Image(v)
111	}
112}
113
114impl ShapeOp for Image {
115	#[inline]
116	fn transform(&mut self, transform_matrix: Transform2<f32>) -> &mut Self {
117		self.local_transform = transform_matrix * self.local_transform;
118		self
119	}
120
121	#[inline]
122	fn local_transform(&self) -> &Transform2<f32> {
123		&self.local_transform
124	}
125}
126
127impl ShapeBoundingBox for Image {
128	fn local_bounding_box(&self) -> BoundingBox<UnParticular> {
129		let ImagePosition {
130			top_left,
131			top_right,
132			bottom_right,
133			bottom_left,
134			center: _,
135			width: _,
136			height: _,
137			rotation: _,
138			image: _,
139		} = self.position(&Transform2::default());
140		BoundingBox::new(top_left, top_right, bottom_right, bottom_left)
141	}
142}
143
144#[cfg(test)]
145mod tests {
146	use crate::prelude::*;
147	use ::image::DynamicImage;
148	use assert_float_eq::*;
149	use nalgebra::{Point2, Rotation2, Scale2, Transform2, Translation2};
150	use std::f32::consts::SQRT_2;
151
152	#[test]
153	fn base() {
154		let img = dessin!(Image());
155
156		let empty_image = DynamicImage::default();
157
158		assert_eq!(
159			img.position(&Transform2::default()),
160			ImagePosition {
161				center: Point2::origin(),
162				top_left: Point2::new(-0.5, 0.5),
163				top_right: Point2::new(0.5, 0.5),
164				bottom_right: Point2::new(0.5, -0.5),
165				bottom_left: Point2::new(-0.5, -0.5),
166				width: 1.,
167				height: 1.,
168				rotation: 0.,
169				image: &empty_image,
170			}
171		);
172	}
173
174	#[test]
175	fn bounding_box() {
176		let img = dessin!(Image());
177		let bb = img.local_bounding_box();
178
179		assert_eq!(bb.width(), 1.);
180		assert_eq!(bb.height(), 1.);
181
182		assert_eq!(
183			bb,
184			BoundingBox::new(
185				Point2::new(-0.5, 0.5),
186				Point2::new(0.5, 0.5),
187				Point2::new(0.5, -0.5),
188				Point2::new(-0.5, -0.5),
189			)
190		);
191	}
192
193	#[test]
194	fn local_transform() {
195		let img = dessin!(Image(rotate = Rotation2::new(-45_f32.to_radians())));
196		let img_pos = img.position(&Transform2::default());
197		assert_f32_near!(img_pos.rotation, -45_f32.to_radians());
198		assert_f32_near!(img_pos.width, 1.);
199		assert_f32_near!(img_pos.top_left.x, Point2::new(0., SQRT_2 / 2.).x);
200		assert_f32_near!(img_pos.top_left.y, Point2::new(0., SQRT_2 / 2.).y);
201	}
202
203	#[test]
204	fn global_transform() {
205		let img = dessin!(Image());
206		let parent_transform = Transform2::default() * Rotation2::new(-45_f32.to_radians());
207		let img_pos = img.position(&parent_transform);
208
209		assert_f32_near!(img_pos.rotation, -45_f32.to_radians());
210		assert_f32_near!(img_pos.width, 1.);
211		assert_f32_near!(img_pos.top_left.x, Point2::new(0., SQRT_2 / 2.).x);
212		assert_f32_near!(img_pos.top_left.y, Point2::new(0., SQRT_2 / 2.).y);
213	}
214
215	#[test]
216	fn combined_transform() {
217		let img = dessin!(Image());
218		let img_pos = img.position(&Transform2::default());
219		let empty_image = DynamicImage::default();
220		println!("Base = {img_pos:?}\n");
221		assert_eq!(
222			img_pos,
223			ImagePosition {
224				center: Point2::origin(),
225				top_left: Point2::new(-0.5, 0.5),
226				top_right: Point2::new(0.5, 0.5),
227				bottom_right: Point2::new(0.5, -0.5),
228				bottom_left: Point2::new(-0.5, -0.5),
229				width: 1.,
230				height: 1.,
231				rotation: 0.,
232				image: &empty_image,
233			}
234		);
235
236		let img = dessin!({ img }(rotate = Rotation2::new(-45_f32.to_radians())));
237		let img_pos = img.position(&Transform2::default());
238		println!("Rot(-45deg) = {img_pos:?}\n");
239		assert_f32_near!(img_pos.rotation, -45_f32.to_radians());
240		assert_f32_near!(img_pos.width, 1.);
241		assert_f32_near!(img_pos.top_left.x, Point2::new(0., SQRT_2 / 2.).x);
242		assert_f32_near!(img_pos.top_left.y, Point2::new(0., SQRT_2 / 2.).y);
243
244		let img = dessin!({ img }(translate = Translation2::new(1., 0.)));
245		let img_pos = img.position(&Transform2::default());
246		println!("Translate_x(1) = {img_pos:?}\n");
247		assert_f32_near!(img_pos.rotation, -45_f32.to_radians());
248		assert_f32_near!(img_pos.width, 1.);
249		assert_f32_near!(img_pos.top_left.x, Point2::new(1., SQRT_2 / 2.).x);
250		assert_f32_near!(img_pos.top_left.y, Point2::new(1., SQRT_2 / 2.).y);
251		assert_f32_near!(img_pos.top_right.x, Point2::new(SQRT_2 / 2. + 1., 0.).x);
252		assert_f32_near!(img_pos.top_right.y, Point2::new(SQRT_2 / 2. + 1., 0.).y);
253
254		let img = dessin!({ img }(scale = Scale2::new(3., 2.)));
255		let img_pos = img.position(&Transform2::default());
256		println!("Scale(3, 2) =img_pos:?\n");
257		assert_f32_near!(img_pos.top_left.x, Point2::new(3. * 1., 2. * SQRT_2 / 2.).x);
258		assert_f32_near!(img_pos.top_left.y, Point2::new(3. * 1., 2. * SQRT_2 / 2.).y);
259		assert_f32_near!(
260			img_pos.top_right.x,
261			Point2::new(3. * (SQRT_2 / 2. + 1.), 2. * 0.).x
262		);
263		assert_f32_near!(
264			img_pos.top_right.y,
265			Point2::new(3. * (SQRT_2 / 2. + 1.), 2. * 0.).y
266		);
267	}
268}