use super::{
BlendMode, BrushSampler, COLUMN_BAND, ClipBounds, DitherAxis, DitheredLine, GradientColors,
Pixel, Point, Rect, RenderDiagnostics, RowRuns, Target, color_to_rgba, opaque_bytes, paint_row,
sample_brush_rgba, shape_sdf,
};
pub(super) struct ShapeRows<'a, 'p, P> {
pub(super) target: &'a mut Target<'p, P>,
pub(super) draw: &'a crate::scene::DrawShape,
pub(super) rect: Rect,
pub(super) clip_bounds: ClipBounds,
pub(super) dither_origin: Point,
pub(super) opaque: Option<P>,
pub(super) diagnostics: &'a RenderDiagnostics,
}
impl<P: Pixel> ShapeRows<'_, '_, P> {
pub(super) fn fill(self, runs: impl Fn(f32, i32, i32) -> RowRuns) {
match self.opaque {
Some(pixel) => {
let bounds = self.clip_bounds;
for py in bounds.min_y..bounds.max_y {
for (start, end) in runs(py as f32 + 0.5, bounds.min_x, bounds.max_x) {
P::fill(self.target.row(py, start, end), pixel);
}
}
}
None => {
let brush = BrushSampler::new(&self.draw.brush, self.rect);
if brush.is_solid() {
let color = color_to_rgba(brush.color_at(Point::new(0.0, 0.0)));
self.blend_solid_rows(color, runs);
} else if brush.has_uniform_rows() {
self.fill_uniform_rows(runs);
} else if brush.has_uniform_columns()
&& brush.is_opaque()
&& self.draw.blend_mode == BlendMode::SrcOver
{
self.fill_uniform_columns(runs);
} else {
self.paint(runs, |_| Coverage::of(1.0));
}
}
}
}
#[inline(never)]
fn blend_solid_rows(self, color: [f32; 4], runs: impl Fn(f32, i32, i32) -> RowRuns) {
if color[3] <= 0.0 {
return;
}
let paint = P::paint(color, self.draw.blend_mode);
let mut table = None;
let bounds = self.clip_bounds;
for py in bounds.min_y..bounds.max_y {
for (start, end) in runs(py as f32 + 0.5, bounds.min_x, bounds.max_x) {
let pixels = self.target.row(py, start, end);
P::blend_run(pixels, paint, &mut table, self.diagnostics);
}
}
}
#[inline(never)]
fn fill_uniform_rows(self, runs: impl Fn(f32, i32, i32) -> RowRuns) {
let Self {
target,
draw,
rect,
clip_bounds,
dither_origin,
diagnostics,
..
} = self;
let brush = BrushSampler::new(&draw.brush, rect);
let columns = DitherAxis::new(dither_origin.x);
for py in clip_bounds.min_y..clip_bounds.max_y {
let center_y = py as f32 + 0.5;
let row_runs = runs(center_y, clip_bounds.min_x, clip_bounds.max_x);
let Some(&(first, _)) = row_runs.iter().find(|(start, end)| start < end) else {
continue;
};
let x = first as f32 + 0.5;
if P::GRADIENT == GradientColors::Plain {
let color = color_to_rgba(brush.color_at(Point::new(x, center_y)));
paint_row(target, py, row_runs, color, draw.blend_mode, diagnostics);
continue;
}
let colors = *DitheredLine::row(&brush, x, center_y, dither_origin).colors();
let cells = colors.map(|color| {
(draw.blend_mode == BlendMode::SrcOver && color[3] >= 1.0)
.then(|| P::opaque(opaque_bytes(color)))
});
let opaque = match cells {
[Some(a), Some(b), Some(c), Some(d)] => Some([a, b, c, d]),
_ => None,
};
let paints = colors.map(|color| P::paint(color, draw.blend_mode));
for (start, end) in row_runs {
let pixels = target.row(py, start, end);
if let Some(pattern) = opaque
&& let Some(first) = columns.stepping_cell(start, end)
{
P::fill_pattern(pixels, [0, 1, 2, 3].map(|step| pattern[(first + step) & 3]));
continue;
}
for (px, dst) in (start..).zip(pixels) {
let cell = columns.cell(px);
match cells[cell] {
Some(pixel) => *dst = pixel,
None if colors[cell][3] > 0.0 => {
dst.blend_paint(paints[cell], 1.0, diagnostics);
}
None => {}
}
}
}
}
}
#[inline(never)]
fn fill_uniform_columns(self, runs: impl Fn(f32, i32, i32) -> RowRuns) {
let Self {
target,
draw,
rect,
clip_bounds,
dither_origin,
..
} = self;
let brush = BrushSampler::new(&draw.brush, rect);
let rows = DitherAxis::new(dither_origin.y);
let top = clip_bounds.min_y as f32 + 0.5;
let mut band = [[P::opaque([0, 0, 0, 255]); 4]; COLUMN_BAND];
let mut left = clip_bounds.min_x;
while left < clip_bounds.max_x {
let right = left
.saturating_add(COLUMN_BAND as i32)
.min(clip_bounds.max_x);
for (cells, px) in band.iter_mut().zip(left..right) {
let x = px as f32 + 0.5;
*cells = match P::GRADIENT {
GradientColors::Dithered => DitheredLine::column(&brush, x, top, dither_origin)
.colors()
.map(|color| P::opaque(opaque_bytes(color))),
GradientColors::Plain => {
let color = color_to_rgba(brush.color_at(Point::new(x, top)));
[P::opaque(opaque_bytes(color)); 4]
}
};
}
for py in clip_bounds.min_y..clip_bounds.max_y {
let cell = rows.cell(py);
for (start, end) in runs(py as f32 + 0.5, left, right) {
if start >= end {
continue;
}
let first = (start - left) as usize;
for (dst, cells) in target.row(py, start, end).iter_mut().zip(&band[first..]) {
*dst = cells[cell];
}
}
}
left = right;
}
}
#[inline(never)]
pub(super) fn paint(
self,
runs: impl Fn(f32, i32, i32) -> RowRuns,
coverage: impl Fn(Point) -> Coverage,
) {
self.paint_rows(runs, coverage, None);
}
#[inline(never)]
pub(super) fn paint_stroke(
self,
runs: impl Fn(f32, i32, i32) -> RowRuns,
coverage: impl Fn(Point) -> Coverage,
mirror: Option<i32>,
) {
self.paint_rows(runs, coverage, mirror);
}
#[inline]
fn paint_rows(
self,
runs: impl Fn(f32, i32, i32) -> RowRuns,
coverage: impl Fn(Point) -> Coverage,
mirror: Option<i32>,
) {
let Self {
target,
draw,
rect,
clip_bounds,
dither_origin,
opaque,
diagnostics,
} = self;
let brush = BrushSampler::new(&draw.brush, rect);
let solid = brush.is_solid().then(|| {
P::paint(
color_to_rgba(brush.color_at(Point::default())),
draw.blend_mode,
)
});
let settles = clip_bounds.within(SETTLED_COORDINATES);
let mut left = [0.0; MIRROR_SPAN];
for py in clip_bounds.min_y..clip_bounds.max_y {
let center_y = py as f32 + 0.5;
let mut kept: Option<(i32, i32)> = None;
for (start, end) in runs(center_y, clip_bounds.min_x, clip_bounds.max_x) {
let mut settled = Coverage::of(0.0);
for (px, dst) in (start..).zip(target.row(py, start, end)) {
let center_x = px as f32 + 0.5;
let twin = mirror.map(|mirror| mirror - 1 - px);
let coverage = match (twin, kept) {
(Some(twin), Some((from, to)))
if twin < px && (from..to).contains(&twin) =>
{
settled.next = 0;
left[twin.abs_diff(from) as usize]
}
_ => {
let value = if settled.next > 0 {
settled.next -= 1;
settled.value
} else {
settled = coverage(Point::new(center_x, center_y));
if !settles {
settled.next = 0;
}
settled.value
};
if let Some(twin) = twin
&& twin > px
{
kept = keep_left(&mut left, kept, px, value);
}
value
}
};
if coverage <= 0.0 {
continue;
}
if let Some(pixel) = opaque
&& coverage >= 1.0
{
*dst = pixel;
continue;
}
let paint = solid.unwrap_or_else(|| {
let sample = match P::GRADIENT {
GradientColors::Dithered => {
sample_brush_rgba(&brush, center_x, center_y, dither_origin)
}
GradientColors::Plain => {
color_to_rgba(brush.color_at(Point::new(center_x, center_y)))
}
};
P::paint(sample, draw.blend_mode)
});
dst.blend_paint(paint, coverage, diagnostics);
}
}
}
}
}
const MIRROR_SPAN: usize = 128;
fn keep_left(
left: &mut [f32; MIRROR_SPAN],
kept: Option<(i32, i32)>,
px: i32,
value: f32,
) -> Option<(i32, i32)> {
match kept {
None => {
left[0] = value;
Some((px, px + 1))
}
Some((from, to)) if to == px && (to.abs_diff(from) as usize) < MIRROR_SPAN => {
left[to.abs_diff(from) as usize] = value;
Some((from, to + 1))
}
kept => kept,
}
}
#[derive(Clone, Copy)]
pub(super) struct Coverage {
value: f32,
next: u32,
}
impl Coverage {
pub(super) fn of(value: f32) -> Self {
Self { value, next: 0 }
}
pub(super) fn of_distance(distance: f32) -> Self {
let value = shape_sdf::coverage_for_distance(distance);
let reach = if distance <= -0.5 - SETTLE_MARGIN {
-0.5 - SETTLE_MARGIN - distance
} else if distance >= 0.5 + SETTLE_MARGIN {
distance - 0.5 - SETTLE_MARGIN
} else {
0.0
};
Self {
value,
next: if distance.is_finite() {
reach as u32
} else {
0
},
}
}
}
const SETTLE_MARGIN: f32 = 1.0 / 32.0;
const SETTLED_COORDINATES: i32 = 16_384;