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