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