Order and sample gradient correctly

Zero-width gradient sections should be ‘ignored’.
The visible endpoints should be precisely the first
and last colors added at that point.

https://html.spec.whatwg.org/multipage/canvas.html#interpolation
This commit is contained in:
Veedrac 2020-05-11 21:39:16 +01:00
parent 6d8a89b8c5
commit 033a150639
1 changed files with 38 additions and 3 deletions

View File

@ -15,7 +15,7 @@ use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::vector::Vector2F;
use pathfinder_geometry::util as geometry_util; use pathfinder_geometry::util as geometry_util;
use pathfinder_simd::default::F32x2; use pathfinder_simd::default::F32x2;
use std::cmp::{Ordering, PartialOrd}; use std::cmp::Ordering;
use std::convert; use std::convert;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::mem; use std::mem;
@ -110,7 +110,7 @@ impl Gradient {
#[inline] #[inline]
pub fn add(&mut self, stop: ColorStop) { pub fn add(&mut self, stop: ColorStop) {
let index = self.stops.binary_search_by(|other| { let index = self.stops.binary_search_by(|other| {
other.offset.partial_cmp(&stop.offset).unwrap() if other.offset <= stop.offset { Ordering::Less } else { Ordering::Greater }
}).unwrap_or_else(convert::identity); }).unwrap_or_else(convert::identity);
self.stops.insert(index, stop); self.stops.insert(index, stop);
} }
@ -138,8 +138,9 @@ impl Gradient {
t = geometry_util::clamp(t, 0.0, 1.0); t = geometry_util::clamp(t, 0.0, 1.0);
let last_index = self.stops.len() - 1; let last_index = self.stops.len() - 1;
let upper_index = self.stops.binary_search_by(|stop| { let upper_index = self.stops.binary_search_by(|stop| {
stop.offset.partial_cmp(&t).unwrap_or(Ordering::Less) if stop.offset < t || stop.offset == 0.0 { Ordering::Less } else { Ordering::Greater }
}).unwrap_or_else(convert::identity).min(last_index); }).unwrap_or_else(convert::identity).min(last_index);
let lower_index = if upper_index > 0 { upper_index - 1 } else { upper_index }; let lower_index = if upper_index > 0 { upper_index - 1 } else { upper_index };
@ -205,3 +206,37 @@ impl RadialGradientLine for Vector2F {
LineSegment2F::new(self, self) LineSegment2F::new(self, self)
} }
} }
#[cfg(test)]
mod test {
use crate::gradient::Gradient;
use pathfinder_color::ColorU;
use pathfinder_geometry::vector::Vector2F;
#[test]
fn stable_order() {
let mut grad = Gradient::linear_from_points(Vector2F::default(), Vector2F::default());
for i in 0..110 {
grad.add_color_stop(ColorU::new(i, 0, 0, 1), (i % 11) as f32 / 10.0);
}
// Check that it sorted stably
assert!(grad.stops.windows(2).all(|w| {
w[0].offset < w[1].offset || w[0].color.r < w[1].color.r
}));
}
#[test]
fn never_sample_zero_width() {
let mut grad = Gradient::linear_from_points(Vector2F::default(), Vector2F::default());
for i in 0..110 {
let zero_width = (i == 0) || (11 <= i && i < 99) || (i == 109);
grad.add_color_stop(ColorU::new(if zero_width { 255 } else { 0 }, 0, 0, 1), (i % 11) as f32 / 10.0);
}
for i in 0..11 {
let sample = grad.sample(i as f32 / 10.0);
assert!(sample.r == 0, "{} {}", i, sample.r);
}
}
}