Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb/CSS: Implement background-blend-mode CSS property #3157

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Libraries/LibGfx/BlendMode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025, Stefan Vukanović <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

namespace Gfx {

enum class BlendMode {
Normal,
Darken,
Multiply,
ColorBurn,
Lighten,
Screen,
ColorDodge,
Overlay,
SoftLight,
HardLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity
};

}
41 changes: 41 additions & 0 deletions Libraries/LibGfx/SkiaUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

#include <AK/Assertions.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/BlendMode.h>
#include <LibGfx/Filter.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/WindingRule.h>
#include <core/SkBlendMode.h>
#include <core/SkColor.h>
#include <core/SkColorType.h>
#include <core/SkImageFilter.h>
Expand Down Expand Up @@ -92,3 +94,42 @@ SkPath to_skia_path(Path const& path);
sk_sp<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter);

}

constexpr SkBlendMode to_skia_blend_mode(Gfx::BlendMode blend_mode)
{
switch (blend_mode) {
case Gfx::BlendMode::Normal:
return SkBlendMode::kSrc;
case Gfx::BlendMode::Darken:
return SkBlendMode::kDarken;
case Gfx::BlendMode::Multiply:
return SkBlendMode::kMultiply;
case Gfx::BlendMode::ColorBurn:
return SkBlendMode::kColorBurn;
case Gfx::BlendMode::Lighten:
return SkBlendMode::kLighten;
case Gfx::BlendMode::Screen:
return SkBlendMode::kScreen;
case Gfx::BlendMode::ColorDodge:
return SkBlendMode::kColorDodge;
case Gfx::BlendMode::Overlay:
return SkBlendMode::kOverlay;
case Gfx::BlendMode::SoftLight:
return SkBlendMode::kSoftLight;
case Gfx::BlendMode::HardLight:
return SkBlendMode::kHardLight;
case Gfx::BlendMode::Difference:
return SkBlendMode::kDifference;
case Gfx::BlendMode::Exclusion:
return SkBlendMode::kExclusion;
case Gfx::BlendMode::Hue:
return SkBlendMode::kHue;
case Gfx::BlendMode::Saturation:
return SkBlendMode::kSaturation;
case Gfx::BlendMode::Color:
return SkBlendMode::kColor;
case Gfx::BlendMode::Luminosity:
return SkBlendMode::kLuminosity;
}
VERIFY_NOT_REACHED();
}
42 changes: 42 additions & 0 deletions Libraries/LibWeb/CSS/ComputedValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/Optional.h>
#include <LibGfx/BlendMode.h>
#include <LibGfx/Filter.h>
#include <LibGfx/FontCascadeList.h>
#include <LibGfx/ScalingMode.h>
Expand Down Expand Up @@ -270,6 +271,7 @@ struct BackgroundLayerData {
CSS::LengthPercentage size_y { CSS::Length::make_auto() };
CSS::Repeat repeat_x { CSS::Repeat::Repeat };
CSS::Repeat repeat_y { CSS::Repeat::Repeat };
CSS::BlendMode blend_mode { CSS::BlendMode::Normal };
};

struct BorderData {
Expand Down Expand Up @@ -336,6 +338,46 @@ inline Gfx::ScalingMode to_gfx_scaling_mode(CSS::ImageRendering css_value, Gfx::
VERIFY_NOT_REACHED();
}

// FIXME: Find a better place for this helper.
constexpr Gfx::BlendMode to_gfx_blend_mode(CSS::BlendMode blend_mode)
{
switch (blend_mode) {
case BlendMode::Normal:
return Gfx::BlendMode::Normal;
case BlendMode::Darken:
return Gfx::BlendMode::Darken;
case BlendMode::Multiply:
return Gfx::BlendMode::Multiply;
case BlendMode::ColorBurn:
return Gfx::BlendMode::ColorBurn;
case BlendMode::Lighten:
return Gfx::BlendMode::Lighten;
case BlendMode::Screen:
return Gfx::BlendMode::Screen;
case BlendMode::ColorDodge:
return Gfx::BlendMode::ColorDodge;
case BlendMode::Overlay:
return Gfx::BlendMode::Overlay;
case BlendMode::SoftLight:
return Gfx::BlendMode::SoftLight;
case BlendMode::HardLight:
return Gfx::BlendMode::HardLight;
case BlendMode::Difference:
return Gfx::BlendMode::Difference;
case BlendMode::Exclusion:
return Gfx::BlendMode::Exclusion;
case BlendMode::Hue:
return Gfx::BlendMode::Hue;
case BlendMode::Saturation:
return Gfx::BlendMode::Saturation;
case BlendMode::Color:
return Gfx::BlendMode::Color;
case BlendMode::Luminosity:
return Gfx::BlendMode::Luminosity;
}
VERIFY_NOT_REACHED();
}

class ComputedValues {
AK_MAKE_NONCOPYABLE(ComputedValues);
AK_MAKE_NONMOVABLE(ComputedValues);
Expand Down
18 changes: 18 additions & 0 deletions Libraries/LibWeb/CSS/Enums.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@
"local",
"scroll"
],
"blend-mode": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go after background-box.

"normal",
"darken",
"multiply",
"color-burn",
"lighten",
"screen",
"color-dodge",
"overlay",
"soft-light",
"hard-light",
"difference",
"exclusion",
"hue",
"saturation",
"color",
"luminosity"
],
"background-box": [
"border-box",
"content-box",
Expand Down
17 changes: 16 additions & 1 deletion Libraries/LibWeb/CSS/Keywords.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,5 +457,20 @@
"xx-small",
"xxx-large",
"zoom-in",
"zoom-out"
"zoom-out",
"darken",
"multiply",
"color-burn",
"lighten",
"screen",
"color-dodge",
"overlay",
"soft-light",
"hard-light",
"difference",
"exclusion",
"hue",
"saturation",
"color",
"luminosity"
Comment on lines +461 to +475
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you asked: This whole file should be alphabetically sorted.

]
1 change: 1 addition & 0 deletions Libraries/LibWeb/CSS/Parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8413,6 +8413,7 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(Prope
if (auto parsed_value = parse_background_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::BackgroundBlendMode:
case PropertyID::BackgroundAttachment:
Comment on lines +8416 to 8417
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be alphabetical too.

case PropertyID::BackgroundClip:
case PropertyID::BackgroundImage:
Expand Down
9 changes: 9 additions & 0 deletions Libraries/LibWeb/CSS/Properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@
"background-attachment"
]
},
"background-blend-mode": {
"affects-layout": false,
"animation-type": "none",
"inherited": false,
"initial": "normal",
"valid-types": [
"blend-mode"
]
},
"background-clip": {
"affects-layout": false,
"animation-type": "repeatable-list",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AbstractImageStyleValue : public CSSStyleValue {
virtual void resolve_for_size(Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelSize) const { }

virtual bool is_paintable() const = 0;
virtual void paint(PaintContext& context, DevicePixelRect const& dest_rect, ImageRendering) const = 0;
virtual void paint(PaintContext& context, DevicePixelRect const& dest_rect, ImageRendering, CSS::BlendMode blend_mode = CSS::BlendMode::Normal) const = 0;

virtual Optional<Gfx::Color> color_if_single_pixel_bitmap() const { return {}; }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ void ConicGradientStyleValue::resolve_for_size(Layout::NodeWithStyleAndBoxModelM
m_resolved->position = m_properties.position->resolved(node, CSSPixelRect { { 0, 0 }, size });
}

void ConicGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
void ConicGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering, CSS::BlendMode blend_mode) const
{
VERIFY(m_resolved.has_value());
auto destination_rect = dest_rect.to_type<int>();
auto position = context.rounded_device_point(m_resolved->position).to_type<int>();
context.display_list_recorder().fill_rect_with_conic_gradient(destination_rect, m_resolved->data, position);
context.display_list_recorder().fill_rect_with_conic_gradient(destination_rect, m_resolved->data, position, to_gfx_blend_mode(blend_mode));
}

bool ConicGradientStyleValue::equals(CSSStyleValue const& other) const
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ConicGradientStyleValue final : public AbstractImageStyleValue {

virtual String to_string(SerializationMode) const override;

void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering, CSS::BlendMode blend_mode = CSS::BlendMode::Normal) const override;

virtual bool equals(CSSStyleValue const& other) const override;

Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ Optional<CSSPixelFraction> ImageStyleValue::natural_aspect_ratio() const
return {};
}

void ImageStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const
void ImageStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering, CSS::BlendMode blend_mode) const
{
if (auto const* b = bitmap(m_current_frame_index, dest_rect.size().to_type<int>()); b != nullptr) {
auto scaling_mode = to_gfx_scaling_mode(image_rendering, b->rect(), dest_rect.to_type<int>());
context.display_list_recorder().draw_scaled_immutable_bitmap(dest_rect.to_type<int>(), *b, b->rect(), scaling_mode);
context.display_list_recorder().draw_scaled_immutable_bitmap(dest_rect.to_type<int>(), *b, b->rect(), scaling_mode, to_gfx_blend_mode(blend_mode));
}
}

Expand Down
3 changes: 2 additions & 1 deletion Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#pragma once

#include <LibGC/Root.h>
#include <LibGfx/BlendMode.h>
#include <LibJS/Heap/Cell.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/Enums.h>
Expand Down Expand Up @@ -40,7 +41,7 @@ class ImageStyleValue final
Optional<CSSPixelFraction> natural_aspect_ratio() const override;

virtual bool is_paintable() const override;
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override;
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering, CSS::BlendMode blend_mode = CSS::BlendMode::Normal) const override;

virtual Optional<Gfx::Color> color_if_single_pixel_bitmap() const override;
Gfx::ImmutableBitmap const* current_frame_bitmap(DevicePixelRect const& dest_rect) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ void LinearGradientStyleValue::resolve_for_size(Layout::NodeWithStyleAndBoxModel
m_resolved = ResolvedData { Painting::resolve_linear_gradient_data(node, size, *this), size };
}

void LinearGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
void LinearGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering, CSS::BlendMode blend_mode) const
{
VERIFY(m_resolved.has_value());
context.display_list_recorder().fill_rect_with_linear_gradient(dest_rect.to_type<int>(), m_resolved->data);
context.display_list_recorder().fill_rect_with_linear_gradient(dest_rect.to_type<int>(), m_resolved->data, to_gfx_blend_mode(blend_mode));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class LinearGradientStyleValue final : public AbstractImageStyleValue {
void resolve_for_size(Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelSize) const override;

bool is_paintable() const override { return true; }
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override;
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering, CSS::BlendMode blend_mode = CSS::BlendMode::Normal) const override;

private:
LinearGradientStyleValue(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,12 @@ bool RadialGradientStyleValue::equals(CSSStyleValue const& other) const
return m_properties == other_gradient.m_properties;
}

void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering, CSS::BlendMode blend_mode) const
{
VERIFY(m_resolved.has_value());
auto center = context.rounded_device_point(m_resolved->center).to_type<int>();
auto size = context.rounded_device_size(m_resolved->gradient_size).to_type<int>();
context.display_list_recorder().fill_rect_with_radial_gradient(dest_rect.to_type<int>(), m_resolved->data, center, size);
context.display_list_recorder().fill_rect_with_radial_gradient(dest_rect.to_type<int>(), m_resolved->data, center, size, to_gfx_blend_mode(blend_mode));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class RadialGradientStyleValue final : public AbstractImageStyleValue {

virtual String to_string(SerializationMode) const override;

void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering) const override;
void paint(PaintContext&, DevicePixelRect const& dest_rect, CSS::ImageRendering, CSS::BlendMode blend_mode = CSS::BlendMode::Normal) const override;

virtual bool equals(CSSStyleValue const& other) const override;

Expand Down
Loading
Loading