mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 11:36:10 +00:00
LibWeb+LibGfx: Implement Canvas2D filters
This commit is contained in:
parent
9fd1223992
commit
a6ef6550f3
Notes:
github-actions[bot]
2024-12-18 17:55:41 +00:00
Author: https://github.com/ananas-dev Commit: https://github.com/LadybirdBrowser/ladybird/commit/a6ef6550f32 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2876 Reviewed-by: https://github.com/kalenikaliaksandr Reviewed-by: https://github.com/shlyakpavel
10 changed files with 136 additions and 31 deletions
|
@ -257,11 +257,11 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
|||
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);
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
|
@ -280,7 +280,7 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
|
|||
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);
|
||||
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, 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibGfx/ScalingMode.h>
|
||||
|
@ -23,15 +24,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, 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) = 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&, float thickness, float global_alpha) = 0;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan<Gfx::Filter>, float thickness, float global_alpha) = 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&, float global_alpha, Gfx::WindingRule) = 0;
|
||||
virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan<Gfx::Filter>, float global_alpha, Gfx::WindingRule) = 0;
|
||||
|
||||
virtual void set_transform(Gfx::AffineTransform const&) = 0;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -8,6 +9,7 @@
|
|||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
#include <LibGfx/PathSkia.h>
|
||||
|
@ -128,9 +130,10 @@ 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, 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)
|
||||
{
|
||||
SkPaint paint;
|
||||
apply_filters(paint, filters);
|
||||
paint.setAlpha(static_cast<u8>(global_alpha * 255));
|
||||
|
||||
impl().canvas()->drawImageRect(
|
||||
|
@ -183,14 +186,14 @@ 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::PaintStyle const& paint_style, 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)
|
||||
{
|
||||
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
|
||||
if (thickness <= 0)
|
||||
return;
|
||||
|
||||
auto sk_path = to_skia_path(path);
|
||||
auto paint = to_skia_paint(paint_style, {});
|
||||
auto paint = to_skia_paint(paint_style, filters);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setAlphaf(global_alpha);
|
||||
paint.setStyle(SkPaint::Style::kStroke_Style);
|
||||
|
@ -219,11 +222,11 @@ 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::PaintStyle const& paint_style, 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::WindingRule winding_rule)
|
||||
{
|
||||
auto sk_path = to_skia_path(path);
|
||||
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
|
||||
auto paint = to_skia_paint(paint_style, {});
|
||||
auto paint = to_skia_paint(paint_style, filters);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setAlphaf(global_alpha);
|
||||
impl().canvas()->drawPath(sk_path, paint);
|
||||
|
|
|
@ -20,13 +20,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, 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) 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&, float thickness, float global_alpha) override;
|
||||
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan<Gfx::Filter>, float thickness, float global_alpha) 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&, float global_alpha, Gfx::WindingRule) override;
|
||||
virtual void fill_path(Gfx::Path const&, Gfx::PaintStyle const&, ReadonlySpan<Gfx::Filter>, float global_alpha, Gfx::WindingRule) override;
|
||||
virtual void set_transform(Gfx::AffineTransform const&) override;
|
||||
virtual void save() override;
|
||||
virtual void restore() override;
|
||||
|
|
25
Libraries/LibWeb/HTML/Canvas/CanvasFilters.h
Normal file
25
Libraries/LibWeb/HTML/Canvas/CanvasFilters.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfilters
|
||||
class CanvasFilters {
|
||||
public:
|
||||
~CanvasFilters() = default;
|
||||
|
||||
virtual String filter() const = 0;
|
||||
virtual void set_filter(String filter) = 0;
|
||||
|
||||
protected:
|
||||
CanvasFilters() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfilters
|
||||
interface mixin CanvasFilters {
|
||||
// filters
|
||||
[FIXME] attribute DOMString filter; // (default "none")
|
||||
attribute DOMString filter; // (default "none")
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGfx/AffineTransform.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Font/Font.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibGfx/Path.h>
|
||||
|
@ -84,6 +85,8 @@ public:
|
|||
float shadow_offset_y { 0.0f };
|
||||
float shadow_blur { 0.0f };
|
||||
Gfx::Color shadow_color { Gfx::Color::Transparent };
|
||||
Vector<Gfx::Filter> filters;
|
||||
Optional<String> filters_string;
|
||||
float line_width { 1 };
|
||||
Bindings::CanvasLineCap line_cap { Bindings::CanvasLineCap::Butt };
|
||||
Bindings::CanvasLineJoin line_join { Bindings::CanvasLineJoin::Miter };
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -177,7 +178,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().global_alpha);
|
||||
painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded<int>(), scaling_mode, drawing_state().filters, drawing_state().global_alpha);
|
||||
did_draw(destination_rect);
|
||||
}
|
||||
|
||||
|
@ -301,12 +302,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.
|
||||
|
||||
if (auto color = state.stroke_style.as_color(); color.has_value()) {
|
||||
painter->stroke_path(path, color->with_opacity(state.global_alpha), state.line_width);
|
||||
} else {
|
||||
painter->stroke_path(path, state.stroke_style.to_gfx_paint_style(), 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);
|
||||
|
||||
did_draw(path.bounding_box());
|
||||
}
|
||||
|
@ -342,11 +338,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();
|
||||
if (auto color = state.fill_style.as_color(); color.has_value()) {
|
||||
painter->fill_path(path_to_fill, color->with_opacity(state.global_alpha), winding_rule);
|
||||
} else {
|
||||
painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.global_alpha, winding_rule);
|
||||
}
|
||||
painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.filters, state.global_alpha, winding_rule);
|
||||
|
||||
did_draw(path_to_fill.bounding_box());
|
||||
}
|
||||
|
@ -438,7 +430,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);
|
||||
|
||||
// 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.
|
||||
|
@ -451,7 +443,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, 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);
|
||||
did_draw(dst_rect);
|
||||
}
|
||||
}
|
||||
|
@ -862,4 +854,80 @@ void CanvasRenderingContext2D::paint_shadow_for_stroke_internal(Gfx::Path const&
|
|||
did_draw(path.bounding_box());
|
||||
}
|
||||
|
||||
String CanvasRenderingContext2D::filter() const
|
||||
{
|
||||
if (!drawing_state().filters_string.has_value()) {
|
||||
return String::from_utf8_without_validation("none"sv.bytes());
|
||||
}
|
||||
|
||||
return drawing_state().filters_string.value();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-filter
|
||||
void CanvasRenderingContext2D::set_filter(String filter)
|
||||
{
|
||||
drawing_state().filters.clear();
|
||||
|
||||
// 1. If the given value is "none", then set this's current filter to "none" and return.
|
||||
if (filter == "none"sv) {
|
||||
drawing_state().filters_string.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& realm = static_cast<CanvasRenderingContext2D&>(*this).realm();
|
||||
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), filter);
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given values as a <filter-value-list>.
|
||||
// If any property-independent style sheet syntax like 'inherit' or 'initial' is present,
|
||||
// then this parsing must return failure.
|
||||
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Filter);
|
||||
|
||||
if (style_value && style_value->is_filter_value_list()) {
|
||||
auto filter_value_list = style_value->as_filter_value_list().filter_value_list();
|
||||
|
||||
drawing_state().filters.grow_capacity(filter_value_list.size());
|
||||
|
||||
// Note: The layout must be updated to make sure the canvas's layout node isn't null.
|
||||
canvas_element().document().update_layout();
|
||||
auto layout_node = canvas_element().layout_node();
|
||||
|
||||
// 4. Set this's current filter to the given value.
|
||||
for (auto& item : filter_value_list) {
|
||||
// FIXME: Add support for SVG filters when they get implement by the CSS parser.
|
||||
item.visit(
|
||||
[&](CSS::FilterOperation::Blur const& blur_filter) {
|
||||
float radius = blur_filter.resolved_radius(*layout_node);
|
||||
drawing_state().filters.append(Gfx::BlurFilter { radius });
|
||||
},
|
||||
[&](CSS::FilterOperation::Color const& color) {
|
||||
float amount = color.resolved_amount();
|
||||
drawing_state().filters.append(Gfx::ColorFilter { color.operation, amount });
|
||||
},
|
||||
[&](CSS::FilterOperation::HueRotate const& hue_rotate) {
|
||||
float angle = hue_rotate.angle_degrees(*layout_node);
|
||||
drawing_state().filters.append(Gfx::HueRotateFilter { angle });
|
||||
},
|
||||
[&](CSS::FilterOperation::DropShadow const& drop_shadow) {
|
||||
auto resolution_context = CSS::Length::ResolutionContext::for_layout_node(*layout_node);
|
||||
|
||||
float offset_x = static_cast<float>(drop_shadow.offset_x.resolved(resolution_context).raw_value());
|
||||
float offset_y = static_cast<float>(drop_shadow.offset_y.resolved(resolution_context).raw_value());
|
||||
|
||||
float radius = 0.0f;
|
||||
if (drop_shadow.radius.has_value()) {
|
||||
radius = drop_shadow.radius->resolved(resolution_context).raw_value();
|
||||
};
|
||||
|
||||
auto color = drop_shadow.color.value_or(Gfx::Color { 0, 0, 0, 255 });
|
||||
|
||||
drawing_state().filters.append(Gfx::DropShadowFilter { offset_x, offset_y, radius, color });
|
||||
});
|
||||
}
|
||||
|
||||
drawing_state().filters_string = move(filter);
|
||||
}
|
||||
|
||||
// 3. If parsedValue is failure, then return.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawPath.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasFilters.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasImageData.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasImageSmoothing.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasPath.h>
|
||||
|
@ -43,6 +44,7 @@ class CanvasRenderingContext2D
|
|||
, public CanvasTransform<CanvasRenderingContext2D>
|
||||
, public CanvasFillStrokeStyles<CanvasRenderingContext2D>
|
||||
, public CanvasShadowStyles<CanvasRenderingContext2D>
|
||||
, public CanvasFilters
|
||||
, public CanvasRect
|
||||
, public CanvasDrawPath
|
||||
, public CanvasText
|
||||
|
@ -101,6 +103,9 @@ public:
|
|||
virtual float global_alpha() const override;
|
||||
virtual void set_global_alpha(float) override;
|
||||
|
||||
virtual String filter() const override;
|
||||
virtual void set_filter(String) override;
|
||||
|
||||
virtual float shadow_offset_x() const override;
|
||||
virtual void set_shadow_offset_x(float) override;
|
||||
virtual float shadow_offset_y() const override;
|
||||
|
|
Loading…
Add table
Reference in a new issue