use crate::{
BlendMode, Brush, CommandRecorder, DashPathEffect, DrawPrimitive, ImageSampling, PathContour,
PathFillRule, Point, Rect, Size, Stroke, TileMode, VectorPath,
path::{DrawStyle, Path},
stroke::LineGeometry,
vector_path_mask::{
PathSide, VECTOR_PATH_SUPERSAMPLE, rasterize_vector_path, vector_path_mask_cache_get,
vector_path_mask_cache_put, vector_path_mask_key,
},
};
pub(crate) fn record_path(
recording: &mut CommandRecorder<'_>,
size: Size,
path: &Path,
brush: &Brush,
style: DrawStyle,
blend_mode: BlendMode,
) {
let (stroke, dash) = match style {
DrawStyle::Fill => {
record_path_fill(recording, size, path.contours(), brush, blend_mode);
return;
}
DrawStyle::Stroke(stroke) => (stroke, None),
DrawStyle::DashedStroke(stroke, dash) => (stroke, Some(dash)),
};
if !stroke.is_visible() {
return;
}
if overlaps_show(brush, blend_mode) && !is_one_segment(path) {
record_stroke_outline(recording, size, path, (stroke, dash), brush, blend_mode);
return;
}
let mut run = recording.line_run(brush, stroke, blend_mode);
for_each_line(path, stroke, dash, |line| {
run.push(line.start, line.end, line.cap);
});
}
fn overlaps_show(brush: &Brush, blend_mode: BlendMode) -> bool {
blend_mode != BlendMode::SrcOver
|| match brush {
Brush::Solid(color) => color.3 < 1.0,
Brush::LinearGradient {
tile_mode: TileMode::Decal,
..
}
| Brush::RadialGradient {
tile_mode: TileMode::Decal,
..
} => true,
gradient => gradient.colors().iter().any(|color| color.3 < 1.0),
}
}
fn is_one_segment(path: &Path) -> bool {
matches!(path.contours(), [contour] if !contour.closed && contour.points.len() <= 2)
}
fn record_stroke_outline(
recording: &mut CommandRecorder<'_>,
size: Size,
path: &Path,
(stroke, dash): (Stroke, Option<DashPathEffect>),
brush: &Brush,
blend_mode: BlendMode,
) {
let mut outline = crate::thread_state::with(|state| state.stroke_outline.take());
outline.clear();
for_each_line(path, stroke, dash, |line| outline.add_line(line));
record_path_fill(recording, size, outline.contours(), brush, blend_mode);
crate::thread_state::with(|state| state.stroke_outline.replace(outline));
}
fn for_each_line(
path: &Path,
stroke: Stroke,
dash: Option<DashPathEffect>,
mut line: impl FnMut(LineGeometry),
) {
for contour in path.contours() {
let Some(dash) = dash else {
crate::for_each_stroke_line(&contour.points, contour.closed, stroke, &mut line);
continue;
};
let closing = contour
.closed
.then(|| contour.points.first().copied())
.flatten();
let points: std::borrow::Cow<'_, [Point]> = match closing {
Some(first) => {
std::borrow::Cow::Owned(contour.points.iter().copied().chain([first]).collect())
}
None => std::borrow::Cow::Borrowed(&contour.points),
};
dash.for_each_dash(&points, |piece| {
crate::for_each_stroke_line(piece, false, stroke, &mut line);
});
}
}
fn record_path_fill(
recording: &mut CommandRecorder<'_>,
size: Size,
path: &[PathContour],
brush: &Brush,
blend_mode: BlendMode,
) {
let brush_rect = Rect::from_size(size);
let sliced = crate::thread_state::with(|state| {
let mut slicer = state.path_slicer.borrow_mut();
let contours = path.iter().map(|contour| contour.points.as_slice());
slicer
.slice(contours, PathFillRule::NonZero)
.map(|slices| recording.push_trapezoids(brush_rect, slices, brush, blend_mode))
.is_some()
});
if !sliced {
record_vector_path(
recording,
&crate::path::contours_vector_path(path, PathFillRule::NonZero),
brush,
brush_rect,
blend_mode,
PathSide::Inside,
);
}
}
pub(crate) fn record_vector_path(
recording: &mut CommandRecorder<'_>,
path: &VectorPath,
brush: &Brush,
brush_rect: Rect,
blend_mode: BlendMode,
side: PathSide,
) {
if path.is_empty() || !brush.paints() {
return;
}
let bounds = path.bounds();
if bounds.width <= 0.0 || bounds.height <= 0.0 {
return;
}
let rect = vector_mask_rect(bounds);
for_each_mask_tile(rect, |tile| {
let origin = Point::new(tile.x, tile.y);
let mask_size = (
(tile.width * VECTOR_PATH_SUPERSAMPLE).ceil().max(1.0) as usize,
(tile.height * VECTOR_PATH_SUPERSAMPLE).ceil().max(1.0) as usize,
);
let key = vector_path_mask_key(path, origin, mask_size, brush, brush_rect, side);
let image = match vector_path_mask_cache_get(key) {
Some(image) => image,
None => {
let Some(image) =
rasterize_vector_path(path, brush, brush_rect, origin, mask_size, side)
else {
return;
};
vector_path_mask_cache_put(key, image.clone());
image
}
};
let image = DrawPrimitive::Image {
rect: tile,
image,
alpha: 1.0,
color_filter: None,
sampling: ImageSampling::Linear,
src_rect: None,
};
recording.push_other(if blend_mode == BlendMode::SrcOver {
image
} else {
DrawPrimitive::Blend {
primitive: Box::new(image),
blend_mode,
}
});
});
}
fn for_each_mask_tile(rect: Rect, mut tile: impl FnMut(Rect)) {
const MAX_MASK_PIXELS: f32 = 4096.0;
const SIDE: f32 = MAX_MASK_PIXELS / VECTOR_PATH_SUPERSAMPLE;
let mut y = 0.0;
while y < rect.height {
let height = (rect.height - y).min(SIDE);
let mut x = 0.0;
while x < rect.width {
let width = (rect.width - x).min(SIDE);
tile(Rect {
x: rect.x + x,
y: rect.y + y,
width,
height,
});
x += SIDE;
}
y += SIDE;
}
}
pub(crate) fn vector_mask_rect(bounds: Rect) -> Rect {
let x = bounds.x.floor() - 1.0;
let y = bounds.y.floor() - 1.0;
Rect {
x,
y,
width: (bounds.x + bounds.width).ceil() - x + 1.0,
height: (bounds.y + bounds.height).ceil() - y + 1.0,
}
}