mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 11:36:10 +00:00
LibWeb/Canvas: Support globalCompositionOperation
Canvas now supports compositing and various blending modes via the `globalCompositeOperation` attribute.
This commit is contained in:
parent
19ab213b16
commit
8575bddfe6
Notes:
github-actions[bot]
2025-02-05 11:28:07 +00:00
Author: https://github.com/skyz1 Commit: https://github.com/LadybirdBrowser/ladybird/commit/8575bddfe64 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3267 Reviewed-by: https://github.com/AtkinsSJ ✅ Reviewed-by: https://github.com/konradekk
16 changed files with 283 additions and 32 deletions
44
Libraries/LibGfx/CompositingAndBlendingOperator.h
Normal file
44
Libraries/LibGfx/CompositingAndBlendingOperator.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
|
||||
*
|
||||
* 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
|
||||
};
|
||||
|
||||
}
|
|
@ -283,12 +283,11 @@ ErrorOr<size_t> 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<size_t> 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();
|
||||
|
|
|
@ -481,7 +481,7 @@ void TinyVGDecodedImageData::draw(Painter& painter) const
|
|||
command.fill->visit(
|
||||
[&](Color color) { painter.fill_path(fill_path, color, WindingRule::EvenOdd); },
|
||||
[&](NonnullRefPtr<SVGGradientPaintStyle> 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<SVGGradientPaintStyle> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
|
@ -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<Gfx::Filter> 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<Gfx::Filter> 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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::WindingRule) = 0;
|
||||
|
||||
virtual void set_transform(Gfx::AffineTransform const&) = 0;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
|
@ -16,6 +17,7 @@
|
|||
#include <LibGfx/SkiaUtils.h>
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <core/SkBlender.h>
|
||||
#include <core/SkCanvas.h>
|
||||
#include <core/SkPath.h>
|
||||
#include <effects/SkBlurMaskFilter.h>
|
||||
|
@ -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<Gfx::Filter> 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<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
|
||||
{
|
||||
SkPaint paint;
|
||||
apply_filters(paint, filters);
|
||||
paint.setAlpha(static_cast<u8>(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<Gfx::Filter> filters, float thickness, float global_alpha)
|
||||
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> 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<Gfx::Filter> filters, float global_alpha, Gfx::WindingRule winding_rule)
|
||||
void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan<Gfx::Filter> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/PaintingSurface.h>
|
||||
|
||||
|
@ -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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, 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<Gfx::Filter>, 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;
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
#include <AK/Assertions.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/SkiaUtils.h>
|
||||
#include <core/SkBlender.h>
|
||||
#include <core/SkColorFilter.h>
|
||||
#include <core/SkImageFilter.h>
|
||||
#include <core/SkString.h>
|
||||
#include <effects/SkImageFilters.h>
|
||||
#include <effects/SkRuntimeEffect.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
|
@ -140,4 +143,85 @@ sk_sp<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter)
|
|||
});
|
||||
}
|
||||
|
||||
sk_sp<SkBlender> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <AK/Assertions.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/PathSkia.h>
|
||||
#include <LibGfx/ScalingMode.h>
|
||||
|
@ -18,6 +19,7 @@
|
|||
#include <core/SkImageFilter.h>
|
||||
#include <core/SkPath.h>
|
||||
#include <core/SkSamplingOptions.h>
|
||||
#include <include/core/SkBlender.h>
|
||||
|
||||
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<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter);
|
||||
|
||||
sk_sp<SkBlender> to_skia_blender(Gfx::CompositingAndBlendingOperator compositing_and_blending_operator);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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")
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGfx/AffineTransform.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Font/Font.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
|
@ -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<CSS::CSSStyleValue> font_style_value { nullptr };
|
||||
RefPtr<Gfx::Font const> current_font { nullptr };
|
||||
Bindings::CanvasTextAlign text_align { Bindings::CanvasTextAlign::Start };
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibUnicode/Segmenter.h>
|
||||
|
@ -178,7 +179,7 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
|
|||
}
|
||||
|
||||
if (auto* painter = this->painter()) {
|
||||
painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded<int>(), scaling_mode, drawing_state().filters, drawing_state().global_alpha);
|
||||
painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded<int>(), 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<GC::Ptr<ImageData>> 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<float>(), *snapshot, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, {}, drawing_state().global_alpha);
|
||||
painter->draw_bitmap(image_data->bitmap().rect().to_type<float>(), *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 <blend-mode> or the <composite-mode> 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.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 1 tests
|
||||
|
||||
1 Pass
|
||||
Pass Canvas test: 2d.composite.transparent.lighter
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Canvas test: 2d.composite.transparent.lighter</title>
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../../html/canvas/resources/canvas-tests.js"></script>
|
||||
<link rel="stylesheet" href="../../../../html/canvas/resources/canvas-tests.css">
|
||||
<body class="show_output">
|
||||
|
||||
<h1>2d.composite.transparent.lighter</h1>
|
||||
<p class="desc"></p>
|
||||
|
||||
|
||||
<p class="output">Actual output:</p>
|
||||
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||
<p class="output expectedtext">Expected output:<p><img src="2d.composite.transparent.lighter.png" class="output expected" id="expected" alt="">
|
||||
<ul id="d"></ul>
|
||||
<script>
|
||||
var t = async_test("");
|
||||
_addTest(function(canvas, ctx) {
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
|
||||
ctx.fillRect(0, 0, 100, 50);
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
ctx.fillStyle = 'rgba(0, 0, 255, 0.75)';
|
||||
ctx.fillRect(0, 0, 100, 50);
|
||||
_assertPixelApprox(canvas, 50,25, 0,128,191,255, 5);
|
||||
|
||||
});
|
||||
</script>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 208 B |
Loading…
Add table
Reference in a new issue