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    cell::RefCell,
7    collections::HashMap,
8    rc::Rc,
9};
10
11use bytes::Bytes;
12use freya_engine::prelude::{
13    ClipOp,
14    CubicResampler,
15    FilterMode,
16    MipmapMode,
17    Paint,
18    SamplingOptions,
19    SkImage,
20    SkRect,
21};
22use rustc_hash::FxHashMap;
23use torin::prelude::Size2D;
24
25use crate::{
26    data::{
27        AccessibilityData,
28        EffectData,
29        LayoutData,
30        StyleState,
31        TextStyleData,
32    },
33    diff_key::DiffKey,
34    element::{
35        ClipContext,
36        Element,
37        ElementExt,
38        EventHandlerType,
39        LayoutContext,
40        RenderContext,
41    },
42    events::name::EventName,
43    layers::Layer,
44    prelude::{
45        AccessibilityExt,
46        ChildrenExt,
47        ContainerExt,
48        ContainerWithContentExt,
49        EffectExt,
50        EventHandlersExt,
51        ImageExt,
52        KeyExt,
53        LayerExt,
54        LayoutExt,
55        MaybeExt,
56    },
57    style::corner_radius::CornerRadius,
58    tree::DiffModifies,
59};
60
61/// [image] makes it possible to render a Skia image into the canvas.
62/// You most likely want to use a higher level than this, like the component `ImageViewer`.
63///
64/// See the available methods in [Image].
65pub fn image(image_holder: ImageHolder) -> Image {
66    let mut accessibility = AccessibilityData::default();
67    accessibility.builder.set_role(accesskit::Role::Image);
68    Image {
69        key: DiffKey::None,
70        element: ImageElement {
71            image_holder,
72            accessibility,
73            layout: LayoutData::default(),
74            event_handlers: HashMap::default(),
75            image_data: ImageData::default(),
76            relative_layer: Layer::default(),
77            effect: None,
78            corner_radius: None,
79        },
80        elements: Vec::new(),
81    }
82}
83
84#[derive(Default, Clone, Debug, PartialEq)]
85pub enum ImageCover {
86    #[default]
87    Fill,
88    Center,
89}
90
91#[derive(Default, Clone, Debug, PartialEq)]
92pub enum AspectRatio {
93    #[default]
94    Min,
95    Max,
96    Fit,
97    None,
98}
99
100#[derive(Clone, Debug, PartialEq, Default)]
101pub enum SamplingMode {
102    #[default]
103    Nearest,
104    Bilinear,
105    Trilinear,
106    Mitchell,
107    CatmullRom,
108}
109
110#[derive(Clone)]
111pub struct ImageHolder {
112    pub image: Rc<RefCell<SkImage>>,
113    pub bytes: Bytes,
114}
115
116impl PartialEq for ImageHolder {
117    fn eq(&self, other: &Self) -> bool {
118        Rc::ptr_eq(&self.image, &other.image)
119    }
120}
121
122#[derive(Debug, Default, Clone, PartialEq)]
123pub struct ImageData {
124    pub sampling_mode: SamplingMode,
125    pub aspect_ratio: AspectRatio,
126    pub image_cover: ImageCover,
127}
128
129#[derive(PartialEq, Clone)]
130pub struct ImageElement {
131    pub accessibility: AccessibilityData,
132    pub layout: LayoutData,
133    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
134    pub image_holder: ImageHolder,
135    pub image_data: ImageData,
136    pub relative_layer: Layer,
137    pub effect: Option<EffectData>,
138    pub corner_radius: Option<CornerRadius>,
139}
140
141impl ElementExt for ImageElement {
142    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
143        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
144            return false;
145        };
146        self != image
147    }
148
149    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
150        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
151            return DiffModifies::all();
152        };
153
154        let mut diff = DiffModifies::empty();
155
156        if self.accessibility != image.accessibility {
157            diff.insert(DiffModifies::ACCESSIBILITY);
158        }
159
160        if self.relative_layer != image.relative_layer {
161            diff.insert(DiffModifies::LAYER);
162        }
163
164        if self.layout != image.layout {
165            diff.insert(DiffModifies::LAYOUT);
166        }
167
168        if self.image_holder != image.image_holder {
169            diff.insert(DiffModifies::LAYOUT);
170            diff.insert(DiffModifies::STYLE);
171        }
172
173        if self.effect != image.effect || self.corner_radius != image.corner_radius {
174            diff.insert(DiffModifies::STYLE);
175        }
176
177        diff
178    }
179
180    fn layout(&'_ self) -> Cow<'_, LayoutData> {
181        Cow::Borrowed(&self.layout)
182    }
183
184    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
185        self.effect.as_ref().map(Cow::Borrowed)
186    }
187
188    fn style(&'_ self) -> Cow<'_, StyleState> {
189        Cow::Owned(StyleState {
190            corner_radius: self.corner_radius.unwrap_or_default(),
191            ..StyleState::default()
192        })
193    }
194
195    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
196        Cow::Owned(TextStyleData::default())
197    }
198
199    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
200        Cow::Borrowed(&self.accessibility)
201    }
202
203    fn layer(&self) -> Layer {
204        self.relative_layer
205    }
206
207    fn should_measure_inner_children(&self) -> bool {
208        true
209    }
210
211    fn should_hook_measurement(&self) -> bool {
212        true
213    }
214
215    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
216        let image = self.image_holder.image.borrow();
217
218        let image_width = image.width() as f32;
219        let image_height = image.height() as f32;
220
221        let width_ratio = context.area_size.width / image.width() as f32;
222        let height_ratio = context.area_size.height / image.height() as f32;
223
224        let size = match self.image_data.aspect_ratio {
225            AspectRatio::Max => {
226                let ratio = width_ratio.max(height_ratio);
227
228                Size2D::new(image_width * ratio, image_height * ratio)
229            }
230            AspectRatio::Min => {
231                let ratio = width_ratio.min(height_ratio);
232
233                Size2D::new(image_width * ratio, image_height * ratio)
234            }
235            AspectRatio::Fit => Size2D::new(image_width, image_height),
236            AspectRatio::None => *context.area_size,
237        };
238
239        Some((size, Rc::new(size)))
240    }
241
242    fn clip(&self, context: ClipContext) {
243        let rrect = self.render_rect(context.visible_area, context.scale_factor as f32);
244        context.canvas.clip_rrect(rrect, ClipOp::Intersect, true);
245    }
246
247    fn render(&self, context: RenderContext) {
248        let size = context
249            .layout_node
250            .data
251            .as_ref()
252            .unwrap()
253            .downcast_ref::<Size2D>()
254            .unwrap();
255
256        let area = context.layout_node.visible_area();
257        let image = self.image_holder.image.borrow();
258
259        let mut rect = SkRect::new(
260            area.min_x(),
261            area.min_y(),
262            area.min_x() + size.width,
263            area.min_y() + size.height,
264        );
265        if self.image_data.image_cover == ImageCover::Center {
266            let width_offset = (size.width - area.width()) / 2.;
267            let height_offset = (size.height - area.height()) / 2.;
268
269            rect.left -= width_offset;
270            rect.right -= width_offset;
271            rect.top -= height_offset;
272            rect.bottom -= height_offset;
273        }
274
275        context.canvas.save();
276        let clip_rrect = self.render_rect(&area, context.scale_factor as f32);
277        context
278            .canvas
279            .clip_rrect(clip_rrect, ClipOp::Intersect, true);
280
281        let sampling = match self.image_data.sampling_mode {
282            SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
283            SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
284            SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
285            SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
286            SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
287        };
288
289        let mut paint = Paint::default();
290        paint.set_anti_alias(true);
291
292        context
293            .canvas
294            .draw_image_rect_with_sampling_options(&*image, None, rect, sampling, &paint);
295
296        context.canvas.restore();
297    }
298}
299
300impl From<Image> for Element {
301    fn from(value: Image) -> Self {
302        Element::Element {
303            key: value.key,
304            element: Rc::new(value.element),
305            elements: value.elements,
306        }
307    }
308}
309
310impl KeyExt for Image {
311    fn write_key(&mut self) -> &mut DiffKey {
312        &mut self.key
313    }
314}
315
316impl EventHandlersExt for Image {
317    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
318        &mut self.element.event_handlers
319    }
320}
321
322impl AccessibilityExt for Image {
323    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
324        &mut self.element.accessibility
325    }
326}
327impl MaybeExt for Image {}
328
329impl LayoutExt for Image {
330    fn get_layout(&mut self) -> &mut LayoutData {
331        &mut self.element.layout
332    }
333}
334
335impl ContainerExt for Image {}
336impl ContainerWithContentExt for Image {}
337
338impl ImageExt for Image {
339    fn get_image_data(&mut self) -> &mut ImageData {
340        &mut self.element.image_data
341    }
342}
343
344impl ChildrenExt for Image {
345    fn get_children(&mut self) -> &mut Vec<Element> {
346        &mut self.elements
347    }
348}
349
350impl LayerExt for Image {
351    fn get_layer(&mut self) -> &mut Layer {
352        &mut self.element.relative_layer
353    }
354}
355
356impl EffectExt for Image {
357    fn get_effect(&mut self) -> &mut EffectData {
358        self.element.effect.get_or_insert_with(EffectData::default)
359    }
360}
361
362pub struct Image {
363    key: DiffKey,
364    element: ImageElement,
365    elements: Vec<Element>,
366}
367
368impl Image {
369    pub fn try_downcast(element: &dyn ElementExt) -> Option<ImageElement> {
370        (element as &dyn Any)
371            .downcast_ref::<ImageElement>()
372            .cloned()
373    }
374
375    pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
376        self.element.corner_radius = Some(corner_radius.into());
377        self
378    }
379}