use alloc::vec::Vec;
use tiny_skia_path::{NormalizedF32, Scalar};
use crate::{Color, SpreadMode, Transform};
use crate::pipeline::RasterPipelineBuilder;
use crate::pipeline::{self, EvenlySpaced2StopGradientCtx, GradientColor, GradientCtx};
pub const DEGENERATE_THRESHOLD: f32 = 1.0 / (1 << 15) as f32;
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct GradientStop {
pub(crate) position: NormalizedF32,
pub(crate) color: Color,
}
impl GradientStop {
pub fn new(position: f32, color: Color) -> Self {
GradientStop {
position: NormalizedF32::new_clamped(position),
color,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Gradient {
stops: Vec<GradientStop>,
tile_mode: SpreadMode,
pub(crate) transform: Transform,
points_to_unit: Transform,
pub(crate) colors_are_opaque: bool,
has_uniform_stops: bool,
}
impl Gradient {
pub fn new(
mut stops: Vec<GradientStop>,
tile_mode: SpreadMode,
transform: Transform,
points_to_unit: Transform,
) -> Self {
debug_assert!(stops.len() > 1);
let dummy_first = stops[0].position.get() != 0.0;
let dummy_last = stops[stops.len() - 1].position.get() != 1.0;
if dummy_first {
stops.insert(0, GradientStop::new(0.0, stops[0].color));
}
if dummy_last {
stops.push(GradientStop::new(1.0, stops[stops.len() - 1].color));
}
let colors_are_opaque = stops.iter().all(|p| p.color.is_opaque());
let start_index = if dummy_first { 0 } else { 1 };
let mut prev = 0.0;
let mut has_uniform_stops = true;
let uniform_step = stops[start_index].position.get() - prev;
for i in start_index..stops.len() {
let curr = if i + 1 == stops.len() {
1.0
} else {
stops[i].position.get().bound(prev, 1.0)
};
has_uniform_stops &= uniform_step.is_nearly_equal(curr - prev);
stops[i].position = NormalizedF32::new_clamped(curr);
prev = curr;
}
Gradient {
stops,
tile_mode,
transform,
points_to_unit,
colors_are_opaque,
has_uniform_stops,
}
}
pub fn push_stages(
&self,
p: &mut RasterPipelineBuilder,
push_stages_pre: &dyn Fn(&mut RasterPipelineBuilder),
push_stages_post: &dyn Fn(&mut RasterPipelineBuilder),
) -> bool {
p.push(pipeline::Stage::SeedShader);
let ts = match self.transform.invert() {
Some(v) => v,
None => {
log::warn!("failed to invert a gradient transform. Nothing will be rendered");
return false;
}
};
let ts = ts.post_concat(self.points_to_unit);
p.push_transform(ts);
push_stages_pre(p);
match self.tile_mode {
SpreadMode::Reflect => {
p.push(pipeline::Stage::ReflectX1);
}
SpreadMode::Repeat => {
p.push(pipeline::Stage::RepeatX1);
}
SpreadMode::Pad => {
if self.has_uniform_stops {
p.push(pipeline::Stage::PadX1);
}
}
}
if self.stops.len() == 2 {
debug_assert!(self.has_uniform_stops);
let c0 = self.stops[0].color;
let c1 = self.stops[1].color;
p.ctx.evenly_spaced_2_stop_gradient = EvenlySpaced2StopGradientCtx {
factor: GradientColor::new(
c1.red() - c0.red(),
c1.green() - c0.green(),
c1.blue() - c0.blue(),
c1.alpha() - c0.alpha(),
),
bias: GradientColor::from(c0),
};
p.push(pipeline::Stage::EvenlySpaced2StopGradient);
} else {
let mut ctx = GradientCtx::default();
ctx.factors.reserve((self.stops.len() + 1).max(16));
ctx.biases.reserve((self.stops.len() + 1).max(16));
ctx.t_values.reserve(self.stops.len() + 1);
let (first_stop, last_stop) = if self.stops.len() > 2 {
let first = if self.stops[0].color != self.stops[1].color {
0
} else {
1
};
let len = self.stops.len();
let last = if self.stops[len - 2].color != self.stops[len - 1].color {
len - 1
} else {
len - 2
};
(first, last)
} else {
(0, 1)
};
let mut t_l = self.stops[first_stop].position.get();
let mut c_l = GradientColor::from(self.stops[first_stop].color);
ctx.push_const_color(c_l);
ctx.t_values.push(NormalizedF32::ZERO);
for i in first_stop..last_stop {
let t_r = self.stops[i + 1].position.get();
let c_r = GradientColor::from(self.stops[i + 1].color);
debug_assert!(t_l <= t_r);
if t_l < t_r {
let f = GradientColor::new(
(c_r.r - c_l.r) / (t_r - t_l),
(c_r.g - c_l.g) / (t_r - t_l),
(c_r.b - c_l.b) / (t_r - t_l),
(c_r.a - c_l.a) / (t_r - t_l),
);
ctx.factors.push(f);
ctx.biases.push(GradientColor::new(
c_l.r - f.r * t_l,
c_l.g - f.g * t_l,
c_l.b - f.b * t_l,
c_l.a - f.a * t_l,
));
ctx.t_values.push(NormalizedF32::new_clamped(t_l));
}
t_l = t_r;
c_l = c_r;
}
ctx.push_const_color(c_l);
ctx.t_values.push(NormalizedF32::new_clamped(t_l));
ctx.len = ctx.factors.len();
debug_assert_eq!(ctx.factors.len(), ctx.t_values.len());
debug_assert_eq!(ctx.biases.len(), ctx.t_values.len());
while ctx.factors.len() < 16 {
ctx.factors.push(GradientColor::default());
ctx.biases.push(GradientColor::default());
}
p.push(pipeline::Stage::Gradient);
p.ctx.gradient = ctx;
}
if !self.colors_are_opaque {
p.push(pipeline::Stage::Premultiply);
}
push_stages_post(p);
true
}
pub fn apply_opacity(&mut self, opacity: f32) {
for stop in &mut self.stops {
stop.color.apply_opacity(opacity);
}
self.colors_are_opaque = self.stops.iter().all(|p| p.color.is_opaque());
}
}