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