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)]
7pub struct ImagePosition<'a> {
9 pub top_left: Point2<f32>,
11 pub top_right: Point2<f32>,
13 pub bottom_right: Point2<f32>,
15 pub bottom_left: Point2<f32>,
17 pub center: Point2<f32>,
19
20 pub width: f32,
22 pub height: f32,
24
25 pub rotation: f32,
27
28 pub image: &'a DynamicImage,
30}
31
32#[derive(Default, Debug, Clone, PartialEq)]
33pub struct Image {
35 pub image: DynamicImage,
37 pub local_transform: Transform2<f32>,
39}
40impl Image {
41 #[inline]
42 pub fn image_size_pixel(&self) -> (u32, u32) {
44 (self.image.width(), self.image.height())
45 }
46
47 #[inline]
48 pub fn aspect_ratio(&self) -> f32 {
50 let (w, h) = self.image_size_pixel();
51 w as f32 / h as f32
52 }
53
54 pub fn image(&mut self, image: DynamicImage) -> &mut Self {
56 self.image = image;
57 self
58 }
59
60 #[inline]
61 pub fn with_image(mut self, image: DynamicImage) -> Self {
63 self.image(image);
64 self
65 }
66
67 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 pub fn with_keep_aspect_ratio(mut self) -> Self {
76 self.keep_aspect_ratio();
77 self
78 }
79
80 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}