use tiny_skia::{FillRule, Mask, Paint, PathBuilder, Pixmap, Rect, Stroke, Transform};
use zenith_scene::{Paint as ScenePaint, SceneCommand, StrokeAlign};
use super::super::commands::{DrawCtx, build_stroke_dash, map_line_cap};
use super::super::gradient::gradient_shader;
use super::super::paths::{
build_align_mask, build_poly_path, build_rounded_rect_path, clip_mask, intersect_rects,
};
fn ts_fill_paint(paint: &ScenePaint, x: f64, y: f64, w: f64, h: f64) -> Option<Paint<'static>> {
match paint {
ScenePaint::Solid { color } => {
let mut p = Paint::default();
p.set_color_rgba8(color.r, color.g, color.b, color.a);
p.anti_alias = true;
Some(p)
}
ScenePaint::Gradient(gradient) => {
let shader = gradient_shader(x, y, w, h, gradient)?;
Some(Paint {
shader,
anti_alias: true,
..Default::default()
})
}
}
}
fn flat_points_bbox(points: &[f64]) -> Option<(f64, f64, f64, f64)> {
let mut xs = points.iter().step_by(2).copied();
let mut ys = points.iter().skip(1).step_by(2).copied();
let (mut min_x, mut max_x) = {
let first = xs.next()?;
(first, first)
};
let (mut min_y, mut max_y) = {
let first = ys.next()?;
(first, first)
};
for x in xs {
min_x = min_x.min(x);
max_x = max_x.max(x);
}
for y in ys {
min_y = min_y.min(y);
max_y = max_y.max(y);
}
Some((min_x, min_y, max_x - min_x, max_y - min_y))
}
pub(in crate::tiny_skia) fn fill_rect(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::FillRect { x, y, w, h, paint } = cmd else {
return;
};
match paint {
ScenePaint::Solid { color } => {
if ctx.current_ts.is_identity() {
let fill_rect = (*x, *y, x + w, y + h);
let effective_clip = ctx.effective_clip;
let (ix, iy, ix2, iy2) = match intersect_rects(fill_rect, effective_clip) {
Some(r) => r,
None => return, };
let iw = ix2 - ix;
let ih = iy2 - iy;
if iw <= 0.0
|| ih <= 0.0
|| !ix.is_finite()
|| !iy.is_finite()
|| !iw.is_finite()
|| !ih.is_finite()
{
return;
}
let rect = match Rect::from_xywh(ix as f32, iy as f32, iw as f32, ih as f32) {
Some(r) => r,
None => return,
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = false;
target.fill_rect(rect, &paint, Transform::identity(), None);
} else {
let effective_clip = ctx.effective_clip;
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let Some(rect) = Rect::from_xywh(*x as f32, *y as f32, *w as f32, *h as f32) else {
return;
};
let path = PathBuilder::from_rect(rect);
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
target.fill_path(
&path,
&paint,
FillRule::Winding,
ctx.current_ts,
mask.as_ref(),
);
}
}
ScenePaint::Gradient(_) => {
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let effective_clip = ctx.effective_clip;
if intersect_rects((*x, *y, x + w, y + h), effective_clip).is_none() {
return;
}
let Some(rect) = Rect::from_xywh(*x as f32, *y as f32, *w as f32, *h as f32) else {
return;
};
let path = PathBuilder::from_rect(rect);
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let Some(paint_ts) = ts_fill_paint(paint, *x, *y, *w, *h) else {
return;
};
target.fill_path(
&path,
&paint_ts,
FillRule::Winding,
ctx.current_ts,
mask.as_ref(),
);
}
}
}
pub(in crate::tiny_skia) fn fill_ellipse(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::FillEllipse {
x,
y,
w,
h,
rx,
ry,
paint,
} = cmd
else {
return;
};
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let ow = rx.map_or(*w, |r| r * 2.0);
let oh = ry.map_or(*h, |r| r * 2.0);
let ox = x + (w - ow) / 2.0;
let oy = y + (h - oh) / 2.0;
let effective_clip = ctx.effective_clip;
if intersect_rects((ox, oy, ox + ow, oy + oh), effective_clip).is_none() {
return;
}
let Some(rect) = Rect::from_xywh(ox as f32, oy as f32, ow as f32, oh as f32) else {
return;
};
let Some(path) = PathBuilder::from_oval(rect) else {
return; };
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let Some(paint_ts) = ts_fill_paint(paint, *x, *y, *w, *h) else {
return;
};
target.fill_path(
&path,
&paint_ts,
FillRule::Winding,
ctx.current_ts,
mask.as_ref(),
);
}
pub(in crate::tiny_skia) fn stroke_ellipse(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::StrokeEllipse {
x,
y,
w,
h,
rx,
ry,
color,
stroke_width,
stroke_dash,
stroke_gap,
stroke_linecap,
} = cmd
else {
return;
};
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| !stroke_width.is_finite()
|| *stroke_width > f64::from(f32::MAX)
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let ow = rx.map_or(*w, |r| r * 2.0);
let oh = ry.map_or(*h, |r| r * 2.0);
let ox = x + (w - ow) / 2.0;
let oy = y + (h - oh) / 2.0;
let effective_clip = ctx.effective_clip;
let half_sw = stroke_width / 2.0;
if intersect_rects(
(
ox - half_sw,
oy - half_sw,
ox + ow + half_sw,
oy + oh + half_sw,
),
effective_clip,
)
.is_none()
{
return;
}
let Some(rect) = Rect::from_xywh(ox as f32, oy as f32, ow as f32, oh as f32) else {
return;
};
let Some(path) = PathBuilder::from_oval(rect) else {
return; };
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let line_cap = map_line_cap(*stroke_linecap);
let dash = build_stroke_dash(*stroke_dash, *stroke_gap);
let stroke = Stroke {
width: *stroke_width as f32,
line_cap,
dash,
..Default::default()
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
target.stroke_path(&path, &paint, &stroke, ctx.current_ts, mask.as_ref());
}
pub(in crate::tiny_skia) fn stroke_line(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::StrokeLine {
x1,
y1,
x2,
y2,
color,
stroke_width,
stroke_dash,
stroke_gap,
stroke_linecap,
} = cmd
else {
return;
};
if !x1.is_finite()
|| !y1.is_finite()
|| !x2.is_finite()
|| !y2.is_finite()
|| !stroke_width.is_finite()
|| *stroke_width > f64::from(f32::MAX)
{
return;
}
let effective_clip = ctx.effective_clip;
let half_sw = stroke_width / 2.0;
let ink_x = x1.min(*x2) - half_sw;
let ink_y = y1.min(*y2) - half_sw;
let ink_x2 = x1.max(*x2) + half_sw;
let ink_y2 = y1.max(*y2) + half_sw;
if intersect_rects((ink_x, ink_y, ink_x2, ink_y2), effective_clip).is_none() {
return;
}
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let mut pb = PathBuilder::new();
pb.move_to(*x1 as f32, *y1 as f32);
pb.line_to(*x2 as f32, *y2 as f32);
let path = match pb.finish() {
Some(p) => p,
None => return, };
let line_cap = map_line_cap(*stroke_linecap);
let dash = build_stroke_dash(*stroke_dash, *stroke_gap);
let stroke = Stroke {
width: *stroke_width as f32,
line_cap,
dash,
..Default::default()
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
target.stroke_path(&path, &paint, &stroke, ctx.current_ts, mask.as_ref());
}
pub(in crate::tiny_skia) fn fill_polygon(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::FillPolygon {
points,
paint,
even_odd,
} = cmd
else {
return;
};
if points.len() < 6 {
return;
}
if points.iter().any(|v| !v.is_finite()) {
return;
}
let path = match build_poly_path(points, true) {
Some(p) => p,
None => return,
};
let effective_clip = ctx.effective_clip;
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let fill_rule = if *even_odd {
FillRule::EvenOdd
} else {
FillRule::Winding
};
let Some((bx, by, bw, bh)) = flat_points_bbox(points) else {
return;
};
let Some(paint_ts) = ts_fill_paint(paint, bx, by, bw, bh) else {
return;
};
target.fill_path(&path, &paint_ts, fill_rule, ctx.current_ts, mask.as_ref());
}
pub(in crate::tiny_skia) fn stroke_polyline(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::StrokePolyline {
points,
color,
stroke_width,
closed,
align,
fill_even_odd,
} = cmd
else {
return;
};
if points.len() < 4 {
return;
}
if points.iter().any(|v| !v.is_finite())
|| !stroke_width.is_finite()
|| *stroke_width > f64::from(f32::MAX)
{
return;
}
let path = match build_poly_path(points, *closed) {
Some(p) => p,
None => return,
};
let effective_clip = ctx.effective_clip;
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
let aligned_mask: Option<Mask> = match align {
StrokeAlign::Center => None,
StrokeAlign::Inside | StrokeAlign::Outside if *closed => build_align_mask(
points,
*align,
*fill_even_odd,
effective_clip,
ctx.width,
ctx.height,
ctx.current_ts,
),
StrokeAlign::Inside | StrokeAlign::Outside => None,
};
let stroke_width_px = if aligned_mask.is_some() {
(*stroke_width * 2.0) as f32
} else {
*stroke_width as f32
};
let stroke = Stroke {
width: stroke_width_px,
..Default::default()
};
let draw_mask: Option<&Mask> = match &aligned_mask {
Some(m) => Some(m),
None => mask.as_ref(),
};
target.stroke_path(&path, &paint, &stroke, ctx.current_ts, draw_mask);
}
pub(in crate::tiny_skia) fn stroke_rect(target: &mut Pixmap, ctx: DrawCtx, cmd: &SceneCommand) {
let SceneCommand::StrokeRect {
x,
y,
w,
h,
color,
stroke_width,
stroke_dash,
stroke_gap,
stroke_linecap,
} = cmd
else {
return;
};
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| !stroke_width.is_finite()
|| *stroke_width > f64::from(f32::MAX)
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let effective_clip = ctx.effective_clip;
let half_sw = stroke_width / 2.0;
if intersect_rects(
(x - half_sw, y - half_sw, x + w + half_sw, y + h + half_sw),
effective_clip,
)
.is_none()
{
return;
}
let Some(rect) = Rect::from_xywh(*x as f32, *y as f32, *w as f32, *h as f32) else {
return;
};
let path = PathBuilder::from_rect(rect);
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let line_cap = map_line_cap(*stroke_linecap);
let dash = build_stroke_dash(*stroke_dash, *stroke_gap);
let stroke = Stroke {
width: *stroke_width as f32,
line_cap,
dash,
..Default::default()
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
target.stroke_path(&path, &paint, &stroke, ctx.current_ts, mask.as_ref());
}
pub(in crate::tiny_skia) fn fill_rounded_rect(
target: &mut Pixmap,
ctx: DrawCtx,
cmd: &SceneCommand,
) {
let SceneCommand::FillRoundedRect {
x,
y,
w,
h,
radius,
radii,
paint,
} = cmd
else {
return;
};
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| !radius.is_finite()
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let effective_clip = ctx.effective_clip;
if intersect_rects((*x, *y, x + w, y + h), effective_clip).is_none() {
return;
}
let corner_radii = radii.map_or([*radius as f32; 4], |a| a.map(|v| v as f32));
let Some(path) =
build_rounded_rect_path(*x as f32, *y as f32, *w as f32, *h as f32, corner_radii)
else {
return;
};
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let Some(paint_ts) = ts_fill_paint(paint, *x, *y, *w, *h) else {
return;
};
target.fill_path(
&path,
&paint_ts,
FillRule::Winding,
ctx.current_ts,
mask.as_ref(),
);
}
pub(in crate::tiny_skia) fn stroke_rounded_rect(
target: &mut Pixmap,
ctx: DrawCtx,
cmd: &SceneCommand,
) {
let SceneCommand::StrokeRoundedRect {
x,
y,
w,
h,
radius,
radii,
color,
stroke_width,
stroke_dash,
stroke_gap,
stroke_linecap,
} = cmd
else {
return;
};
if !x.is_finite()
|| !y.is_finite()
|| !w.is_finite()
|| !h.is_finite()
|| !radius.is_finite()
|| !stroke_width.is_finite()
|| *stroke_width > f64::from(f32::MAX)
|| *w <= 0.0
|| *h <= 0.0
{
return;
}
let effective_clip = ctx.effective_clip;
let half_sw = stroke_width / 2.0;
if intersect_rects(
(x - half_sw, y - half_sw, x + w + half_sw, y + h + half_sw),
effective_clip,
)
.is_none()
{
return;
}
let corner_radii = radii.map_or([*radius as f32; 4], |a| a.map(|v| v as f32));
let Some(path) =
build_rounded_rect_path(*x as f32, *y as f32, *w as f32, *h as f32, corner_radii)
else {
return;
};
let mask = match clip_mask(effective_clip, ctx.width, ctx.height) {
None => return,
Some(m) => m,
};
let line_cap = map_line_cap(*stroke_linecap);
let dash = build_stroke_dash(*stroke_dash, *stroke_gap);
let stroke = Stroke {
width: *stroke_width as f32,
line_cap,
dash,
..Default::default()
};
let mut paint = Paint::default();
paint.set_color_rgba8(color.r, color.g, color.b, color.a);
paint.anti_alias = true;
target.stroke_path(&path, &paint, &stroke, ctx.current_ts, mask.as_ref());
}