Rasterize direct gradients at native scale
This commit is contained in:
1 parent
c8a5477296
commit
cfac7f2003
1 file changed
+181
-11
@@ -1870,16 +1870,53 @@ pub fn paint_prepared_region_with_scroll(
|
|||||||
/// intermediate surfaces retain the proven CSS-raster + resample path until
|
/// intermediate surfaces retain the proven CSS-raster + resample path until
|
||||||
/// those layers can carry an explicit source/device transform.
|
/// those layers can carry an explicit source/device transform.
|
||||||
fn native_raster_scale_supported(tree: &DomTree, laid: &crate::DomLayout) -> bool {
|
fn native_raster_scale_supported(tree: &DomTree, laid: &crate::DomLayout) -> bool {
|
||||||
|
fn direct_gradient_geometry_supported(style: &crate::LayoutStyle) -> bool {
|
||||||
|
if style.background_size.is_some()
|
||||||
|
|| style.background_size_expression.is_some()
|
||||||
|
|| style.background_size_fit.is_some()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The direct vector painter is scale-safe only when no logical-pixel
|
||||||
|
// tile surface is needed. Equal positioning and clipping boxes make
|
||||||
|
// the implicit gradient tile exactly the painted path; position and
|
||||||
|
// repeat then have no leftover space on which to operate.
|
||||||
|
let geometry = background_geometry(
|
||||||
|
&crate::Rect {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: 100.0,
|
||||||
|
height: 100.0,
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
);
|
||||||
|
(geometry.origin_rect.x - geometry.clip_rect.x).abs() <= 0.01
|
||||||
|
&& (geometry.origin_rect.y - geometry.clip_rect.y).abs() <= 0.01
|
||||||
|
&& (geometry.origin_rect.width - geometry.clip_rect.width).abs() <= 0.01
|
||||||
|
&& (geometry.origin_rect.height - geometry.clip_rect.height).abs() <= 0.01
|
||||||
|
}
|
||||||
|
|
||||||
fn style_supported(style: &crate::LayoutStyle) -> bool {
|
fn style_supported(style: &crate::LayoutStyle) -> bool {
|
||||||
|
let has_gradient = style.background_gradient.is_some()
|
||||||
|
|| style.background_radial_gradient.is_some()
|
||||||
|
|| !style.background_gradient_layers.is_empty();
|
||||||
|
let gradients_supported = style.background_conic_gradient.is_none()
|
||||||
|
&& style
|
||||||
|
.background_gradient_layers
|
||||||
|
.iter()
|
||||||
|
.all(|layer| match layer {
|
||||||
|
crate::BackgroundGradientLayer::Linear { repeating, .. } => !repeating,
|
||||||
|
crate::BackgroundGradientLayer::Radial { .. } => true,
|
||||||
|
crate::BackgroundGradientLayer::Conic { .. } => false,
|
||||||
|
})
|
||||||
|
&& (!has_gradient || direct_gradient_geometry_supported(style));
|
||||||
let simple = !has_authored_transform(style)
|
let simple = !has_authored_transform(style)
|
||||||
&& style.opacity.is_none_or(|opacity| opacity >= 1.0)
|
&& style.opacity.is_none_or(|opacity| opacity >= 1.0)
|
||||||
&& style.clip_path.is_none()
|
&& style.clip_path.is_none()
|
||||||
&& style.background_image.is_none()
|
&& style.background_image.is_none()
|
||||||
&& style.mask_image.is_none()
|
&& style.mask_image.is_none()
|
||||||
&& style.background_gradient.is_none()
|
&& gradients_supported
|
||||||
&& style.background_radial_gradient.is_none()
|
|
||||||
&& style.background_conic_gradient.is_none()
|
|
||||||
&& style.background_gradient_layers.is_empty()
|
|
||||||
&& !style.background_clip_text
|
&& !style.background_clip_text
|
||||||
&& style.box_shadow.is_none()
|
&& style.box_shadow.is_none()
|
||||||
&& style.content_image.is_none()
|
&& style.content_image.is_none()
|
||||||
@@ -2641,6 +2678,7 @@ fn paint_laid_dom_scrolled(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2653,6 +2691,7 @@ fn paint_laid_dom_scrolled(
|
|||||||
*center,
|
*center,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2677,6 +2716,7 @@ fn paint_laid_dom_scrolled(
|
|||||||
*angle,
|
*angle,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3302,6 +3342,7 @@ fn paint_inline_fragment_decorations(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -3316,6 +3357,7 @@ fn paint_inline_fragment_decorations(
|
|||||||
*center,
|
*center,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some((angle, center, stops)) = &fragment_style.background_conic_gradient {
|
if let Some((angle, center, stops)) = &fragment_style.background_conic_gradient {
|
||||||
@@ -3341,6 +3383,7 @@ fn paint_inline_fragment_decorations(
|
|||||||
*angle,
|
*angle,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5238,6 +5281,7 @@ fn paint_linear_gradient(
|
|||||||
angle: f32,
|
angle: f32,
|
||||||
stops: &[([u8; 4], Option<f32>)],
|
stops: &[([u8; 4], Option<f32>)],
|
||||||
clip: Option<&tiny_skia::Mask>,
|
clip: Option<&tiny_skia::Mask>,
|
||||||
|
raster_scale: f32,
|
||||||
) {
|
) {
|
||||||
if stops.len() < 2 {
|
if stops.len() < 2 {
|
||||||
return;
|
return;
|
||||||
@@ -5270,7 +5314,13 @@ fn paint_linear_gradient(
|
|||||||
let mut paint = Paint::default();
|
let mut paint = Paint::default();
|
||||||
paint.shader = shader;
|
paint.shader = shader;
|
||||||
paint.anti_alias = true;
|
paint.anti_alias = true;
|
||||||
pixmap.fill_path(path, &paint, FillRule::Winding, Transform::identity(), clip);
|
pixmap.fill_path(
|
||||||
|
path,
|
||||||
|
&paint,
|
||||||
|
FillRule::Winding,
|
||||||
|
raster_transform(raster_scale),
|
||||||
|
clip,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5286,6 +5336,7 @@ fn paint_linear_gradient_layer(
|
|||||||
rem: f32,
|
rem: f32,
|
||||||
viewport: (f32, f32),
|
viewport: (f32, f32),
|
||||||
clip: Option<&tiny_skia::Mask>,
|
clip: Option<&tiny_skia::Mask>,
|
||||||
|
raster_scale: f32,
|
||||||
) {
|
) {
|
||||||
if stops.len() < 2 {
|
if stops.len() < 2 {
|
||||||
return;
|
return;
|
||||||
@@ -5351,7 +5402,13 @@ fn paint_linear_gradient_layer(
|
|||||||
let color = gradient_stop_color(stops, last_index);
|
let color = gradient_stop_color(stops, last_index);
|
||||||
let mut paint = Paint::default();
|
let mut paint = Paint::default();
|
||||||
paint.set_color(Color::from_rgba8(color[0], color[1], color[2], color[3]));
|
paint.set_color(Color::from_rgba8(color[0], color[1], color[2], color[3]));
|
||||||
pixmap.fill_path(path, &paint, FillRule::Winding, Transform::identity(), clip);
|
pixmap.fill_path(
|
||||||
|
path,
|
||||||
|
&paint,
|
||||||
|
FillRule::Winding,
|
||||||
|
raster_transform(raster_scale),
|
||||||
|
clip,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (start, end, spread, normalize) = if repeating {
|
let (start, end, spread, normalize) = if repeating {
|
||||||
@@ -5393,7 +5450,13 @@ fn paint_linear_gradient_layer(
|
|||||||
let mut paint = Paint::default();
|
let mut paint = Paint::default();
|
||||||
paint.shader = shader;
|
paint.shader = shader;
|
||||||
paint.anti_alias = true;
|
paint.anti_alias = true;
|
||||||
pixmap.fill_path(path, &paint, FillRule::Winding, Transform::identity(), clip);
|
pixmap.fill_path(
|
||||||
|
path,
|
||||||
|
&paint,
|
||||||
|
FillRule::Winding,
|
||||||
|
raster_transform(raster_scale),
|
||||||
|
clip,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5494,6 +5557,7 @@ fn paint_radial_gradient(
|
|||||||
center: (f32, f32),
|
center: (f32, f32),
|
||||||
stops: &[([u8; 4], Option<f32>)],
|
stops: &[([u8; 4], Option<f32>)],
|
||||||
clip: Option<&tiny_skia::Mask>,
|
clip: Option<&tiny_skia::Mask>,
|
||||||
|
raster_scale: f32,
|
||||||
) {
|
) {
|
||||||
if stops.len() < 2 {
|
if stops.len() < 2 {
|
||||||
return;
|
return;
|
||||||
@@ -5532,7 +5596,13 @@ fn paint_radial_gradient(
|
|||||||
let mut paint = Paint::default();
|
let mut paint = Paint::default();
|
||||||
paint.shader = shader;
|
paint.shader = shader;
|
||||||
paint.anti_alias = true;
|
paint.anti_alias = true;
|
||||||
pixmap.fill_path(path, &paint, FillRule::Winding, Transform::identity(), clip);
|
pixmap.fill_path(
|
||||||
|
path,
|
||||||
|
&paint,
|
||||||
|
FillRule::Winding,
|
||||||
|
raster_transform(raster_scale),
|
||||||
|
clip,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5546,6 +5616,7 @@ fn paint_background_gradient_layers(
|
|||||||
root_font_size: f32,
|
root_font_size: f32,
|
||||||
viewport: (f32, f32),
|
viewport: (f32, f32),
|
||||||
clip: Option<&tiny_skia::Mask>,
|
clip: Option<&tiny_skia::Mask>,
|
||||||
|
raster_scale: f32,
|
||||||
) {
|
) {
|
||||||
let layers = &style.background_gradient_layers;
|
let layers = &style.background_gradient_layers;
|
||||||
let em = style.font_size.unwrap_or(16.0);
|
let em = style.font_size.unwrap_or(16.0);
|
||||||
@@ -5562,6 +5633,7 @@ fn paint_background_gradient_layers(
|
|||||||
// coordinates and would also ignore `background-repeat: no-repeat`.
|
// coordinates and would also ignore `background-repeat: no-repeat`.
|
||||||
|| clip_differs_from_origin;
|
|| clip_differs_from_origin;
|
||||||
if needs_tile && tile_size.0 > 0.0 && tile_size.1 > 0.0 {
|
if needs_tile && tile_size.0 > 0.0 && tile_size.1 > 0.0 {
|
||||||
|
debug_assert_eq!(raster_scale, 1.0);
|
||||||
let width = tile_size.0.ceil().clamp(1.0, 4096.0) as u32;
|
let width = tile_size.0.ceil().clamp(1.0, 4096.0) as u32;
|
||||||
let height = tile_size.1.ceil().clamp(1.0, 4096.0) as u32;
|
let height = tile_size.1.ceil().clamp(1.0, 4096.0) as u32;
|
||||||
if let Some(mut tile) = Pixmap::new(width, height) {
|
if let Some(mut tile) = Pixmap::new(width, height) {
|
||||||
@@ -5589,6 +5661,7 @@ fn paint_background_gradient_layers(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
None,
|
None,
|
||||||
|
1.0,
|
||||||
);
|
);
|
||||||
let tile_x = origin_rect.x
|
let tile_x = origin_rect.x
|
||||||
+ style
|
+ style
|
||||||
@@ -5682,6 +5755,7 @@ fn paint_background_gradient_layers(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
clip,
|
clip,
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5696,6 +5770,7 @@ fn paint_gradient_layer_stack(
|
|||||||
root_font_size: f32,
|
root_font_size: f32,
|
||||||
viewport: (f32, f32),
|
viewport: (f32, f32),
|
||||||
clip: Option<&tiny_skia::Mask>,
|
clip: Option<&tiny_skia::Mask>,
|
||||||
|
raster_scale: f32,
|
||||||
) {
|
) {
|
||||||
// CSS lists the topmost background first. Paint back-to-front so every
|
// CSS lists the topmost background first. Paint back-to-front so every
|
||||||
// translucent layer composites over the layers authored after it.
|
// translucent layer composites over the layers authored after it.
|
||||||
@@ -5719,10 +5794,19 @@ fn paint_gradient_layer_stack(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
clip,
|
clip,
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
crate::BackgroundGradientLayer::Radial { center, stops } => {
|
crate::BackgroundGradientLayer::Radial { center, stops } => {
|
||||||
paint_radial_gradient(pixmap, path, sampling_rect, *center, stops, clip);
|
paint_radial_gradient(
|
||||||
|
pixmap,
|
||||||
|
path,
|
||||||
|
sampling_rect,
|
||||||
|
*center,
|
||||||
|
stops,
|
||||||
|
clip,
|
||||||
|
raster_scale,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
crate::BackgroundGradientLayer::Conic {
|
crate::BackgroundGradientLayer::Conic {
|
||||||
angle,
|
angle,
|
||||||
@@ -6231,6 +6315,7 @@ fn paint_in_flow_generated_box(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -6243,6 +6328,7 @@ fn paint_in_flow_generated_box(
|
|||||||
*center,
|
*center,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6267,6 +6353,7 @@ fn paint_in_flow_generated_box(
|
|||||||
*angle,
|
*angle,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6452,6 +6539,7 @@ fn paint_positioned_pseudo(
|
|||||||
root_font_size,
|
root_font_size,
|
||||||
viewport,
|
viewport,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -6464,6 +6552,7 @@ fn paint_positioned_pseudo(
|
|||||||
*center,
|
*center,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6488,6 +6577,7 @@ fn paint_positioned_pseudo(
|
|||||||
*angle,
|
*angle,
|
||||||
stops,
|
stops,
|
||||||
background_mask.as_ref(),
|
background_mask.as_ref(),
|
||||||
|
raster_scale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11646,17 +11736,97 @@ mod tests {
|
|||||||
assert!(dark_text_pixels > 10, "native 2x text must remain painted");
|
assert!(dark_text_pixels > 10, "native 2x text must remain painted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direct_vector_gradients_rasterize_natively_at_two_x() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<html style="margin:0"><body style="margin:0">
|
||||||
|
<div style="width:20px;height:16px;background:linear-gradient(90deg,#f00 0%,#00f 100%)"></div>
|
||||||
|
<div style="width:20px;height:16px;background:radial-gradient(circle at 50% 50%,#fff 0%,#000 100%)"></div>
|
||||||
|
</body></html>"#,
|
||||||
|
);
|
||||||
|
let mut resources = RenderResourceCache::with_loader(|_url: &str| None);
|
||||||
|
let mut prepared =
|
||||||
|
prepare_dom(&tree, (30.0, 40.0), None, &mut resources).expect("prepared render");
|
||||||
|
assert!(
|
||||||
|
native_raster_scale_supported(&tree, &prepared.layout),
|
||||||
|
"direct non-repeating linear/radial gradients should use device-scale paint"
|
||||||
|
);
|
||||||
|
let scroll = prepared.resolve_scroll_state(&tree, (0.0, 0.0), &HashMap::new());
|
||||||
|
let one_x = paint_prepared_region_with_scroll(
|
||||||
|
&tree,
|
||||||
|
&mut prepared,
|
||||||
|
&mut resources,
|
||||||
|
&scroll,
|
||||||
|
CaptureRegion::new(0.0, 0.0, 30.0, 40.0, 1.0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let two_x = paint_prepared_region_with_scroll(
|
||||||
|
&tree,
|
||||||
|
&mut prepared,
|
||||||
|
&mut resources,
|
||||||
|
&scroll,
|
||||||
|
CaptureRegion::new(0.0, 0.0, 30.0, 40.0, 2.0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!((one_x.width(), one_x.height()), (30, 40));
|
||||||
|
assert_eq!((two_x.width(), two_x.height()), (60, 80));
|
||||||
|
|
||||||
|
let linear_left = two_x.pixel(1, 8).unwrap();
|
||||||
|
let linear_right = two_x.pixel(38, 8).unwrap();
|
||||||
|
assert!(
|
||||||
|
linear_left.red() > 220 && linear_left.blue() < 40,
|
||||||
|
"linear gradient start must remain red at 2x: {linear_left:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
linear_right.blue() > 220 && linear_right.red() < 40,
|
||||||
|
"linear gradient end must remain blue at 2x: {linear_right:?}"
|
||||||
|
);
|
||||||
|
let radial_center = two_x.pixel(20, 48).unwrap();
|
||||||
|
let radial_edge = two_x.pixel(1, 48).unwrap();
|
||||||
|
assert!(
|
||||||
|
radial_center.red() > 230,
|
||||||
|
"radial center must remain light at 2x: {radial_center:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
radial_edge.red() < 100,
|
||||||
|
"radial edge must remain dark at 2x: {radial_edge:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let source =
|
||||||
|
image::RgbaImage::from_raw(one_x.width(), one_x.height(), one_x.data().to_vec())
|
||||||
|
.unwrap();
|
||||||
|
let post_scaled = image::imageops::resize(
|
||||||
|
&source,
|
||||||
|
two_x.width(),
|
||||||
|
two_x.height(),
|
||||||
|
image::imageops::FilterType::Lanczos3,
|
||||||
|
);
|
||||||
|
let differing = two_x
|
||||||
|
.data()
|
||||||
|
.iter()
|
||||||
|
.zip(post_scaled.as_raw())
|
||||||
|
.filter(|(native, post)| native != post)
|
||||||
|
.count();
|
||||||
|
assert!(
|
||||||
|
differing > 200,
|
||||||
|
"2x vector gradient samples must be rerasterized, not resize-equivalent ({differing} differing channels)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn effectful_region_capture_keeps_the_proven_bounded_resample_fallback() {
|
fn effectful_region_capture_keeps_the_proven_bounded_resample_fallback() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
r#"<html style="margin:0"><body style="margin:0">
|
r#"<html style="margin:0"><body style="margin:0">
|
||||||
<div style="width:20px;height:20px;background:linear-gradient(red,blue)"></div>
|
<div style="width:20px;height:20px;background:conic-gradient(red,blue,red)"></div>
|
||||||
</body></html>"#,
|
</body></html>"#,
|
||||||
);
|
);
|
||||||
let mut resources = RenderResourceCache::with_loader(|_url: &str| None);
|
let mut resources = RenderResourceCache::with_loader(|_url: &str| None);
|
||||||
let mut prepared =
|
let mut prepared =
|
||||||
prepare_dom(&tree, (30.0, 30.0), None, &mut resources).expect("prepared render");
|
prepare_dom(&tree, (30.0, 30.0), None, &mut resources).expect("prepared render");
|
||||||
assert!(!native_raster_scale_supported(&tree, &prepared.layout));
|
assert!(
|
||||||
|
!native_raster_scale_supported(&tree, &prepared.layout),
|
||||||
|
"sampled conic gradients must retain the logical-pixel fallback"
|
||||||
|
);
|
||||||
let scroll = prepared.resolve_scroll_state(&tree, (0.0, 0.0), &HashMap::new());
|
let scroll = prepared.resolve_scroll_state(&tree, (0.0, 0.0), &HashMap::new());
|
||||||
let one_x = paint_prepared_region_with_scroll(
|
let one_x = paint_prepared_region_with_scroll(
|
||||||
&tree,
|
&tree,
|
||||||
|
|||||||
Reference in new issue
Block a user