azul_webrender_api/gradient_builder.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::display_item as di;
6use crate::units::*;
7
8
9/// Construct a gradient to be used in display lists.
10///
11/// Each gradient needs at least two stops.
12pub struct GradientBuilder {
13 stops: Vec<di::GradientStop>,
14}
15
16impl GradientBuilder {
17 /// Create a new gradient builder.
18 pub fn new() -> Self {
19 GradientBuilder {
20 stops: Vec::new(),
21 }
22 }
23
24 /// Create a gradient builder with a list of stops.
25 pub fn with_stops(stops: Vec<di::GradientStop>) -> GradientBuilder {
26 GradientBuilder { stops }
27 }
28
29 /// Push an additional stop for the gradient.
30 pub fn push(&mut self, stop: di::GradientStop) {
31 self.stops.push(stop);
32 }
33
34 /// Get a reference to the list of stops.
35 pub fn stops(&self) -> &[di::GradientStop] {
36 self.stops.as_ref()
37 }
38
39 /// Return the gradient stops vector.
40 pub fn into_stops(self) -> Vec<di::GradientStop> {
41 self.stops
42 }
43
44 /// Produce a linear gradient, normalize the stops.
45 pub fn gradient(
46 &mut self,
47 start_point: LayoutPoint,
48 end_point: LayoutPoint,
49 extend_mode: di::ExtendMode,
50 ) -> di::Gradient {
51 let (start_offset, end_offset) = self.normalize(extend_mode);
52 let start_to_end = end_point - start_point;
53
54 di::Gradient {
55 start_point: start_point + start_to_end * start_offset,
56 end_point: start_point + start_to_end * end_offset,
57 extend_mode,
58 }
59 }
60
61 /// Produce a radial gradient, normalize the stops.
62 ///
63 /// Will replace the gradient with a single color
64 /// if the radius negative.
65 pub fn radial_gradient(
66 &mut self,
67 center: LayoutPoint,
68 radius: LayoutSize,
69 extend_mode: di::ExtendMode,
70 ) -> di::RadialGradient {
71 if radius.width <= 0.0 || radius.height <= 0.0 {
72 // The shader cannot handle a non positive radius. So
73 // reuse the stops vector and construct an equivalent
74 // gradient.
75 let last_color = self.stops.last().unwrap().color;
76
77 self.stops.clear();
78 self.stops.push(di::GradientStop { offset: 0.0, color: last_color, });
79 self.stops.push(di::GradientStop { offset: 1.0, color: last_color, });
80
81 return di::RadialGradient {
82 center,
83 radius: LayoutSize::new(1.0, 1.0),
84 start_offset: 0.0,
85 end_offset: 1.0,
86 extend_mode,
87 };
88 }
89
90 let (start_offset, end_offset) =
91 self.normalize(extend_mode);
92
93 di::RadialGradient {
94 center,
95 radius,
96 start_offset,
97 end_offset,
98 extend_mode,
99 }
100 }
101
102 /// Produce a conic gradient, normalize the stops.
103 pub fn conic_gradient(
104 &mut self,
105 center: LayoutPoint,
106 angle: f32,
107 extend_mode: di::ExtendMode,
108 ) -> di::ConicGradient {
109 let (start_offset, end_offset) =
110 self.normalize(extend_mode);
111
112 di::ConicGradient {
113 center,
114 angle,
115 start_offset,
116 end_offset,
117 extend_mode,
118 }
119 }
120
121 /// Gradients can be defined with stops outside the range of [0, 1]
122 /// when this happens the gradient needs to be normalized by adjusting
123 /// the gradient stops and gradient line into an equivalent gradient
124 /// with stops in the range [0, 1]. this is done by moving the beginning
125 /// of the gradient line to where stop[0] and the end of the gradient line
126 /// to stop[n-1]. this function adjusts the stops in place, and returns
127 /// the amount to adjust the gradient line start and stop.
128 fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) {
129 let stops = &mut self.stops;
130 assert!(stops.len() >= 2);
131
132 let first = *stops.first().unwrap();
133 let last = *stops.last().unwrap();
134
135 // Express the assertion so that if one of the offsets is NaN, we don't panic
136 // and instead take the branch that handles degenerate gradients.
137 assert!(!(first.offset > last.offset));
138
139 let stops_delta = last.offset - first.offset;
140
141 if stops_delta > 0.000001 {
142 for stop in stops {
143 stop.offset = (stop.offset - first.offset) / stops_delta;
144 }
145
146 (first.offset, last.offset)
147 } else {
148 // We have a degenerate gradient and can't accurately transform the stops
149 // what happens here depends on the repeat behavior, but in any case
150 // we reconstruct the gradient stops to something simpler and equivalent
151 stops.clear();
152
153 match extend_mode {
154 di::ExtendMode::Clamp => {
155 // This gradient is two colors split at the offset of the stops,
156 // so create a gradient with two colors split at 0.5 and adjust
157 // the gradient line so 0.5 is at the offset of the stops
158 stops.push(di::GradientStop { color: first.color, offset: 0.0, });
159 stops.push(di::GradientStop { color: first.color, offset: 0.5, });
160 stops.push(di::GradientStop { color: last.color, offset: 0.5, });
161 stops.push(di::GradientStop { color: last.color, offset: 1.0, });
162
163 let offset = last.offset;
164
165 (offset - 0.5, offset + 0.5)
166 }
167 di::ExtendMode::Repeat => {
168 // A repeating gradient with stops that are all in the same
169 // position should just display the last color. I believe the
170 // spec says that it should be the average color of the gradient,
171 // but this matches what Gecko and Blink does
172 stops.push(di::GradientStop { color: last.color, offset: 0.0, });
173 stops.push(di::GradientStop { color: last.color, offset: 1.0, });
174
175 (0.0, 1.0)
176 }
177 }
178 }
179 }
180}