mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-31 05:09:12 +00:00
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:
parent
7b7bb60393
commit
96a35767b6
Notes:
github-actions[bot]
2024-11-18 21:59:52 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 96a35767b6
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2418
11 changed files with 149 additions and 58 deletions
|
@ -486,6 +486,7 @@ public:
|
||||||
Color stop_color() const { return m_noninherited.stop_color; }
|
Color stop_color() const { return m_noninherited.stop_color; }
|
||||||
float stop_opacity() const { return m_noninherited.stop_opacity; }
|
float stop_opacity() const { return m_noninherited.stop_opacity; }
|
||||||
CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; }
|
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; }
|
Optional<MaskReference> const& mask() const { return m_noninherited.mask; }
|
||||||
CSS::MaskType mask_type() const { return m_noninherited.mask_type; }
|
CSS::MaskType mask_type() const { return m_noninherited.mask_type; }
|
||||||
Optional<ClipPathReference> const& clip_path() const { return m_noninherited.clip_path; }
|
Optional<ClipPathReference> const& clip_path() const { return m_noninherited.clip_path; }
|
||||||
|
@ -681,6 +682,7 @@ protected:
|
||||||
Optional<MaskReference> mask;
|
Optional<MaskReference> mask;
|
||||||
CSS::MaskType mask_type { InitialValues::mask_type() };
|
CSS::MaskType mask_type { InitialValues::mask_type() };
|
||||||
Optional<ClipPathReference> clip_path;
|
Optional<ClipPathReference> clip_path;
|
||||||
|
RefPtr<CSS::AbstractImageStyleValue> mask_image;
|
||||||
|
|
||||||
LengthPercentage cx { InitialValues::cx() };
|
LengthPercentage cx { InitialValues::cx() };
|
||||||
LengthPercentage cy { InitialValues::cy() };
|
LengthPercentage cy { InitialValues::cy() };
|
||||||
|
@ -838,6 +840,7 @@ public:
|
||||||
void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
|
void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
|
||||||
void set_mask(MaskReference value) { m_noninherited.mask = 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_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_path(ClipPathReference value) { m_noninherited.clip_path = value; }
|
||||||
void set_clip_rule(CSS::ClipRule value) { m_inherited.clip_rule = value; }
|
void set_clip_rule(CSS::ClipRule value) { m_inherited.clip_rule = value; }
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,9 @@
|
||||||
"-webkit-mask": {
|
"-webkit-mask": {
|
||||||
"legacy-alias-for": "mask"
|
"legacy-alias-for": "mask"
|
||||||
},
|
},
|
||||||
|
"-webkit-mask-image": {
|
||||||
|
"legacy-alias-for": "mask-image"
|
||||||
|
},
|
||||||
"-webkit-order": {
|
"-webkit-order": {
|
||||||
"legacy-alias-for": "order"
|
"legacy-alias-for": "order"
|
||||||
},
|
},
|
||||||
|
@ -1859,6 +1862,18 @@
|
||||||
],
|
],
|
||||||
"initial": "none"
|
"initial": "none"
|
||||||
},
|
},
|
||||||
|
"mask-image": {
|
||||||
|
"animation-type": "discrete",
|
||||||
|
"inherited": false,
|
||||||
|
"affects-layout": false,
|
||||||
|
"valid-types": [
|
||||||
|
"image"
|
||||||
|
],
|
||||||
|
"valid-identifiers": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"initial": "none"
|
||||||
|
},
|
||||||
"mask-type": {
|
"mask-type": {
|
||||||
"animation-type": "discrete",
|
"animation-type": "discrete",
|
||||||
"inherited": false,
|
"inherited": false,
|
||||||
|
|
|
@ -199,7 +199,7 @@ bool Node::establishes_stacking_context() const
|
||||||
// - perspective
|
// - perspective
|
||||||
// - clip-path
|
// - clip-path
|
||||||
// - mask / mask-image / mask-border
|
// - 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 true;
|
||||||
|
|
||||||
return computed_values().opacity() < 1.0f;
|
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())
|
else if (stroke_width.is_percentage())
|
||||||
computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width.as_percentage().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())
|
if (auto mask_type = computed_style.mask_type(); mask_type.has_value())
|
||||||
computed_values.set_mask_type(*mask_type);
|
computed_values.set_mask_type(*mask_type);
|
||||||
|
|
||||||
|
|
|
@ -1177,6 +1177,10 @@ void PaintableBox::resolve_paint_properties()
|
||||||
if (background_layers) {
|
if (background_layers) {
|
||||||
m_resolved_background = resolve_background_layers(*background_layers, *this, background_color, background_rect, normalized_border_radii_data());
|
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()
|
void PaintableWithLines::resolve_paint_properties()
|
||||||
|
|
|
@ -329,6 +329,15 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
}
|
}
|
||||||
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
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 (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
|
||||||
if (masking_area->is_empty())
|
if (masking_area->is_empty())
|
||||||
return;
|
return;
|
||||||
|
|
BIN
Tests/LibWeb/Ref/data/transparent-100x50-blue-100x50.png
Normal file
BIN
Tests/LibWeb/Ref/data/transparent-100x50-blue-100x50.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 233 B |
|
@ -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>
|
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
||||||
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
|
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
|
||||||
'cssText': ''
|
'cssText': ''
|
||||||
'length': '201'
|
'length': '202'
|
||||||
'parentRule': 'null'
|
'parentRule': 'null'
|
||||||
'cssFloat': 'none'
|
'cssFloat': 'none'
|
||||||
'WebkitAlignContent': 'normal'
|
'WebkitAlignContent': 'normal'
|
||||||
|
@ -99,6 +99,9 @@ All supported properties and their default values exposed from CSSStyleDeclarati
|
||||||
'WebkitMask': 'none'
|
'WebkitMask': 'none'
|
||||||
'webkitMask': 'none'
|
'webkitMask': 'none'
|
||||||
'-webkit-mask': 'none'
|
'-webkit-mask': 'none'
|
||||||
|
'WebkitMaskImage': 'none'
|
||||||
|
'webkitMaskImage': 'none'
|
||||||
|
'-webkit-mask-image': 'none'
|
||||||
'WebkitOrder': '0'
|
'WebkitOrder': '0'
|
||||||
'webkitOrder': '0'
|
'webkitOrder': '0'
|
||||||
'-webkit-order': '0'
|
'-webkit-order': '0'
|
||||||
|
@ -403,6 +406,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati
|
||||||
'marginTop': '8px'
|
'marginTop': '8px'
|
||||||
'margin-top': '8px'
|
'margin-top': '8px'
|
||||||
'mask': 'none'
|
'mask': 'none'
|
||||||
|
'maskImage': 'none'
|
||||||
|
'mask-image': 'none'
|
||||||
'maskType': 'luminance'
|
'maskType': 'luminance'
|
||||||
'mask-type': 'luminance'
|
'mask-type': 'luminance'
|
||||||
'mathDepth': '0'
|
'mathDepth': '0'
|
||||||
|
|
|
@ -145,62 +145,63 @@ All properties associated with getComputedStyle(document.body):
|
||||||
"142": "margin-right",
|
"142": "margin-right",
|
||||||
"143": "margin-top",
|
"143": "margin-top",
|
||||||
"144": "mask",
|
"144": "mask",
|
||||||
"145": "mask-type",
|
"145": "mask-image",
|
||||||
"146": "max-height",
|
"146": "mask-type",
|
||||||
"147": "max-inline-size",
|
"147": "max-height",
|
||||||
"148": "max-width",
|
"148": "max-inline-size",
|
||||||
"149": "min-height",
|
"149": "max-width",
|
||||||
"150": "min-inline-size",
|
"150": "min-height",
|
||||||
"151": "min-width",
|
"151": "min-inline-size",
|
||||||
"152": "object-fit",
|
"152": "min-width",
|
||||||
"153": "object-position",
|
"153": "object-fit",
|
||||||
"154": "opacity",
|
"154": "object-position",
|
||||||
"155": "order",
|
"155": "opacity",
|
||||||
"156": "outline-color",
|
"156": "order",
|
||||||
"157": "outline-offset",
|
"157": "outline-color",
|
||||||
"158": "outline-style",
|
"158": "outline-offset",
|
||||||
"159": "outline-width",
|
"159": "outline-style",
|
||||||
"160": "overflow-x",
|
"160": "outline-width",
|
||||||
"161": "overflow-y",
|
"161": "overflow-x",
|
||||||
"162": "padding-block-end",
|
"162": "overflow-y",
|
||||||
"163": "padding-block-start",
|
"163": "padding-block-end",
|
||||||
"164": "padding-bottom",
|
"164": "padding-block-start",
|
||||||
"165": "padding-inline-end",
|
"165": "padding-bottom",
|
||||||
"166": "padding-inline-start",
|
"166": "padding-inline-end",
|
||||||
"167": "padding-left",
|
"167": "padding-inline-start",
|
||||||
"168": "padding-right",
|
"168": "padding-left",
|
||||||
"169": "padding-top",
|
"169": "padding-right",
|
||||||
"170": "position",
|
"170": "padding-top",
|
||||||
"171": "r",
|
"171": "position",
|
||||||
"172": "right",
|
"172": "r",
|
||||||
"173": "rotate",
|
"173": "right",
|
||||||
"174": "row-gap",
|
"174": "rotate",
|
||||||
"175": "rx",
|
"175": "row-gap",
|
||||||
"176": "ry",
|
"176": "rx",
|
||||||
"177": "scrollbar-gutter",
|
"177": "ry",
|
||||||
"178": "scrollbar-width",
|
"178": "scrollbar-gutter",
|
||||||
"179": "stop-color",
|
"179": "scrollbar-width",
|
||||||
"180": "stop-opacity",
|
"180": "stop-color",
|
||||||
"181": "table-layout",
|
"181": "stop-opacity",
|
||||||
"182": "text-decoration-color",
|
"182": "table-layout",
|
||||||
"183": "text-decoration-style",
|
"183": "text-decoration-color",
|
||||||
"184": "text-decoration-thickness",
|
"184": "text-decoration-style",
|
||||||
"185": "text-overflow",
|
"185": "text-decoration-thickness",
|
||||||
"186": "top",
|
"186": "text-overflow",
|
||||||
"187": "transform",
|
"187": "top",
|
||||||
"188": "transform-box",
|
"188": "transform",
|
||||||
"189": "transform-origin",
|
"189": "transform-box",
|
||||||
"190": "transition-delay",
|
"190": "transform-origin",
|
||||||
"191": "transition-duration",
|
"191": "transition-delay",
|
||||||
"192": "transition-property",
|
"192": "transition-duration",
|
||||||
"193": "transition-timing-function",
|
"193": "transition-property",
|
||||||
"194": "unicode-bidi",
|
"194": "transition-timing-function",
|
||||||
"195": "user-select",
|
"195": "unicode-bidi",
|
||||||
"196": "vertical-align",
|
"196": "user-select",
|
||||||
"197": "width",
|
"197": "vertical-align",
|
||||||
"198": "x",
|
"198": "width",
|
||||||
"199": "y",
|
"199": "x",
|
||||||
"200": "z-index"
|
"200": "y",
|
||||||
|
"201": "z-index"
|
||||||
}
|
}
|
||||||
All properties associated with document.body.style by default:
|
All properties associated with document.body.style by default:
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -143,6 +143,7 @@ margin-left: 8px
|
||||||
margin-right: 8px
|
margin-right: 8px
|
||||||
margin-top: 8px
|
margin-top: 8px
|
||||||
mask: none
|
mask: none
|
||||||
|
mask-image: none
|
||||||
mask-type: luminance
|
mask-type: luminance
|
||||||
max-height: none
|
max-height: none
|
||||||
max-inline-size: none
|
max-inline-size: none
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue