diff --git a/Libraries/LibGfx/CompositingAndBlendingOperator.h b/Libraries/LibGfx/CompositingAndBlendingOperator.h new file mode 100644 index 00000000000..927afd9188a --- /dev/null +++ b/Libraries/LibGfx/CompositingAndBlendingOperator.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025, Glenn Skrzypczak + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Gfx { + +enum class CompositingAndBlendingOperator { + Normal, + Multiply, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Exclusion, + Hue, + Saturation, + Color, + Luminosity, + Clear, + Copy, + SourceOver, + DestinationOver, + SourceIn, + DestinationIn, + SourceOut, + DestinationOut, + SourceATop, + DestinationATop, + Xor, + Lighter, + PlusDarker, + PlusLighter +}; + +} diff --git a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp index 5df0f210d47..ef796a793b4 100644 --- a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp @@ -283,12 +283,11 @@ ErrorOr PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in switch (blend_op) { case PNG_BLEND_OP_SOURCE: // All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. - painter->clear_rect(frame_rect, Gfx::Color::Transparent); - painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), Gfx::ScalingMode::NearestNeighbor, {}, 1.0f); + painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), Gfx::ScalingMode::NearestNeighbor, {}, 1.0f, Gfx::CompositingAndBlendingOperator::Copy); break; case PNG_BLEND_OP_OVER: // The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification. - painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), ScalingMode::NearestNeighbor, {}, 1.0f); + painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*decoded_frame_bitmap), decoded_frame_bitmap->rect(), ScalingMode::NearestNeighbor, {}, 1.0f, Gfx::CompositingAndBlendingOperator::SourceOver); break; default: VERIFY_NOT_REACHED(); @@ -306,8 +305,7 @@ ErrorOr PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in break; case PNG_DISPOSE_OP_PREVIOUS: // The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame. - painter->clear_rect(frame_rect, Gfx::Color::Transparent); - painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*prev_output_buffer), IntRect { x, y, width, height }, Gfx::ScalingMode::NearestNeighbor, {}, 1.0f); + painter->draw_bitmap(frame_rect, Gfx::ImmutableBitmap::create(*prev_output_buffer), IntRect { x, y, width, height }, Gfx::ScalingMode::NearestNeighbor, {}, 1.0f, Gfx::CompositingAndBlendingOperator::Copy); break; default: VERIFY_NOT_REACHED(); diff --git a/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp b/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp index b05aaf4bd70..0f53759d78d 100644 --- a/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp @@ -481,7 +481,7 @@ void TinyVGDecodedImageData::draw(Painter& painter) const command.fill->visit( [&](Color color) { painter.fill_path(fill_path, color, WindingRule::EvenOdd); }, [&](NonnullRefPtr const& style) { - painter.fill_path(fill_path, style, {}, 1.0f, WindingRule::EvenOdd); + painter.fill_path(fill_path, style, {}, 1.0f, CompositingAndBlendingOperator::SourceOver, WindingRule::EvenOdd); }); } @@ -489,7 +489,7 @@ void TinyVGDecodedImageData::draw(Painter& painter) const command.stroke->visit( [&](Color color) { painter.stroke_path(draw_path, color, command.stroke_width); }, [&](NonnullRefPtr const& style) { - painter.stroke_path(draw_path, style, {}, command.stroke_width, 1.0f); + painter.stroke_path(draw_path, style, {}, command.stroke_width, 1.0f, CompositingAndBlendingOperator::SourceOver); }); } } diff --git a/Libraries/LibGfx/Painter.h b/Libraries/LibGfx/Painter.h index 1df515c95c2..67aef92dfda 100644 --- a/Libraries/LibGfx/Painter.h +++ b/Libraries/LibGfx/Painter.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -24,15 +25,15 @@ public: virtual void clear_rect(Gfx::FloatRect const&, Gfx::Color) = 0; virtual void fill_rect(Gfx::FloatRect const&, Gfx::Color) = 0; - virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, ReadonlySpan filters, float global_alpha) = 0; + virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, ReadonlySpan filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0; virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) = 0; - virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius) = 0; - virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float thickness, float global_alpha) = 0; + virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0; + virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0; virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) = 0; - virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule, float blur_radius) = 0; - virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float global_alpha, Gfx::WindingRule) = 0; + virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0; + virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule) = 0; virtual void set_transform(Gfx::AffineTransform const&) = 0; diff --git a/Libraries/LibGfx/PainterSkia.cpp b/Libraries/LibGfx/PainterSkia.cpp index f34343dbf49..082b8e9af7c 100644 --- a/Libraries/LibGfx/PainterSkia.cpp +++ b/Libraries/LibGfx/PainterSkia.cpp @@ -9,6 +9,7 @@ #define AK_DONT_REPLACE_STD #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include #include #include @@ -130,11 +132,12 @@ void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color) impl().canvas()->drawRect(to_skia_rect(rect), paint); } -void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan filters, float global_alpha) +void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) { SkPaint paint; apply_filters(paint, filters); paint.setAlpha(static_cast(global_alpha * 255)); + paint.setBlender(to_skia_blender(compositing_and_blending_operator)); impl().canvas()->drawImageRect( src_bitmap.sk_image(), @@ -170,7 +173,7 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi impl().canvas()->drawPath(sk_path, paint); } -void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius) +void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) { // Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing. if (thickness <= 0) @@ -182,11 +185,12 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(thickness); paint.setColor(to_skia_color(color)); + paint.setBlender(to_skia_blender(compositing_and_blending_operator)); auto sk_path = to_skia_path(path); impl().canvas()->drawPath(sk_path, paint); } -void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float thickness, float global_alpha) +void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) { // Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing. if (thickness <= 0) @@ -199,6 +203,7 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain paint.setAlphaf(alpha * global_alpha); paint.setStyle(SkPaint::Style::kStroke_Style); paint.setStrokeWidth(thickness); + paint.setBlender(to_skia_blender(compositing_and_blending_operator)); impl().canvas()->drawPath(sk_path, paint); } @@ -212,18 +217,19 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::Windin impl().canvas()->drawPath(sk_path, paint); } -void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule, float blur_radius) +void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) { SkPaint paint; paint.setAntiAlias(true); paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, blur_radius / 2)); paint.setColor(to_skia_color(color)); + paint.setBlender(to_skia_blender(compositing_and_blending_operator)); auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); impl().canvas()->drawPath(sk_path, paint); } -void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float global_alpha, Gfx::WindingRule winding_rule) +void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule winding_rule) { auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); @@ -231,6 +237,7 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_ paint.setAntiAlias(true); float alpha = paint.getAlphaf(); paint.setAlphaf(alpha * global_alpha); + paint.setBlender(to_skia_blender(compositing_and_blending_operator)); impl().canvas()->drawPath(sk_path, paint); } diff --git a/Libraries/LibGfx/PainterSkia.h b/Libraries/LibGfx/PainterSkia.h index 9ac3d4f52af..1c3427ab46d 100644 --- a/Libraries/LibGfx/PainterSkia.h +++ b/Libraries/LibGfx/PainterSkia.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -20,13 +21,13 @@ public: virtual void clear_rect(Gfx::FloatRect const&, Color) override; virtual void fill_rect(Gfx::FloatRect const&, Color) override; - virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, ReadonlySpan, float global_alpha) override; + virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, ReadonlySpan, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override; virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) override; - virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius) override; - virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float thickness, float global_alpha) override; + virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override; + virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override; virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override; - virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule, float blur_radius) override; - virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float global_alpha, Gfx::WindingRule) override; + virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override; + virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule) override; virtual void set_transform(Gfx::AffineTransform const&) override; virtual void save() override; virtual void restore() override; diff --git a/Libraries/LibGfx/SkiaUtils.cpp b/Libraries/LibGfx/SkiaUtils.cpp index 1e952120fd6..9228e3e6b9b 100644 --- a/Libraries/LibGfx/SkiaUtils.cpp +++ b/Libraries/LibGfx/SkiaUtils.cpp @@ -7,9 +7,12 @@ #include #include #include +#include #include #include +#include #include +#include namespace Gfx { @@ -140,4 +143,85 @@ sk_sp to_skia_image_filter(Gfx::Filter const& filter) }); } +sk_sp to_skia_blender(Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) +{ + switch (compositing_and_blending_operator) { + case CompositingAndBlendingOperator::Normal: + return SkBlender::Mode(SkBlendMode::kSrcOver); + case CompositingAndBlendingOperator::Multiply: + return SkBlender::Mode(SkBlendMode::kMultiply); + case CompositingAndBlendingOperator::Screen: + return SkBlender::Mode(SkBlendMode::kScreen); + case CompositingAndBlendingOperator::Overlay: + return SkBlender::Mode(SkBlendMode::kOverlay); + case CompositingAndBlendingOperator::Darken: + return SkBlender::Mode(SkBlendMode::kDarken); + case CompositingAndBlendingOperator::Lighten: + return SkBlender::Mode(SkBlendMode::kLighten); + case CompositingAndBlendingOperator::ColorDodge: + return SkBlender::Mode(SkBlendMode::kColorDodge); + case CompositingAndBlendingOperator::ColorBurn: + return SkBlender::Mode(SkBlendMode::kColorBurn); + case CompositingAndBlendingOperator::HardLight: + return SkBlender::Mode(SkBlendMode::kHardLight); + case CompositingAndBlendingOperator::SoftLight: + return SkBlender::Mode(SkBlendMode::kSoftLight); + case CompositingAndBlendingOperator::Difference: + return SkBlender::Mode(SkBlendMode::kDifference); + case CompositingAndBlendingOperator::Exclusion: + return SkBlender::Mode(SkBlendMode::kExclusion); + case CompositingAndBlendingOperator::Hue: + return SkBlender::Mode(SkBlendMode::kHue); + case CompositingAndBlendingOperator::Saturation: + return SkBlender::Mode(SkBlendMode::kSaturation); + case CompositingAndBlendingOperator::Color: + return SkBlender::Mode(SkBlendMode::kColor); + case CompositingAndBlendingOperator::Luminosity: + return SkBlender::Mode(SkBlendMode::kLuminosity); + case CompositingAndBlendingOperator::Clear: + return SkBlender::Mode(SkBlendMode::kClear); + case CompositingAndBlendingOperator::Copy: + return SkBlender::Mode(SkBlendMode::kSrc); + case CompositingAndBlendingOperator::SourceOver: + return SkBlender::Mode(SkBlendMode::kSrcOver); + case CompositingAndBlendingOperator::DestinationOver: + return SkBlender::Mode(SkBlendMode::kDstOver); + case CompositingAndBlendingOperator::SourceIn: + return SkBlender::Mode(SkBlendMode::kSrcIn); + case CompositingAndBlendingOperator::DestinationIn: + return SkBlender::Mode(SkBlendMode::kDstIn); + case CompositingAndBlendingOperator::SourceOut: + return SkBlender::Mode(SkBlendMode::kSrcOut); + case CompositingAndBlendingOperator::DestinationOut: + return SkBlender::Mode(SkBlendMode::kDstOut); + case CompositingAndBlendingOperator::SourceATop: + return SkBlender::Mode(SkBlendMode::kSrcATop); + case CompositingAndBlendingOperator::DestinationATop: + return SkBlender::Mode(SkBlendMode::kDstATop); + case CompositingAndBlendingOperator::Xor: + return SkBlender::Mode(SkBlendMode::kXor); + case CompositingAndBlendingOperator::Lighter: + return SkBlender::Mode(SkBlendMode::kPlus); + case CompositingAndBlendingOperator::PlusDarker: + // https://drafts.fxtf.org/compositing/#porterduffcompositingoperators_plus_darker + // FIXME: This does not match the spec, however it looks like Safari, the only popular browser supporting this operator. + return SkRuntimeEffect::MakeForBlender(SkString(R"( + vec4 main(vec4 source, vec4 destination) { + return saturate(saturate(destination.a + source.a) - saturate(destination.a - destination) - saturate(source.a - source)); + } + )")) + .effect->makeBlender(nullptr); + case CompositingAndBlendingOperator::PlusLighter: + // https://drafts.fxtf.org/compositing/#porterduffcompositingoperators_plus_lighter + return SkRuntimeEffect::MakeForBlender(SkString(R"( + vec4 main(vec4 source, vec4 destination) { + return saturate(source + destination); + } + )")) + .effect->makeBlender(nullptr); + default: + VERIFY_NOT_REACHED(); + } +} + } diff --git a/Libraries/LibGfx/SkiaUtils.h b/Libraries/LibGfx/SkiaUtils.h index 656bc02ab55..8cf969e8b00 100644 --- a/Libraries/LibGfx/SkiaUtils.h +++ b/Libraries/LibGfx/SkiaUtils.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include namespace Gfx { @@ -90,5 +92,5 @@ constexpr SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mo SkPath to_skia_path(Path const& path); sk_sp to_skia_image_filter(Gfx::Filter const& filter); - +sk_sp to_skia_blender(Gfx::CompositingAndBlendingOperator compositing_and_blending_operator); } diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h b/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h index 1861e6e4c4d..e61931b0d9f 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h +++ b/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h @@ -18,6 +18,9 @@ public: virtual float global_alpha() const = 0; virtual void set_global_alpha(float) = 0; + virtual String global_composite_operation() const = 0; + virtual void set_global_composite_operation(String) = 0; + protected: CanvasCompositing() = default; }; diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl b/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl index c67ed293e81..d7b93be3e85 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl +++ b/Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl @@ -2,5 +2,5 @@ interface mixin CanvasCompositing { // compositing attribute unrestricted double globalAlpha; // (default 1.0) - [FIXME] attribute DOMString globalCompositeOperation; // (default "source-over") + attribute DOMString globalCompositeOperation; // (default "source-over") }; diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasState.h b/Libraries/LibWeb/HTML/Canvas/CanvasState.h index 9c4b65b2665..daf111a41c8 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasState.h +++ b/Libraries/LibWeb/HTML/Canvas/CanvasState.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,7 @@ public: bool image_smoothing_enabled { true }; Bindings::ImageSmoothingQuality image_smoothing_quality { Bindings::ImageSmoothingQuality::Low }; float global_alpha = { 1 }; + Gfx::CompositingAndBlendingOperator current_compositing_and_blending_operator = Gfx::CompositingAndBlendingOperator::SourceOver; RefPtr font_style_value { nullptr }; RefPtr current_font { nullptr }; Bindings::CanvasTextAlign text_align { Bindings::CanvasTextAlign::Start }; diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 9e52b423e81..b5b54ba719b 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -178,7 +179,7 @@ WebIDL::ExceptionOr CanvasRenderingContext2D::draw_image_internal(CanvasIm } if (auto* painter = this->painter()) { - painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded(), scaling_mode, drawing_state().filters, drawing_state().global_alpha); + painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded(), scaling_mode, drawing_state().filters, drawing_state().global_alpha, drawing_state().current_compositing_and_blending_operator); did_draw(destination_rect); } @@ -302,7 +303,7 @@ void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path) auto& state = drawing_state(); // FIXME: Honor state's line_cap, line_join, miter_limit, dash_list, and line_dash_offset. - painter->stroke_path(path, state.stroke_style.to_gfx_paint_style(), state.filters, state.line_width, state.global_alpha); + painter->stroke_path(path, state.stroke_style.to_gfx_paint_style(), state.filters, state.line_width, state.global_alpha, state.current_compositing_and_blending_operator); did_draw(path.bounding_box()); } @@ -338,7 +339,7 @@ void CanvasRenderingContext2D::fill_internal(Gfx::Path const& path, Gfx::Winding auto path_to_fill = path; path_to_fill.close_all_subpaths(); auto& state = this->drawing_state(); - painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.filters, state.global_alpha, winding_rule); + painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.filters, state.global_alpha, state.current_compositing_and_blending_operator, winding_rule); did_draw(path_to_fill.bounding_box()); } @@ -430,7 +431,7 @@ WebIDL::ExceptionOr> CanvasRenderingContext2D::get_image_data ASSERT(image_data->bitmap().alpha_type() == Gfx::AlphaType::Unpremultiplied); auto painter = Gfx::Painter::create(image_data->bitmap()); - painter->draw_bitmap(image_data->bitmap().rect().to_type(), *snapshot, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, {}, drawing_state().global_alpha); + painter->draw_bitmap(image_data->bitmap().rect().to_type(), *snapshot, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, {}, drawing_state().global_alpha, Gfx::CompositingAndBlendingOperator::SourceOver); // 7. Set the pixels values of imageData for areas of the source rectangle that are outside of the output bitmap to transparent black. // NOTE: No-op, already done during creation. @@ -443,7 +444,7 @@ void CanvasRenderingContext2D::put_image_data(ImageData const& image_data, float { if (auto* painter = this->painter()) { auto dst_rect = Gfx::FloatRect(x, y, image_data.width(), image_data.height()); - painter->draw_bitmap(dst_rect, Gfx::ImmutableBitmap::create(image_data.bitmap()), image_data.bitmap().rect(), Gfx::ScalingMode::NearestNeighbor, drawing_state().filters, 1.0f); + painter->draw_bitmap(dst_rect, Gfx::ImmutableBitmap::create(image_data.bitmap()), image_data.bitmap().rect(), Gfx::ScalingMode::NearestNeighbor, drawing_state().filters, 1.0f, Gfx::CompositingAndBlendingOperator::SourceOver); did_draw(dst_rect); } } @@ -751,6 +752,68 @@ void CanvasRenderingContext2D::set_global_alpha(float alpha) drawing_state().global_alpha = alpha; } +#define ENUMERATE_COMPOSITE_OPERATIONS(E) \ + E("normal", Normal) \ + E("multiply", Multiply) \ + E("screen", Screen) \ + E("overlay", Overlay) \ + E("darken", Darken) \ + E("lighten", Lighten) \ + E("color-dodge", ColorDodge) \ + E("color-burn", ColorBurn) \ + E("hard-light", HardLight) \ + E("soft-light", SoftLight) \ + E("difference", Difference) \ + E("exclusion", Exclusion) \ + E("hue", Hue) \ + E("saturation", Saturation) \ + E("color", Color) \ + E("luminosity", Luminosity) \ + E("clear", Clear) \ + E("copy", Copy) \ + E("source-over", SourceOver) \ + E("destination-over", DestinationOver) \ + E("source-in", SourceIn) \ + E("destination-in", DestinationIn) \ + E("source-out", SourceOut) \ + E("destination-out", DestinationOut) \ + E("source-atop", SourceATop) \ + E("destination-atop", DestinationATop) \ + E("xor", Xor) \ + E("lighter", Lighter) \ + E("plus-darker", PlusDarker) \ + E("plus-lighter", PlusLighter) + +String CanvasRenderingContext2D::global_composite_operation() const +{ + auto current_compositing_and_blending_operator = drawing_state().current_compositing_and_blending_operator; + switch (current_compositing_and_blending_operator) { +#undef __ENUMERATE +#define __ENUMERATE(operation, compositing_and_blending_operator) \ + case Gfx::CompositingAndBlendingOperator::compositing_and_blending_operator: \ + return #operation##_string; + ENUMERATE_COMPOSITE_OPERATIONS(__ENUMERATE) +#undef __ENUMERATE + default: + VERIFY_NOT_REACHED(); + } +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-globalcompositeoperation +void CanvasRenderingContext2D::set_global_composite_operation(String global_composite_operation) +{ + // 1. If the given value is not identical to any of the values that the or the properties are defined to take, then return. + // 2. Otherwise, set this's current compositing and blending operator to the given value. +#undef __ENUMERATE +#define __ENUMERATE(operation, compositing_and_blending_operator) \ + if (global_composite_operation == operation##sv) { \ + drawing_state().current_compositing_and_blending_operator = Gfx::CompositingAndBlendingOperator::compositing_and_blending_operator; \ + return; \ + } + ENUMERATE_COMPOSITE_OPERATIONS(__ENUMERATE) +#undef __ENUMERATE +} + float CanvasRenderingContext2D::shadow_offset_x() const { return drawing_state().shadow_offset_x; @@ -822,12 +885,15 @@ void CanvasRenderingContext2D::paint_shadow_for_fill_internal(Gfx::Path const& p auto& state = this->drawing_state(); + if (state.current_compositing_and_blending_operator == Gfx::CompositingAndBlendingOperator::Copy) + return; + painter->save(); Gfx::AffineTransform transform; transform.translate(state.shadow_offset_x, state.shadow_offset_y); painter->set_transform(transform); - painter->fill_path(path_to_fill, state.shadow_color.with_opacity(state.global_alpha), winding_rule, state.shadow_blur); + painter->fill_path(path_to_fill, state.shadow_color.with_opacity(state.global_alpha), winding_rule, state.shadow_blur, state.current_compositing_and_blending_operator); painter->restore(); @@ -842,12 +908,15 @@ void CanvasRenderingContext2D::paint_shadow_for_stroke_internal(Gfx::Path const& auto& state = drawing_state(); + if (state.current_compositing_and_blending_operator == Gfx::CompositingAndBlendingOperator::Copy) + return; + painter->save(); Gfx::AffineTransform transform; transform.translate(state.shadow_offset_x, state.shadow_offset_y); painter->set_transform(transform); - painter->stroke_path(path, state.shadow_color.with_opacity(state.global_alpha), state.line_width, state.shadow_blur); + painter->stroke_path(path, state.shadow_color.with_opacity(state.global_alpha), state.line_width, state.shadow_blur, state.current_compositing_and_blending_operator); painter->restore(); @@ -933,5 +1002,4 @@ void CanvasRenderingContext2D::set_filter(String filter) // 3. If parsedValue is failure, then return. } - } diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 08e7c015153..9cf096bc7ca 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -103,6 +103,9 @@ public: virtual float global_alpha() const override; virtual void set_global_alpha(float) override; + virtual String global_composite_operation() const override; + virtual void set_global_composite_operation(String) override; + virtual String filter() const override; virtual void set_filter(String) override; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.txt new file mode 100644 index 00000000000..916c3ddf9b5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Canvas test: 2d.composite.transparent.lighter \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.html new file mode 100644 index 00000000000..6241a369cdd --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.html @@ -0,0 +1,32 @@ + + + +Canvas test: 2d.composite.transparent.lighter + + + + + + +

2d.composite.transparent.lighter

+

+ + +

Actual output:

+

FAIL (fallback content)

+

Expected output:

+

    + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.png b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.png new file mode 100644 index 00000000000..0e1c28c0cd1 Binary files /dev/null and b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/compositing/2d.composite.transparent.lighter.png differ