LibWeb: Implement mask-image CSS property support

Implemented by reusing AddMask display list item that was initially
added for `background-clip` property.

Progress on flashlight effect on https://null.com/games/athena-crisis
This commit is contained in:
Aliaksandr Kalenik 2024-11-16 03:25:48 +03:00 committed by Andreas Kling
parent 7b7bb60393
commit 96a35767b6
Notes: github-actions[bot] 2024-11-18 21:59:52 +00:00
11 changed files with 149 additions and 58 deletions

View file

@ -486,6 +486,7 @@ public:
Color stop_color() const { return m_noninherited.stop_color; }
float stop_opacity() const { return m_noninherited.stop_opacity; }
CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; }
RefPtr<AbstractImageStyleValue const> mask_image() const { return m_noninherited.mask_image; }
Optional<MaskReference> const& mask() const { return m_noninherited.mask; }
CSS::MaskType mask_type() const { return m_noninherited.mask_type; }
Optional<ClipPathReference> const& clip_path() const { return m_noninherited.clip_path; }
@ -681,6 +682,7 @@ protected:
Optional<MaskReference> mask;
CSS::MaskType mask_type { InitialValues::mask_type() };
Optional<ClipPathReference> clip_path;
RefPtr<CSS::AbstractImageStyleValue> mask_image;
LengthPercentage cx { InitialValues::cx() };
LengthPercentage cy { InitialValues::cy() };
@ -838,6 +840,7 @@ public:
void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
void set_mask(MaskReference value) { m_noninherited.mask = value; }
void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = value; }
void set_mask_image(CSS::AbstractImageStyleValue const& value) { m_noninherited.mask_image = value; }
void set_clip_path(ClipPathReference value) { m_noninherited.clip_path = value; }
void set_clip_rule(CSS::ClipRule value) { m_inherited.clip_rule = value; }

View file

@ -95,6 +95,9 @@
"-webkit-mask": {
"legacy-alias-for": "mask"
},
"-webkit-mask-image": {
"legacy-alias-for": "mask-image"
},
"-webkit-order": {
"legacy-alias-for": "order"
},
@ -1859,6 +1862,18 @@
],
"initial": "none"
},
"mask-image": {
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"valid-types": [
"image"
],
"valid-identifiers": [
"none"
],
"initial": "none"
},
"mask-type": {
"animation-type": "discrete",
"inherited": false,

View file

@ -199,7 +199,7 @@ bool Node::establishes_stacking_context() const
// - perspective
// - clip-path
// - mask / mask-image / mask-border
if (computed_values().mask().has_value() || computed_values().clip_path().has_value())
if (computed_values().mask().has_value() || computed_values().clip_path().has_value() || computed_values().mask_image())
return true;
return computed_values().opacity() < 1.0f;
@ -829,6 +829,12 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
else if (stroke_width.is_percentage())
computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width.as_percentage().percentage() });
if (auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage); mask_image.is_abstract_image()) {
auto const& abstract_image = mask_image.as_abstract_image();
computed_values.set_mask_image(abstract_image);
const_cast<CSS::AbstractImageStyleValue&>(abstract_image).load_any_resources(document());
}
if (auto mask_type = computed_style.mask_type(); mask_type.has_value())
computed_values.set_mask_type(*mask_type);

View file

@ -1177,6 +1177,10 @@ void PaintableBox::resolve_paint_properties()
if (background_layers) {
m_resolved_background = resolve_background_layers(*background_layers, *this, background_color, background_rect, normalized_border_radii_data());
};
if (auto mask_image = computed_values.mask_image()) {
mask_image->resolve_for_size(layout_node_with_style_and_box_metrics(), absolute_padding_box_rect().size());
}
}
void PaintableWithLines::resolve_paint_properties()

View file

@ -329,6 +329,15 @@ void StackingContext::paint(PaintContext& context) const
}
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
if (auto mask_image = computed_values.mask_image()) {
auto mask_display_list = DisplayList::create();
DisplayListRecorder display_list_recorder(*mask_display_list);
auto mask_painting_context = context.clone(display_list_recorder);
auto mask_rect_in_device_pixels = context.enclosing_device_rect(paintable_box().absolute_padding_box_rect());
mask_image->paint(mask_painting_context, { {}, mask_rect_in_device_pixels.size() }, CSS::ImageRendering::Auto);
context.display_list_recorder().add_mask(mask_display_list, mask_rect_in_device_pixels.to_type<int>());
}
if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
if (masking_area->is_empty())
return;

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Masking: mask-image: mask layer image</title>
<link rel="author" title="Astley Chen" href="mailto:aschen@mozilla.com">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style type="text/css">
div {
background-color: purple;
width: 100px;
height: 50px;
}
</style>
</head>
<body>
<div></div>
</body>
</html>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Masking: mask-image: mask layer image</title>
<link rel="author" title="Astley Chen" href="mailto:aschen@mozilla.com">
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-mask-image">
<link rel="match" href="../../../../../expected/wpt-import/css/css-masking/mask-image/mask-image-1-ref.html">
<meta name="assert" content="Test checks whether image as mask layer works correctly or not.">
<style type="text/css">
div {
background-color: purple;
width: 100px;
height: 100px;
}
div.mask-by-png {
mask-image: url(../../../../../data/transparent-100x50-blue-100x50.png);
}
</style>
</head>
<body>
<div class="mask-by-png"></div>
<!-- Hack to make the test runner wait for the image to load -->
<img src="../../../../../data/transparent-100x50-blue-100x50.png" style="opacity: 0"/>
</body>
</html>

View file

@ -1,6 +1,6 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': ''
'length': '201'
'length': '202'
'parentRule': 'null'
'cssFloat': 'none'
'WebkitAlignContent': 'normal'
@ -99,6 +99,9 @@ All supported properties and their default values exposed from CSSStyleDeclarati
'WebkitMask': 'none'
'webkitMask': 'none'
'-webkit-mask': 'none'
'WebkitMaskImage': 'none'
'webkitMaskImage': 'none'
'-webkit-mask-image': 'none'
'WebkitOrder': '0'
'webkitOrder': '0'
'-webkit-order': '0'
@ -403,6 +406,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati
'marginTop': '8px'
'margin-top': '8px'
'mask': 'none'
'maskImage': 'none'
'mask-image': 'none'
'maskType': 'luminance'
'mask-type': 'luminance'
'mathDepth': '0'

View file

@ -145,62 +145,63 @@ All properties associated with getComputedStyle(document.body):
"142": "margin-right",
"143": "margin-top",
"144": "mask",
"145": "mask-type",
"146": "max-height",
"147": "max-inline-size",
"148": "max-width",
"149": "min-height",
"150": "min-inline-size",
"151": "min-width",
"152": "object-fit",
"153": "object-position",
"154": "opacity",
"155": "order",
"156": "outline-color",
"157": "outline-offset",
"158": "outline-style",
"159": "outline-width",
"160": "overflow-x",
"161": "overflow-y",
"162": "padding-block-end",
"163": "padding-block-start",
"164": "padding-bottom",
"165": "padding-inline-end",
"166": "padding-inline-start",
"167": "padding-left",
"168": "padding-right",
"169": "padding-top",
"170": "position",
"171": "r",
"172": "right",
"173": "rotate",
"174": "row-gap",
"175": "rx",
"176": "ry",
"177": "scrollbar-gutter",
"178": "scrollbar-width",
"179": "stop-color",
"180": "stop-opacity",
"181": "table-layout",
"182": "text-decoration-color",
"183": "text-decoration-style",
"184": "text-decoration-thickness",
"185": "text-overflow",
"186": "top",
"187": "transform",
"188": "transform-box",
"189": "transform-origin",
"190": "transition-delay",
"191": "transition-duration",
"192": "transition-property",
"193": "transition-timing-function",
"194": "unicode-bidi",
"195": "user-select",
"196": "vertical-align",
"197": "width",
"198": "x",
"199": "y",
"200": "z-index"
"145": "mask-image",
"146": "mask-type",
"147": "max-height",
"148": "max-inline-size",
"149": "max-width",
"150": "min-height",
"151": "min-inline-size",
"152": "min-width",
"153": "object-fit",
"154": "object-position",
"155": "opacity",
"156": "order",
"157": "outline-color",
"158": "outline-offset",
"159": "outline-style",
"160": "outline-width",
"161": "overflow-x",
"162": "overflow-y",
"163": "padding-block-end",
"164": "padding-block-start",
"165": "padding-bottom",
"166": "padding-inline-end",
"167": "padding-inline-start",
"168": "padding-left",
"169": "padding-right",
"170": "padding-top",
"171": "position",
"172": "r",
"173": "right",
"174": "rotate",
"175": "row-gap",
"176": "rx",
"177": "ry",
"178": "scrollbar-gutter",
"179": "scrollbar-width",
"180": "stop-color",
"181": "stop-opacity",
"182": "table-layout",
"183": "text-decoration-color",
"184": "text-decoration-style",
"185": "text-decoration-thickness",
"186": "text-overflow",
"187": "top",
"188": "transform",
"189": "transform-box",
"190": "transform-origin",
"191": "transition-delay",
"192": "transition-duration",
"193": "transition-property",
"194": "transition-timing-function",
"195": "unicode-bidi",
"196": "user-select",
"197": "vertical-align",
"198": "width",
"199": "x",
"200": "y",
"201": "z-index"
}
All properties associated with document.body.style by default:
{}

View file

@ -143,6 +143,7 @@ margin-left: 8px
margin-right: 8px
margin-top: 8px
mask: none
mask-image: none
mask-type: luminance
max-height: none
max-inline-size: none