use taffy::{Layout, Point, Size};
use tiny_skia::{IntSize, Pixmap};
use crate::{
Error, Result,
layout::style::{Affine, BlendMode, BoxShadow, Color, ImageScalingAlgorithm, Sides, TextShadow},
rendering::{
BlurFormat, BlurType, BorderProperties, BufferPool, Canvas, Command, Fill, Placement,
SamplingOptions, Sizing, Style, apply_blur, attenuate_alpha_by_mask, fast_div_255, render_mask,
},
};
#[derive(Clone, Copy)]
pub(crate) struct SizedShadow {
pub offset_x: f32,
pub offset_y: f32,
pub blur_radius: f32,
pub spread_radius: f32,
pub color: Color,
}
impl SizedShadow {
pub fn from_box_shadow(
shadow: BoxShadow,
sizing: &Sizing,
current_color: Color,
size: Size<f32>,
) -> Self {
Self {
offset_x: shadow.offset_x.to_px(sizing, size.width),
offset_y: shadow.offset_y.to_px(sizing, size.height),
blur_radius: shadow.blur_radius.to_px(sizing, size.width),
spread_radius: shadow.spread_radius.to_px(sizing, size.width),
color: shadow.color.resolve(current_color),
}
}
pub fn from_text_shadow(
shadow: TextShadow,
sizing: &Sizing,
current_color: Color,
size: Size<f32>,
) -> Self {
Self {
offset_x: shadow.offset_x.to_px(sizing, size.width),
offset_y: shadow.offset_y.to_px(sizing, size.height),
blur_radius: shadow.blur_radius.to_px(sizing, size.width),
spread_radius: 0.0,
color: shadow.color.resolve(current_color),
}
}
pub fn draw_outset(
&self,
canvas: &mut Canvas,
paths: &[Command],
transform: Affine,
style: Style,
cutout_paths: Option<&[Command]>,
) -> Result<()> {
let (mask, mut placement) =
render_mask(paths, Some(transform), Some(style), &mut canvas.buffer_pool);
placement.left += self.offset_x as i32;
placement.top += self.offset_y as i32;
if self.blur_radius <= 0.0 && cutout_paths.is_none() {
canvas.draw_mask(&mask, placement, self.color, BlendMode::Normal);
canvas.buffer_pool.release(mask);
return Ok(());
}
let blur_padding = if self.blur_radius > 0.0 {
self.blur_radius * BlurType::Shadow.extent_multiplier()
} else {
0.0
};
let shadow_width = placement.width + (blur_padding * 2.0) as u32;
let shadow_height = placement.height + (blur_padding * 2.0) as u32;
let mut shadow_alpha = canvas
.buffer_pool
.acquire((shadow_width * shadow_height) as usize);
let padding = blur_padding as u32;
for y in 0..placement.height {
let src_row = y as usize * placement.width as usize;
let dst_row = (y + padding) as usize * shadow_width as usize + padding as usize;
shadow_alpha[dst_row..dst_row + placement.width as usize]
.copy_from_slice(&mask[src_row..src_row + placement.width as usize]);
}
canvas.buffer_pool.release(mask);
apply_blur(
BlurFormat::Alpha {
data: &mut shadow_alpha,
width: shadow_width,
height: shadow_height,
},
self.blur_radius,
BlurType::Shadow,
&mut canvas.buffer_pool,
)?;
let img_origin_x = placement.left as f32 - blur_padding;
let img_origin_y = placement.top as f32 - blur_padding;
if let Some(cutout_paths) = cutout_paths {
let (erase_mask, erase_placement) = render_mask(
cutout_paths,
Some(transform),
Some(Fill::NonZero.into()),
&mut canvas.buffer_pool,
);
if !erase_mask.is_empty() {
let shadow_placement = Placement {
left: img_origin_x as i32,
top: img_origin_y as i32,
width: shadow_width,
height: shadow_height,
};
attenuate_alpha_by_mask(
&mut shadow_alpha,
shadow_placement,
&erase_mask,
erase_placement,
);
}
canvas.buffer_pool.release(erase_mask);
}
canvas.draw_mask(
&shadow_alpha,
Placement {
left: img_origin_x as i32,
top: img_origin_y as i32,
width: shadow_width,
height: shadow_height,
},
self.color,
BlendMode::Normal,
);
canvas.buffer_pool.release(shadow_alpha);
Ok(())
}
pub fn draw_inset(
&self,
transform: Affine,
border_radius: BorderProperties,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
let image = draw_inset_shadow(self, border_radius, layout.size, &mut canvas.buffer_pool)?;
canvas.overlay_sampled_pixmap(
&image,
Size {
width: image.width(),
height: image.height(),
},
border_radius,
transform,
SamplingOptions {
logical_to_source: Affine::IDENTITY,
algorithm: ImageScalingAlgorithm::Auto,
},
BlendMode::Normal,
);
Ok(())
}
}
pub(crate) fn draw_inset_shadow(
shadow: &SizedShadow,
mut border: BorderProperties,
border_box: Size<f32>,
buffer_pool: &mut BufferPool,
) -> Result<Pixmap> {
let width = border_box.width as u32;
let height = border_box.height as u32;
let [red, green, blue, alpha] = shadow.color.0;
let mut shadow_alpha = buffer_pool.acquire_dirty((width * height) as usize);
shadow_alpha.fill(alpha);
let offset = Point {
x: shadow.offset_x,
y: shadow.offset_y,
};
let mut paths = Vec::new();
border.expand_by(Sides([-shadow.spread_radius; 4]).into());
border.append_mask_commands(
&mut paths,
border_box
- Size {
width: shadow.spread_radius * 2.0,
height: shadow.spread_radius * 2.0,
},
offset
+ Point {
x: shadow.spread_radius,
y: shadow.spread_radius,
},
);
let (mask, placement) = render_mask(&paths, None, Some(Fill::NonZero.into()), buffer_pool);
if !mask.is_empty() {
let shadow_placement = Placement {
left: 0,
top: 0,
width,
height,
};
attenuate_alpha_by_mask(&mut shadow_alpha, shadow_placement, &mask, placement);
}
buffer_pool.release(mask);
apply_blur(
BlurFormat::Alpha {
data: &mut shadow_alpha,
width,
height,
},
shadow.blur_radius,
BlurType::Shadow,
buffer_pool,
)?;
let mut data = vec![0u8; (width * height * 4) as usize];
for (pixel, &alpha) in bytemuck::cast_slice_mut::<u8, [u8; 4]>(&mut data)
.iter_mut()
.zip(&shadow_alpha)
{
if alpha == u8::MAX {
*pixel = [red, green, blue, alpha];
continue;
}
if alpha == 0 {
*pixel = [0, 0, 0, 0];
continue;
}
let alpha_u32 = alpha as u32;
*pixel = [
fast_div_255(red as u32 * alpha_u32),
fast_div_255(green as u32 * alpha_u32),
fast_div_255(blue as u32 * alpha_u32),
alpha,
];
}
buffer_pool.release(shadow_alpha);
let Some(size) = IntSize::from_wh(width, height) else {
return Err(Error::InvalidViewport);
};
let Some(pixmap) = Pixmap::from_vec(data, size) else {
return Err(Error::InvalidViewport);
};
Ok(pixmap)
}