1use app_units::Au;
6use euclid::Size2D;
7use style::Zero;
8use style::color::mix::ColorInterpolationMethod;
9use style::properties::ComputedValues;
10use style::values::computed::image::{EndingShape, Gradient, LineDirection};
11use style::values::computed::{Angle, AngleOrPercentage, Color, LengthPercentage, Position};
12use style::values::generics::image::{
13 Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
14};
15use webrender_api::units::LayoutPixel;
16use webrender_api::{
17 self as wr, ConicGradient as WebRenderConicGradient, Gradient as WebRenderLinearGradient,
18 RadialGradient as WebRenderRadialGradient, units,
19};
20use wr::ColorF;
21
22pub(super) enum WebRenderGradient {
23 Linear(WebRenderLinearGradient),
24 Radial(WebRenderRadialGradient),
25 Conic(WebRenderConicGradient),
26}
27
28pub(super) fn build(
29 style: &ComputedValues,
30 gradient: &Gradient,
31 size: Size2D<f32, LayoutPixel>,
32 builder: &mut super::DisplayListBuilder,
33) -> WebRenderGradient {
34 match gradient {
35 Gradient::Linear {
36 items,
37 direction,
38 color_interpolation_method,
39 flags,
40 compat_mode: _,
41 } => build_linear(
42 style,
43 items,
44 direction,
45 color_interpolation_method,
46 *flags,
47 size,
48 builder,
49 ),
50 Gradient::Radial {
51 shape,
52 position,
53 color_interpolation_method,
54 items,
55 flags,
56 compat_mode: _,
57 } => build_radial(
58 style,
59 items,
60 shape,
61 position,
62 color_interpolation_method,
63 *flags,
64 size,
65 builder,
66 ),
67 Gradient::Conic {
68 angle,
69 position,
70 color_interpolation_method,
71 items,
72 flags,
73 } => build_conic(
74 style,
75 *angle,
76 position,
77 *color_interpolation_method,
78 items,
79 *flags,
80 size,
81 builder,
82 ),
83 }
84}
85
86pub(super) fn build_linear(
88 style: &ComputedValues,
89 items: &[GradientItem<Color, LengthPercentage>],
90 line_direction: &LineDirection,
91 _color_interpolation_method: &ColorInterpolationMethod,
92 flags: GradientFlags,
93 gradient_box: Size2D<f32, LayoutPixel>,
94 builder: &mut super::DisplayListBuilder,
95) -> WebRenderGradient {
96 use style::values::specified::position::HorizontalPositionKeyword::*;
97 use style::values::specified::position::VerticalPositionKeyword::*;
98 use units::LayoutVector2D as Vec2;
99
100 let direction = match line_direction {
102 LineDirection::Horizontal(Right) => Vec2::new(1., 0.),
103 LineDirection::Vertical(Top) => Vec2::new(0., -1.),
104 LineDirection::Horizontal(Left) => Vec2::new(-1., 0.),
105 LineDirection::Vertical(Bottom) => Vec2::new(0., 1.),
106
107 LineDirection::Angle(angle) => {
108 let radians = angle.radians();
109 Vec2::new(radians.sin(), -radians.cos())
113 },
114
115 LineDirection::Corner(horizontal, vertical) => {
116 let x = match horizontal {
137 Right => gradient_box.height,
138 Left => -gradient_box.height,
139 };
140 let y = match vertical {
141 Top => -gradient_box.width,
142 Bottom => gradient_box.width,
143 };
144
145 Vec2::new(x, y).normalize()
149 },
150 };
151
152 let gradient_line_length =
166 (gradient_box.width * direction.x).abs() + (gradient_box.height * direction.y).abs();
167
168 let half_gradient_line = direction * (gradient_line_length / 2.);
169 let center = (gradient_box / 2.).to_vector().to_point();
170 let start_point = center - half_gradient_line;
171 let end_point = center + half_gradient_line;
172
173 let mut color_stops =
174 gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
175 let stops = fixup_stops(&mut color_stops);
176 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
177 wr::ExtendMode::Repeat
178 } else {
179 wr::ExtendMode::Clamp
180 };
181 WebRenderGradient::Linear(builder.wr().create_gradient(
182 start_point,
183 end_point,
184 stops,
185 extend_mode,
186 ))
187}
188
189#[allow(clippy::too_many_arguments)]
191pub(super) fn build_radial(
192 style: &ComputedValues,
193 items: &[GradientItem<Color, LengthPercentage>],
194 shape: &EndingShape,
195 center: &Position,
196 _color_interpolation_method: &ColorInterpolationMethod,
197 flags: GradientFlags,
198 gradient_box: Size2D<f32, LayoutPixel>,
199 builder: &mut super::DisplayListBuilder,
200) -> WebRenderGradient {
201 let center = units::LayoutPoint::new(
202 center
203 .horizontal
204 .to_used_value(Au::from_f32_px(gradient_box.width))
205 .to_f32_px(),
206 center
207 .vertical
208 .to_used_value(Au::from_f32_px(gradient_box.height))
209 .to_f32_px(),
210 );
211 let radii = match shape {
212 EndingShape::Circle(circle) => {
213 let radius = match circle {
214 Circle::Radius(r) => r.0.px(),
215 Circle::Extent(extent) => match extent {
216 ShapeExtent::ClosestSide | ShapeExtent::Contain => {
217 let vec = abs_vector_to_corner(gradient_box, center, f32::min);
218 vec.x.min(vec.y)
219 },
220 ShapeExtent::FarthestSide => {
221 let vec = abs_vector_to_corner(gradient_box, center, f32::max);
222 vec.x.max(vec.y)
223 },
224 ShapeExtent::ClosestCorner => {
225 abs_vector_to_corner(gradient_box, center, f32::min).length()
226 },
227 ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
228 abs_vector_to_corner(gradient_box, center, f32::max).length()
229 },
230 },
231 };
232 units::LayoutSize::new(radius, radius)
233 },
234 EndingShape::Ellipse(Ellipse::Radii(rx, ry)) => units::LayoutSize::new(
235 rx.0.to_used_value(Au::from_f32_px(gradient_box.width))
236 .to_f32_px(),
237 ry.0.to_used_value(Au::from_f32_px(gradient_box.height))
238 .to_f32_px(),
239 ),
240 EndingShape::Ellipse(Ellipse::Extent(extent)) => match extent {
241 ShapeExtent::ClosestSide | ShapeExtent::Contain => {
242 abs_vector_to_corner(gradient_box, center, f32::min).to_size()
243 },
244 ShapeExtent::FarthestSide => {
245 abs_vector_to_corner(gradient_box, center, f32::max).to_size()
246 },
247 ShapeExtent::ClosestCorner => {
248 abs_vector_to_corner(gradient_box, center, f32::min).to_size() *
249 (std::f32::consts::FRAC_1_SQRT_2 * 2.0)
250 },
251 ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
252 abs_vector_to_corner(gradient_box, center, f32::max).to_size() *
253 (std::f32::consts::FRAC_1_SQRT_2 * 2.0)
254 },
255 },
256 };
257
258 fn abs_vector_to_corner(
261 gradient_box: units::LayoutSize,
262 center: units::LayoutPoint,
263 select: impl Fn(f32, f32) -> f32,
264 ) -> units::LayoutVector2D {
265 let left = center.x.abs();
266 let top = center.y.abs();
267 let right = (gradient_box.width - center.x).abs();
268 let bottom = (gradient_box.height - center.y).abs();
269 units::LayoutVector2D::new(select(left, right), select(top, bottom))
270 }
271
272 let gradient_line_length = radii.width;
276
277 let mut color_stops =
278 gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
279 let stops = fixup_stops(&mut color_stops);
280 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
281 wr::ExtendMode::Repeat
282 } else {
283 wr::ExtendMode::Clamp
284 };
285 WebRenderGradient::Radial(builder.wr().create_radial_gradient(
286 center,
287 radii,
288 stops,
289 extend_mode,
290 ))
291}
292
293#[allow(clippy::too_many_arguments)]
295fn build_conic(
296 style: &ComputedValues,
297 angle: Angle,
298 center: &Position,
299 _color_interpolation_method: ColorInterpolationMethod,
300 items: &[GradientItem<Color, AngleOrPercentage>],
301 flags: GradientFlags,
302 gradient_box: Size2D<f32, LayoutPixel>,
303 builder: &mut super::DisplayListBuilder<'_>,
304) -> WebRenderGradient {
305 let center = units::LayoutPoint::new(
306 center
307 .horizontal
308 .to_used_value(Au::from_f32_px(gradient_box.width))
309 .to_f32_px(),
310 center
311 .vertical
312 .to_used_value(Au::from_f32_px(gradient_box.height))
313 .to_f32_px(),
314 );
315 let mut color_stops = conic_gradient_items_to_color_stops(style, items);
316 let stops = fixup_stops(&mut color_stops);
317 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
318 wr::ExtendMode::Repeat
319 } else {
320 wr::ExtendMode::Clamp
321 };
322 WebRenderGradient::Conic(builder.wr().create_conic_gradient(
323 center,
324 angle.radians(),
325 stops,
326 extend_mode,
327 ))
328}
329
330fn conic_gradient_items_to_color_stops(
331 style: &ComputedValues,
332 items: &[GradientItem<Color, AngleOrPercentage>],
333) -> Vec<ColorStop<ColorF, f32>> {
334 items
345 .iter()
346 .filter_map(|item| {
347 match item {
348 GradientItem::SimpleColorStop(color) => Some(ColorStop {
349 color: super::rgba(style.resolve_color(color)),
350 position: None,
351 }),
352 GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
353 color: super::rgba(style.resolve_color(color)),
354 position: match position {
355 AngleOrPercentage::Percentage(percentage) => Some(percentage.0),
356 AngleOrPercentage::Angle(angle) => Some(angle.degrees() / 360.),
357 },
358 }),
359 GradientItem::InterpolationHint(_) => None,
362 }
363 })
364 .collect()
365}
366
367fn gradient_items_to_color_stops(
368 style: &ComputedValues,
369 items: &[GradientItem<Color, LengthPercentage>],
370 gradient_line_length: Au,
371) -> Vec<ColorStop<ColorF, f32>> {
372 items
383 .iter()
384 .filter_map(|item| {
385 match item {
386 GradientItem::SimpleColorStop(color) => Some(ColorStop {
387 color: super::rgba(style.resolve_color(color)),
388 position: None,
389 }),
390 GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
391 color: super::rgba(style.resolve_color(color)),
392 position: Some(if gradient_line_length.is_zero() {
393 0.
394 } else {
395 position
396 .to_used_value(gradient_line_length)
397 .scale_by(1. / gradient_line_length.to_f32_px())
398 .to_f32_px()
399 }),
400 }),
401 GradientItem::InterpolationHint(_) => None,
404 }
405 })
406 .collect()
407}
408
409fn fixup_stops(stops: &mut [ColorStop<ColorF, f32>]) -> Vec<wr::GradientStop> {
411 assert!(!stops.is_empty());
412
413 if let first_position @ None = &mut stops.first_mut().unwrap().position {
415 *first_position = Some(0.);
416 }
417 if let last_position @ None = &mut stops.last_mut().unwrap().position {
418 *last_position = Some(1.);
419 }
420
421 let mut iter = stops.iter_mut();
422 let mut max_so_far = iter.next().unwrap().position.unwrap();
423 for stop in iter {
424 if let Some(position) = &mut stop.position {
425 if *position < max_so_far {
426 *position = max_so_far
427 } else {
428 max_so_far = *position
429 }
430 }
431 }
432
433 let mut wr_stops = Vec::with_capacity(stops.len());
434 let mut iter = stops.iter().enumerate();
435 let (_, first) = iter.next().unwrap();
436 let first_stop_position = first.position.unwrap();
437 wr_stops.push(wr::GradientStop {
438 offset: first_stop_position,
439 color: first.color,
440 });
441 if stops.len() == 1 {
442 wr_stops.push(wr_stops[0]);
443 }
444
445 let mut last_positioned_stop_index = 0;
446 let mut last_positioned_stop_position = first_stop_position;
447 for (i, stop) in iter {
448 if let Some(position) = stop.position {
449 let step_count = i - last_positioned_stop_index;
450 if step_count > 1 {
451 let step = (position - last_positioned_stop_position) / step_count as f32;
452 for j in 1..step_count {
453 let color = stops[last_positioned_stop_index + j].color;
454 let offset = last_positioned_stop_position + j as f32 * step;
455 wr_stops.push(wr::GradientStop { offset, color })
456 }
457 }
458 last_positioned_stop_index = i;
459 last_positioned_stop_position = position;
460 wr_stops.push(wr::GradientStop {
461 offset: position,
462 color: stop.color,
463 })
464 }
465 }
466
467 wr_stops
468}