penpot/render-wasm/src/render/drop_shadow.rs
Aitor Moreno 2c42d4fb06 WIP
2026-07-03 10:42:37 +02:00

358 lines
12 KiB
Rust

use std::borrow::Cow;
use skia_safe as skia;
use crate::error::Result;
use crate::shapes::{radius_to_sigma, Blur, Fill, Shadow, Shape, SolidColor, Type};
use crate::state::ShapesPoolRef;
use super::{
filters, get_simplified_children, layer_blur, ClipStack, NodeRenderState, RenderState,
SurfaceId,
};
#[allow(clippy::too_many_arguments)]
pub fn render_drop_black_shadow(
render_state: &mut RenderState,
shape: &Shape,
shape_bounds: &skia::Rect,
shadow: &Shadow,
clip_bounds: Option<ClipStack>,
scale: f32,
extra_layer_blur: Option<Blur>,
target_surface: SurfaceId,
) -> Result<()> {
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
transformed_shadow.to_mut().offset = (0.0, 0.0);
transformed_shadow.to_mut().color = skia::Color::BLACK;
let mut plain_shape = Cow::Borrowed(shape);
let combined_blur = layer_blur::combine_blur_values(
layer_blur::combined_layer_blur(
&render_state.nested_blurs,
&mut render_state.cached_layer_blur,
shape.blur,
),
extra_layer_blur,
);
let blur_filter = combined_blur.and_then(|blur| {
let sigma = blur.sigma();
skia::image_filters::blur((sigma, sigma), None, None, None)
});
let use_low_zoom_path = scale <= 1.0 && combined_blur.is_none();
let mut transform_matrix = shape.transform;
let center = shape.center();
transform_matrix.post_translate(center);
transform_matrix.pre_translate(-center);
let mapped = transform_matrix.map_vector((shadow.offset.0, shadow.offset.1));
let world_offset = (mapped.x, mapped.y);
let plain_shape_mut = plain_shape.to_mut();
plain_shape_mut.clear_fills();
if shape.has_fills() {
plain_shape_mut.add_fill(Fill::Solid(SolidColor(skia::Color::BLACK)));
}
for stroke in plain_shape_mut.strokes.iter_mut() {
stroke.fill = Fill::Solid(SolidColor(skia::Color::BLACK));
}
plain_shape_mut.clear_shadows();
plain_shape_mut.blur = None;
plain_shape_mut.clip_content = false;
let Some(drop_filter) = transformed_shadow.get_drop_shadow_filter() else {
return Ok(());
};
let mut bounds = drop_filter.compute_fast_bounds(shape_bounds);
bounds.offset(world_offset);
if !bounds.intersects(render_state.render_area_with_margins)
&& target_surface != SurfaceId::Export
{
return Ok(());
}
if scale > 1.0 && shadow.blur <= 0.0 {
let drop_canvas = render_state.surfaces.canvas(SurfaceId::DropShadows);
drop_canvas.save();
render_state.with_nested_blurs_suppressed(|state| {
state.render_shape(
&plain_shape,
clip_bounds,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
false,
Some(shadow.offset),
None,
Some(shadow.spread),
target_surface,
)
})?;
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.restore();
return Ok(());
}
let blur_only_filter = if transformed_shadow.blur > 0.0 {
let sigma = radius_to_sigma(transformed_shadow.blur);
Some(skia::image_filters::blur((sigma, sigma), None, None, None))
} else {
None
};
let mut shadow_paint = skia::Paint::default();
if let Some(blur_filter) = blur_only_filter {
shadow_paint.set_image_filter(blur_filter);
}
shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&shadow_paint);
if use_low_zoom_path {
let drop_canvas = render_state.surfaces.canvas(SurfaceId::DropShadows);
drop_canvas.save_layer(&layer_rec);
render_state.with_nested_blurs_suppressed(|state| {
state.render_shape(
&plain_shape,
clip_bounds,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
false,
Some(shadow.offset),
None,
Some(shadow.spread),
target_surface,
)
})?;
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.restore();
return Ok(());
}
let blur_downscale_threshold: f32 = render_state.options.blur_downscale_threshold;
let min_blur_downscale: f32 = 1.0 / blur_downscale_threshold;
let blur_downscale = if shadow.blur > blur_downscale_threshold {
(blur_downscale_threshold / shadow.blur).max(min_blur_downscale)
} else {
1.0
};
let filter_result = filters::render_into_filter_surface(
render_state,
bounds,
blur_downscale,
|state, temp_surface| {
let canvas = state.surfaces.canvas(temp_surface);
canvas.save_layer(&layer_rec);
state.with_nested_blurs_suppressed(|state| {
state.render_shape(
&plain_shape,
clip_bounds,
temp_surface,
temp_surface,
temp_surface,
temp_surface,
false,
Some(shadow.offset),
None,
Some(shadow.spread),
target_surface,
)
})?;
state.surfaces.canvas(temp_surface).restore();
Ok(())
},
)?;
if let Some((mut surface, filter_scale)) = filter_result {
let drop_canvas = render_state.surfaces.canvas(SurfaceId::DropShadows);
drop_canvas.save();
let mut drop_paint = skia::Paint::default();
drop_paint.set_image_filter(blur_filter.clone());
if filter_scale < 1.0 {
drop_canvas.save();
drop_canvas.scale((1.0 / filter_scale, 1.0 / filter_scale));
drop_canvas.translate((bounds.left * filter_scale, bounds.top * filter_scale));
surface.draw(
drop_canvas,
(0.0, 0.0),
render_state.sampling_options,
Some(&drop_paint),
);
drop_canvas.restore();
} else {
drop_canvas.save();
drop_canvas.translate((bounds.left, bounds.top));
surface.draw(
drop_canvas,
(0.0, 0.0),
render_state.sampling_options,
Some(&drop_paint),
);
drop_canvas.restore();
}
drop_canvas.restore();
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn render_element_drop_shadows_and_composite(
render_state: &mut RenderState,
element: &Shape,
tree: ShapesPoolRef,
extrect: &mut Option<skia::Rect>,
clip_bounds: Option<ClipStack>,
scale: f32,
node_render_state: &NodeRenderState,
target_surface: SurfaceId,
) -> Result<()> {
let element_extrect = extrect.get_or_insert_with(|| element.extrect(tree, scale));
let inherited_layer_blur = match element.shape_type {
Type::Frame(_) | Type::Group(_) => element.blur,
_ => None,
};
for shadow in element.drop_shadows_visible() {
let paint = skia::Paint::default();
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.save_layer(&layer_rec);
render_drop_black_shadow(
render_state,
element,
element_extrect,
shadow,
clip_bounds.clone(),
scale,
None,
target_surface,
)?;
if !matches!(element.shape_type, Type::Bool(_)) {
let mut shadow_children = Vec::new();
if element.is_recursive() {
get_simplified_children(tree, element, &mut shadow_children);
}
for shadow_shape_id in shadow_children.iter() {
let Some(shadow_shape) = tree.get(shadow_shape_id) else {
continue;
};
if shadow_shape.hidden {
continue;
}
let nested_clip_bounds =
node_render_state.get_nested_shadow_clip_bounds(element, shadow);
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
render_drop_black_shadow(
render_state,
shadow_shape,
&shadow_shape.extrect(tree, scale),
shadow,
nested_clip_bounds,
scale,
inherited_layer_blur,
target_surface,
)?;
} else {
let paint = skia::Paint::default();
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.save_layer(&layer_rec);
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
transformed_shadow.to_mut().color = skia::Color::BLACK;
transformed_shadow.to_mut().blur = transformed_shadow.blur;
transformed_shadow.to_mut().spread = transformed_shadow.spread;
let mut new_shadow_paint = skia::Paint::default();
new_shadow_paint.set_image_filter(transformed_shadow.get_drop_shadow_filter());
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
render_state.with_nested_blurs_suppressed(|state| {
state.render_shape(
shadow_shape,
nested_clip_bounds,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
SurfaceId::DropShadows,
true,
None,
Some(vec![new_shadow_paint.clone()]),
None,
target_surface,
)
})?;
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.restore();
}
}
}
let mut paint = skia::Paint::default();
paint.set_color(shadow.color);
paint.set_blend_mode(skia::BlendMode::SrcIn);
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.draw_paint(&paint);
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.restore();
}
if let Some(clips) = clip_bounds.as_ref() {
let antialias =
element.should_use_antialias(scale, render_state.options.antialias_threshold);
render_state.surfaces.canvas(target_surface).save();
render_state.clip_target_surface_to_stack(clips, target_surface, scale, antialias);
render_state
.surfaces
.draw_into(SurfaceId::DropShadows, target_surface, None);
render_state.surfaces.canvas(target_surface).restore();
} else {
render_state
.surfaces
.draw_into(SurfaceId::DropShadows, target_surface, None);
}
render_state
.surfaces
.canvas(SurfaceId::DropShadows)
.clear(skia::Color::TRANSPARENT);
Ok(())
}