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    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    Nearest,
103    Bilinear,
104    #[default]
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::STYLE);
170
171            if self.image_holder.image.borrow().dimensions()
172                != image.image_holder.image.borrow().dimensions()
173            {
174                diff.insert(DiffModifies::LAYOUT);
175            }
176        }
177
178        if self.effect != image.effect || self.corner_radius != image.corner_radius {
179            diff.insert(DiffModifies::STYLE);
180        }
181
182        diff
183    }
184
185    fn layout(&'_ self) -> Cow<'_, LayoutData> {
186        Cow::Borrowed(&self.layout)
187    }
188
189    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
190        self.effect.as_ref().map(Cow::Borrowed)
191    }
192
193    fn style(&'_ self) -> Cow<'_, StyleState> {
194        Cow::Owned(StyleState {
195            corner_radius: self.corner_radius.unwrap_or_default(),
196            ..StyleState::default()
197        })
198    }
199
200    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
201        Cow::Owned(TextStyleData::default())
202    }
203
204    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
205        Cow::Borrowed(&self.accessibility)
206    }
207
208    fn layer(&self) -> Layer {
209        self.relative_layer
210    }
211
212    fn should_measure_inner_children(&self) -> bool {
213        true
214    }
215
216    fn should_hook_measurement(&self) -> bool {
217        true
218    }
219
220    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
221        let image = self.image_holder.image.borrow();
222
223        let image_width = image.width() as f32;
224        let image_height = image.height() as f32;
225
226        let width_ratio = context.area_size.width / image.width() as f32;
227        let height_ratio = context.area_size.height / image.height() as f32;
228
229        let size = match self.image_data.aspect_ratio {
230            AspectRatio::Max => {
231                let ratio = width_ratio.max(height_ratio);
232
233                Size2D::new(image_width * ratio, image_height * ratio)
234            }
235            AspectRatio::Min => {
236                let ratio = width_ratio.min(height_ratio);
237
238                Size2D::new(image_width * ratio, image_height * ratio)
239            }
240            AspectRatio::Fit => Size2D::new(image_width, image_height),
241            AspectRatio::None => *context.area_size,
242        };
243
244        Some((size, Rc::new(size)))
245    }
246
247    fn clip(&self, context: ClipContext) {
248        let rrect = self.render_rect(context.visible_area, context.scale_factor as f32);
249        context.canvas.clip_rrect(rrect, ClipOp::Intersect, true);
250    }
251
252    fn render(&self, context: RenderContext) {
253        let size = context
254            .layout_node
255            .data
256            .as_ref()
257            .unwrap()
258            .downcast_ref::<Size2D>()
259            .unwrap();
260
261        let area = context.layout_node.visible_area();
262        let image = self.image_holder.image.borrow();
263
264        let mut rect = SkRect::new(
265            area.min_x(),
266            area.min_y(),
267            area.min_x() + size.width,
268            area.min_y() + size.height,
269        );
270        if self.image_data.image_cover == ImageCover::Center {
271            let width_offset = (size.width - area.width()) / 2.;
272            let height_offset = (size.height - area.height()) / 2.;
273
274            rect.left -= width_offset;
275            rect.right -= width_offset;
276            rect.top -= height_offset;
277            rect.bottom -= height_offset;
278        }
279
280        context.canvas.save();
281        let clip_rrect = self.render_rect(&area, context.scale_factor as f32);
282        context
283            .canvas
284            .clip_rrect(clip_rrect, ClipOp::Intersect, true);
285
286        let sampling = match self.image_data.sampling_mode {
287            SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
288            SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
289            SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
290            SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
291            SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
292        };
293
294        let mut paint = Paint::default();
295        paint.set_anti_alias(true);
296
297        context
298            .canvas
299            .draw_image_rect_with_sampling_options(&*image, None, rect, sampling, &paint);
300
301        context.canvas.restore();
302    }
303}
304
305impl From<Image> for Element {
306    fn from(value: Image) -> Self {
307        Element::Element {
308            key: value.key,
309            element: Rc::new(value.element),
310            elements: value.elements,
311        }
312    }
313}
314
315impl KeyExt for Image {
316    fn write_key(&mut self) -> &mut DiffKey {
317        &mut self.key
318    }
319}
320
321impl EventHandlersExt for Image {
322    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
323        &mut self.element.event_handlers
324    }
325}
326
327impl AccessibilityExt for Image {
328    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
329        &mut self.element.accessibility
330    }
331}
332impl MaybeExt for Image {}
333
334impl LayoutExt for Image {
335    fn get_layout(&mut self) -> &mut LayoutData {
336        &mut self.element.layout
337    }
338}
339
340impl ContainerExt for Image {}
341impl ContainerWithContentExt for Image {}
342
343impl ImageExt for Image {
344    fn get_image_data(&mut self) -> &mut ImageData {
345        &mut self.element.image_data
346    }
347}
348
349impl ChildrenExt for Image {
350    fn get_children(&mut self) -> &mut Vec<Element> {
351        &mut self.elements
352    }
353}
354
355impl LayerExt for Image {
356    fn get_layer(&mut self) -> &mut Layer {
357        &mut self.element.relative_layer
358    }
359}
360
361impl EffectExt for Image {
362    fn get_effect(&mut self) -> &mut EffectData {
363        self.element.effect.get_or_insert_with(EffectData::default)
364    }
365}
366
367pub struct Image {
368    key: DiffKey,
369    element: ImageElement,
370    elements: Vec<Element>,
371}
372
373impl Image {
374    pub fn try_downcast(element: &dyn ElementExt) -> Option<ImageElement> {
375        (element as &dyn Any)
376            .downcast_ref::<ImageElement>()
377            .cloned()
378    }
379
380    pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
381        self.element.corner_radius = Some(corner_radius.into());
382        self
383    }
384}