Skip to main content

webrender/prim_store/gradient/
radial.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
5//! Radial gradients
6//!
7//! Specification: https://drafts.csswg.org/css-images-4/#radial-gradients
8//!
9//! Radial gradients are rendered via cached render tasks and composited with the image brush.
10
11use euclid::{vec2, size2};
12use api::{ColorU, ExtendMode, GradientStop};
13use api::units::*;
14use crate::pattern::gradient::{radial_gradient_pattern};
15use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState};
16use crate::scene_building::IsVisible;
17use crate::intern::{Internable, InternDebug, Handle as InternHandle};
18use crate::internal_types::LayoutPrimitiveInfo;
19use crate::prim_store::{InternablePrimitive};
20use crate::prim_store::{PrimitiveKind, PrimitiveOpacity};
21use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
22use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey};
23use crate::segment::EdgeMask;
24
25use std::{hash, ops::{Deref, DerefMut}};
26use super::{
27    stops_and_min_alpha, GradientStopKey,
28    apply_gradient_local_clip,
29};
30
31/// Hashable radial gradient parameters, for use during prim interning.
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
35pub struct RadialGradientParams {
36    pub start_radius: f32,
37    pub end_radius: f32,
38    pub ratio_xy: f32,
39}
40
41impl Eq for RadialGradientParams {}
42
43impl hash::Hash for RadialGradientParams {
44    fn hash<H: hash::Hasher>(&self, state: &mut H) {
45        self.start_radius.to_bits().hash(state);
46        self.end_radius.to_bits().hash(state);
47        self.ratio_xy.to_bits().hash(state);
48    }
49}
50
51/// Identifying key for a radial gradient.
52#[cfg_attr(feature = "capture", derive(Serialize))]
53#[cfg_attr(feature = "replay", derive(Deserialize))]
54#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
55pub struct RadialGradientKey {
56    pub common: PrimKeyCommonData,
57    pub extend_mode: ExtendMode,
58    pub center: PointKey,
59    pub params: RadialGradientParams,
60    /// Per-axis tile size encoded as a fraction of `common.prim_size`. The
61    /// runtime `stretch_size` is `stretch_ratio * common.prim_size`.
62    pub stretch_ratio: SizeKey,
63    pub stops: Vec<GradientStopKey>,
64    pub tile_spacing: SizeKey,
65    pub nine_patch: Option<Box<NinePatchDescriptor>>,
66}
67
68impl RadialGradientKey {
69    pub fn new(
70        info: &LayoutPrimitiveInfo,
71        radial_grad: RadialGradient,
72    ) -> Self {
73        RadialGradientKey {
74            common: info.into(),
75            extend_mode: radial_grad.extend_mode,
76            center: radial_grad.center,
77            params: radial_grad.params,
78            stretch_ratio: radial_grad.stretch_ratio,
79            stops: radial_grad.stops,
80            tile_spacing: radial_grad.tile_spacing,
81            nine_patch: radial_grad.nine_patch,
82        }
83    }
84}
85
86impl InternDebug for RadialGradientKey {}
87
88#[cfg_attr(feature = "capture", derive(Serialize))]
89#[cfg_attr(feature = "replay", derive(Deserialize))]
90#[derive(MallocSizeOf)]
91#[derive(Debug)]
92pub struct RadialGradientTemplate {
93    pub common: PrimTemplateCommonData,
94    pub extend_mode: ExtendMode,
95    pub params: RadialGradientParams,
96    pub center: LayoutPoint,
97    /// Per-axis fraction of `common.prim_size` covered by one tile of the
98    /// gradient pattern. Multiply by `common.prim_size` at use to recover the
99    /// absolute stretch_size.
100    pub stretch_ratio: LayoutSize,
101    pub tile_spacing: LayoutSize,
102    pub border_nine_patch: Option<Box<NinePatchDescriptor>>,
103    pub stops_opacity: PrimitiveOpacity,
104    pub stops: Vec<GradientStop>,
105}
106
107impl PatternBuilder for RadialGradientTemplate {
108    fn build(
109        &self,
110        _sub_rect: Option<DeviceRect>,
111        offset: LayoutVector2D,
112        ctx: &PatternBuilderContext,
113        state: &mut PatternBuilderState,
114    ) -> Pattern {
115        // The scaling parameter is used to compensate for when we reduce the size
116        // of the render task for cached gradients. Here we aren't applying any.
117        let no_scale = DeviceVector2D::one();
118
119        // RadialGradientTemplate stores the center point relative to the primitive
120        // origin, but the shader works with start/end points in "proper" layout
121        // coordinates (relative to the primitive's spatial node).
122        let center = self.center.cast_unit() + ctx.prim_origin.to_vector() + offset;
123
124        radial_gradient_pattern(
125            center,
126            no_scale,
127            self.params.start_radius,
128            self.params.end_radius,
129            self.params.ratio_xy,
130            self.extend_mode,
131            &self.stops,
132            ctx.fb_config.is_software,
133            state.frame_gpu_data,
134        )
135    }
136}
137
138impl Deref for RadialGradientTemplate {
139    type Target = PrimTemplateCommonData;
140    fn deref(&self) -> &Self::Target {
141        &self.common
142    }
143}
144
145impl DerefMut for RadialGradientTemplate {
146    fn deref_mut(&mut self) -> &mut Self::Target {
147        &mut self.common
148    }
149}
150
151impl From<RadialGradientKey> for RadialGradientTemplate {
152    fn from(item: RadialGradientKey) -> Self {
153        let common = PrimTemplateCommonData::with_key_common(item.common);
154
155        let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
156
157        // Save opacity of the stops for use in
158        // selecting which pass this gradient
159        // should be drawn in.
160        let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
161
162        RadialGradientTemplate {
163            common,
164            center: item.center.into(),
165            extend_mode: item.extend_mode,
166            params: item.params,
167            stretch_ratio: item.stretch_ratio.into(),
168            tile_spacing: item.tile_spacing.into(),
169            border_nine_patch: item.nine_patch,
170            stops_opacity,
171            stops,
172        }
173    }
174}
175
176pub type RadialGradientDataHandle = InternHandle<RadialGradient>;
177
178#[derive(Debug, MallocSizeOf)]
179#[cfg_attr(feature = "capture", derive(Serialize))]
180#[cfg_attr(feature = "replay", derive(Deserialize))]
181pub struct RadialGradient {
182    pub extend_mode: ExtendMode,
183    pub center: PointKey,
184    pub params: RadialGradientParams,
185    /// Per-axis tile size encoded as a fraction of the prim's size. See
186    /// [`RadialGradientKey::stretch_ratio`].
187    pub stretch_ratio: SizeKey,
188    pub stops: Vec<GradientStopKey>,
189    pub tile_spacing: SizeKey,
190    pub nine_patch: Option<Box<NinePatchDescriptor>>,
191}
192
193impl Internable for RadialGradient {
194    type Key = RadialGradientKey;
195    type StoreData = RadialGradientTemplate;
196    type InternData = ();
197    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_RADIAL_GRADIENTS;
198}
199
200impl InternablePrimitive for RadialGradient {
201    fn into_key(
202        self,
203        info: &LayoutPrimitiveInfo,
204    ) -> RadialGradientKey {
205        RadialGradientKey::new(info, self)
206    }
207
208    fn make_instance_kind(
209        _key: RadialGradientKey,
210        data_handle: RadialGradientDataHandle,
211        _prim_store: &mut PrimitiveStore,
212    ) -> PrimitiveKind {
213        PrimitiveKind::RadialGradient {
214            data_handle,
215        }
216    }
217}
218
219impl IsVisible for RadialGradient {
220    fn is_visible(&self) -> bool {
221        true
222    }
223}
224
225
226/// Avoid invoking the radial gradient shader on large areas where the color is
227/// constant.
228///
229/// If the extend mode is set to clamp, the "interesting" part
230/// of the gradient is only in the bounds of the gradient's ellipse, and the rest
231/// is the color of the last gradient stop.
232///
233/// Sometimes we run into radial gradient with a small radius compared to the
234/// primitive bounds, which means a large area of the primitive is a constant color
235/// This function tries to detect that, potentially shrink the gradient primitive to only
236/// the useful part and if needed insert solid color primitives around the gradient where
237/// parts of it have been removed.
238///
239/// If the radial gradient is split into multiple primitives, we must prevent anti-aliasing
240/// from being appplied at the edges connecting these primitives to prevent seams. This is
241/// done by masking out sides in `aa_mask` for the central gradient primitive and providing
242/// an edge mask for each extracted solid primitive.
243pub fn optimize_radial_gradient(
244    prim_rect: &mut LayoutRect,
245    stretch_size: &mut LayoutSize,
246    center: &mut LayoutPoint,
247    tile_spacing: &mut LayoutSize,
248    aa_mask: &mut EdgeMask,
249    clip_rect: &LayoutRect,
250    radius: LayoutSize,
251    end_offset: f32,
252    extend_mode: ExtendMode,
253    stops: &[GradientStopKey],
254    solid_parts: &mut dyn FnMut(&LayoutRect, ColorU, EdgeMask),
255) {
256    let offset = apply_gradient_local_clip(
257        prim_rect,
258        stretch_size,
259        tile_spacing,
260        clip_rect
261    );
262
263    *center += offset;
264
265    if extend_mode != ExtendMode::Clamp || stops.is_empty() {
266        return;
267    }
268
269    // Bounding box of the "interesting" part of the gradient.
270    let min = prim_rect.min + center.to_vector() - radius.to_vector() * end_offset;
271    let max = prim_rect.min + center.to_vector() + radius.to_vector() * end_offset;
272
273    // The (non-repeated) gradient primitive rect.
274    let gradient_rect = LayoutRect::from_origin_and_size(
275        prim_rect.min,
276        *stretch_size,
277    );
278
279    // How much internal margin between the primitive bounds and the gradient's
280    // bounding rect (areas that are a constant color).
281    let mut l = (min.x - gradient_rect.min.x).max(0.0).floor();
282    let mut t = (min.y - gradient_rect.min.y).max(0.0).floor();
283    let mut r = (gradient_rect.max.x - max.x).max(0.0).floor();
284    let mut b = (gradient_rect.max.y - max.y).max(0.0).floor();
285
286    let is_tiled = prim_rect.width() > stretch_size.width + tile_spacing.width
287        || prim_rect.height() > stretch_size.height + tile_spacing.height;
288
289    let bg_color = stops.last().unwrap().color;
290
291    if bg_color.a != 0 && is_tiled {
292        // If the primitive has repetitions, it's not enough to insert solid rects around it,
293        // so bail out.
294        return;
295    }
296
297    // If the background is fully transparent, shrinking the primitive bounds as much as possible
298    // is always a win. If the background is not transparent, we have to insert solid rectangles
299    // around the shrunk parts.
300    // If the background is transparent and the primitive is tiled, the optimization may introduce
301    // tile spacing which forces the tiling to be manually decomposed.
302    // Either way, don't bother optimizing unless it saves a significant amount of pixels.
303    if bg_color.a != 0 || (is_tiled && tile_spacing.is_empty()) {
304        let threshold = 128.0;
305        if l < threshold { l = 0.0 }
306        if t < threshold { t = 0.0 }
307        if r < threshold { r = 0.0 }
308        if b < threshold { b = 0.0 }
309    }
310
311    if l + t + r + b == 0.0 {
312        // No adjustment to make;
313        return;
314    }
315
316    // Insert solid rectangles around the gradient, in the places where the primitive will be
317    // shrunk.
318    if bg_color.a != 0 {
319        if l != 0.0 && t != 0.0 {
320            let solid_rect = LayoutRect::from_origin_and_size(
321                gradient_rect.min,
322                size2(l, t),
323            );
324            solid_parts(&solid_rect, bg_color, EdgeMask::LEFT | EdgeMask::TOP);
325        }
326
327        if l != 0.0 && b != 0.0 {
328            let solid_rect = LayoutRect::from_origin_and_size(
329                gradient_rect.bottom_left() - vec2(0.0, b),
330                size2(l, b),
331            );
332            solid_parts(&solid_rect, bg_color, EdgeMask::LEFT | EdgeMask::BOTTOM);
333        }
334
335        if t != 0.0 && r != 0.0 {
336            let solid_rect = LayoutRect::from_origin_and_size(
337                gradient_rect.top_right() - vec2(r, 0.0),
338                size2(r, t),
339            );
340            solid_parts(&solid_rect, bg_color, EdgeMask::TOP | EdgeMask::RIGHT);
341        }
342
343        if r != 0.0 && b != 0.0 {
344            let solid_rect = LayoutRect::from_origin_and_size(
345                gradient_rect.bottom_right() - vec2(r, b),
346                size2(r, b),
347            );
348            solid_parts(&solid_rect, bg_color, EdgeMask::RIGHT | EdgeMask::BOTTOM);
349        }
350
351        if l != 0.0 {
352            let solid_rect = LayoutRect::from_origin_and_size(
353                gradient_rect.min + vec2(0.0, t),
354                size2(l, gradient_rect.height() - t - b),
355            );
356            let mut solid_aa = EdgeMask::LEFT;
357            solid_aa.set(EdgeMask::TOP, t == 0.0);
358            solid_aa.set(EdgeMask::BOTTOM, b == 0.0);
359            solid_parts(&solid_rect, bg_color, solid_aa);
360            aa_mask.remove(EdgeMask::LEFT);
361        }
362
363        if r != 0.0 {
364            let solid_rect = LayoutRect::from_origin_and_size(
365                gradient_rect.top_right() + vec2(-r, t),
366                size2(r, gradient_rect.height() - t - b),
367            );
368            let mut solid_aa = EdgeMask::RIGHT;
369            solid_aa.set(EdgeMask::TOP, t == 0.0);
370            solid_aa.set(EdgeMask::BOTTOM, b == 0.0);
371            solid_parts(&solid_rect, bg_color, solid_aa);
372            aa_mask.remove(EdgeMask::RIGHT);
373        }
374
375        if t != 0.0 {
376            let solid_rect = LayoutRect::from_origin_and_size(
377                gradient_rect.min + vec2(l, 0.0),
378                size2(gradient_rect.width() - l - r, t),
379            );
380            let mut solid_aa = EdgeMask::TOP;
381            solid_aa.set(EdgeMask::LEFT, l == 0.0);
382            solid_aa.set(EdgeMask::RIGHT, r == 0.0);
383            solid_parts(&solid_rect, bg_color, solid_aa);
384            aa_mask.remove(EdgeMask::TOP);
385        }
386
387        if b != 0.0 {
388            let solid_rect = LayoutRect::from_origin_and_size(
389                gradient_rect.bottom_left() + vec2(l, -b),
390                size2(gradient_rect.width() - l - r, b),
391            );
392            let mut solid_aa = EdgeMask::BOTTOM;
393            solid_aa.set(EdgeMask::LEFT, l == 0.0);
394            solid_aa.set(EdgeMask::RIGHT, r == 0.0);
395            solid_parts(&solid_rect, bg_color, solid_aa);
396            aa_mask.remove(EdgeMask::BOTTOM);
397        }
398    }
399
400    // Shrink the gradient primitive.
401
402    prim_rect.min.x += l;
403    prim_rect.min.y += t;
404
405    stretch_size.width -= l + r;
406    stretch_size.height -= b + t;
407
408    center.x -= l;
409    center.y -= t;
410
411    tile_spacing.width += l + r;
412    tile_spacing.height += t + b;
413}