1pub mod viewer;
20pub use viewer::Viewer;
21
22use crate::core::border;
23use crate::core::image;
24use crate::core::layout;
25use crate::core::mouse;
26use crate::core::renderer;
27use crate::core::widget::Tree;
28use crate::core::{
29 ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size, Vector, Widget,
30};
31
32pub use image::{FilterMethod, Handle};
33
34pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
36 Viewer::new(handle)
37}
38
39pub struct Image<Handle = image::Handle> {
58 handle: Handle,
59 width: Length,
60 height: Length,
61 crop: Option<Rectangle<u32>>,
62 border_radius: border::Radius,
63 content_fit: ContentFit,
64 filter_method: FilterMethod,
65 rotation: Rotation,
66 opacity: f32,
67 scale: f32,
68 expand: bool,
69}
70
71impl<Handle> Image<Handle> {
72 pub fn new(handle: impl Into<Handle>) -> Self {
74 Image {
75 handle: handle.into(),
76 width: Length::Shrink,
77 height: Length::Shrink,
78 crop: None,
79 border_radius: border::Radius::default(),
80 content_fit: ContentFit::default(),
81 filter_method: FilterMethod::default(),
82 rotation: Rotation::default(),
83 opacity: 1.0,
84 scale: 1.0,
85 expand: false,
86 }
87 }
88
89 pub fn width(mut self, width: impl Into<Length>) -> Self {
91 self.width = width.into();
92 self
93 }
94
95 pub fn height(mut self, height: impl Into<Length>) -> Self {
97 self.height = height.into();
98 self
99 }
100
101 pub fn expand(mut self, expand: bool) -> Self {
110 self.expand = expand;
111 self
112 }
113
114 pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
118 self.content_fit = content_fit;
119 self
120 }
121
122 pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
124 self.filter_method = filter_method;
125 self
126 }
127
128 pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
130 self.rotation = rotation.into();
131 self
132 }
133
134 pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
139 self.opacity = opacity.into();
140 self
141 }
142
143 pub fn scale(mut self, scale: impl Into<f32>) -> Self {
148 self.scale = scale.into();
149 self
150 }
151
152 pub fn crop(mut self, region: Rectangle<u32>) -> Self {
166 self.crop = Some(region);
167 self
168 }
169
170 pub fn border_radius(mut self, border_radius: impl Into<border::Radius>) -> Self {
175 self.border_radius = border_radius.into();
176 self
177 }
178}
179
180pub fn layout<Renderer, Handle>(
182 renderer: &Renderer,
183 limits: &layout::Limits,
184 handle: &Handle,
185 width: Length,
186 height: Length,
187 region: Option<Rectangle<u32>>,
188 content_fit: ContentFit,
189 rotation: Rotation,
190 expand: bool,
191) -> layout::Node
192where
193 Renderer: image::Renderer<Handle = Handle>,
194{
195 let image_size = crop(renderer.measure_image(handle).unwrap_or_default(), region);
197
198 let rotated_size = rotation.apply(image_size);
200
201 let bounds = if expand {
203 limits.width(width).height(height).max()
204 } else {
205 limits.resolve(width, height, rotated_size)
206 };
207
208 let full_size = content_fit.fit(rotated_size, bounds);
210
211 let final_size = Size {
213 width: match width {
214 Length::Shrink => f32::min(bounds.width, full_size.width),
215 _ => bounds.width,
216 },
217 height: match height {
218 Length::Shrink => f32::min(bounds.height, full_size.height),
219 _ => bounds.height,
220 },
221 };
222
223 layout::Node::new(final_size)
224}
225
226fn drawing_bounds<Renderer, Handle>(
227 renderer: &Renderer,
228 bounds: Rectangle,
229 handle: &Handle,
230 region: Option<Rectangle<u32>>,
231 content_fit: ContentFit,
232 rotation: Rotation,
233 scale: f32,
234) -> Rectangle
235where
236 Renderer: image::Renderer<Handle = Handle>,
237{
238 let original_size = renderer.measure_image(handle).unwrap_or_default();
239 let image_size = crop(original_size, region);
240 let rotated_size = rotation.apply(image_size);
241 let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
242
243 let fit_scale = Vector::new(
244 adjusted_fit.width / rotated_size.width,
245 adjusted_fit.height / rotated_size.height,
246 );
247
248 let final_size = image_size * fit_scale * scale;
249
250 let (crop_offset, final_size) = if let Some(region) = region {
251 let x = region.x.min(original_size.width) as f32;
252 let y = region.y.min(original_size.height) as f32;
253 let width = image_size.width;
254 let height = image_size.height;
255
256 let ratio = Vector::new(
257 original_size.width as f32 / width,
258 original_size.height as f32 / height,
259 );
260
261 let final_size = final_size * ratio;
262
263 let scale = Vector::new(
264 final_size.width / original_size.width as f32,
265 final_size.height / original_size.height as f32,
266 );
267
268 let offset = match content_fit {
269 ContentFit::None => Vector::new(x * scale.x, y * scale.y),
270 _ => Vector::new(
271 ((original_size.width as f32 - width) / 2.0 - x) * scale.x,
272 ((original_size.height as f32 - height) / 2.0 - y) * scale.y,
273 ),
274 };
275
276 (offset, final_size)
277 } else {
278 (Vector::ZERO, final_size)
279 };
280
281 let position = match content_fit {
282 ContentFit::None => Point::new(
283 bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
284 bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
285 ),
286 _ => Point::new(
287 bounds.center_x() - final_size.width / 2.0,
288 bounds.center_y() - final_size.height / 2.0,
289 ),
290 };
291
292 Rectangle::new(position + crop_offset, final_size)
293}
294
295fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
296 if let Some(region) = region {
297 Size::new(
298 region.width.min(size.width) as f32,
299 region.height.min(size.height) as f32,
300 )
301 } else {
302 Size::new(size.width as f32, size.height as f32)
303 }
304}
305
306pub fn draw<Renderer, Handle>(
308 renderer: &mut Renderer,
309 layout: Layout<'_>,
310 handle: &Handle,
311 crop: Option<Rectangle<u32>>,
312 border_radius: border::Radius,
313 content_fit: ContentFit,
314 filter_method: FilterMethod,
315 rotation: Rotation,
316 opacity: f32,
317 scale: f32,
318) where
319 Renderer: image::Renderer<Handle = Handle>,
320 Handle: Clone,
321{
322 let bounds = layout.bounds();
323 let drawing_bounds =
324 drawing_bounds(renderer, bounds, handle, crop, content_fit, rotation, scale);
325
326 renderer.draw_image(
327 image::Image {
328 handle: handle.clone(),
329 border_radius,
330 filter_method,
331 rotation: rotation.radians(),
332 opacity,
333 snap: true,
334 },
335 drawing_bounds,
336 bounds,
337 );
338}
339
340impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer> for Image<Handle>
341where
342 Renderer: image::Renderer<Handle = Handle>,
343 Handle: Clone,
344{
345 fn size(&self) -> Size<Length> {
346 Size {
347 width: self.width,
348 height: self.height,
349 }
350 }
351
352 fn layout(
353 &mut self,
354 _tree: &mut Tree,
355 renderer: &Renderer,
356 limits: &layout::Limits,
357 ) -> layout::Node {
358 layout(
359 renderer,
360 limits,
361 &self.handle,
362 self.width,
363 self.height,
364 self.crop,
365 self.content_fit,
366 self.rotation,
367 self.expand,
368 )
369 }
370
371 fn draw(
372 &self,
373 _tree: &Tree,
374 renderer: &mut Renderer,
375 _theme: &Theme,
376 _style: &renderer::Style,
377 layout: Layout<'_>,
378 _cursor: mouse::Cursor,
379 _viewport: &Rectangle,
380 ) {
381 draw(
382 renderer,
383 layout,
384 &self.handle,
385 self.crop,
386 self.border_radius,
387 self.content_fit,
388 self.filter_method,
389 self.rotation,
390 self.opacity,
391 self.scale,
392 );
393 }
394}
395
396impl<'a, Message, Theme, Renderer, Handle> From<Image<Handle>>
397 for Element<'a, Message, Theme, Renderer>
398where
399 Renderer: image::Renderer<Handle = Handle>,
400 Handle: Clone + 'a,
401{
402 fn from(image: Image<Handle>) -> Element<'a, Message, Theme, Renderer> {
403 Element::new(image)
404 }
405}