2021-06-24 04:50:51 +10:00
|
|
|
// Copyright 2021 The piet-gpu authors.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
// Also licensed under MIT license, at your choice.
|
|
|
|
|
|
|
|
//! Implementation of gradients.
|
|
|
|
|
|
|
|
use std::collections::hash_map::{Entry, HashMap};
|
|
|
|
|
2022-03-18 09:00:08 +11:00
|
|
|
use piet::kurbo::Point;
|
|
|
|
use piet::{Color, FixedLinearGradient, GradientStop, FixedRadialGradient};
|
|
|
|
|
|
|
|
/// Radial gradient compatible with COLRv1 spec
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Colrv1RadialGradient {
|
|
|
|
/// The center of the iner circle.
|
|
|
|
pub center0: Point,
|
|
|
|
/// The offset of the origin relative to the center.
|
|
|
|
pub center1: Point,
|
|
|
|
/// The radius of the inner circle.
|
|
|
|
pub radius0: f64,
|
|
|
|
/// The radius of the outer circle.
|
|
|
|
pub radius1: f64,
|
|
|
|
/// The stops.
|
|
|
|
pub stops: Vec<GradientStop>,
|
|
|
|
}
|
2021-06-24 04:50:51 +10:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct BakedGradient {
|
|
|
|
ramp: Vec<u32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct LinearGradient {
|
2021-08-04 02:04:19 +10:00
|
|
|
pub(crate) start: [f32; 2],
|
|
|
|
pub(crate) end: [f32; 2],
|
|
|
|
pub(crate) ramp_id: u32,
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
|
|
|
|
2022-03-18 09:00:08 +11:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct RadialGradient {
|
|
|
|
pub(crate) start: [f32; 2],
|
|
|
|
pub(crate) end: [f32; 2],
|
|
|
|
pub(crate) r0: f32,
|
|
|
|
pub(crate) r1: f32,
|
|
|
|
pub(crate) ramp_id: u32,
|
|
|
|
}
|
|
|
|
|
2021-06-24 04:50:51 +10:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct RampCache {
|
|
|
|
ramps: Vec<GradientRamp>,
|
|
|
|
map: HashMap<GradientRamp, usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
|
|
|
struct GradientRamp(Vec<u32>);
|
|
|
|
|
|
|
|
pub const N_SAMPLES: usize = 512;
|
|
|
|
// TODO: make this dynamic
|
|
|
|
pub const N_GRADIENTS: usize = 256;
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
struct PremulRgba([f64; 4]);
|
|
|
|
|
|
|
|
impl PremulRgba {
|
|
|
|
fn from_color(c: &Color) -> PremulRgba {
|
|
|
|
let rgba = c.as_rgba();
|
|
|
|
let a = rgba.3;
|
|
|
|
// TODO: sRGB nonlinearity? This is complicated.
|
|
|
|
PremulRgba([rgba.0 * a, rgba.1 * a, rgba.2 * a, a])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_u32(&self) -> u32 {
|
|
|
|
let z = self.0;
|
2021-08-04 02:04:19 +10:00
|
|
|
let r = (z[0].max(0.0).min(1.0) * 255.0).round() as u32;
|
|
|
|
let g = (z[1].max(0.0).min(1.0) * 255.0).round() as u32;
|
|
|
|
let b = (z[2].max(0.0).min(1.0) * 255.0).round() as u32;
|
|
|
|
let a = (z[3].max(0.0).min(1.0) * 255.0).round() as u32;
|
|
|
|
r | (g << 8) | (b << 16) | (a << 24)
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
fn lerp(&self, other: PremulRgba, t: f64) -> PremulRgba {
|
|
|
|
fn l(a: f64, b: f64, t: f64) -> f64 {
|
|
|
|
a * (1.0 - t) + b * t
|
|
|
|
}
|
|
|
|
let a = self.0;
|
|
|
|
let b = other.0;
|
2021-08-04 02:04:19 +10:00
|
|
|
PremulRgba([
|
|
|
|
l(a[0], b[0], t),
|
|
|
|
l(a[1], b[1], t),
|
|
|
|
l(a[2], b[2], t),
|
|
|
|
l(a[3], b[3], t),
|
|
|
|
])
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GradientRamp {
|
|
|
|
fn from_stops(stops: &[GradientStop]) -> GradientRamp {
|
|
|
|
let mut last_u = 0.0;
|
|
|
|
let mut last_c = PremulRgba::from_color(&stops[0].color);
|
|
|
|
let mut this_u = last_u;
|
|
|
|
let mut this_c = last_c;
|
|
|
|
let mut j = 0;
|
2021-08-04 02:04:19 +10:00
|
|
|
let v = (0..N_SAMPLES)
|
|
|
|
.map(|i| {
|
|
|
|
let u = (i as f64) / (N_SAMPLES - 1) as f64;
|
|
|
|
while u > this_u {
|
|
|
|
last_u = this_u;
|
|
|
|
last_c = this_c;
|
|
|
|
if let Some(s) = stops.get(j + 1) {
|
|
|
|
this_u = s.pos as f64;
|
|
|
|
this_c = PremulRgba::from_color(&s.color);
|
|
|
|
j += 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
2021-08-04 02:04:19 +10:00
|
|
|
let du = this_u - last_u;
|
|
|
|
let c = if du < 1e-9 {
|
|
|
|
this_c
|
|
|
|
} else {
|
|
|
|
last_c.lerp(this_c, (u - last_u) / du)
|
|
|
|
};
|
|
|
|
c.to_u32()
|
|
|
|
})
|
|
|
|
.collect();
|
2021-06-24 04:50:51 +10:00
|
|
|
GradientRamp(v)
|
|
|
|
}
|
2021-08-04 02:04:19 +10:00
|
|
|
|
|
|
|
/// For debugging/development.
|
|
|
|
pub(crate) fn dump(&self) {
|
|
|
|
for val in &self.0 {
|
|
|
|
println!("{:x}", val);
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RampCache {
|
|
|
|
/// Add a gradient ramp to the cache.
|
|
|
|
///
|
|
|
|
/// Currently there is no eviction, so if the gradient is animating, there may
|
|
|
|
/// be resource leaks. In order to support lifetime management, the signature
|
|
|
|
/// should probably change so it returns a ref-counted handle, so that eviction
|
|
|
|
/// is deferred until the last handle is dropped.
|
|
|
|
///
|
|
|
|
/// This function is pretty expensive, but the result is lightweight.
|
|
|
|
fn add_ramp(&mut self, ramp: &[GradientStop]) -> usize {
|
|
|
|
let ramp = GradientRamp::from_stops(ramp);
|
|
|
|
match self.map.entry(ramp) {
|
|
|
|
Entry::Occupied(o) => *o.get(),
|
|
|
|
Entry::Vacant(v) => {
|
|
|
|
let idx = self.ramps.len();
|
|
|
|
self.ramps.push(v.key().clone());
|
|
|
|
v.insert(idx);
|
|
|
|
idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_linear_gradient(&mut self, lin: &FixedLinearGradient) -> LinearGradient {
|
|
|
|
let ramp_id = self.add_ramp(&lin.stops);
|
|
|
|
LinearGradient {
|
|
|
|
ramp_id: ramp_id as u32,
|
|
|
|
start: crate::render_ctx::to_f32_2(lin.start),
|
|
|
|
end: crate::render_ctx::to_f32_2(lin.end),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 09:00:08 +11:00
|
|
|
pub fn add_radial_gradient(&mut self, rad: &FixedRadialGradient) -> RadialGradient {
|
|
|
|
let ramp_id = self.add_ramp(&rad.stops);
|
|
|
|
RadialGradient {
|
|
|
|
ramp_id: ramp_id as u32,
|
|
|
|
start: crate::render_ctx::to_f32_2(rad.center + rad.origin_offset),
|
|
|
|
end: crate::render_ctx::to_f32_2(rad.center),
|
|
|
|
r0: 0.0,
|
|
|
|
r1: rad.radius as f32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_radial_gradient_colrv1(&mut self, rad: &Colrv1RadialGradient) -> RadialGradient {
|
|
|
|
let ramp_id = self.add_ramp(&rad.stops);
|
|
|
|
RadialGradient {
|
|
|
|
ramp_id: ramp_id as u32,
|
|
|
|
start: crate::render_ctx::to_f32_2(rad.center0),
|
|
|
|
end: crate::render_ctx::to_f32_2(rad.center1),
|
|
|
|
r0: rad.radius0 as f32,
|
|
|
|
r1: rad.radius1 as f32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 02:04:19 +10:00
|
|
|
/// Dump the contents of a gradient. This is for debugging.
|
|
|
|
#[allow(unused)]
|
|
|
|
pub(crate) fn dump_gradient(&self, lin: &LinearGradient) {
|
|
|
|
println!("id = {}", lin.ramp_id);
|
|
|
|
self.ramps[lin.ramp_id as usize].dump();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the ramp data.
|
|
|
|
///
|
|
|
|
/// This concatenates all the ramps; we'll want a more sophisticated approach to
|
|
|
|
/// incremental update.
|
|
|
|
pub fn get_ramp_data(&self) -> Vec<u32> {
|
|
|
|
let mut result = Vec::with_capacity(N_SAMPLES * self.ramps.len());
|
|
|
|
for ramp in &self.ramps {
|
|
|
|
result.extend(&ramp.0);
|
|
|
|
}
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 04:50:51 +10:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2021-08-04 02:04:19 +10:00
|
|
|
use super::RampCache;
|
|
|
|
use piet::kurbo::Point;
|
|
|
|
use piet::{Color, FixedLinearGradient, GradientStop};
|
|
|
|
|
2021-06-24 04:50:51 +10:00
|
|
|
#[test]
|
2021-08-04 02:04:19 +10:00
|
|
|
fn simple_ramp() {
|
|
|
|
let stops = vec![
|
|
|
|
GradientStop {
|
|
|
|
color: Color::WHITE,
|
|
|
|
pos: 0.0,
|
|
|
|
},
|
|
|
|
GradientStop {
|
|
|
|
color: Color::BLACK,
|
|
|
|
pos: 1.0,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let mut cache = RampCache::default();
|
|
|
|
let lin = FixedLinearGradient {
|
|
|
|
start: Point::new(0.0, 0.0),
|
|
|
|
end: Point::new(0.0, 1.0),
|
|
|
|
stops,
|
|
|
|
};
|
|
|
|
let our_lin = cache.add_linear_gradient(&lin);
|
|
|
|
cache.dump_gradient(&our_lin);
|
2021-06-24 04:50:51 +10:00
|
|
|
}
|
|
|
|
}
|