use tiny_skia::{Pixmap, Transform};
use crate::backends::blur::apply_gaussian_blur;
use crate::utils::RenderError;
#[cfg(not(feature = "nostd"))]
use super::dirty::note_dirty_bbox;
impl super::SoftwareBackend {
pub(super) fn draw_raster_layer(
&mut self,
data: &crate::pipeline::RasterData,
) -> Result<(), RenderError> {
if data.pixels.len() != (data.width * data.height * 4) as usize {
return Err(RenderError::InvalidBufferSize {
expected: (data.width * data.height * 4) as usize,
actual: data.pixels.len(),
});
}
let src_pixmap = Pixmap::from_vec(
data.pixels.clone(),
tiny_skia::IntSize::from_wh(data.width, data.height)
.ok_or(RenderError::InvalidDimensions)?,
)
.ok_or(RenderError::InvalidPixmap)?;
let transform = Transform::from_translate(data.x as f32, data.y as f32);
let paint = tiny_skia::PixmapPaint {
blend_mode: tiny_skia::BlendMode::SourceOver,
..Default::default()
};
self.pixmap
.draw_pixmap(0, 0, src_pixmap.as_ref(), &paint, transform, None);
Ok(())
}
pub(super) fn draw_vector_layer(
&mut self,
data: &crate::pipeline::VectorData,
) -> Result<(), RenderError> {
let Some(path) = &data.path else {
return Ok(());
};
let b = path.bounds();
let margin = 2.0 + data.stroke.as_ref().map_or(0.0, |s| s.width) + data.blur * 3.0;
note_dirty_bbox((
(b.left() - margin).floor() as i32,
(b.top() - margin).floor() as i32,
(b.right() + margin).ceil() as i32,
(b.bottom() + margin).ceil() as i32,
));
let clip_mask = self.vector_clip_mask(data.clip);
let mut paint = tiny_skia::Paint::default();
paint.set_color_rgba8(data.color[0], data.color[1], data.color[2], data.color[3]);
paint.anti_alias = true;
paint.blend_mode = tiny_skia::BlendMode::SourceOver;
if data.blur <= 0.0 {
self.pixmap.fill_path(
path,
&paint,
tiny_skia::FillRule::Winding,
Transform::identity(),
clip_mask.as_ref(),
);
if let Some(stroke) = &data.stroke {
paint.set_color_rgba8(
stroke.color[0],
stroke.color[1],
stroke.color[2],
stroke.color[3],
);
let sk_stroke = tiny_skia::Stroke {
width: stroke.width,
..Default::default()
};
self.pixmap.stroke_path(
path,
&paint,
&sk_stroke,
Transform::identity(),
clip_mask.as_ref(),
);
}
return Ok(());
}
let pad = (data.blur * 3.0).ceil().max(1.0);
let tw = ((b.width() + pad * 2.0).ceil() as u32).max(1);
let th = ((b.height() + pad * 2.0).ceil() as u32).max(1);
let Some(mut temp) = Pixmap::new(tw, th) else {
return Ok(());
};
let off = Transform::from_translate(pad - b.left(), pad - b.top());
temp.fill_path(path, &paint, tiny_skia::FillRule::Winding, off, None);
if let Some(stroke) = &data.stroke {
let mut sp = tiny_skia::Paint::default();
sp.set_color_rgba8(
stroke.color[0],
stroke.color[1],
stroke.color[2],
stroke.color[3],
);
sp.anti_alias = true;
let sk_stroke = tiny_skia::Stroke {
width: stroke.width,
..Default::default()
};
temp.stroke_path(path, &sp, &sk_stroke, off, None);
}
apply_gaussian_blur(&mut temp, data.blur * (2.0 / 256.0_f32.ln().sqrt()));
let blend = Transform::from_translate(b.left() - pad, b.top() - pad);
self.pixmap.draw_pixmap(
0,
0,
temp.as_ref(),
&tiny_skia::PixmapPaint {
blend_mode: tiny_skia::BlendMode::SourceOver,
..Default::default()
},
blend,
clip_mask.as_ref(),
);
Ok(())
}
fn vector_clip_mask(
&self,
clip: Option<(f32, f32, f32, f32, bool)>,
) -> Option<tiny_skia::Mask> {
let (x1, y1, x2, y2, inverse) = clip?;
let width = self.pixmap.width();
let height = self.pixmap.height();
let mut mask = tiny_skia::Mask::new(width, height)?;
let mut builder = tiny_skia::PathBuilder::new();
builder.move_to(x1, y1);
builder.line_to(x2, y1);
builder.line_to(x2, y2);
builder.line_to(x1, y2);
builder.close();
let fill_rule = if inverse {
builder.move_to(0.0, 0.0);
builder.line_to(width as f32, 0.0);
builder.line_to(width as f32, height as f32);
builder.line_to(0.0, height as f32);
builder.close();
tiny_skia::FillRule::EvenOdd
} else {
tiny_skia::FillRule::Winding
};
let clip_path = builder.finish()?;
mask.fill_path(&clip_path, fill_rule, false, Transform::identity());
Some(mask)
}
}