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