Merge pull request #7098 from penpot/superalex-fix-big-blur-2

🐛 Fix big blur rendering for wasm render
This commit is contained in:
Elena Torró
2025-08-21 09:23:26 +02:00
committed by GitHub
14 changed files with 6534 additions and 66 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -161,3 +161,19 @@ test("Updates canvas background", async ({
await expect(workspace.canvas).toHaveScreenshot(); await expect(workspace.canvas).toHaveScreenshot();
}); });
test("Renders a file with blurs applied to any kind of shape", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("render-wasm/get-file-blurs.json");
await workspace.goToWorkspace({
id: "aa0a383a-7553-808a-8006-ae1237b52cf9",
pageId: "aa0a383a-7553-808a-8006-ae160ba8bd86",
});
await workspace.waitForFirstRender();
await expect(workspace.canvas).toHaveScreenshot();
});

View File

@@ -1,6 +1,7 @@
mod blend; mod blend;
mod debug; mod debug;
mod fills; mod fills;
pub mod filters;
mod fonts; mod fonts;
mod gpu_state; mod gpu_state;
pub mod grid_layout; pub mod grid_layout;
@@ -21,7 +22,7 @@ use options::RenderOptions;
pub use surfaces::{SurfaceId, Surfaces}; pub use surfaces::{SurfaceId, Surfaces};
use crate::performance; use crate::performance;
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type}; use crate::shapes::{Blur, BlurType, Corners, Fill, Shape, SolidColor, StructureEntry, Type};
use crate::state::ShapesPool; use crate::state::ShapesPool;
use crate::tiles::{self, PendingTiles, TileRect}; use crate::tiles::{self, PendingTiles, TileRect};
use crate::uuid::Uuid; use crate::uuid::Uuid;
@@ -179,6 +180,7 @@ pub(crate) struct RenderState {
// can affect its child elements if they don't specify one themselves. If the planned // can affect its child elements if they don't specify one themselves. If the planned
// migration to remove group-level fills is completed, this code should be removed. // migration to remove group-level fills is completed, this code should be removed.
pub nested_fills: Vec<Vec<Fill>>, pub nested_fills: Vec<Vec<Fill>>,
pub nested_blurs: Vec<Option<Blur>>,
pub show_grid: Option<Uuid>, pub show_grid: Option<Uuid>,
pub focus_mode: FocusMode, pub focus_mode: FocusMode,
} }
@@ -269,6 +271,7 @@ impl RenderState {
), ),
pending_tiles: PendingTiles::new_empty(), pending_tiles: PendingTiles::new_empty(),
nested_fills: vec![], nested_fills: vec![],
nested_blurs: vec![],
show_grid: None, show_grid: None,
focus_mode: FocusMode::new(), focus_mode: FocusMode::new(),
} }
@@ -451,6 +454,23 @@ impl RenderState {
shape.to_mut().apply_transform(shape_modifiers); shape.to_mut().apply_transform(shape_modifiers);
} }
let mut nested_blur_value = 0.;
for nested_blur in self.nested_blurs.iter().flatten() {
if !nested_blur.hidden && nested_blur.blur_type == BlurType::Layer {
nested_blur_value += nested_blur.value.powf(2.);
}
}
if !shape.blur.hidden && shape.blur.blur_type == BlurType::Layer {
nested_blur_value += shape.blur.value.powf(2.);
}
if nested_blur_value > 0. {
shape
.to_mut()
.set_blur(BlurType::Layer as u8, false, nested_blur_value.sqrt());
}
let center = shape.center(); let center = shape.center();
let mut matrix = shape.transform; let mut matrix = shape.transform;
matrix.post_translate(center); matrix.post_translate(center);
@@ -491,7 +511,10 @@ impl RenderState {
}); });
let text_content = text_content.new_bounds(shape.selrect()); let text_content = text_content.new_bounds(shape.selrect());
let mut paragraphs = text_content.get_skia_paragraphs(); let mut paragraphs = text_content.get_skia_paragraphs(
shape.image_filter(1.).as_ref(),
shape.mask_filter(1.).as_ref(),
);
if !shape.has_visible_strokes() { if !shape.has_visible_strokes() {
shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias); shadows::render_text_drop_shadows(self, &shape, &mut paragraphs, antialias);
@@ -500,8 +523,12 @@ impl RenderState {
text::render(self, &shape, &mut paragraphs, None, None); text::render(self, &shape, &mut paragraphs, None, None);
for stroke in shape.visible_strokes().rev() { for stroke in shape.visible_strokes().rev() {
let mut stroke_paragraphs = let mut stroke_paragraphs = text_content.get_skia_stroke_paragraphs(
text_content.get_skia_stroke_paragraphs(stroke, &shape.selrect()); stroke,
&shape.selrect(),
shape.image_filter(1.).as_ref(),
shape.mask_filter(1.).as_ref(),
);
shadows::render_text_drop_shadows( shadows::render_text_drop_shadows(
self, self,
&shape, &shape,
@@ -751,6 +778,13 @@ impl RenderState {
} }
} }
match element.shape_type {
Type::Frame(_) | Type::Group(_) => {
self.nested_blurs.push(Some(element.blur));
}
_ => {}
}
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
paint.set_blend_mode(element.blend_mode().into()); paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity()); paint.set_alpha_f(element.opacity());
@@ -767,10 +801,6 @@ impl RenderState {
.save_layer(&mask_rec); .save_layer(&mask_rec);
} }
if let Some(image_filter) = element.image_filter(self.get_scale()) {
paint.set_image_filter(image_filter);
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
self.surfaces self.surfaces
.canvas(SurfaceId::Current) .canvas(SurfaceId::Current)
@@ -830,6 +860,13 @@ impl RenderState {
self.nested_fills.pop(); self.nested_fills.pop();
} }
match element.shape_type {
Type::Frame(_) | Type::Group(_) => {
self.nested_blurs.pop();
}
_ => {}
}
// Detect clipping and apply it properly // Detect clipping and apply it properly
if let Type::Frame(_) = &element.shape_type { if let Type::Frame(_) = &element.shape_type {
if element.clip() { if element.clip() {

View File

@@ -24,8 +24,15 @@ fn draw_image_fill(
let src_rect = get_source_rect(size, container, image_fill); let src_rect = get_source_rect(size, container, image_fill);
let dest_rect = container; let dest_rect = container;
let mut image_paint = skia::Paint::default();
image_paint.set_anti_alias(antialias);
if let Some(filter) = shape.image_filter(1.) {
image_paint.set_image_filter(filter.clone());
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&image_paint);
// Save the current canvas state // Save the current canvas state
canvas.save(); canvas.save_layer(&layer_rec);
// Set the clipping rectangle to the container bounds // Set the clipping rectangle to the container bounds
match &shape.shape_type { match &shape.shape_type {
@@ -84,26 +91,29 @@ fn draw_image_fill(
* This SHOULD be the only public function in this module. * This SHOULD be the only public function in this module.
*/ */
pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill, antialias: bool) { pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill, antialias: bool) {
let paint = &fill.to_paint(&shape.selrect, antialias); let mut paint = fill.to_paint(&shape.selrect, antialias);
if let Some(image_filter) = shape.image_filter(1.) {
paint.set_image_filter(image_filter);
}
match (fill, &shape.shape_type) { match (fill, &shape.shape_type) {
(Fill::Image(image_fill), _) => { (Fill::Image(image_fill), _) => {
draw_image_fill(render_state, shape, image_fill, paint, antialias); draw_image_fill(render_state, shape, image_fill, &paint, antialias);
} }
(_, Type::Rect(_) | Type::Frame(_)) => { (_, Type::Rect(_) | Type::Frame(_)) => {
render_state render_state
.surfaces .surfaces
.draw_rect_to(SurfaceId::Fills, shape, paint); .draw_rect_to(SurfaceId::Fills, shape, &paint);
} }
(_, Type::Circle) => { (_, Type::Circle) => {
render_state render_state
.surfaces .surfaces
.draw_circle_to(SurfaceId::Fills, shape, paint); .draw_circle_to(SurfaceId::Fills, shape, &paint);
} }
(_, Type::Path(_)) | (_, Type::Bool(_)) => { (_, Type::Path(_)) | (_, Type::Bool(_)) => {
render_state render_state
.surfaces .surfaces
.draw_path_to(SurfaceId::Fills, shape, paint); .draw_path_to(SurfaceId::Fills, shape, &paint);
} }
(_, Type::Group(_)) => { (_, Type::Group(_)) => {
// Groups can have fills but they propagate them to their children // Groups can have fills but they propagate them to their children

View File

@@ -0,0 +1,23 @@
use skia_safe::ImageFilter;
/// Composes two image filters, returning a combined filter if both are present,
/// or the individual filter if only one is present, or None if neither is present.
///
/// # Parameters
/// - `filter_a`: The first optional image filter.
/// - `filter_b`: The second optional image filter.
///
/// # Returns
/// - `Some(ImageFilter)`: The composed or single filter.
/// - `None`: If both filters are `None`.
pub fn compose_filters(
filter_a: Option<&ImageFilter>,
filter_b: Option<&ImageFilter>,
) -> Option<ImageFilter> {
match (filter_a, filter_b) {
(Some(fa), Some(fb)) => ImageFilter::compose(fa, fb),
(Some(fa), None) => Some(fa.clone()),
(None, Some(fb)) => Some(fb.clone()),
(None, None) => None,
}
}

View File

@@ -20,7 +20,7 @@ fn render_fill_drop_shadow(
shadow: &Shadow, shadow: &Shadow,
antialias: bool, antialias: bool,
) { ) {
let paint = &shadow.get_drop_shadow_paint(antialias); let paint = &shadow.get_drop_shadow_paint(antialias, shape.image_filter(1.).as_ref());
render_shadow_paint(render_state, shape, paint, SurfaceId::DropShadows); render_shadow_paint(render_state, shape, paint, SurfaceId::DropShadows);
} }
@@ -38,7 +38,7 @@ fn render_fill_inner_shadow(
shadow: &Shadow, shadow: &Shadow,
antialias: bool, antialias: bool,
) { ) {
let paint = &shadow.get_inner_shadow_paint(antialias); let paint = &shadow.get_inner_shadow_paint(antialias, shape.image_filter(1.).as_ref());
render_shadow_paint(render_state, shape, paint, SurfaceId::InnerShadows); render_shadow_paint(render_state, shape, paint, SurfaceId::InnerShadows);
} }
@@ -129,7 +129,7 @@ pub fn render_text_drop_shadow(
paragraphs: &mut [Vec<ParagraphBuilder>], paragraphs: &mut [Vec<ParagraphBuilder>],
antialias: bool, antialias: bool,
) { ) {
let paint = shadow.get_drop_shadow_paint(antialias); let paint = shadow.get_drop_shadow_paint(antialias, shape.image_filter(1.).as_ref());
text::render( text::render(
render_state, render_state,
@@ -158,7 +158,7 @@ pub fn render_text_inner_shadow(
paragraphs: &mut [Vec<ParagraphBuilder>], paragraphs: &mut [Vec<ParagraphBuilder>],
antialias: bool, antialias: bool,
) { ) {
let paint = shadow.get_inner_shadow_paint(antialias); let paint = shadow.get_inner_shadow_paint(antialias, shape.image_filter(1.).as_ref());
text::render( text::render(
render_state, render_state,

View File

@@ -6,6 +6,7 @@ use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, St
use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, RRect}; use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, RRect};
use super::{RenderState, SurfaceId}; use super::{RenderState, SurfaceId};
use crate::render::filters::compose_filters;
use crate::render::text::{self}; use crate::render::text::{self};
use crate::render::{get_dest_rect, get_source_rect}; use crate::render::{get_dest_rect, get_source_rect};
@@ -20,6 +21,7 @@ fn draw_stroke_on_rect(
svg_attrs: &HashMap<String, String>, svg_attrs: &HashMap<String, String>,
scale: f32, scale: f32,
shadow: Option<&ImageFilter>, shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
// Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to: // Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to:
@@ -29,9 +31,9 @@ fn draw_stroke_on_rect(
let stroke_rect = stroke.outer_rect(rect); let stroke_rect = stroke.outer_rect(rect);
let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias); let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias);
if let Some(filter) = shadow { // Apply both blur and shadow filters if present, composing them if necessary.
paint.set_image_filter(filter.clone()); let filter = compose_filters(blur, shadow);
} paint.set_image_filter(filter);
match corners { match corners {
Some(radii) => { Some(radii) => {
@@ -55,6 +57,7 @@ fn draw_stroke_on_circle(
svg_attrs: &HashMap<String, String>, svg_attrs: &HashMap<String, String>,
scale: f32, scale: f32,
shadow: Option<&ImageFilter>, shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
// Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to: // Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to:
@@ -64,9 +67,9 @@ fn draw_stroke_on_circle(
let stroke_rect = stroke.outer_rect(rect); let stroke_rect = stroke.outer_rect(rect);
let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias); let mut paint = stroke.to_paint(selrect, svg_attrs, scale, antialias);
if let Some(filter) = shadow { // Apply both blur and shadow filters if present, composing them if necessary.
paint.set_image_filter(filter.clone()); let filter = compose_filters(blur, shadow);
} paint.set_image_filter(filter);
canvas.draw_oval(stroke_rect, &paint); canvas.draw_oval(stroke_rect, &paint);
} }
@@ -75,11 +78,17 @@ fn draw_outer_stroke_path(
canvas: &skia::Canvas, canvas: &skia::Canvas,
path: &skia::Path, path: &skia::Path,
paint: &skia::Paint, paint: &skia::Paint,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
let mut outer_paint = skia::Paint::default(); let mut outer_paint = skia::Paint::default();
outer_paint.set_blend_mode(skia::BlendMode::SrcOver); outer_paint.set_blend_mode(skia::BlendMode::SrcOver);
outer_paint.set_anti_alias(antialias); outer_paint.set_anti_alias(antialias);
if let Some(filter) = blur {
outer_paint.set_image_filter(filter.clone());
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&outer_paint); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&outer_paint);
canvas.save_layer(&layer_rec); canvas.save_layer(&layer_rec);
canvas.draw_path(path, paint); canvas.draw_path(path, paint);
@@ -97,9 +106,17 @@ fn draw_inner_stroke_path(
canvas: &skia::Canvas, canvas: &skia::Canvas,
path: &skia::Path, path: &skia::Path,
paint: &skia::Paint, paint: &skia::Paint,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
canvas.save(); let mut inner_paint = skia::Paint::default();
inner_paint.set_anti_alias(antialias);
if let Some(filter) = blur {
inner_paint.set_image_filter(filter.clone());
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&inner_paint);
canvas.save_layer(&layer_rec);
canvas.clip_path(path, skia::ClipOp::Intersect, antialias); canvas.clip_path(path, skia::ClipOp::Intersect, antialias);
canvas.draw_path(path, paint); canvas.draw_path(path, paint);
canvas.restore(); canvas.restore();
@@ -117,6 +134,7 @@ pub fn draw_stroke_on_path(
svg_attrs: &HashMap<String, String>, svg_attrs: &HashMap<String, String>,
scale: f32, scale: f32,
shadow: Option<&ImageFilter>, shadow: Option<&ImageFilter>,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
let mut skia_path = path.to_skia_path(); let mut skia_path = path.to_skia_path();
@@ -127,19 +145,18 @@ pub fn draw_stroke_on_path(
let mut paint: skia_safe::Handle<_> = let mut paint: skia_safe::Handle<_> =
stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias); stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias);
if let Some(filter) = shadow { let filter = compose_filters(blur, shadow);
paint.set_image_filter(filter.clone()); paint.set_image_filter(filter);
}
match stroke.render_kind(is_open) { match stroke.render_kind(is_open) {
StrokeKind::Inner => { StrokeKind::Inner => {
draw_inner_stroke_path(canvas, &skia_path, &paint, antialias); draw_inner_stroke_path(canvas, &skia_path, &paint, blur, antialias);
} }
StrokeKind::Center => { StrokeKind::Center => {
canvas.draw_path(&skia_path, &paint); canvas.draw_path(&skia_path, &paint);
} }
StrokeKind::Outer => { StrokeKind::Outer => {
draw_outer_stroke_path(canvas, &skia_path, &paint, antialias); draw_outer_stroke_path(canvas, &skia_path, &paint, blur, antialias);
} }
} }
@@ -151,6 +168,7 @@ pub fn draw_stroke_on_path(
is_open, is_open,
svg_attrs, svg_attrs,
scale, scale,
blur,
antialias, antialias,
); );
} }
@@ -164,7 +182,6 @@ fn handle_stroke_cap(
p2: &Point, p2: &Point,
) { ) {
paint.set_style(skia::PaintStyle::Fill); paint.set_style(skia::PaintStyle::Fill);
paint.set_blend_mode(skia::BlendMode::Src);
match cap { match cap {
StrokeCap::None => {} StrokeCap::None => {}
StrokeCap::Line => { StrokeCap::Line => {
@@ -204,6 +221,7 @@ fn handle_stroke_caps(
is_open: bool, is_open: bool,
svg_attrs: &HashMap<String, String>, svg_attrs: &HashMap<String, String>,
scale: f32, scale: f32,
blur: Option<&ImageFilter>,
antialias: bool, antialias: bool,
) { ) {
let mut points = vec![Point::default(); path.count_points()]; let mut points = vec![Point::default(); path.count_points()];
@@ -220,6 +238,10 @@ fn handle_stroke_caps(
let mut paint_stroke = let mut paint_stroke =
stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias); stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias);
if let Some(filter) = blur {
paint_stroke.set_image_filter(filter.clone());
}
handle_stroke_cap( handle_stroke_cap(
canvas, canvas,
stroke.cap_start, stroke.cap_start,
@@ -228,6 +250,7 @@ fn handle_stroke_caps(
first_point, first_point,
&points[1], &points[1],
); );
handle_stroke_cap( handle_stroke_cap(
canvas, canvas,
stroke.cap_end, stroke.cap_end,
@@ -370,6 +393,10 @@ fn draw_image_stroke_in_container(
let mut pb = skia::Paint::default(); let mut pb = skia::Paint::default();
pb.set_blend_mode(skia::BlendMode::SrcOver); pb.set_blend_mode(skia::BlendMode::SrcOver);
pb.set_anti_alias(antialias); pb.set_anti_alias(antialias);
if let Some(filter) = shape.image_filter(1.) {
pb.set_image_filter(filter);
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb);
canvas.save_layer(&layer_rec); canvas.save_layer(&layer_rec);
@@ -388,6 +415,7 @@ fn draw_image_stroke_in_container(
svg_attrs, svg_attrs,
scale, scale,
None, None,
None,
antialias, antialias,
); );
} }
@@ -399,6 +427,7 @@ fn draw_image_stroke_in_container(
svg_attrs, svg_attrs,
scale, scale,
None, None,
None,
antialias, antialias,
), ),
@@ -435,6 +464,7 @@ fn draw_image_stroke_in_container(
is_open, is_open,
svg_attrs, svg_attrs,
scale, scale,
shape.image_filter(1.).as_ref(),
antialias, antialias,
); );
canvas.restore(); canvas.restore();
@@ -450,6 +480,9 @@ fn draw_image_stroke_in_container(
let mut image_paint = skia::Paint::default(); let mut image_paint = skia::Paint::default();
image_paint.set_blend_mode(skia::BlendMode::SrcIn); image_paint.set_blend_mode(skia::BlendMode::SrcIn);
image_paint.set_anti_alias(antialias); image_paint.set_anti_alias(antialias);
if let Some(filter) = shape.image_filter(1.) {
image_paint.set_image_filter(filter);
}
let src_rect = get_source_rect(size, container, image_fill); let src_rect = get_source_rect(size, container, image_fill);
let dest_rect = get_dest_rect(container, stroke.delta()); let dest_rect = get_dest_rect(container, stroke.delta());
@@ -517,11 +550,20 @@ pub fn render(
svg_attrs, svg_attrs,
scale, scale,
shadow, shadow,
shape.image_filter(1.).as_ref(),
antialias, antialias,
); );
} }
Type::Circle => draw_stroke_on_circle( Type::Circle => draw_stroke_on_circle(
canvas, stroke, &selrect, &selrect, svg_attrs, scale, shadow, antialias, canvas,
stroke,
&selrect,
&selrect,
svg_attrs,
scale,
shadow,
shape.image_filter(1.).as_ref(),
antialias,
), ),
Type::Text(_) => { Type::Text(_) => {
text::render( text::render(
@@ -543,6 +585,7 @@ pub fn render(
svg_attrs, svg_attrs,
scale, scale,
shadow, shadow,
shape.image_filter(1.).as_ref(),
antialias, antialias,
); );
} }
@@ -579,7 +622,7 @@ pub fn render_text_paths(
match stroke.render_kind(false) { match stroke.render_kind(false) {
StrokeKind::Inner => { StrokeKind::Inner => {
for (path, _) in paths { for (path, _) in paths {
draw_inner_stroke_path(canvas, path, &paint, antialias); draw_inner_stroke_path(canvas, path, &paint, None, antialias);
} }
} }
StrokeKind::Center => { StrokeKind::Center => {
@@ -589,7 +632,7 @@ pub fn render_text_paths(
} }
StrokeKind::Outer => { StrokeKind::Outer => {
for (path, _) in paths { for (path, _) in paths {
draw_outer_stroke_path(canvas, path, &paint, antialias); draw_outer_stroke_path(canvas, path, &paint, None, antialias);
} }
} }
} }

View File

@@ -923,6 +923,21 @@ impl Shape {
} }
} }
pub fn mask_filter(&self, scale: f32) -> Option<skia::MaskFilter> {
if !self.blur.hidden {
match self.blur.blur_type {
BlurType::None => None,
BlurType::Layer => skia::MaskFilter::blur(
skia::BlurStyle::Normal,
self.blur.value * scale,
Some(true),
),
}
} else {
None
}
}
pub fn is_recursive(&self) -> bool { pub fn is_recursive(&self) -> bool {
matches!( matches!(
self.shape_type, self.shape_type,

View File

@@ -198,7 +198,7 @@ fn propagate_transform(
if let Type::Text(content) = &shape.shape_type { if let Type::Text(content) = &shape.shape_type {
if content.grow_type() == GrowType::AutoHeight { if content.grow_type() == GrowType::AutoHeight {
let mut paragraphs = content.get_skia_paragraphs(); let mut paragraphs = content.get_skia_paragraphs(None, None);
set_paragraphs_width(shape_bounds_after.width(), &mut paragraphs); set_paragraphs_width(shape_bounds_after.width(), &mut paragraphs);
let height = auto_height(&mut paragraphs, shape_bounds_after.width()); let height = auto_height(&mut paragraphs, shape_bounds_after.width());
let resize_transform = math::resize_matrix( let resize_transform = math::resize_matrix(

View File

@@ -1,6 +1,7 @@
use skia_safe::{self as skia, image_filters, ImageFilter, Paint}; use skia_safe::{self as skia, image_filters, ImageFilter, Paint};
use super::Color; use super::Color;
use crate::render::filters::compose_filters;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum ShadowStyle { pub enum ShadowStyle {
@@ -62,13 +63,16 @@ impl Shadow {
self.hidden self.hidden
} }
pub fn get_drop_shadow_paint(&self, antialias: bool) -> Paint { pub fn get_drop_shadow_paint(
&self,
antialias: bool,
blur_filter: Option<&ImageFilter>,
) -> Paint {
let mut paint = Paint::default(); let mut paint = Paint::default();
let image_filter = self.get_drop_shadow_filter(); let shadow_filter = self.get_drop_shadow_filter();
let filter = compose_filters(blur_filter, shadow_filter.as_ref());
paint.set_image_filter(image_filter); paint.set_image_filter(filter);
paint.set_anti_alias(antialias); paint.set_anti_alias(antialias);
paint paint
} }
@@ -89,14 +93,16 @@ impl Shadow {
filter filter
} }
pub fn get_inner_shadow_paint(&self, antialias: bool) -> Paint { pub fn get_inner_shadow_paint(
&self,
antialias: bool,
blur_filter: Option<&ImageFilter>,
) -> Paint {
let mut paint = Paint::default(); let mut paint = Paint::default();
let shadow_filter = self.get_inner_shadow_filter();
let image_filter = self.get_inner_shadow_filter(); let filter = compose_filters(blur_filter, shadow_filter.as_ref());
paint.set_image_filter(filter);
paint.set_image_filter(image_filter);
paint.set_anti_alias(antialias); paint.set_anti_alias(antialias);
paint paint
} }

View File

@@ -1,11 +1,12 @@
use crate::{ use crate::{
math::{Matrix, Rect}, math::{Matrix, Rect},
render::{default_font, DEFAULT_EMOJI_FONT}, render::{default_font, filters::compose_filters, DEFAULT_EMOJI_FONT},
}; };
use skia_safe::{ use skia_safe::{
self as skia, self as skia,
paint::Paint, paint::Paint,
textlayout::{ParagraphBuilder, ParagraphStyle}, textlayout::{ParagraphBuilder, ParagraphStyle},
ImageFilter, MaskFilter,
}; };
use std::collections::HashSet; use std::collections::HashSet;
@@ -92,7 +93,11 @@ impl TextContent {
self.paragraphs.push(paragraph); self.paragraphs.push(paragraph);
} }
pub fn to_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> { pub fn to_paragraphs(
&self,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> Vec<Vec<ParagraphBuilder>> {
let fonts = get_font_collection(); let fonts = get_font_collection();
let fallback_fonts = get_fallback_fonts(); let fallback_fonts = get_fallback_fonts();
let mut paragraph_group = Vec::new(); let mut paragraph_group = Vec::new();
@@ -101,7 +106,8 @@ impl TextContent {
let paragraph_style = paragraph.paragraph_to_style(); let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts); let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &paragraph.children { for leaf in &paragraph.children {
let text_style = leaf.to_style(paragraph, &self.bounds, fallback_fonts); let text_style =
leaf.to_style(paragraph, &self.bounds, fallback_fonts, blur, blur_mask);
let text = leaf.apply_text_transform(); let text = leaf.apply_text_transform();
builder.push_style(&text_style); builder.push_style(&text_style);
builder.add_text(&text); builder.add_text(&text);
@@ -116,6 +122,8 @@ impl TextContent {
&self, &self,
stroke: &Stroke, stroke: &Stroke,
bounds: &Rect, bounds: &Rect,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> Vec<Vec<ParagraphBuilder>> { ) -> Vec<Vec<ParagraphBuilder>> {
let fallback_fonts = get_fallback_fonts(); let fallback_fonts = get_fallback_fonts();
let fonts = get_font_collection(); let fonts = get_font_collection();
@@ -126,8 +134,12 @@ impl TextContent {
std::collections::HashMap::new(); std::collections::HashMap::new();
for leaf in paragraph.children.iter() { for leaf in paragraph.children.iter() {
let text_paint = merge_fills(&leaf.fills, *bounds); let mut text_paint = merge_fills(&leaf.fills, *bounds);
let stroke_paints = get_text_stroke_paints(stroke, bounds, &text_paint); if let Some(blur_mask) = blur_mask {
text_paint.set_mask_filter(blur_mask.clone());
}
let stroke_paints =
get_text_stroke_paints(stroke, bounds, &text_paint, blur, blur_mask);
let text: String = leaf.apply_text_transform(); let text: String = leaf.apply_text_transform();
for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() { for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() {
@@ -135,9 +147,14 @@ impl TextContent {
let paragraph_style = paragraph.paragraph_to_style(); let paragraph_style = paragraph.paragraph_to_style();
ParagraphBuilder::new(&paragraph_style, fonts) ParagraphBuilder::new(&paragraph_style, fonts)
}); });
let stroke_paint = stroke_paint.clone();
let stroke_style = let stroke_style = leaf.to_stroke_style(
leaf.to_stroke_style(paragraph, stroke_paint, fallback_fonts); paragraph,
&stroke_paint,
fallback_fonts,
blur,
blur_mask,
);
builder.push_style(&stroke_style); builder.push_style(&stroke_style);
builder.add_text(&text); builder.add_text(&text);
} }
@@ -167,16 +184,22 @@ impl TextContent {
paragraphs paragraphs
} }
pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> { pub fn get_skia_paragraphs(
self.collect_paragraphs(self.to_paragraphs()) &self,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> Vec<Vec<ParagraphBuilder>> {
self.collect_paragraphs(self.to_paragraphs(blur, blur_mask))
} }
pub fn get_skia_stroke_paragraphs( pub fn get_skia_stroke_paragraphs(
&self, &self,
stroke: &Stroke, stroke: &Stroke,
bounds: &Rect, bounds: &Rect,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> Vec<Vec<ParagraphBuilder>> { ) -> Vec<Vec<ParagraphBuilder>> {
self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds)) self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds, blur, blur_mask))
} }
pub fn grow_type(&self) -> GrowType { pub fn grow_type(&self) -> GrowType {
@@ -188,7 +211,7 @@ impl TextContent {
} }
pub fn visual_bounds(&self) -> (f32, f32) { pub fn visual_bounds(&self) -> (f32, f32) {
let mut paragraphs = self.to_paragraphs(); let mut paragraphs = self.to_paragraphs(None, None);
let height = auto_height(&mut paragraphs, self.width()); let height = auto_height(&mut paragraphs, self.width());
(self.width(), height) (self.width(), height)
} }
@@ -378,10 +401,16 @@ impl TextLeaf {
paragraph: &Paragraph, paragraph: &Paragraph,
content_bounds: &Rect, content_bounds: &Rect,
fallback_fonts: &HashSet<String>, fallback_fonts: &HashSet<String>,
_blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> skia::textlayout::TextStyle { ) -> skia::textlayout::TextStyle {
let mut style = skia::textlayout::TextStyle::default(); let mut style = skia::textlayout::TextStyle::default();
let mut paint = merge_fills(&self.fills, *content_bounds);
if let Some(blur_mask) = blur_mask {
paint.set_mask_filter(blur_mask.clone());
}
let paint = merge_fills(&self.fills, *content_bounds);
style.set_foreground_paint(&paint); style.set_foreground_paint(&paint);
style.set_font_size(self.font_size); style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing); style.set_letter_spacing(paragraph.letter_spacing);
@@ -417,8 +446,10 @@ impl TextLeaf {
paragraph: &Paragraph, paragraph: &Paragraph,
stroke_paint: &Paint, stroke_paint: &Paint,
fallback_fonts: &HashSet<String>, fallback_fonts: &HashSet<String>,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> skia::textlayout::TextStyle { ) -> skia::textlayout::TextStyle {
let mut style = self.to_style(paragraph, &Rect::default(), fallback_fonts); let mut style = self.to_style(paragraph, &Rect::default(), fallback_fonts, blur, blur_mask);
style.set_foreground_paint(stroke_paint); style.set_foreground_paint(stroke_paint);
style.set_font_size(self.font_size); style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing); style.set_letter_spacing(paragraph.letter_spacing);
@@ -714,7 +745,13 @@ pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32
}) })
} }
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) -> Vec<Paint> { fn get_text_stroke_paints(
stroke: &Stroke,
bounds: &Rect,
text_paint: &Paint,
blur: Option<&ImageFilter>,
blur_mask: Option<&MaskFilter>,
) -> Vec<Paint> {
let mut paints = Vec::new(); let mut paints = Vec::new();
match stroke.kind { match stroke.kind {
@@ -730,6 +767,9 @@ fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) ->
let mut paint = text_paint.clone(); let mut paint = text_paint.clone();
paint.set_style(skia::PaintStyle::Fill); paint.set_style(skia::PaintStyle::Fill);
paint.set_anti_alias(true); paint.set_anti_alias(true);
if let Some(blur) = blur {
paint.set_image_filter(blur.clone());
}
paints.push(paint); paints.push(paint);
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
@@ -738,6 +778,9 @@ fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) ->
paint.set_anti_alias(true); paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0); paint.set_stroke_width(stroke.width * 2.0);
set_paint_fill(&mut paint, &stroke.fill, bounds); set_paint_fill(&mut paint, &stroke.fill, bounds);
if let Some(blur) = blur {
paint.set_image_filter(blur.clone());
}
paints.push(paint); paints.push(paint);
} else { } else {
let mut paint = text_paint.clone(); let mut paint = text_paint.clone();
@@ -749,7 +792,9 @@ fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) ->
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
let image_filter = let image_filter =
skia_safe::image_filters::erode((stroke.width, stroke.width), None, None); skia_safe::image_filters::erode((stroke.width, stroke.width), None, None);
paint.set_image_filter(image_filter);
let filter = compose_filters(blur, image_filter.as_ref());
paint.set_image_filter(filter);
paint.set_anti_alias(false); paint.set_anti_alias(false);
paint.set_blend_mode(skia::BlendMode::DstOut); paint.set_blend_mode(skia::BlendMode::DstOut);
paints.push(paint); paints.push(paint);
@@ -762,6 +807,9 @@ fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) ->
paint.set_stroke_width(stroke.width); paint.set_stroke_width(stroke.width);
set_paint_fill(&mut paint, &stroke.fill, bounds); set_paint_fill(&mut paint, &stroke.fill, bounds);
if let Some(blur) = blur {
paint.set_image_filter(blur.clone());
}
paints.push(paint); paints.push(paint);
} }
@@ -771,14 +819,20 @@ fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) ->
paint.set_blend_mode(skia::BlendMode::DstOver); paint.set_blend_mode(skia::BlendMode::DstOver);
paint.set_anti_alias(true); paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0); paint.set_stroke_width(stroke.width * 2.0);
set_paint_fill(&mut paint, &stroke.fill, bounds); set_paint_fill(&mut paint, &stroke.fill, bounds);
if let Some(blur_mask) = blur_mask {
paint.set_mask_filter(blur_mask.clone());
}
paints.push(paint); paints.push(paint);
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Fill);
paint.set_blend_mode(skia::BlendMode::Clear); paint.set_blend_mode(skia::BlendMode::Clear);
paint.set_color(skia::Color::TRANSPARENT);
paint.set_anti_alias(true); paint.set_anti_alias(true);
if let Some(blur_mask) = blur_mask {
paint.set_mask_filter(blur_mask.clone());
}
paints.push(paint); paints.push(paint);
} }
} }

View File

@@ -18,7 +18,7 @@ impl TextPaths {
} }
pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> { pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> {
let paragraphs = self.to_paragraphs(); let paragraphs = self.to_paragraphs(None, None);
self.collect_paragraphs(paragraphs) self.collect_paragraphs(paragraphs)
} }

View File

@@ -44,7 +44,10 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 {
height = shape.selrect.height(); height = shape.selrect.height();
if let Type::Text(content) = &shape.shape_type { if let Type::Text(content) = &shape.shape_type {
let mut paragraphs = content.get_skia_paragraphs(); let mut paragraphs = content.get_skia_paragraphs(
shape.image_filter(1.).as_ref(),
shape.mask_filter(1.).as_ref(),
);
m_width = max_width(&mut paragraphs, width); m_width = max_width(&mut paragraphs, width);
match content.grow_type() { match content.grow_type() {