1use app_units::Au;
6use euclid::Size2D;
7use style::Zero;
8use style::color::mix::{ColorInterpolationMethod, ColorMixItem, HueInterpolationMethod, mix_many};
9use style::color::{AbsoluteColor, ColorSpace};
10use style::properties::ComputedValues;
11use style::values::computed::image::{EndingShape, Gradient, LineDirection};
12use style::values::computed::{Angle, AngleOrPercentage, Color, LengthPercentage, Position};
13use style::values::generics::color::ColorMixFlags;
14use style::values::generics::image::{
15 Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
16};
17use webrender_api::units::LayoutPixel;
18use webrender_api::{
19 self as wr, ConicGradient as WebRenderConicGradient, ExtendMode,
20 Gradient as WebRenderLinearGradient, RadialGradient as WebRenderRadialGradient, units,
21};
22
23pub(super) enum WebRenderGradient {
24 Linear(WebRenderLinearGradient),
25 Radial(WebRenderRadialGradient),
26 Conic(WebRenderConicGradient),
27}
28
29pub(super) fn build(
30 style: &ComputedValues,
31 gradient: &Gradient,
32 size: Size2D<f32, LayoutPixel>,
33 builder: &mut super::DisplayListBuilder,
34) -> WebRenderGradient {
35 match gradient {
36 Gradient::Linear {
37 items,
38 direction,
39 color_interpolation_method,
40 flags,
41 compat_mode: _,
42 } => build_linear(
43 style,
44 items,
45 direction,
46 color_interpolation_method,
47 *flags,
48 size,
49 builder,
50 ),
51 Gradient::Radial {
52 shape,
53 position,
54 color_interpolation_method,
55 items,
56 flags,
57 compat_mode: _,
58 } => build_radial(
59 style,
60 items,
61 shape,
62 position,
63 color_interpolation_method,
64 *flags,
65 size,
66 builder,
67 ),
68 Gradient::Conic {
69 angle,
70 position,
71 color_interpolation_method,
72 items,
73 flags,
74 } => build_conic(
75 style,
76 *angle,
77 position,
78 color_interpolation_method,
79 items,
80 *flags,
81 size,
82 builder,
83 ),
84 }
85}
86
87pub(super) fn build_linear(
89 style: &ComputedValues,
90 items: &[GradientItem<Color, LengthPercentage>],
91 line_direction: &LineDirection,
92 color_interpolation_method: &ColorInterpolationMethod,
93 flags: GradientFlags,
94 gradient_box: Size2D<f32, LayoutPixel>,
95 builder: &mut super::DisplayListBuilder,
96) -> WebRenderGradient {
97 use style::values::specified::position::HorizontalPositionKeyword::*;
98 use style::values::specified::position::VerticalPositionKeyword::*;
99 use units::LayoutVector2D as Vec2;
100
101 let direction = match line_direction {
103 LineDirection::Horizontal(Right) => Vec2::new(1., 0.),
104 LineDirection::Vertical(Top) => Vec2::new(0., -1.),
105 LineDirection::Horizontal(Left) => Vec2::new(-1., 0.),
106 LineDirection::Vertical(Bottom) => Vec2::new(0., 1.),
107
108 LineDirection::Angle(angle) => {
109 let radians = angle.radians();
110 Vec2::new(radians.sin(), -radians.cos())
114 },
115
116 LineDirection::Corner(horizontal, vertical) => {
117 let x = match horizontal {
138 Right => gradient_box.height,
139 Left => -gradient_box.height,
140 };
141 let y = match vertical {
142 Top => -gradient_box.width,
143 Bottom => gradient_box.width,
144 };
145
146 Vec2::new(x, y).normalize()
150 },
151 };
152
153 let gradient_line_length =
167 (gradient_box.width * direction.x).abs() + (gradient_box.height * direction.y).abs();
168
169 let half_gradient_line = direction * (gradient_line_length / 2.);
170 let center = (gradient_box / 2.).to_vector().to_point();
171 let start_point = center - half_gradient_line;
172 let end_point = center + half_gradient_line;
173
174 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
175 wr::ExtendMode::Repeat
176 } else {
177 wr::ExtendMode::Clamp
178 };
179
180 let mut color_stops =
181 gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
182 let stops = create_webrender_stops(&mut color_stops, color_interpolation_method, extend_mode);
183
184 WebRenderGradient::Linear(builder.wr().create_gradient(
185 start_point,
186 end_point,
187 stops,
188 extend_mode,
189 ))
190}
191
192#[expect(clippy::too_many_arguments)]
194pub(super) fn build_radial(
195 style: &ComputedValues,
196 items: &[GradientItem<Color, LengthPercentage>],
197 shape: &EndingShape,
198 center: &Position,
199 color_interpolation_method: &ColorInterpolationMethod,
200 flags: GradientFlags,
201 gradient_box: Size2D<f32, LayoutPixel>,
202 builder: &mut super::DisplayListBuilder,
203) -> WebRenderGradient {
204 let center = units::LayoutPoint::new(
205 center
206 .horizontal
207 .to_used_value(Au::from_f32_px(gradient_box.width))
208 .to_f32_px(),
209 center
210 .vertical
211 .to_used_value(Au::from_f32_px(gradient_box.height))
212 .to_f32_px(),
213 );
214 let radii = match shape {
215 EndingShape::Circle(circle) => {
216 let radius = match circle {
217 Circle::Radius(r) => r.0.px(),
218 Circle::Extent(extent) => match extent {
219 ShapeExtent::ClosestSide | ShapeExtent::Contain => {
220 let vec = abs_vector_to_corner(gradient_box, center, f32::min);
221 vec.x.min(vec.y)
222 },
223 ShapeExtent::FarthestSide => {
224 let vec = abs_vector_to_corner(gradient_box, center, f32::max);
225 vec.x.max(vec.y)
226 },
227 ShapeExtent::ClosestCorner => {
228 abs_vector_to_corner(gradient_box, center, f32::min).length()
229 },
230 ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
231 abs_vector_to_corner(gradient_box, center, f32::max).length()
232 },
233 },
234 };
235 units::LayoutSize::new(radius, radius)
236 },
237 EndingShape::Ellipse(Ellipse::Radii(rx, ry)) => units::LayoutSize::new(
238 rx.0.to_used_value(Au::from_f32_px(gradient_box.width))
239 .to_f32_px(),
240 ry.0.to_used_value(Au::from_f32_px(gradient_box.height))
241 .to_f32_px(),
242 ),
243 EndingShape::Ellipse(Ellipse::Extent(extent)) => match extent {
244 ShapeExtent::ClosestSide | ShapeExtent::Contain => {
245 abs_vector_to_corner(gradient_box, center, f32::min).to_size()
246 },
247 ShapeExtent::FarthestSide => {
248 abs_vector_to_corner(gradient_box, center, f32::max).to_size()
249 },
250 ShapeExtent::ClosestCorner => {
251 abs_vector_to_corner(gradient_box, center, f32::min).to_size() *
252 (std::f32::consts::FRAC_1_SQRT_2 * 2.0)
253 },
254 ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
255 abs_vector_to_corner(gradient_box, center, f32::max).to_size() *
256 (std::f32::consts::FRAC_1_SQRT_2 * 2.0)
257 },
258 },
259 };
260
261 fn abs_vector_to_corner(
264 gradient_box: units::LayoutSize,
265 center: units::LayoutPoint,
266 select: impl Fn(f32, f32) -> f32,
267 ) -> units::LayoutVector2D {
268 let left = center.x.abs();
269 let top = center.y.abs();
270 let right = (gradient_box.width - center.x).abs();
271 let bottom = (gradient_box.height - center.y).abs();
272 units::LayoutVector2D::new(select(left, right), select(top, bottom))
273 }
274
275 let gradient_line_length = radii.width;
279
280 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
281 wr::ExtendMode::Repeat
282 } else {
283 wr::ExtendMode::Clamp
284 };
285
286 let mut color_stops =
287 gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
288 let stops = create_webrender_stops(&mut color_stops, color_interpolation_method, extend_mode);
289
290 WebRenderGradient::Radial(builder.wr().create_radial_gradient(
291 center,
292 radii,
293 stops,
294 extend_mode,
295 ))
296}
297
298#[expect(clippy::too_many_arguments)]
300fn build_conic(
301 style: &ComputedValues,
302 angle: Angle,
303 center: &Position,
304 color_interpolation_method: &ColorInterpolationMethod,
305 items: &[GradientItem<Color, AngleOrPercentage>],
306 flags: GradientFlags,
307 gradient_box: Size2D<f32, LayoutPixel>,
308 builder: &mut super::DisplayListBuilder<'_>,
309) -> WebRenderGradient {
310 let center = units::LayoutPoint::new(
311 center
312 .horizontal
313 .to_used_value(Au::from_f32_px(gradient_box.width))
314 .to_f32_px(),
315 center
316 .vertical
317 .to_used_value(Au::from_f32_px(gradient_box.height))
318 .to_f32_px(),
319 );
320
321 let extend_mode = if flags.contains(GradientFlags::REPEATING) {
322 wr::ExtendMode::Repeat
323 } else {
324 wr::ExtendMode::Clamp
325 };
326
327 let mut color_stops = conic_gradient_items_to_color_stops(style, items);
328 let stops = create_webrender_stops(&mut color_stops, color_interpolation_method, extend_mode);
329
330 WebRenderGradient::Conic(builder.wr().create_conic_gradient(
331 center,
332 angle.radians(),
333 stops,
334 extend_mode,
335 ))
336}
337
338fn conic_gradient_items_to_color_stops(
339 style: &ComputedValues,
340 items: &[GradientItem<Color, AngleOrPercentage>],
341) -> Vec<ColorStop<AbsoluteColor, f32>> {
342 items
353 .iter()
354 .filter_map(|item| {
355 match item {
356 GradientItem::SimpleColorStop(color) => Some(ColorStop {
357 color: style.resolve_color(color),
358 position: None,
359 }),
360 GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
361 color: style.resolve_color(color),
362 position: match position {
363 AngleOrPercentage::Percentage(percentage) => Some(percentage.0),
364 AngleOrPercentage::Angle(angle) => Some(angle.degrees() / 360.),
365 },
366 }),
367 GradientItem::InterpolationHint(_) => None,
370 }
371 })
372 .collect()
373}
374
375fn gradient_items_to_color_stops(
376 style: &ComputedValues,
377 items: &[GradientItem<Color, LengthPercentage>],
378 gradient_line_length: Au,
379) -> Vec<ColorStop<AbsoluteColor, f32>> {
380 items
391 .iter()
392 .filter_map(|item| {
393 match item {
394 GradientItem::SimpleColorStop(color) => Some(ColorStop {
395 color: style.resolve_color(color),
396 position: None,
397 }),
398 GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
399 color: style.resolve_color(color),
400 position: Some(if gradient_line_length.is_zero() {
401 0.
402 } else {
403 position
404 .to_used_value(gradient_line_length)
405 .scale_by(1. / gradient_line_length.to_f32_px())
406 .to_f32_px()
407 }),
408 }),
409 GradientItem::InterpolationHint(_) => None,
412 }
413 })
414 .collect()
415}
416
417fn create_webrender_stops(
418 stops: &mut [ColorStop<AbsoluteColor, f32>],
419 interpolation_method: &ColorInterpolationMethod,
420 extend_mode: ExtendMode,
421) -> Vec<wr::GradientStop> {
422 let stops = fixup_stops(stops);
423 if interpolation_method.space != ColorSpace::Srgb {
424 return interpolate_gradient_stop_colors(&stops, interpolation_method, extend_mode);
425 }
426
427 stops
428 .iter()
429 .map(|stop| wr::GradientStop {
430 color: super::rgba(stop.color),
431 offset: stop.position,
432 })
433 .collect()
434}
435
436#[derive(Clone, Copy)]
437struct UsedColorStop {
438 color: AbsoluteColor,
439 position: f32,
440}
441
442fn fixup_stops(stops: &mut [ColorStop<AbsoluteColor, f32>]) -> Vec<UsedColorStop> {
444 assert!(!stops.is_empty());
445
446 if let first_position @ None = &mut stops.first_mut().unwrap().position {
448 *first_position = Some(0.);
449 }
450 if let last_position @ None = &mut stops.last_mut().unwrap().position {
451 *last_position = Some(1.);
452 }
453
454 let mut iter = stops.iter_mut();
455 let mut max_so_far = iter.next().unwrap().position.unwrap();
456 for stop in iter {
457 if let Some(position) = &mut stop.position {
458 if *position < max_so_far {
459 *position = max_so_far
460 } else {
461 max_so_far = *position
462 }
463 }
464 }
465
466 let mut used_color_stops = Vec::with_capacity(stops.len());
467 let mut iter = stops.iter().enumerate();
468 let (_, first) = iter.next().unwrap();
469 let first_stop_position = first.position.unwrap();
470 used_color_stops.push(UsedColorStop {
471 position: first_stop_position,
472 color: first.color,
473 });
474 if stops.len() == 1 {
475 used_color_stops.push(used_color_stops[0]);
476 }
477
478 let mut last_positioned_stop_index = 0;
479 let mut last_positioned_stop_position = first_stop_position;
480 for (i, stop) in iter {
481 if let Some(position) = stop.position {
482 let step_count = i - last_positioned_stop_index;
483 if step_count > 1 {
484 let step = (position - last_positioned_stop_position) / step_count as f32;
485 for j in 1..step_count {
486 let color = stops[last_positioned_stop_index + j].color;
487 let position = last_positioned_stop_position + j as f32 * step;
488 used_color_stops.push(UsedColorStop { position, color })
489 }
490 }
491 last_positioned_stop_index = i;
492 last_positioned_stop_position = position;
493 used_color_stops.push(UsedColorStop {
494 position,
495 color: stop.color,
496 })
497 }
498 }
499
500 used_color_stops
501}
502
503fn interpolate_gradient_stop_colors(
508 stops: &[UsedColorStop],
509 interpolation_method: &ColorInterpolationMethod,
510 extend_mode: wr::ExtendMode,
511) -> Vec<wr::GradientStop> {
512 const FULL_RANGE_EXTRA_STOPS: usize = 128;
519
520 let extend = extend_mode == wr::ExtendMode::Clamp &&
529 interpolation_method.hue == HueInterpolationMethod::Longer;
530
531 let mut output = Vec::with_capacity(stops.len() * 2 + FULL_RANGE_EXTRA_STOPS);
536
537 let extend = extend || stops.len() == 1;
541 let mut iter_stops = stops.len() - 1;
542 if extend {
543 iter_stops += 2;
544 }
545
546 for index in 0..iter_stops {
547 let this_index = if extend {
548 index.saturating_sub(1)
549 } else {
550 index
551 };
552
553 let next_index = if extend && (index == iter_stops - 1 || index == 0) {
554 this_index
555 } else {
556 this_index + 1
557 };
558
559 let start = &stops[this_index];
560 let end = &stops[next_index];
561 let mut start_position = start.position;
562 let mut end_position = end.position;
563
564 let mut extra_stops = 0;
570 if extend {
571 if index == 0 {
575 start_position = start_position.min(0.0);
576 extra_stops = 1;
577 }
578 if index == iter_stops - 1 {
579 end_position = end_position.max(1.0);
580 extra_stops = 1;
581 }
582 }
583
584 if extra_stops == 0 {
585 extra_stops = (end_position * FULL_RANGE_EXTRA_STOPS as f32).floor() as u32;
588 extra_stops = extra_stops.clamp(1, FULL_RANGE_EXTRA_STOPS as u32);
589 }
590
591 let step = 1.0 / (extra_stops as f32);
592 for extra_stop in 0..=extra_stops {
593 let progress = (extra_stop as f32) * step;
594 let position = start_position + progress * (end_position - start_position);
595
596 let start_color = start.color;
597 let end_color = end.color;
598 let color = mix_many(
599 *interpolation_method,
600 [
601 ColorMixItem::new(start_color, 1.0 - progress),
602 ColorMixItem::new(end_color, progress),
603 ],
604 ColorMixFlags::empty(),
605 );
606
607 output.push(wr::GradientStop {
608 color: super::rgba(color),
609 offset: position,
610 });
611 }
612 }
613
614 output
615}