Skip to content

Commit

Permalink
feat: Integrate images into layout (#1082)
Browse files Browse the repository at this point in the history
* feat: `cover` attribute for `image` element

* fix from_str impl

* adjustements

* fix clip area

* fix clip area again

* fix clip area again, again

* fmt

* feat: Integrate images into layout

* fmt and clippy

* clean up

* clean up
  • Loading branch information
marc2332 authored Feb 1, 2025
1 parent 92366f3 commit a4a10c6
Show file tree
Hide file tree
Showing 19 changed files with 269 additions and 114 deletions.
20 changes: 16 additions & 4 deletions crates/components/src/network_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ use crate::Loader;
/// Properties for the [`NetworkImage`] component.
#[derive(Props, Clone, PartialEq)]
pub struct NetworkImageProps {
/// Width of the image container. Default to `fill`.
#[props(default = "fill".into())]
/// Width of the image container. Default to `auto`.
#[props(default = "auto".into())]
pub width: String,
/// Height of the image container. Default to `fill`.
#[props(default = "fill".into())]
/// Height of the image container. Default to `auto`.
#[props(default = "auto".into())]
pub height: String,
/// Min width of the image container.
pub min_width: Option<String>,
/// Min height of the image container.
pub min_height: Option<String>,
/// URL of the image.
pub url: ReadOnlySignal<Url>,
/// Fallback element.
Expand Down Expand Up @@ -88,6 +92,8 @@ pub fn NetworkImage(
NetworkImageProps {
width,
height,
min_width,
min_height,
url,
fallback,
loading,
Expand Down Expand Up @@ -156,6 +162,8 @@ pub fn NetworkImage(
rsx!(image {
height,
width,
min_width,
min_height,
a11y_id,
image_data,
a11y_role: "image",
Expand All @@ -174,6 +182,8 @@ pub fn NetworkImage(
rect {
height,
width,
min_width,
min_height,
main_align: "center",
cross_align: "center",
Loader {}
Expand All @@ -189,6 +199,8 @@ pub fn NetworkImage(
rect {
height,
width,
min_width,
min_height,
main_align: "center",
cross_align: "center",
label {
Expand Down
133 changes: 48 additions & 85 deletions crates/core/src/elements/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ use freya_common::ImagesCache;
use freya_engine::prelude::*;
use freya_native_core::real_dom::NodeImmutable;
use freya_node_state::{
AspectRatio,
ImageCover,
ReferencesState,
SamplingMode,
StyleState,
TransformState,
};

use super::utils::ElementUtils;
use crate::dom::DioxusNode;
use crate::{
dom::DioxusNode,
render::{
get_or_create_image,
ImageData,
},
};

pub struct ImageElement;

Expand All @@ -28,98 +32,57 @@ impl ElementUtils for ImageElement {
_scale_factor: f32,
) {
let area = layout_node.visible_area();
let node_style = node_ref.get::<StyleState>().unwrap();
let node_references = node_ref.get::<ReferencesState>().unwrap();
let node_transform = node_ref.get::<TransformState>().unwrap();

let mut draw_img = |bytes: &[u8]| {
let image = if let Some(image_cache_key) = &node_style.image_cache_key {
images_cache.get(image_cache_key).cloned().or_else(|| {
Image::from_encoded(unsafe { Data::new_bytes(bytes) }).inspect(|image| {
images_cache.insert(image_cache_key.clone(), image.clone());
})
})
} else {
Image::from_encoded(unsafe { Data::new_bytes(bytes) })
};

let Some(image) = image else {
return;
};

let width_ratio = area.width() / image.width() as f32;
let height_ratio = area.height() / image.height() as f32;

let (width, height) = match node_transform.aspect_ratio {
AspectRatio::Max => {
let ratio = width_ratio.max(height_ratio);

(image.width() as f32 * ratio, image.height() as f32 * ratio)
}
AspectRatio::Min => {
let ratio = width_ratio.min(height_ratio);

(image.width() as f32 * ratio, image.height() as f32 * ratio)
}
AspectRatio::None => (area.width(), area.height()),
};
let Some(ImageData { image, size }) =
get_or_create_image(node_ref, &area.size, images_cache)
else {
return;
};

let mut rect = Rect::new(
area.min_x(),
area.min_y(),
area.min_x() + width,
area.min_y() + height,
);
let node_transform = node_ref.get::<TransformState>().unwrap();
let node_style = node_ref.get::<StyleState>().unwrap();

if node_transform.image_cover == ImageCover::Center {
let width_offset = (width - area.width()) / 2.;
let height_offset = (height - area.height()) / 2.;
let mut rect = Rect::new(
area.min_x(),
area.min_y(),
area.min_x() + size.width,
area.min_y() + size.height,
);

let clip_rect = Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y());
let clip_rect = Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y());

rect.left -= width_offset;
rect.right -= width_offset;
rect.top -= height_offset;
rect.bottom -= height_offset;
if node_transform.image_cover == ImageCover::Center {
let width_offset = (size.width - area.width()) / 2.;
let height_offset = (size.height - area.height()) / 2.;

canvas.save();
canvas.clip_rect(clip_rect, ClipOp::Intersect, true);
}
rect.left -= width_offset;
rect.right -= width_offset;
rect.top -= height_offset;
rect.bottom -= height_offset;
}

let sampling = match node_style.image_sampling {
SamplingMode::Nearest => {
SamplingOptions::new(FilterMode::Nearest, MipmapMode::None)
}
SamplingMode::Bilinear => {
SamplingOptions::new(FilterMode::Linear, MipmapMode::None)
}
SamplingMode::Trilinear => {
SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear)
}
SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
};
canvas.save();
canvas.clip_rect(clip_rect, ClipOp::Intersect, true);

canvas.draw_image_rect_with_sampling_options(
image,
None,
rect,
sampling,
&Paint::default(),
);
let mut paint = Paint::default();
paint.set_anti_alias(true);

if node_transform.image_cover == ImageCover::Center {
canvas.restore();
}
let sampling = match node_style.image_sampling {
SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
};

if let Some(image_ref) = &node_references.image_ref {
let image_data = image_ref.0.lock().unwrap();
if let Some(image_data) = image_data.as_ref() {
draw_img(image_data)
}
} else if let Some(image_data) = &node_style.image_data {
draw_img(image_data.as_slice())
}
canvas.draw_image_rect_with_sampling_options(
image,
None,
rect,
sampling,
&Paint::default(),
);

canvas.restore();
}
}
9 changes: 8 additions & 1 deletion crates/core/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ pub fn process_layout(
) {
{
let rdom = fdom.rdom();
let mut images_cache = fdom.images_cache();
let mut dom_adapter = DioxusDOMAdapter::new(rdom, scale_factor);
let skia_measurer = SkiaMeasurer::new(rdom, font_collection, default_fonts, scale_factor);
let skia_measurer = SkiaMeasurer::new(
rdom,
font_collection,
default_fonts,
scale_factor,
&mut images_cache,
);

let mut layout = fdom.layout();

Expand Down
14 changes: 14 additions & 0 deletions crates/core/src/render/skia_measurer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;

use freya_common::{
CachedParagraph,
ImagesCache,
NodeReferenceLayout,
};
use freya_engine::prelude::*;
Expand All @@ -26,6 +27,8 @@ use torin::prelude::{
use super::{
create_label,
create_paragraph,
get_or_create_image,
ImageData,
};
use crate::{
dom::*,
Expand All @@ -38,6 +41,7 @@ pub struct SkiaMeasurer<'a> {
pub rdom: &'a DioxusDOM,
pub default_fonts: &'a [String],
pub scale_factor: f32,
pub images_cache: &'a mut ImagesCache,
}

impl<'a> SkiaMeasurer<'a> {
Expand All @@ -46,12 +50,14 @@ impl<'a> SkiaMeasurer<'a> {
font_collection: &'a FontCollection,
default_fonts: &'a [String],
scale_factor: f32,
images_cache: &'a mut ImagesCache,
) -> Self {
Self {
font_collection,
rdom,
default_fonts,
scale_factor,
images_cache,
}
}
}
Expand Down Expand Up @@ -93,6 +99,14 @@ impl<'a> LayoutMeasurer<NodeId> for SkiaMeasurer<'a> {
map.insert(CachedParagraph(paragraph, size.height));
Some((size, Arc::new(map)))
}
NodeType::Element(ElementNode { tag, .. }) if tag == &TagName::Image => {
let Some(ImageData { size, .. }) =
get_or_create_image(&node, area_size, self.images_cache)
else {
return Some((*area_size, Arc::default()));
};
Some((size, Arc::default()))
}
_ => None,
}
}
Expand Down
79 changes: 79 additions & 0 deletions crates/core/src/render/utils/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use freya_common::ImagesCache;
use freya_engine::prelude::{
Data,
Image,
};
use freya_native_core::prelude::NodeImmutable;
use freya_node_state::{
AspectRatio,
ReferencesState,
StyleState,
TransformState,
};
use torin::prelude::Size2D;

use crate::dom::DioxusNode;

pub struct ImageData {
pub image: Image,
pub size: Size2D,
}

pub fn get_or_create_image(
node_ref: &DioxusNode,
area_size: &Size2D,
images_cache: &mut ImagesCache,
) -> Option<ImageData> {
let node_style = node_ref.get::<StyleState>().unwrap();
let node_references = node_ref.get::<ReferencesState>().unwrap();

let mut get_or_create_image = |bytes: &[u8]| -> Option<Image> {
if let Some(image_cache_key) = &node_style.image_cache_key {
images_cache.get(image_cache_key).cloned().or_else(|| {
Image::from_encoded(unsafe { Data::new_bytes(bytes) }).inspect(|image| {
images_cache.insert(image_cache_key.clone(), image.clone());
})
})
} else {
Image::from_encoded(unsafe { Data::new_bytes(bytes) })
}
};

let image = if let Some(image_ref) = &node_references.image_ref {
let image_data = image_ref.0.lock().unwrap();
if let Some(bytes) = image_data.as_ref() {
get_or_create_image(bytes)
} else {
None
}
} else if let Some(image_data) = &node_style.image_data {
get_or_create_image(image_data.as_slice())
} else {
None
}?;

let node_transform = node_ref.get::<TransformState>().unwrap();

let image_width = image.width() as f32;
let image_height = image.height() as f32;

let width_ratio = area_size.width / image.width() as f32;
let height_ratio = area_size.height / image.height() as f32;

let size = match node_transform.aspect_ratio {
AspectRatio::Max => {
let ratio = width_ratio.max(height_ratio);

Size2D::new(image_width * ratio, image_height * ratio)
}
AspectRatio::Min => {
let ratio = width_ratio.min(height_ratio);

Size2D::new(image_width * ratio, image_height * ratio)
}
AspectRatio::Fit => Size2D::new(image_width, image_height),
AspectRatio::None => *area_size,
};

Some(ImageData { image, size })
}
2 changes: 2 additions & 0 deletions crates/core/src/render/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod borders;
mod image;
mod label;
mod paragraph;
mod shadows;

pub use borders::*;
pub use image::*;
pub use label::*;
pub use paragraph::*;
pub use shadows::*;
Loading

0 comments on commit a4a10c6

Please sign in to comment.