use super::Extend;
use color::{
AlphaColor, ColorSpace, ColorSpaceTag, DynamicColor, HueDirection, OpaqueColor,
cache_key::{BitEq, BitHash},
};
use kurbo::Point;
use smallvec::SmallVec;
use core::{
hash::Hasher,
ops::{Deref, DerefMut},
};
const DEFAULT_GRADIENT_COLOR_SPACE: ColorSpaceTag = ColorSpaceTag::Srgb;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColorStop {
pub offset: f32,
pub color: DynamicColor,
}
impl BitHash for ColorStop {
fn bit_hash<H: Hasher>(&self, state: &mut H) {
self.offset.bit_hash(state);
self.color.bit_hash(state);
}
}
impl BitEq for ColorStop {
fn bit_eq(&self, other: &Self) -> bool {
self.offset.bit_eq(&other.offset) && self.color.bit_eq(&other.color)
}
}
impl ColorStop {
#[must_use]
pub const fn with_alpha(self, alpha: f32) -> Self {
Self {
offset: self.offset,
color: self.color.with_alpha(alpha),
}
}
#[must_use]
pub const fn multiply_alpha(self, alpha: f32) -> Self {
Self {
offset: self.offset,
color: self.color.multiply_alpha(alpha),
}
}
}
impl<CS: ColorSpace> From<(f32, AlphaColor<CS>)> for ColorStop {
fn from(pair: (f32, AlphaColor<CS>)) -> Self {
Self {
offset: pair.0,
color: DynamicColor::from_alpha_color(pair.1),
}
}
}
impl From<(f32, DynamicColor)> for ColorStop {
fn from(pair: (f32, DynamicColor)) -> Self {
Self {
offset: pair.0,
color: pair.1,
}
}
}
impl<CS: ColorSpace> From<(f32, OpaqueColor<CS>)> for ColorStop {
fn from(pair: (f32, OpaqueColor<CS>)) -> Self {
Self {
offset: pair.0,
color: DynamicColor::from_alpha_color(pair.1.with_alpha(1.)),
}
}
}
#[derive(Clone, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColorStops(pub SmallVec<[ColorStop; 4]>);
impl Deref for ColorStops {
type Target = SmallVec<[ColorStop; 4]>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ColorStops {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl ColorStops {
pub fn new() -> Self {
Self::default()
}
}
impl BitEq for ColorStops {
fn bit_eq(&self, other: &Self) -> bool {
self.as_slice().bit_eq(other.as_slice())
}
}
impl BitHash for ColorStops {
fn bit_hash<H: Hasher>(&self, state: &mut H) {
self.as_slice().bit_hash(state);
}
}
impl From<&[ColorStop]> for ColorStops {
fn from(slice: &[ColorStop]) -> Self {
Self(slice.into())
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LinearGradientPosition {
pub start: Point,
pub end: Point,
}
impl LinearGradientPosition {
pub fn new(start: impl Into<Point>, end: impl Into<Point>) -> Self {
Self {
start: start.into(),
end: end.into(),
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RadialGradientPosition {
pub start_center: Point,
pub start_radius: f32,
pub end_center: Point,
pub end_radius: f32,
}
impl RadialGradientPosition {
pub fn new(center: impl Into<Point>, radius: f32) -> Self {
let center = center.into();
Self {
start_center: center,
start_radius: 0.0,
end_center: center,
end_radius: radius,
}
}
pub fn new_two_point(
start_center: impl Into<Point>,
start_radius: f32,
end_center: impl Into<Point>,
end_radius: f32,
) -> Self {
Self {
start_center: start_center.into(),
start_radius,
end_center: end_center.into(),
end_radius,
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SweepGradientPosition {
pub center: Point,
pub start_angle: f32,
pub end_angle: f32,
}
impl SweepGradientPosition {
pub fn new(center: impl Into<Point>, start_angle: f32, end_angle: f32) -> Self {
Self {
center: center.into(),
start_angle,
end_angle,
}
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum InterpolationAlphaSpace {
#[default]
Premultiplied = 0,
Unpremultiplied = 1,
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GradientKind {
Linear(LinearGradientPosition),
Radial(RadialGradientPosition),
Sweep(SweepGradientPosition),
}
impl From<LinearGradientPosition> for GradientKind {
#[inline(always)]
fn from(value: LinearGradientPosition) -> Self {
Self::Linear(value)
}
}
impl From<RadialGradientPosition> for GradientKind {
#[inline(always)]
fn from(value: RadialGradientPosition) -> Self {
Self::Radial(value)
}
}
impl From<SweepGradientPosition> for GradientKind {
#[inline(always)]
fn from(value: SweepGradientPosition) -> Self {
Self::Sweep(value)
}
}
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Gradient {
pub kind: GradientKind,
pub extend: Extend,
pub interpolation_cs: ColorSpaceTag,
pub hue_direction: HueDirection,
pub interpolation_alpha_space: InterpolationAlphaSpace,
pub stops: ColorStops,
}
impl Default for Gradient {
fn default() -> Self {
Self {
kind: LinearGradientPosition {
start: Point::default(),
end: Point::default(),
}
.into(),
extend: Extend::default(),
interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
stops: ColorStops::default(),
}
}
}
impl Gradient {
pub fn new_linear(start: impl Into<Point>, end: impl Into<Point>) -> Self {
Self {
kind: LinearGradientPosition::new(start, end).into(),
extend: Extend::default(),
interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
stops: ColorStops::default(),
}
}
pub fn new_radial(center: impl Into<Point>, radius: f32) -> Self {
let center = center.into();
Self {
kind: RadialGradientPosition::new(center, radius).into(),
extend: Extend::default(),
interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
stops: ColorStops::default(),
}
}
pub fn new_two_point_radial(
start_center: impl Into<Point>,
start_radius: f32,
end_center: impl Into<Point>,
end_radius: f32,
) -> Self {
Self {
kind: RadialGradientPosition::new_two_point(
start_center,
start_radius,
end_center,
end_radius,
)
.into(),
extend: Extend::default(),
interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
stops: ColorStops::default(),
}
}
pub fn new_sweep(center: impl Into<Point>, start_angle: f32, end_angle: f32) -> Self {
Self {
kind: SweepGradientPosition::new(center, start_angle, end_angle).into(),
extend: Extend::default(),
interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
stops: ColorStops::default(),
}
}
#[must_use]
pub const fn with_extend(mut self, mode: Extend) -> Self {
self.extend = mode;
self
}
#[must_use]
pub const fn with_interpolation_cs(mut self, interpolation_cs: ColorSpaceTag) -> Self {
self.interpolation_cs = interpolation_cs;
self
}
#[must_use]
pub const fn with_interpolation_alpha_space(
mut self,
interpolation_alpha_space: InterpolationAlphaSpace,
) -> Self {
self.interpolation_alpha_space = interpolation_alpha_space;
self
}
#[must_use]
pub const fn with_hue_direction(mut self, hue_direction: HueDirection) -> Self {
self.hue_direction = hue_direction;
self
}
#[must_use]
pub fn with_stops(mut self, stops: impl ColorStopsSource) -> Self {
self.stops.clear();
stops.collect_stops(&mut self.stops);
self
}
#[must_use]
pub fn with_alpha(mut self, alpha: f32) -> Self {
self.stops
.iter_mut()
.for_each(|stop| *stop = stop.with_alpha(alpha));
self
}
#[must_use]
pub fn multiply_alpha(mut self, alpha: f32) -> Self {
self.stops
.iter_mut()
.for_each(|stop| *stop = stop.multiply_alpha(alpha));
self
}
}
pub trait ColorStopsSource {
fn collect_stops(self, stops: &mut ColorStops);
}
impl<T> ColorStopsSource for &'_ [T]
where
T: Into<ColorStop> + Copy,
{
fn collect_stops(self, stops: &mut ColorStops) {
for &stop in self {
stops.push(stop.into());
}
}
}
impl<T, const N: usize> ColorStopsSource for [T; N]
where
T: Into<ColorStop>,
{
fn collect_stops(self, stops: &mut ColorStops) {
for stop in self.into_iter() {
stops.push(stop.into());
}
}
}
impl<CS: ColorSpace> ColorStopsSource for &'_ [AlphaColor<CS>] {
fn collect_stops(self, stops: &mut ColorStops) {
if !self.is_empty() {
let denom = (self.len() - 1).max(1) as f32;
stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
offset: (i as f32) / denom,
color: DynamicColor::from_alpha_color(*c),
}));
}
}
}
impl ColorStopsSource for &'_ [DynamicColor] {
fn collect_stops(self, stops: &mut ColorStops) {
if !self.is_empty() {
let denom = (self.len() - 1).max(1) as f32;
stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
offset: (i as f32) / denom,
color: (*c),
}));
}
}
}
impl<CS: ColorSpace> ColorStopsSource for &'_ [OpaqueColor<CS>] {
fn collect_stops(self, stops: &mut ColorStops) {
if !self.is_empty() {
let denom = (self.len() - 1).max(1) as f32;
stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
offset: (i as f32) / denom,
color: DynamicColor::from_alpha_color((*c).with_alpha(1.)),
}));
}
}
}
impl<const N: usize, CS: ColorSpace> ColorStopsSource for [AlphaColor<CS>; N] {
fn collect_stops(self, stops: &mut ColorStops) {
(&self[..]).collect_stops(stops);
}
}
impl<const N: usize> ColorStopsSource for [DynamicColor; N] {
fn collect_stops(self, stops: &mut ColorStops) {
(&self[..]).collect_stops(stops);
}
}
impl<const N: usize, CS: ColorSpace> ColorStopsSource for [OpaqueColor<CS>; N] {
fn collect_stops(self, stops: &mut ColorStops) {
(&self[..]).collect_stops(stops);
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
extern crate std;
use super::Gradient;
use alloc::vec;
use color::{cache_key::CacheKey, palette, parse_color};
use std::collections::HashSet;
#[test]
fn color_stops_cache() {
let mut set = HashSet::new();
let stops = Gradient::default()
.with_stops([palette::css::RED, palette::css::LIME, palette::css::BLUE])
.stops;
let stops_clone = stops.clone();
let parsed_gradient = Gradient::default().with_stops(
vec![
parse_color("red").unwrap(),
parse_color("lime").unwrap(),
parse_color("blue").unwrap(),
]
.as_slice(),
);
let parsed_stops = parsed_gradient.stops.clone();
set.insert(CacheKey(stops));
assert!(set.contains(&CacheKey(stops_clone)));
set.insert(CacheKey(parsed_stops));
let new_grad = parsed_gradient.clone();
assert!(set.contains(&CacheKey(new_grad.stops)));
}
}