use alloc::vec::Vec;
use tiny_skia_path::Scalar;
use crate::{GradientStop, Point, Shader, SpreadMode, Transform};
use super::gradient::{Gradient, DEGENERATE_THRESHOLD};
use crate::pipeline;
use crate::pipeline::RasterPipelineBuilder;
use crate::wide::u32x8;
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use tiny_skia_path::NoStdFloat;
#[derive(Copy, Clone, PartialEq, Debug)]
struct FocalData {
r1: f32, }
impl FocalData {
fn is_focal_on_circle(&self) -> bool {
(1.0 - self.r1).is_nearly_zero()
}
fn is_well_behaved(&self) -> bool {
!self.is_focal_on_circle() && self.r1 > 1.0
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct RadialGradient {
pub(crate) base: Gradient,
focal_data: Option<FocalData>,
}
impl RadialGradient {
#[allow(clippy::new_ret_no_self)]
pub fn new(
start: Point,
end: Point,
radius: f32,
stops: Vec<GradientStop>,
mode: SpreadMode,
transform: Transform,
) -> Option<Shader<'static>> {
if radius < 0.0 || radius.is_nearly_zero() {
return None;
}
if stops.is_empty() {
return None;
}
if stops.len() == 1 {
return Some(Shader::SolidColor(stops[0].color));
}
transform.invert()?;
let length = (end - start).length();
if !length.is_finite() {
return None;
}
if length.is_nearly_zero_within_tolerance(DEGENERATE_THRESHOLD) {
let inv = radius.invert();
let mut ts = Transform::from_translate(-start.x, -start.y);
ts = ts.post_scale(inv, inv);
Some(Shader::RadialGradient(RadialGradient {
base: Gradient::new(stops, mode, transform, ts),
focal_data: None,
}))
} else {
let mut ts = ts_from_poly_to_poly(
start,
end,
Point::from_xy(0.0, 0.0),
Point::from_xy(1.0, 0.0),
)?;
let d_center = (start - end).length();
let r1 = radius / d_center;
let focal_data = FocalData { r1 };
if focal_data.is_focal_on_circle() {
ts = ts.post_scale(0.5, 0.5);
} else {
ts = ts.post_scale(r1 / (r1 * r1 - 1.0), 1.0 / ((r1 * r1 - 1.0).abs()).sqrt());
}
Some(Shader::RadialGradient(RadialGradient {
base: Gradient::new(stops, mode, transform, ts),
focal_data: Some(focal_data),
}))
}
}
pub(crate) fn push_stages(&self, p: &mut RasterPipelineBuilder) -> bool {
let p0 = if let Some(focal_data) = self.focal_data {
1.0 / focal_data.r1
} else {
1.0
};
p.ctx.two_point_conical_gradient = pipeline::TwoPointConicalGradientCtx {
mask: u32x8::default(),
p0,
};
self.base.push_stages(
p,
&|p| {
if let Some(focal_data) = self.focal_data {
if focal_data.is_focal_on_circle() {
p.push(pipeline::Stage::XYTo2PtConicalFocalOnCircle);
} else if focal_data.is_well_behaved() {
p.push(pipeline::Stage::XYTo2PtConicalWellBehaved);
} else {
p.push(pipeline::Stage::XYTo2PtConicalGreater);
}
if !focal_data.is_well_behaved() {
p.push(pipeline::Stage::Mask2PtConicalDegenerates);
}
} else {
p.push(pipeline::Stage::XYToRadius);
}
},
&|p| {
if let Some(focal_data) = self.focal_data {
if !focal_data.is_well_behaved() {
p.push(pipeline::Stage::ApplyVectorMask);
}
}
},
)
}
}
fn ts_from_poly_to_poly(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Option<Transform> {
let tmp = from_poly2(src1, src2);
let res = tmp.invert()?;
let tmp = from_poly2(dst1, dst2);
Some(tmp.pre_concat(res))
}
fn from_poly2(p0: Point, p1: Point) -> Transform {
Transform::from_row(
p1.y - p0.y,
p0.x - p1.x,
p1.x - p0.x,
p1.y - p0.y,
p0.x,
p0.y,
)
}