use image::RgbaImage;
use taffy::{Layout, Point, Size};
use zeno::{Command, Fill, PathData, Placement};
use crate::{
Result,
layout::style::{Affine, BlendMode, BoxShadow, Color, ImageScalingAlgorithm, Sides, TextShadow},
rendering::{
BlurFormat, BlurType, BorderProperties, BufferPool, Canvas, MaskMemory, Sizing, apply_blur,
draw_mask, overlay_image,
},
};
#[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),
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw_outset<D: PathData>(
&self,
canvas: &mut Canvas,
paths: D,
transform: Affine,
style: zeno::Style,
cutout_paths: Option<&[Command]>,
) -> Result<()> {
let (mask, mut placement) = canvas.mask_memory.render(
&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() {
draw_mask(
&mut canvas.image,
&mask,
placement,
self.color,
BlendMode::Normal,
&canvas.constrains,
);
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 mut image = canvas.buffer_pool.acquire_image(
placement.width + (blur_padding * 2.0) as u32,
placement.height + (blur_padding * 2.0) as u32,
)?;
draw_mask(
&mut image,
&mask,
Placement {
left: blur_padding as i32,
top: blur_padding as i32,
width: placement.width,
height: placement.height,
},
self.color,
BlendMode::Normal,
&[],
);
canvas.buffer_pool.release(mask);
apply_blur(
BlurFormat::Rgba(&mut image),
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) = canvas.mask_memory.render(
cutout_paths,
Some(transform),
Some(Fill::NonZero.into()),
&mut canvas.buffer_pool,
);
let img_w = image.width() as i32;
let img_h = image.height() as i32;
if !erase_mask.is_empty() {
for my in 0..erase_placement.height as i32 {
for mx in 0..erase_placement.width as i32 {
let canvas_x = erase_placement.left + mx;
let canvas_y = erase_placement.top + my;
let ix = canvas_x - img_origin_x as i32;
let iy = canvas_y - img_origin_y as i32;
if ix >= 0 && iy >= 0 && ix < img_w && iy < img_h {
let mask_alpha =
erase_mask[(my as u32 * erase_placement.width + mx as u32) as usize] as u32;
if mask_alpha > 0 {
let pixel = image.get_pixel_mut(ix as u32, iy as u32);
pixel.0[3] = ((pixel.0[3] as u32 * (255 - mask_alpha)) / 255) as u8;
}
}
}
}
canvas.buffer_pool.release(erase_mask);
}
}
overlay_image(
&mut canvas.image,
&image,
BorderProperties::zero(),
Affine::translation(img_origin_x, img_origin_y),
ImageScalingAlgorithm::Auto,
BlendMode::Normal,
&canvas.constrains,
&mut canvas.mask_memory,
&mut canvas.buffer_pool,
);
canvas.buffer_pool.release_image(image);
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.mask_memory,
&mut canvas.buffer_pool,
)?;
canvas.overlay_image(
&image,
border_radius,
transform,
ImageScalingAlgorithm::Auto,
BlendMode::Normal,
);
canvas.buffer_pool.release_image(image);
Ok(())
}
}
pub(crate) fn draw_inset_shadow(
shadow: &SizedShadow,
mut border: BorderProperties,
border_box: Size<f32>,
mask_memory: &mut MaskMemory,
buffer_pool: &mut BufferPool,
) -> Result<RgbaImage> {
let mut shadow_image =
buffer_pool.acquire_image(border_box.width as u32, border_box.height as u32)?;
let shadow_raw = shadow_image.as_mut();
let color_rgba: [u8; 4] = shadow.color.0;
for pixel in shadow_raw.chunks_exact_mut(4) {
pixel.copy_from_slice(&color_rgba);
}
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) = mask_memory.render(&paths, None, Some(Fill::NonZero.into()), buffer_pool);
if !mask.is_empty() {
let img_w = shadow_image.width() as i32;
let img_h = shadow_image.height() as i32;
for my in 0..placement.height as i32 {
for mx in 0..placement.width as i32 {
let ix = placement.left + mx;
let iy = placement.top + my;
if ix >= 0 && iy >= 0 && ix < img_w && iy < img_h {
let mask_alpha = mask[(my as u32 * placement.width + mx as u32) as usize] as u32;
if mask_alpha > 0 {
let pixel = shadow_image.get_pixel_mut(ix as u32, iy as u32);
pixel.0[3] = ((pixel.0[3] as u32 * (255 - mask_alpha)) / 255) as u8;
}
}
}
}
buffer_pool.release(mask);
}
apply_blur(
BlurFormat::Rgba(&mut shadow_image),
shadow.blur_radius,
BlurType::Shadow,
buffer_pool,
)?;
Ok(shadow_image)
}