diff options
Diffstat (limited to 'gfx/wr/webrender_api/src/gradient_builder.rs')
-rw-r--r-- | gfx/wr/webrender_api/src/gradient_builder.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/gfx/wr/webrender_api/src/gradient_builder.rs b/gfx/wr/webrender_api/src/gradient_builder.rs new file mode 100644 index 0000000000..6347396f79 --- /dev/null +++ b/gfx/wr/webrender_api/src/gradient_builder.rs @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::display_item as di; +use crate::units::*; + + +/// Construct a gradient to be used in display lists. +/// +/// Each gradient needs at least two stops. +pub struct GradientBuilder { + stops: Vec<di::GradientStop>, +} + +impl GradientBuilder { + /// Create a new gradient builder. + pub fn new() -> Self { + GradientBuilder { + stops: Vec::new(), + } + } + + /// Create a gradient builder with a list of stops. + pub fn with_stops(stops: Vec<di::GradientStop>) -> GradientBuilder { + GradientBuilder { stops } + } + + /// Push an additional stop for the gradient. + pub fn push(&mut self, stop: di::GradientStop) { + self.stops.push(stop); + } + + /// Get a reference to the list of stops. + pub fn stops(&self) -> &[di::GradientStop] { + self.stops.as_ref() + } + + /// Return the gradient stops vector. + pub fn into_stops(self) -> Vec<di::GradientStop> { + self.stops + } + + /// Produce a linear gradient, normalize the stops. + pub fn gradient( + &mut self, + start_point: LayoutPoint, + end_point: LayoutPoint, + extend_mode: di::ExtendMode, + ) -> di::Gradient { + let (start_offset, end_offset) = self.normalize(extend_mode); + let start_to_end = end_point - start_point; + + di::Gradient { + start_point: start_point + start_to_end * start_offset, + end_point: start_point + start_to_end * end_offset, + extend_mode, + } + } + + /// Produce a radial gradient, normalize the stops. + /// + /// Will replace the gradient with a single color + /// if the radius negative. + pub fn radial_gradient( + &mut self, + center: LayoutPoint, + radius: LayoutSize, + extend_mode: di::ExtendMode, + ) -> di::RadialGradient { + if radius.width <= 0.0 || radius.height <= 0.0 { + // The shader cannot handle a non positive radius. So + // reuse the stops vector and construct an equivalent + // gradient. + let last_color = self.stops.last().unwrap().color; + + self.stops.clear(); + self.stops.push(di::GradientStop { offset: 0.0, color: last_color, }); + self.stops.push(di::GradientStop { offset: 1.0, color: last_color, }); + + return di::RadialGradient { + center, + radius: LayoutSize::new(1.0, 1.0), + start_offset: 0.0, + end_offset: 1.0, + extend_mode, + }; + } + + let (start_offset, end_offset) = + self.normalize(extend_mode); + + di::RadialGradient { + center, + radius, + start_offset, + end_offset, + extend_mode, + } + } + + /// Produce a conic gradient, normalize the stops. + pub fn conic_gradient( + &mut self, + center: LayoutPoint, + angle: f32, + extend_mode: di::ExtendMode, + ) -> di::ConicGradient { + let (start_offset, end_offset) = + self.normalize(extend_mode); + + di::ConicGradient { + center, + angle, + start_offset, + end_offset, + extend_mode, + } + } + + /// Gradients can be defined with stops outside the range of [0, 1] + /// when this happens the gradient needs to be normalized by adjusting + /// the gradient stops and gradient line into an equivalent gradient + /// with stops in the range [0, 1]. this is done by moving the beginning + /// of the gradient line to where stop[0] and the end of the gradient line + /// to stop[n-1]. this function adjusts the stops in place, and returns + /// the amount to adjust the gradient line start and stop. + fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) { + let stops = &mut self.stops; + assert!(stops.len() >= 2); + + let first = *stops.first().unwrap(); + let last = *stops.last().unwrap(); + + // Express the assertion so that if one of the offsets is NaN, we don't panic + // and instead take the branch that handles degenerate gradients. + assert!(!(first.offset > last.offset)); + + let stops_delta = last.offset - first.offset; + + if stops_delta > 0.000001 { + for stop in stops { + stop.offset = (stop.offset - first.offset) / stops_delta; + } + + (first.offset, last.offset) + } else { + // We have a degenerate gradient and can't accurately transform the stops + // what happens here depends on the repeat behavior, but in any case + // we reconstruct the gradient stops to something simpler and equivalent + stops.clear(); + + match extend_mode { + di::ExtendMode::Clamp => { + // This gradient is two colors split at the offset of the stops, + // so create a gradient with two colors split at 0.5 and adjust + // the gradient line so 0.5 is at the offset of the stops + stops.push(di::GradientStop { color: first.color, offset: 0.0, }); + stops.push(di::GradientStop { color: first.color, offset: 0.5, }); + stops.push(di::GradientStop { color: last.color, offset: 0.5, }); + stops.push(di::GradientStop { color: last.color, offset: 1.0, }); + + let offset = last.offset; + + (offset - 0.5, offset + 0.5) + } + di::ExtendMode::Repeat => { + // A repeating gradient with stops that are all in the same + // position should just display the last color. I believe the + // spec says that it should be the average color of the gradient, + // but this matches what Gecko and Blink does + stops.push(di::GradientStop { color: last.color, offset: 0.0, }); + stops.push(di::GradientStop { color: last.color, offset: 1.0, }); + + (0.0, 1.0) + } + } + } + } +} |