Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backdrop blurring and blend modes #924

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
65 changes: 65 additions & 0 deletions crates/core/src/elements/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use freya_node_state::{
ReferencesState,
ShadowPosition,
StyleState,
TransformState,
};
use torin::{
prelude::{
Expand Down Expand Up @@ -337,6 +338,70 @@ impl ElementUtils for RectElement {
path.add_rrect(rounded_rect, None);
}

// This is only done to dupe the borrow checker since [`SaveLayerRec::backdrop`]
// takes an [`ImageFilter`] by reference and it'd be otherwise dropped in the
// if-statement below.
let blur_filter = blur(
(
node_style.backdrop_blur * scale_factor,
node_style.backdrop_blur * scale_factor,
),
None,
None,
rounded_rect.rect(),
)
.unwrap();

// If we have a backdrop blur applied, we need to draw that by creating a new
// layer, clipping it to the rect's roundness, then blurring behind it before
// drawing the rect's initial background box.
if node_style.backdrop_blur != 0.0 {
// If we can guarantee that the node entirely draws over it's backdrop,
// we can avoid this whole (possibly intense) process, since the node's
// backdrop is never visible.
//
// There's probably more that can be done in this area, like checking individual gradient
// stops but I'm not completely sure of the diminishing returns here.
//
// Currently we verify the following:
// - Node has a single solid color background.
// - The background has 100% opacity.
// - The node has no parents with the `opacity` attribute applied.
let is_possibly_translucent = if let Fill::Color(color) = node_style.background {
let node_transform = &*node_ref.get::<TransformState>().unwrap();

color.a() != u8::MAX
|| node_style.blend_mode.is_some()
|| !node_transform.opacities.is_empty()
} else {
true
};

if is_possibly_translucent {
let layer_rec = SaveLayerRec::default()
.bounds(rounded_rect.rect())
.backdrop(&blur_filter);

// Depending on if the rect is rounded or not, we might need to clip the blur
// layer to the shape of the rounded rect.
if corner_radius.bottom_left == 0.0
&& corner_radius.bottom_right == 0.0
&& corner_radius.top_left == 0.0
&& corner_radius.top_right == 0.0
{
canvas.save_layer(&layer_rec);
canvas.restore();
} else {
canvas.save();
canvas.clip_rrect(rounded_rect, ClipOp::Intersect, true);
canvas.save_layer(&layer_rec);
canvas.restore();
canvas.restore();
}
}
}

// Paint the rect's background.
canvas.draw_path(&path, &paint);

// Shadows
Expand Down
24 changes: 15 additions & 9 deletions crates/core/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ use freya_engine::prelude::{
FontCollection,
FontMgr,
Matrix,
Paint,
Point,
Rect,
SamplingOptions,
SaveLayerRec,
Surface,
};
use freya_native_core::{
Expand All @@ -23,6 +25,7 @@ use freya_native_core::{
NodeId,
};
use freya_node_state::{
StyleState,
TransformState,
ViewportState,
};
Expand Down Expand Up @@ -193,6 +196,7 @@ impl RenderPipeline<'_> {
) {
let dirty_canvas = self.dirty_surface.canvas();
let area = layout_node.visible_area();
let rect = Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y());
let node_type = &*node_ref.node_type();
if let NodeType::Element(ElementNode { tag, .. }) = node_type {
let Some(element_utils) = tag.utils() else {
Expand All @@ -201,6 +205,7 @@ impl RenderPipeline<'_> {

let initial_layer = dirty_canvas.save();
let node_transform = &*node_ref.get::<TransformState>().unwrap();
let node_style = &*node_ref.get::<StyleState>().unwrap();

// Pass rotate effect to children
for (id, rotate_degs) in &node_transform.rotations {
Expand All @@ -217,17 +222,18 @@ impl RenderPipeline<'_> {
dirty_canvas.concat(&matrix);
}

// Apply blend mode
if let Some(blend) = node_style.blend_mode {
let mut paint = Paint::default();
paint.set_blend_mode(blend);

let layer_rec = SaveLayerRec::default().bounds(&rect).paint(&paint);
dirty_canvas.save_layer(&layer_rec);
}

// Apply inherited opacity effects
for opacity in &node_transform.opacities {
dirty_canvas.save_layer_alpha_f(
Rect::new(
self.canvas_area.min_x(),
self.canvas_area.min_y(),
self.canvas_area.max_x(),
self.canvas_area.max_y(),
),
*opacity,
);
dirty_canvas.save_layer_alpha_f(rect, *opacity);
}

// Clip all elements with their corresponding viewports
Expand Down
12 changes: 12 additions & 0 deletions crates/elements/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ builder_constructors! {
line_height: String,
#[doc = include_str!("_docs/attributes/spacing.md")]
spacing: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
blend_mode: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
backdrop_blur: String,

a11y_auto_focus: String,
a11y_name: String,
Expand Down Expand Up @@ -303,6 +307,8 @@ builder_constructors! {
margin: String,
#[doc = include_str!("_docs/attributes/opacity.md")]
opacity: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
blend_mode: String,

layer: String,
a11y_auto_focus: String,
Expand Down Expand Up @@ -377,6 +383,8 @@ builder_constructors! {
margin: String,
#[doc = include_str!("_docs/attributes/opacity.md")]
opacity: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
blend_mode: String,

layer: String,
cursor_index: String,
Expand Down Expand Up @@ -453,6 +461,8 @@ builder_constructors! {
rotate: String,
#[doc = include_str!("_docs/attributes/opacity.md")]
opacity: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
blend_mode: String,

image_data: String,
image_reference: String,
Expand Down Expand Up @@ -492,6 +502,8 @@ builder_constructors! {
rotate: String,
#[doc = include_str!("_docs/attributes/opacity.md")]
opacity: String,
// #[doc = include_str!("_docs/attributes/blend_mode.md")]
blend_mode: String,

svg_data: String,
svg_content: String,
Expand Down
69 changes: 69 additions & 0 deletions crates/engine/src/mocked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,41 @@ impl Canvas {
#[derive(Default)]
pub struct SamplingOptions;

#[repr(C)]
#[derive(Default)]
pub struct SaveLayerRec<'_>;

impl<'a> SaveLayerRec<'a> {
pub fn bounds(mut self, bounds: &'a Rect) -> Self {
unimplemented!("This is mocked")
}

pub fn paint(mut self, paint: &'a Paint) -> Self {
unimplemented!("This is mocked")
}

pub fn backdrop(mut self, backdrop: &'a ImageFilter) -> Self {
unimplemented!("This is mocked")
}

pub fn color_space(mut self, color_space: &'a ColorSpace) -> Self {
unimplemented!("This is mocked")
}

pub fn flags(mut self, flags: SaveLayerFlags) -> Self {
unimplemented!("This is mocked")
}
}

bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SaveLayerFlags: u32 {
const PRESERVE_LCD_TEXT = sb::SkCanvas_SaveLayerFlagsSet_kPreserveLCDText_SaveLayerFlag as _;
const INIT_WITH_PREVIOUS = sb::SkCanvas_SaveLayerFlagsSet_kInitWithPrevious_SaveLayerFlag as _;
const F16_COLOR_TYPE = sb::SkCanvas_SaveLayerFlagsSet_kF16ColorType as _;
}
}

#[repr(i32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Default)]
pub enum RectHeightStyle {
Expand Down Expand Up @@ -1741,3 +1776,37 @@ pub enum EncodedImageFormat {
AVIF = 12,
JPEGXL = 13,
}

#[repr(i32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum BlendMode {
Clear = 0,
Src = 1,
Dst = 2,
SrcOver = 3,
DstOver = 4,
SrcIn = 5,
DstIn = 6,
SrcOut = 7,
DstOut = 8,
SrcATop = 9,
DstATop = 10,
Xor = 11,
Plus = 12,
Modulate = 13,
Screen = 14,
Overlay = 15,
Darken = 16,
Lighten = 17,
ColorDodge = 18,
ColorBurn = 19,
HardLight = 20,
SoftLight = 21,
Difference = 22,
Exclusion = 23,
Multiply = 24,
Hue = 25,
Saturation = 26,
Color = 27,
Luminosity = 28,
}
7 changes: 7 additions & 0 deletions crates/engine/src/skia.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
pub use skia_safe::{
canvas::{
SaveLayerFlags,
SaveLayerRec,
},
font_style::{
Slant,
Weight,
Expand All @@ -23,6 +27,7 @@ pub use skia_safe::{
set_resource_cache_single_allocation_byte_limit,
set_resource_cache_total_bytes_limit,
},
image_filters::blur,
path::ArcSize,
rrect::Corner,
runtime_effect::Uniform,
Expand Down Expand Up @@ -56,6 +61,7 @@ pub use skia_safe::{
TypefaceFontProvider,
},
Bitmap,
BlendMode,
BlurStyle,
Canvas,
ClipOp,
Expand All @@ -71,6 +77,7 @@ pub use skia_safe::{
IPoint,
IRect,
Image,
ImageFilter,
ImageInfo,
MaskFilter,
Matrix,
Expand Down
4 changes: 4 additions & 0 deletions crates/native-core/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub enum AttributeName {
SvgData,
SvgContent,
Spacing,
BlendMode,
BackdropBlur,
}

impl FromStr for AttributeName {
Expand Down Expand Up @@ -139,6 +141,8 @@ impl FromStr for AttributeName {
"svg_data" => Ok(AttributeName::SvgData),
"svg_content" => Ok(AttributeName::SvgContent),
"spacing" => Ok(AttributeName::Spacing),
"blend_mode" => Ok(AttributeName::BlendMode),
"backdrop_blur" => Ok(AttributeName::BackdropBlur),
_ => Err(format!("{attr} not supported.")),
}
}
Expand Down
15 changes: 15 additions & 0 deletions crates/state/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::{
};

use freya_common::CompositorDirtyNodes;
use freya_engine::prelude::BlendMode;
use freya_native_core::{
attributes::AttributeName,
exports::shipyard::Component,
Expand Down Expand Up @@ -42,6 +43,8 @@ pub struct StyleState {
pub image_data: Option<AttributesBytes>,
pub svg_data: Option<AttributesBytes>,
pub overflow: OverflowMode,
pub blend_mode: Option<BlendMode>,
pub backdrop_blur: f32,
}

impl ParseAttribute for StyleState {
Expand Down Expand Up @@ -114,6 +117,16 @@ impl ParseAttribute for StyleState {
self.overflow = OverflowMode::parse(value)?;
}
}
AttributeName::BlendMode => {
if let Some(value) = attr.value.as_text() {
self.blend_mode = Some(BlendMode::parse(value)?);
}
}
AttributeName::BackdropBlur => {
if let Some(value) = attr.value.as_text() {
self.backdrop_blur = value.parse::<f32>().map_err(|_| ParseError)?;
}
}
_ => {}
}

Expand Down Expand Up @@ -141,6 +154,8 @@ impl State<CustomAttributeValues> for StyleState {
AttributeName::SvgData,
AttributeName::SvgContent,
AttributeName::Overflow,
AttributeName::BackdropBlur,
AttributeName::BlendMode,
]));

fn update<'a>(
Expand Down
Loading
Loading