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;
28use crate::core::widget::Tree;
29use crate::core::widget::operation::accessible::{Accessible, Role};
30use crate::core::{
31 ContentFit, Element, Layout, Length, Point, Rectangle, Rotation, Size, Vector, Widget,
32};
33
34pub use image::{FilterMethod, Handle};
35
36pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
38 Viewer::new(handle)
39}
40
41pub struct Image<Handle = image::Handle> {
60 handle: Handle,
61 width: Length,
62 height: Length,
63 crop: Option<Rectangle<u32>>,
64 border_radius: border::Radius,
65 content_fit: ContentFit,
66 filter_method: FilterMethod,
67 rotation: Rotation,
68 opacity: f32,
69 scale: f32,
70 expand: bool,
71 alt: Option<String>,
72 description: Option<String>,
73}
74
75impl<Handle> Image<Handle> {
76 pub fn new(handle: impl Into<Handle>) -> Self {
78 Image {
79 handle: handle.into(),
80 width: Length::Shrink,
81 height: Length::Shrink,
82 crop: None,
83 border_radius: border::Radius::default(),
84 content_fit: ContentFit::default(),
85 filter_method: FilterMethod::default(),
86 rotation: Rotation::default(),
87 opacity: 1.0,
88 scale: 1.0,
89 expand: false,
90 alt: None,
91 description: None,
92 }
93 }
94
95 pub fn width(mut self, width: impl Into<Length>) -> Self {
97 self.width = width.into();
98 self
99 }
100
101 pub fn height(mut self, height: impl Into<Length>) -> Self {
103 self.height = height.into();
104 self
105 }
106
107 pub fn expand(mut self, expand: bool) -> Self {
116 self.expand = expand;
117 self
118 }
119
120 pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
124 self.content_fit = content_fit;
125 self
126 }
127
128 pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
130 self.filter_method = filter_method;
131 self
132 }
133
134 pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
136 self.rotation = rotation.into();
137 self
138 }
139
140 pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
145 self.opacity = opacity.into();
146 self
147 }
148
149 pub fn scale(mut self, scale: impl Into<f32>) -> Self {
154 self.scale = scale.into();
155 self
156 }
157
158 pub fn crop(mut self, region: Rectangle<u32>) -> Self {
172 self.crop = Some(region);
173 self
174 }
175
176 pub fn border_radius(mut self, border_radius: impl Into<border::Radius>) -> Self {
181 self.border_radius = border_radius.into();
182 self
183 }
184
185 pub fn alt(mut self, text: impl Into<String>) -> Self {
190 self.alt = Some(text.into());
191 self
192 }
193
194 pub fn description(mut self, description: impl Into<String>) -> Self {
199 self.description = Some(description.into());
200 self
201 }
202}
203
204pub fn layout<Renderer, Handle>(
206 renderer: &Renderer,
207 limits: &layout::Limits,
208 handle: &Handle,
209 width: Length,
210 height: Length,
211 region: Option<Rectangle<u32>>,
212 content_fit: ContentFit,
213 rotation: Rotation,
214 expand: bool,
215) -> layout::Node
216where
217 Renderer: image::Renderer<Handle = Handle>,
218{
219 let image_size = crop(renderer.measure_image(handle).unwrap_or_default(), region);
221
222 let rotated_size = rotation.apply(image_size);
224
225 let bounds = if expand {
227 limits.width(width).height(height).max()
228 } else {
229 limits.resolve(width, height, rotated_size)
230 };
231
232 let full_size = content_fit.fit(rotated_size, bounds);
234
235 let final_size = Size {
237 width: match width {
238 Length::Shrink => f32::min(bounds.width, full_size.width),
239 _ => bounds.width,
240 },
241 height: match height {
242 Length::Shrink => f32::min(bounds.height, full_size.height),
243 _ => bounds.height,
244 },
245 };
246
247 layout::Node::new(final_size)
248}
249
250fn drawing_bounds<Renderer, Handle>(
251 renderer: &Renderer,
252 bounds: Rectangle,
253 handle: &Handle,
254 region: Option<Rectangle<u32>>,
255 content_fit: ContentFit,
256 rotation: Rotation,
257 scale: f32,
258) -> Rectangle
259where
260 Renderer: image::Renderer<Handle = Handle>,
261{
262 let original_size = renderer.measure_image(handle).unwrap_or_default();
263 let image_size = crop(original_size, region);
264 let rotated_size = rotation.apply(image_size);
265 let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
266
267 let fit_scale = Vector::new(
268 adjusted_fit.width / rotated_size.width,
269 adjusted_fit.height / rotated_size.height,
270 );
271
272 let final_size = image_size * fit_scale * scale;
273
274 let (crop_offset, final_size) = if let Some(region) = region {
275 let x = region.x.min(original_size.width) as f32;
276 let y = region.y.min(original_size.height) as f32;
277 let width = image_size.width;
278 let height = image_size.height;
279
280 let ratio = Vector::new(
281 original_size.width as f32 / width,
282 original_size.height as f32 / height,
283 );
284
285 let final_size = final_size * ratio;
286
287 let scale = Vector::new(
288 final_size.width / original_size.width as f32,
289 final_size.height / original_size.height as f32,
290 );
291
292 let offset = match content_fit {
293 ContentFit::None => Vector::new(x * scale.x, y * scale.y),
294 _ => Vector::new(
295 ((original_size.width as f32 - width) / 2.0 - x) * scale.x,
296 ((original_size.height as f32 - height) / 2.0 - y) * scale.y,
297 ),
298 };
299
300 (offset, final_size)
301 } else {
302 (Vector::ZERO, final_size)
303 };
304
305 let position = match content_fit {
306 ContentFit::None => Point::new(
307 bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
308 bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
309 ),
310 _ => Point::new(
311 bounds.center_x() - final_size.width / 2.0,
312 bounds.center_y() - final_size.height / 2.0,
313 ),
314 };
315
316 Rectangle::new(position + crop_offset, final_size)
317}
318
319fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
320 if let Some(region) = region {
321 Size::new(
322 region.width.min(size.width) as f32,
323 region.height.min(size.height) as f32,
324 )
325 } else {
326 Size::new(size.width as f32, size.height as f32)
327 }
328}
329
330pub fn draw<Renderer, Handle>(
332 renderer: &mut Renderer,
333 layout: Layout<'_>,
334 handle: &Handle,
335 crop: Option<Rectangle<u32>>,
336 border_radius: border::Radius,
337 content_fit: ContentFit,
338 filter_method: FilterMethod,
339 rotation: Rotation,
340 opacity: f32,
341 scale: f32,
342) where
343 Renderer: image::Renderer<Handle = Handle>,
344 Handle: Clone,
345{
346 let bounds = layout.bounds();
347 let drawing_bounds =
348 drawing_bounds(renderer, bounds, handle, crop, content_fit, rotation, scale);
349
350 renderer.draw_image(
351 image::Image {
352 handle: handle.clone(),
353 border_radius,
354 filter_method,
355 rotation: rotation.radians(),
356 opacity,
357 },
358 drawing_bounds,
359 bounds,
360 );
361}
362
363impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer> for Image<Handle>
364where
365 Renderer: image::Renderer<Handle = Handle>,
366 Handle: Clone,
367{
368 fn size(&self) -> Size<Length> {
369 Size {
370 width: self.width,
371 height: self.height,
372 }
373 }
374
375 fn layout(
376 &mut self,
377 _tree: &mut Tree,
378 renderer: &Renderer,
379 limits: &layout::Limits,
380 ) -> layout::Node {
381 layout(
382 renderer,
383 limits,
384 &self.handle,
385 self.width,
386 self.height,
387 self.crop,
388 self.content_fit,
389 self.rotation,
390 self.expand,
391 )
392 }
393
394 fn draw(
395 &self,
396 _tree: &Tree,
397 renderer: &mut Renderer,
398 _theme: &Theme,
399 _style: &renderer::Style,
400 layout: Layout<'_>,
401 _cursor: mouse::Cursor,
402 _viewport: &Rectangle,
403 ) {
404 draw(
405 renderer,
406 layout,
407 &self.handle,
408 self.crop,
409 self.border_radius,
410 self.content_fit,
411 self.filter_method,
412 self.rotation,
413 self.opacity,
414 self.scale,
415 );
416 }
417
418 fn operate(
419 &mut self,
420 _tree: &mut Tree,
421 layout: Layout<'_>,
422 _renderer: &Renderer,
423 operation: &mut dyn widget::Operation,
424 ) {
425 operation.accessible(
426 None,
427 layout.bounds(),
428 &Accessible {
429 role: Role::Image,
430 label: self.alt.as_deref(),
431 description: self.description.as_deref(),
432 ..Accessible::default()
433 },
434 );
435 }
436}
437
438impl<'a, Message, Theme, Renderer, Handle> From<Image<Handle>>
439 for Element<'a, Message, Theme, Renderer>
440where
441 Renderer: image::Renderer<Handle = Handle>,
442 Handle: Clone + 'a,
443{
444 fn from(image: Image<Handle>) -> Element<'a, Message, Theme, Renderer> {
445 Element::new(image)
446 }
447}