Skip to main content

freya_core/elements/
image.rs

1//! [image()] makes it possible to render a Skia image into the canvas.
2
3use std::{
4    any::Any,
5    borrow::Cow,
6    collections::HashMap,
7    rc::Rc,
8};
9
10use bytes::Bytes;
11use freya_engine::prelude::{
12    AlphaType,
13    ClipOp,
14    ColorType,
15    CubicResampler,
16    Data,
17    FilterMode,
18    ISize,
19    ImageInfo,
20    MipmapMode,
21    Paint,
22    PathBuilder,
23    SamplingOptions,
24    SkImage,
25    SkRect,
26    TileMode,
27    blur,
28    raster_from_data,
29};
30use torin::{
31    prelude::Size2D,
32    scaled::Scaled,
33};
34
35use crate::{
36    data::{
37        AccessibilityData,
38        EffectData,
39        LayoutData,
40        StyleState,
41        TextStyleData,
42    },
43    diff_key::DiffKey,
44    element::{
45        ClipContext,
46        Element,
47        ElementExt,
48        EventHandlers,
49        LayoutContext,
50        RenderContext,
51    },
52    elements::rect::RectElement,
53    layers::Layer,
54    prelude::{
55        AccessibilityExt,
56        ChildrenExt,
57        ContainerExt,
58        ContainerWithContentExt,
59        DecorationExt,
60        EffectExt,
61        EventHandlersExt,
62        ImageExt,
63        KeyExt,
64        LayerExt,
65        LayoutExt,
66        MaybeExt,
67        StyleExt,
68    },
69    style::color::Color,
70    tree::DiffModifies,
71};
72
73/// [image] makes it possible to render a Skia image into the canvas.
74/// You most likely want to use a higher level than this, like the component `ImageViewer`.
75///
76/// See the available methods in [Image].
77pub fn image(image_handle: ImageHandle) -> Image {
78    let mut accessibility = AccessibilityData::default();
79    accessibility.builder.set_role(accesskit::Role::Image);
80    Image {
81        key: DiffKey::None,
82        element: ImageElement {
83            image_handle,
84            accessibility,
85            layout: LayoutData::default(),
86            event_handlers: HashMap::default(),
87            image_data: ImageData::default(),
88            relative_layer: Layer::default(),
89            effect: None,
90            style: StyleState::default(),
91        },
92        elements: Vec::new(),
93    }
94}
95
96/// How an image is positioned within its bounds once it has been scaled.
97#[derive(Default, Clone, Debug, PartialEq)]
98pub enum ImageCover {
99    /// Anchor the image to the top-left of the bounds. This is the default.
100    #[default]
101    Fill,
102    /// Center the image within the bounds.
103    Center,
104}
105
106/// How an image is scaled to fit its bounds while preserving its aspect ratio.
107#[derive(Default, Clone, Debug, PartialEq)]
108pub enum AspectRatio {
109    /// Scale so the whole image fits inside the bounds. This is the default.
110    #[default]
111    Min,
112    /// Scale so the image covers the whole bounds, cropping the overflow.
113    Max,
114    /// Keep the image at its natural size.
115    Fit,
116    /// Stretch the image to the bounds, ignoring its aspect ratio.
117    None,
118}
119
120/// The filtering algorithm used when an image is scaled.
121#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
122pub enum SamplingMode {
123    /// Nearest-neighbor, fastest and sharpest, best for pixel art.
124    Nearest,
125    /// Bilinear filtering.
126    Bilinear,
127    /// Trilinear filtering with mipmaps. This is the default.
128    #[default]
129    Trilinear,
130    /// Mitchell-Netravali cubic resampling, a smooth high-quality filter.
131    Mitchell,
132    /// Catmull-Rom cubic resampling, a sharper high-quality filter.
133    CatmullRom,
134}
135
136impl SamplingMode {
137    /// The Skia [`SamplingOptions`] backing this filtering algorithm.
138    pub fn sampling_options(&self) -> SamplingOptions {
139        match self {
140            Self::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
141            Self::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
142            Self::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
143            Self::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
144            Self::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
145        }
146    }
147}
148
149/// A decoded image shared by reference, ready to be rendered by an [`image()`].
150#[derive(Clone)]
151pub struct ImageHandle {
152    pub image: SkImage,
153    /// Backing data of the [`SkImage`], kept alive for as long as the image is used.
154    pub bytes: Bytes,
155}
156
157impl ImageHandle {
158    pub fn new(image: SkImage, bytes: Bytes) -> Self {
159        Self { image, bytes }
160    }
161
162    /// Build a handle from a raw `RGBA8888` pixel buffer, validating its length.
163    pub fn from_rgba(width: u32, height: u32, bytes: Bytes, alpha_type: AlphaType) -> Option<Self> {
164        let row_bytes = (width as usize).checked_mul(4)?;
165        if bytes.len() < row_bytes.checked_mul(height as usize)? {
166            return None;
167        }
168        let info = ImageInfo::new(
169            ISize::new(width as i32, height as i32),
170            ColorType::RGBA8888,
171            alpha_type,
172            None,
173        );
174        // Safety: `bytes` outlives the SkImage because the returned handle owns it.
175        let data = unsafe { Data::new_bytes(&bytes) };
176        let image = raster_from_data(&info, data, row_bytes)?;
177        Some(Self::new(image, bytes))
178    }
179}
180
181impl PartialEq for ImageHandle {
182    fn eq(&self, other: &Self) -> bool {
183        self.image.unique_id() == other.image.unique_id()
184    }
185}
186
187/// How an [`image()`] is scaled and sampled, grouping [`SamplingMode`], [`AspectRatio`] and [`ImageCover`].
188#[derive(Debug, Default, Clone, PartialEq)]
189pub struct ImageData {
190    pub sampling_mode: SamplingMode,
191    pub aspect_ratio: AspectRatio,
192    pub image_cover: ImageCover,
193    /// Snap the image to the pixels grid.
194    pub snap_to_grid: bool,
195    /// Gaussian blur radius applied to the image.
196    pub blur: f32,
197}
198
199#[derive(PartialEq, Clone)]
200pub struct ImageElement {
201    pub accessibility: AccessibilityData,
202    pub layout: LayoutData,
203    pub event_handlers: EventHandlers,
204    pub image_handle: ImageHandle,
205    pub image_data: ImageData,
206    pub relative_layer: Layer,
207    pub effect: Option<EffectData>,
208    pub style: StyleState,
209}
210
211impl ElementExt for ImageElement {
212    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
213        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
214            return false;
215        };
216        self != image
217    }
218
219    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
220        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
221            return DiffModifies::all();
222        };
223
224        let mut diff = DiffModifies::empty();
225
226        if self.accessibility != image.accessibility {
227            diff.insert(DiffModifies::ACCESSIBILITY);
228        }
229
230        if self.relative_layer != image.relative_layer {
231            diff.insert(DiffModifies::LAYER);
232        }
233
234        if self.layout != image.layout {
235            diff.insert(DiffModifies::LAYOUT);
236        }
237
238        if self.image_handle != image.image_handle {
239            diff.insert(DiffModifies::STYLE);
240
241            if self.image_handle.image.dimensions() != image.image_handle.image.dimensions() {
242                diff.insert(DiffModifies::LAYOUT);
243            }
244        }
245
246        if self.image_data != image.image_data {
247            diff.insert(DiffModifies::STYLE);
248        }
249
250        if self.effect != image.effect {
251            diff.insert(DiffModifies::EFFECT);
252        }
253
254        if self.style != image.style {
255            diff.insert(DiffModifies::STYLE);
256        }
257
258        if self.event_handlers != image.event_handlers {
259            diff.insert(DiffModifies::EVENT_HANDLERS);
260        }
261
262        diff
263    }
264
265    fn layout(&'_ self) -> Cow<'_, LayoutData> {
266        Cow::Borrowed(&self.layout)
267    }
268
269    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
270        self.effect.as_ref().map(Cow::Borrowed)
271    }
272
273    fn style(&'_ self) -> Cow<'_, StyleState> {
274        Cow::Borrowed(&self.style)
275    }
276
277    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
278        Cow::Owned(TextStyleData::default())
279    }
280
281    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
282        Cow::Borrowed(&self.accessibility)
283    }
284
285    fn layer(&self) -> Layer {
286        self.relative_layer
287    }
288
289    fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
290        Some(Cow::Borrowed(&self.event_handlers))
291    }
292
293    fn should_measure_inner_children(&self) -> bool {
294        true
295    }
296
297    fn should_hook_measurement(&self) -> bool {
298        true
299    }
300
301    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
302        let image = &self.image_handle.image;
303
304        let image_width = image.width() as f32;
305        let image_height = image.height() as f32;
306
307        let area_size = (*context.area_size - context.torin_node.margin.into()).max(Size2D::zero());
308
309        let width_ratio = area_size.width / image_width;
310        let height_ratio = area_size.height / image_height;
311
312        let size = match self.image_data.aspect_ratio {
313            AspectRatio::Max => {
314                let ratio = width_ratio.max(height_ratio);
315
316                Size2D::new(image_width * ratio, image_height * ratio)
317            }
318            AspectRatio::Min => {
319                let ratio = width_ratio.min(height_ratio);
320
321                Size2D::new(image_width * ratio, image_height * ratio)
322            }
323            AspectRatio::Fit => Size2D::new(image_width, image_height),
324            AspectRatio::None => area_size,
325        };
326
327        Some((size, Rc::new(size)))
328    }
329
330    fn clip(&self, context: ClipContext) {
331        let rrect = self.render_rect(context.visible_area, context.scale_factor as f32);
332        context.canvas.clip_rrect(rrect, ClipOp::Intersect, true);
333    }
334
335    fn render(&self, context: RenderContext) {
336        let size = context
337            .layout_node
338            .data
339            .as_ref()
340            .unwrap()
341            .downcast_ref::<Size2D>()
342            .unwrap();
343
344        let mut area = context.layout_node.visible_area();
345
346        let mut rect = SkRect::new(
347            area.min_x(),
348            area.min_y(),
349            area.min_x() + size.width,
350            area.min_y() + size.height,
351        );
352        if self.image_data.image_cover == ImageCover::Center {
353            let width_offset = (size.width - area.width()) / 2.;
354            let height_offset = (size.height - area.height()) / 2.;
355
356            rect.left -= width_offset;
357            rect.right -= width_offset;
358            rect.top -= height_offset;
359            rect.bottom -= height_offset;
360        }
361
362        if self.image_data.snap_to_grid {
363            rect = SkRect::new(
364                rect.left.round(),
365                rect.top.round(),
366                rect.right.round(),
367                rect.bottom.round(),
368            );
369            area = area.round();
370        }
371
372        context.canvas.save();
373        let clip_rrect = self.render_rect(&area, context.scale_factor as f32);
374        context
375            .canvas
376            .clip_rrect(clip_rrect, ClipOp::Intersect, true);
377
378        let sampling = self.image_data.sampling_mode.sampling_options();
379
380        let mut paint = Paint::default();
381        paint.set_anti_alias(true);
382        if self.image_data.blur > 0. {
383            paint.set_image_filter(blur(
384                (
385                    self.image_data.blur * context.scale_factor as f32,
386                    self.image_data.blur * context.scale_factor as f32,
387                ),
388                TileMode::Clamp,
389                None,
390                None,
391            ));
392        }
393
394        context.canvas.draw_image_rect_with_sampling_options(
395            &self.image_handle.image,
396            None,
397            rect,
398            sampling,
399            &paint,
400        );
401
402        context.canvas.restore();
403
404        let corner_radius = self
405            .style
406            .corner_radius
407            .with_scale(context.scale_factor as f32);
408        let mut path = PathBuilder::new();
409        if corner_radius.smoothing() > 0. {
410            path.add_path(&corner_radius.smoothed_path(clip_rrect), None);
411        } else {
412            path.add_rrect(clip_rrect, None, None);
413        }
414        let mut path = path.detach();
415
416        for shadow in &self.style.shadows {
417            if shadow.color != Color::TRANSPARENT {
418                let shadow = shadow.with_scale(context.scale_factor as f32);
419                RectElement::render_shadow(
420                    context.canvas,
421                    &mut path,
422                    clip_rrect,
423                    area,
424                    &shadow,
425                    &corner_radius,
426                );
427            }
428        }
429
430        for border in &self.style.borders {
431            if border.is_visible() {
432                let border = border.with_scale(context.scale_factor as f32);
433                RectElement::render_border(
434                    context.canvas,
435                    *clip_rrect.rect(),
436                    &border,
437                    &corner_radius,
438                );
439            }
440        }
441    }
442}
443
444impl From<Image> for Element {
445    fn from(value: Image) -> Self {
446        Element::Element {
447            key: value.key,
448            element: Rc::new(value.element),
449            elements: value.elements,
450        }
451    }
452}
453
454impl KeyExt for Image {
455    fn key_mut(&mut self) -> &mut DiffKey {
456        &mut self.key
457    }
458}
459
460impl EventHandlersExt for Image {
461    fn event_handlers_mut(&mut self) -> &mut EventHandlers {
462        &mut self.element.event_handlers
463    }
464}
465
466impl AccessibilityExt for Image {
467    fn accessibility_data_mut(&mut self) -> &mut AccessibilityData {
468        &mut self.element.accessibility
469    }
470}
471impl MaybeExt for Image {}
472
473impl LayoutExt for Image {
474    fn layout_mut(&mut self) -> &mut LayoutData {
475        &mut self.element.layout
476    }
477}
478
479impl DecorationExt for Image {}
480
481impl StyleExt for Image {
482    fn style_mut(&mut self) -> &mut StyleState {
483        &mut self.element.style
484    }
485}
486
487impl ContainerExt for Image {}
488impl ContainerWithContentExt for Image {}
489
490impl ImageExt for Image {
491    fn image_data_mut(&mut self) -> &mut ImageData {
492        &mut self.element.image_data
493    }
494}
495
496impl ChildrenExt for Image {
497    fn children_mut(&mut self) -> &mut Vec<Element> {
498        &mut self.elements
499    }
500}
501
502impl LayerExt for Image {
503    fn layer_mut(&mut self) -> &mut Layer {
504        &mut self.element.relative_layer
505    }
506}
507
508impl EffectExt for Image {
509    fn effect_mut(&mut self) -> &mut EffectData {
510        self.element.effect.get_or_insert_with(EffectData::default)
511    }
512}
513
514#[derive(Clone)]
515pub struct Image {
516    key: DiffKey,
517    element: ImageElement,
518    elements: Vec<Element>,
519}
520
521impl Image {
522    pub fn try_downcast(element: &dyn ElementExt) -> Option<ImageElement> {
523        (element as &dyn Any)
524            .downcast_ref::<ImageElement>()
525            .cloned()
526    }
527}