use crate::{BRUSH_KIND_LINEAR, BrushRecord, GradientStopRecord, TileMode};
const INFINITE_GRADIENT_POINT: f32 = 1.0e30;
const MIN_STOP_SPAN: f32 = 0.00001;
const MIN_SQUARED_LENGTH: f32 = 1.0;
struct TwoStopLine {
start: (f32, f32),
direction: (f32, f32),
length: f32,
stops: (f32, f32),
}
fn two_stop_line(
brush: &BrushRecord,
stops: &[GradientStopRecord],
width: f32,
height: f32,
) -> Option<TwoStopLine> {
let start = brush.stop_start as usize;
let (first, last) = (stops.get(start)?, stops.get(start + 1)?);
if brush.kind != BRUSH_KIND_LINEAR
|| brush.stop_count != 2
|| brush.params.iter().any(|value| value.is_nan())
{
return None;
}
let [start_x, start_y, end_x, end_y] = [
(brush.params[0], width),
(brush.params[1], height),
(brush.params[2], width),
(brush.params[3], height),
]
.map(|(value, extent)| resolved_point(value, extent));
let (dx, dy) = (end_x - start_x, end_y - start_y);
let line = TwoStopLine {
start: (start_x, start_y),
direction: (dx, dy),
length: dx * dx + dy * dy,
stops: (first.position[0], last.position[0]),
};
(line.stops.1 - line.stops.0 >= MIN_STOP_SPAN && line.length >= MIN_SQUARED_LENGTH)
.then_some(line)
}
pub(crate) fn spans_quad(
brush: &BrushRecord,
stops: &[GradientStopRecord],
rect: [f32; 4],
) -> bool {
let [_, _, width, height] = rect;
let Some(line) =
two_stop_line(brush, stops, width, height).filter(|_| continuous(brush.tile_mode))
else {
return false;
};
let (band_start, band_end) = (line.stops.0.max(0.0), line.stops.1.min(1.0));
[(0.0, 0.0), (width, 0.0), (0.0, height), (width, height)]
.into_iter()
.all(|(x, y)| {
let t = ((x - line.start.0) * line.direction.0 + (y - line.start.1) * line.direction.1)
/ line.length;
t >= band_start && t <= band_end
})
}
pub(crate) fn clamps_between_two_stops(
brush: &BrushRecord,
stops: &[GradientStopRecord],
brush_rect: [f32; 4],
) -> bool {
let [_, _, width, height] = brush_rect;
brush.tile_mode == TileMode::Clamp as u32
&& two_stop_line(brush, stops, width, height)
.is_some_and(|line| line.stops.0 >= 0.0 && line.stops.1 <= 1.0)
}
fn continuous(tile_mode: u32) -> bool {
tile_mode == TileMode::Clamp as u32 || tile_mode == TileMode::Mirror as u32
}
fn resolved_point(value: f32, extent: f32) -> f32 {
if value.abs() < INFINITE_GRADIENT_POINT {
value
} else if value > 0.0 {
extent
} else {
0.0
}
}
#[cfg(test)]
#[path = "tests/vertex_gradient_tests.rs"]
mod tests;