1use euclid::approxeq::ApproxEq;
12use euclid::point2;
13use api::{ExtendMode, GradientStop};
14use api::units::*;
15use crate::pattern::gradient::linear_gradient_pattern;
16use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState};
17use crate::scene_building::IsVisible;
18use crate::intern::{Internable, InternDebug, Handle as InternHandle};
19use crate::internal_types::LayoutPrimitiveInfo;
20use crate::image_tiling::simplify_repeated_primitive;
21use crate::prim_store::{PrimitiveKind, PrimitiveOpacity};
22use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
23use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
24use crate::segment::EdgeMask;
25use super::{stops_and_min_alpha, GradientStopKey, apply_gradient_local_clip};
26use std::ops::{Deref, DerefMut};
27use std::mem::swap;
28
29#[cfg_attr(feature = "capture", derive(Serialize))]
31#[cfg_attr(feature = "replay", derive(Deserialize))]
32#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
33pub struct LinearGradientKey {
34 pub common: PrimKeyCommonData,
35 pub extend_mode: ExtendMode,
36 pub start_point: PointKey,
37 pub end_point: PointKey,
38 pub stretch_ratio: SizeKey,
41 pub tile_spacing: SizeKey,
42 pub stops: Vec<GradientStopKey>,
43 pub reverse_stops: bool,
44 pub nine_patch: Option<Box<NinePatchDescriptor>>,
45 pub enable_dithering: bool,
46}
47
48impl LinearGradientKey {
49 pub fn new(
50 info: &LayoutPrimitiveInfo,
51 linear_grad: LinearGradient,
52 ) -> Self {
53 LinearGradientKey {
54 common: info.into(),
55 extend_mode: linear_grad.extend_mode,
56 start_point: linear_grad.start_point,
57 end_point: linear_grad.end_point,
58 stretch_ratio: linear_grad.stretch_ratio,
59 tile_spacing: linear_grad.tile_spacing,
60 stops: linear_grad.stops,
61 reverse_stops: linear_grad.reverse_stops,
62 nine_patch: linear_grad.nine_patch,
63 enable_dithering: linear_grad.enable_dithering,
64 }
65 }
66}
67
68impl InternDebug for LinearGradientKey {}
69
70#[cfg_attr(feature = "capture", derive(Serialize))]
71#[cfg_attr(feature = "replay", derive(Deserialize))]
72#[derive(Debug, MallocSizeOf)]
73pub struct LinearGradientTemplate {
74 pub common: PrimTemplateCommonData,
75 pub extend_mode: ExtendMode,
76 pub start_point: LayoutPoint,
77 pub end_point: LayoutPoint,
78 pub stretch_ratio: LayoutSize,
82 pub tile_spacing: LayoutSize,
83 pub stops_opacity: PrimitiveOpacity,
84 pub stops: Vec<GradientStop>,
85 pub border_nine_patch: Option<Box<NinePatchDescriptor>>,
86 pub reverse_stops: bool,
87}
88
89impl PatternBuilder for LinearGradientTemplate {
90 fn build(
91 &self,
92 _sub_rect: Option<DeviceRect>,
93 offset: LayoutVector2D,
94 ctx: &PatternBuilderContext,
95 state: &mut PatternBuilderState,
96 ) -> Pattern {
97 let (start, end) = if self.reverse_stops {
98 (self.end_point, self.start_point)
99 } else {
100 (self.start_point, self.end_point)
101 };
102 let offset = offset + ctx.prim_origin.to_vector();
106 linear_gradient_pattern(
107 start + offset,
108 end + offset,
109 self.extend_mode,
110 &self.stops,
111 ctx.fb_config.is_software,
112 state.frame_gpu_data,
113 )
114 }
115}
116
117impl Deref for LinearGradientTemplate {
118 type Target = PrimTemplateCommonData;
119 fn deref(&self) -> &Self::Target {
120 &self.common
121 }
122}
123
124impl DerefMut for LinearGradientTemplate {
125 fn deref_mut(&mut self) -> &mut Self::Target {
126 &mut self.common
127 }
128}
129
130pub fn optimize_linear_gradient(
141 prim_rect: &mut LayoutRect,
142 tile_size: &mut LayoutSize,
143 mut tile_spacing: LayoutSize,
144 clip_rect: &LayoutRect,
145 start: &mut LayoutPoint,
146 end: &mut LayoutPoint,
147) {
148 simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect);
149
150 let vertical = start.x.approx_eq(&end.x);
151 let horizontal = start.y.approx_eq(&end.y);
152
153 let horizontally_tiled = prim_rect.width() > tile_size.width;
154 let vertically_tiled = prim_rect.height() > tile_size.height;
155
156 if vertically_tiled && horizontal && tile_spacing.height == 0.0 {
159 tile_size.height = prim_rect.height();
160 }
161
162 if horizontally_tiled && vertical && tile_spacing.width == 0.0 {
163 tile_size.width = prim_rect.width();
164 }
165
166 let offset = apply_gradient_local_clip(
167 prim_rect,
168 &tile_size,
169 &tile_spacing,
170 &clip_rect
171 );
172
173 tile_size.width = tile_size.width.min(prim_rect.width());
176 tile_size.height = tile_size.height.min(prim_rect.height());
177
178 *start += offset;
179 *end += offset;
180}
181
182pub fn linear_gradient_decomposes(
186 prim_rect: &LayoutRect,
187 tile_size: LayoutSize,
188 tile_spacing: LayoutSize,
189 start: LayoutPoint,
190 end: LayoutPoint,
191 extend_mode: ExtendMode,
192 stops: &[GradientStop],
193 enable_dithering: bool,
194) -> bool {
195 if extend_mode != ExtendMode::Clamp || stops.is_empty() {
196 return false;
197 }
198
199 let vertical = start.x.approx_eq(&end.x);
200 let horizontal = start.y.approx_eq(&end.y);
201
202 if !vertical && !horizontal {
203 return false;
204 }
205
206 if vertical && horizontal {
207 return false;
208 }
209
210 if !tile_spacing.is_empty() {
211 return false;
212 }
213
214 let horizontally_tiled = prim_rect.width() > tile_size.width;
215 let vertically_tiled = prim_rect.height() > tile_size.height;
216 if vertically_tiled || horizontally_tiled {
217 return false;
218 }
219
220 if !enable_dithering &&
221 ((horizontal && tile_size.width < 256.0)
222 || (vertical && tile_size.height < 256.0)) {
223 return false;
224 }
225
226 true
227}
228
229pub fn decompose_axis_aligned_gradient(
237 prim_rect: &LayoutRect,
238 tile_size: LayoutSize,
239 start: LayoutPoint,
240 end: LayoutPoint,
241 stops: &[GradientStop],
242 clip_rect: &LayoutRect,
243 mut callback: impl FnMut(&LayoutRect, LayoutPoint, LayoutPoint, [GradientStop; 2], EdgeMask),
244) {
245 debug_assert!(!stops.is_empty());
246
247 let vertical = start.x.approx_eq(&end.x);
248
249 let adjust_rect = &mut |rect: &mut LayoutRect| {
252 if vertical {
253 swap(&mut rect.min.x, &mut rect.min.y);
254 swap(&mut rect.max.x, &mut rect.max.y);
255 }
256 };
257 let adjust_size = &mut |size: &mut LayoutSize| {
258 if vertical { swap(&mut size.width, &mut size.height); }
259 };
260 let adjust_point = &mut |p: &mut LayoutPoint| {
261 if vertical { swap(&mut p.x, &mut p.y); }
262 };
263
264 let clip_rect = match clip_rect.intersection(prim_rect) {
265 Some(clip) => clip,
266 None => return,
267 };
268
269 let mut prim_rect = *prim_rect;
270 let mut start = start;
271 let mut end = end;
272 let mut tile_size = tile_size;
273
274 adjust_rect(&mut prim_rect);
275 adjust_point(&mut start);
276 adjust_point(&mut end);
277 adjust_size(&mut tile_size);
278
279 let length = (end.x - start.x).abs();
285
286 let reverse_stops = start.x > end.x;
294 if reverse_stops {
295 swap(&mut start, &mut end);
296 }
297
298 let (first_stop, last_stop) = if reverse_stops {
299 (*stops.last().unwrap(), *stops.first().unwrap())
300 } else {
301 (*stops.first().unwrap(), *stops.last().unwrap())
302 };
303
304 let mut prev = first_stop;
305 let mut last = last_stop;
306 prev.offset = -start.x / length;
307 last.offset = (tile_size.width - start.x) / length;
308 if reverse_stops {
309 prev.offset = 1.0 - prev.offset;
310 last.offset = 1.0 - last.offset;
311 }
312
313 let (side_edges, first_edge, last_edge) = if vertical {
314 (
315 EdgeMask::LEFT | EdgeMask::RIGHT,
316 EdgeMask::TOP,
317 EdgeMask::BOTTOM,
318 )
319 } else {
320 (
321 EdgeMask::TOP | EdgeMask::BOTTOM,
322 EdgeMask::LEFT,
323 EdgeMask::RIGHT,
324 )
325 };
326
327 let mut is_first = true;
328 let last_offset = last.offset;
329
330 let stops_iter: Box<dyn Iterator<Item = &GradientStop>> = if reverse_stops {
333 Box::new(stops.iter().rev())
334 } else {
335 Box::new(stops.iter())
336 };
337
338 for stop in stops_iter.chain(std::iter::once(&last)) {
339 let prev_stop = prev;
340 prev = *stop;
341
342 if prev_stop.color.a == 0.0 && stop.color.a == 0.0 {
343 continue;
344 }
345
346 let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset };
347 let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset };
348
349 let segment_start = start.x + prev_offset * length;
353 let segment_end = start.x + offset * length;
354 let segment_length = segment_end - segment_start;
355
356 if segment_length <= 0.0 {
357 continue;
358 }
359
360 let mut segment_rect = prim_rect;
361 segment_rect.min.x += segment_start;
362 segment_rect.max.x = segment_rect.min.x + segment_length;
363
364 let mut seg_start = point2(0.0, 0.0);
365 let mut seg_end = point2(segment_length, 0.0);
366
367 adjust_point(&mut seg_start);
368 adjust_point(&mut seg_end);
369 adjust_rect(&mut segment_rect);
370
371 let origin_before_clip = segment_rect.min;
372 segment_rect = match segment_rect.intersection(&clip_rect) {
373 Some(rect) => rect,
374 None => continue,
375 };
376 let clip_offset = segment_rect.min - origin_before_clip;
377 seg_start -= clip_offset;
378 seg_end -= clip_offset;
379
380 let mut edge_flags = side_edges;
381 if is_first {
382 edge_flags |= first_edge;
383 is_first = false;
384 }
385 if stop.offset == last_offset {
386 edge_flags |= last_edge;
387 }
388
389 callback(
390 &segment_rect,
391 seg_start,
392 seg_end,
393 [
394 GradientStop { offset: 0.0, color: prev_stop.color },
395 GradientStop { offset: 1.0, color: stop.color },
396 ],
397 edge_flags,
398 );
399 }
400}
401
402impl From<LinearGradientKey> for LinearGradientTemplate {
403 fn from(item: LinearGradientKey) -> Self {
404
405 let common = PrimTemplateCommonData::with_key_common(item.common);
406
407 let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
408
409 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
413
414 let start_point = LayoutPoint::new(item.start_point.x, item.start_point.y);
415 let end_point = LayoutPoint::new(item.end_point.x, item.end_point.y);
416 let tile_spacing: LayoutSize = item.tile_spacing.into();
417 let stretch_ratio: LayoutSize = item.stretch_ratio.into();
418
419 LinearGradientTemplate {
420 common,
421 extend_mode: item.extend_mode,
422 start_point,
423 end_point,
424 stretch_ratio,
425 tile_spacing,
426 stops_opacity,
427 stops,
428 border_nine_patch: item.nine_patch,
429 reverse_stops: item.reverse_stops,
430 }
431 }
432}
433
434pub type LinearGradientDataHandle = InternHandle<LinearGradient>;
435
436#[derive(Debug, MallocSizeOf)]
437#[cfg_attr(feature = "capture", derive(Serialize))]
438#[cfg_attr(feature = "replay", derive(Deserialize))]
439pub struct LinearGradient {
440 pub extend_mode: ExtendMode,
441 pub start_point: PointKey,
442 pub end_point: PointKey,
443 pub stretch_ratio: SizeKey,
446 pub tile_spacing: SizeKey,
447 pub stops: Vec<GradientStopKey>,
448 pub reverse_stops: bool,
449 pub nine_patch: Option<Box<NinePatchDescriptor>>,
450 pub edge_aa_mask: EdgeMask,
451 pub enable_dithering: bool,
452}
453
454impl Internable for LinearGradient {
455 type Key = LinearGradientKey;
456 type StoreData = LinearGradientTemplate;
457 type InternData = ();
458 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS;
459}
460
461impl InternablePrimitive for LinearGradient {
462 fn into_key(
463 self,
464 info: &LayoutPrimitiveInfo,
465 ) -> LinearGradientKey {
466 LinearGradientKey::new(info, self)
467 }
468
469 fn make_instance_kind(
470 _key: LinearGradientKey,
471 data_handle: LinearGradientDataHandle,
472 _prim_store: &mut PrimitiveStore,
473 ) -> PrimitiveKind {
474 PrimitiveKind::LinearGradient {
475 data_handle,
476 }
477 }
478}
479
480impl IsVisible for LinearGradient {
481 fn is_visible(&self) -> bool {
482 true
483 }
484}
485