gradient_builder.rs (6500B)
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 5 use crate::display_item as di; 6 use crate::units::*; 7 8 9 /// Construct a gradient to be used in display lists. 10 /// 11 /// Each gradient needs at least two stops. 12 pub struct GradientBuilder { 13 stops: Vec<di::GradientStop>, 14 } 15 16 impl 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() >= 1); 131 132 let first = *stops.first().unwrap(); 133 let last = *stops.last().unwrap(); 134 135 let stops_delta = last.offset - first.offset; 136 137 if stops_delta > 0.000001 { 138 for stop in stops { 139 stop.offset = (stop.offset - first.offset) / stops_delta; 140 } 141 142 (first.offset, last.offset) 143 } else if stops_delta.is_nan() { 144 // We have no good way to render a NaN offset, but make something 145 // that is at least renderable. 146 stops.clear(); 147 stops.push(di::GradientStop { color: last.color, offset: 0.0, }); 148 stops.push(di::GradientStop { color: last.color, offset: 1.0, }); 149 150 (0.0, 1.0) 151 } else { 152 // We have a degenerate gradient and can't accurately transform the stops 153 // what happens here depends on the repeat behavior, but in any case 154 // we reconstruct the gradient stops to something simpler and equivalent 155 stops.clear(); 156 157 match extend_mode { 158 di::ExtendMode::Clamp => { 159 // This gradient is two colors split at the offset of the stops, 160 // so create a gradient with two colors split at 0.5 and adjust 161 // the gradient line so 0.5 is at the offset of the stops 162 stops.push(di::GradientStop { color: first.color, offset: 0.0, }); 163 stops.push(di::GradientStop { color: first.color, offset: 0.5, }); 164 stops.push(di::GradientStop { color: last.color, offset: 0.5, }); 165 stops.push(di::GradientStop { color: last.color, offset: 1.0, }); 166 167 let offset = last.offset; 168 169 (offset - 0.5, offset + 0.5) 170 } 171 di::ExtendMode::Repeat => { 172 // A repeating gradient with stops that are all in the same 173 // position should just display the last color. I believe the 174 // spec says that it should be the average color of the gradient, 175 // but this matches what Gecko and Blink does 176 stops.push(di::GradientStop { color: last.color, offset: 0.0, }); 177 stops.push(di::GradientStop { color: last.color, offset: 1.0, }); 178 179 (0.0, 1.0) 180 } 181 } 182 } 183 } 184 }