LibWeb: Add Skia painting command executor

This change introduces Skia painter available under a flag. It's not
yet match capabilities of Gfx::Painter and is not ready to replace it.

Motivation:
- The current CPU painter is a performance bottleneck on most websites.
  Our own GPU painter implementation is very immature and has received
  relatively little attention.
- There is ongoing effort on building painter that supports affine
  transforms (AffineCommandExecutorCPU) but it is far from being on par
  with the default CPU painter. Skia will allow us to immediately get
  full transformation support for all painting commands.

GPU painting:
I experimented with Ganesh GPU-backend, however profiling revealed that
without sharing viewport texture between WebContent and Browser
processes, it won't bring much performance improvement compared to
CPU-backend. Therefore, I decided to keep this PR focused on
implementing most of painting commands and switch to GPU-backend in
future changes.

Text rendring:
Skia painter uses glyph bitmaps produced by LibGfx. Using Skia for text
rendering will require large refactoring of the font rendering
subsystem. Currently, it's impossible to construct SkFont right before
rendering because Gfx::VectorFont can't be serialized back into sequence
of bytes.

There is a problem with ugly include paths like:
`#include <core/SkBitmap.h>`.
I would prefer to have skia prefix in the path. There was an attempt to
fix that but PR was rejected https://github.com/microsoft/vcpkg/pull/32660

Regressions compared to Gfx::Painter:
- DrawText is not implemented
- PaintTextShadow is not implemented
- PaintRadialGradient and PaintLinearGradient do not support "transition
  hints" and repeat length
- PaintConicGradient is not implemented
- DrawTriangleWave is not implemented
- DrawLine does not account for line style property
- DrawScaledBitmap and DrawScaledImmutableBitmap do not account for
  scaling mode property
This commit is contained in:
Aliaksandr Kalenik 2024-06-10 14:22:04 +03:00 committed by Andreas Kling
parent 25c4355406
commit d292152e2e
Notes: sideshowbarker 2024-07-17 09:48:50 +09:00
6 changed files with 960 additions and 1 deletions

View file

@ -539,6 +539,7 @@ set(SOURCES
Painting/CanvasPaintable.cpp
Painting/Command.cpp
Painting/CommandExecutorCPU.cpp
Painting/CommandExecutorSkia.cpp
Painting/CommandList.cpp
Painting/CheckBoxPaintable.cpp
Painting/ClippableAndScrollable.cpp
@ -749,9 +750,11 @@ set(GENERATED_SOURCES
Worker/WebWorkerServerEndpoint.h
)
find_package(unofficial-skia CONFIG REQUIRED)
serenity_lib(LibWeb web)
target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibHTTP LibGfx LibIPC LibLocale LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibVideo LibWasm LibXML LibIDL LibURL LibTLS)
target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibHTTP LibGfx LibIPC LibLocale LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibVideo LibWasm LibXML LibIDL LibURL LibTLS unofficial::skia::skia)
if (HAS_ACCELERATED_GRAPHICS)
target_link_libraries(LibWeb PRIVATE ${ACCEL_GFX_LIBS})

View file

@ -18,6 +18,7 @@
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/CommandExecutorCPU.h>
#include <LibWeb/Painting/CommandExecutorSkia.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#ifdef HAS_ACCELERATED_GRAPHICS
@ -1199,6 +1200,9 @@ void TraversableNavigable::paint(Web::DevicePixelRect const& content_rect, Gfx::
has_warned_about_configuration = true;
}
#endif
} else if (paint_options.use_skia_painter) {
Painting::CommandExecutorSkia painting_command_executor(target);
painting_commands.execute(painting_command_executor);
} else {
Web::Painting::CommandExecutorCPU painting_command_executor(target, paint_options.use_experimental_cpu_transform_support);
painting_commands.execute(painting_command_executor);

View file

@ -0,0 +1,876 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <core/SkBitmap.h>
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h>
#include <core/SkPathBuilder.h>
#include <core/SkRRect.h>
#include <core/SkSurface.h>
#include <effects/SkGradientShader.h>
#include <effects/SkImageFilters.h>
#include <pathops/SkPathOps.h>
#include <LibGfx/Filters/StackBlurFilter.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/CommandExecutorSkia.h>
#include <LibWeb/Painting/ShadowPainting.h>
namespace Web::Painting {
class CommandExecutorSkia::SkiaSurface {
public:
SkCanvas& canvas() const { return *surface->getCanvas(); }
SkiaSurface(sk_sp<SkSurface> surface)
: surface(move(surface))
{
}
private:
sk_sp<SkSurface> surface;
};
static SkRect gfx_rect_to_skia_rect(auto rect)
{
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
}
static SkColor gfx_color_to_skia_color(Gfx::Color color)
{
return SkColorSetARGB(color.alpha(), color.red(), color.green(), color.blue());
}
static SkPath gfx_path_to_skia_path(Gfx::Path path)
{
Optional<Gfx::FloatPoint> subpath_start_point;
Optional<Gfx::FloatPoint> subpath_last_point;
SkPathBuilder path_builder;
auto close_subpath_if_needed = [&](auto last_point) {
if (subpath_start_point == last_point)
path_builder.close();
};
for (auto const& segment : path) {
auto point = segment.point();
subpath_last_point = point;
switch (segment.command()) {
case Gfx::PathSegment::Command::MoveTo: {
if (subpath_start_point.has_value())
close_subpath_if_needed(subpath_start_point.value());
subpath_start_point = point;
path_builder.moveTo({ point.x(), point.y() });
break;
}
case Gfx::PathSegment::Command::LineTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
path_builder.lineTo({ point.x(), point.y() });
break;
}
case Gfx::PathSegment::Command::QuadraticBezierCurveTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
SkPoint pt1 = { segment.through().x(), segment.through().y() };
SkPoint pt2 = { segment.point().x(), segment.point().y() };
path_builder.quadTo(pt1, pt2);
break;
}
case Gfx::PathSegment::Command::CubicBezierCurveTo: {
if (!subpath_start_point.has_value())
subpath_start_point = Gfx::FloatPoint { 0.0f, 0.0f };
SkPoint pt1 = { segment.through_0().x(), segment.through_0().y() };
SkPoint pt2 = { segment.through_1().x(), segment.through_1().y() };
SkPoint pt3 = { segment.point().x(), segment.point().y() };
path_builder.cubicTo(pt1, pt2, pt3);
break;
}
default:
VERIFY_NOT_REACHED();
}
}
close_subpath_if_needed(subpath_last_point);
return path_builder.snapshot();
}
static SkRRect gfx_rrect_to_skia_rrect(auto rect, CornerRadii corner_radii)
{
SkRRect rrect;
SkVector radii[4];
radii[0].set(corner_radii.top_left.horizontal_radius, corner_radii.top_left.vertical_radius);
radii[1].set(corner_radii.top_right.horizontal_radius, corner_radii.top_right.vertical_radius);
radii[2].set(corner_radii.bottom_right.horizontal_radius, corner_radii.bottom_right.vertical_radius);
radii[3].set(corner_radii.bottom_left.horizontal_radius, corner_radii.bottom_left.vertical_radius);
rrect.setRectRadii(gfx_rect_to_skia_rect(rect), radii);
return rrect;
}
static SkColorType gfx_bitmap_format_to_skia_color_type(Gfx::BitmapFormat format)
{
switch (format) {
case Gfx::BitmapFormat::Invalid:
return kUnknown_SkColorType;
case Gfx::BitmapFormat::BGRA8888:
case Gfx::BitmapFormat::BGRx8888:
return kBGRA_8888_SkColorType;
case Gfx::BitmapFormat::RGBA8888:
return kRGBA_8888_SkColorType;
default:
return kUnknown_SkColorType;
}
}
static SkBitmap gfx_bitmap_to_skia_bitmap(Gfx::Bitmap const& bitmap)
{
SkColorType color_type = gfx_bitmap_format_to_skia_color_type(bitmap.format());
SkImageInfo image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, kUnpremul_SkAlphaType);
SkBitmap sk_bitmap;
sk_bitmap.setInfo(image_info);
if (!sk_bitmap.installPixels(image_info, (void*)bitmap.begin(), bitmap.width() * 4)) {
VERIFY_NOT_REACHED();
}
return sk_bitmap;
}
static SkMatrix gfx_affine_transform_to_skia_matrix(Gfx::AffineTransform const& affine_transform)
{
SkScalar affine[6];
affine[0] = affine_transform.a();
affine[1] = affine_transform.b();
affine[2] = affine_transform.c();
affine[3] = affine_transform.d();
affine[4] = affine_transform.e();
affine[5] = affine_transform.f();
SkMatrix matrix;
matrix.setAffine(affine);
return matrix;
}
#define APPLY_PATH_CLIP_IF_NEEDED \
ScopeGuard restore_path_clip { [&] { \
if (command.clip_paths.size() > 0) \
surface().canvas().restore(); \
} }; \
if (command.clip_paths.size() > 0) { \
surface().canvas().save(); \
for (auto const& path : command.clip_paths) \
surface().canvas().clipPath(gfx_path_to_skia_path(path), true); \
}
CommandExecutorSkia::CommandExecutorSkia(Gfx::Bitmap& bitmap)
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), kBGRA_8888_SkColorType, kPremul_SkAlphaType);
auto surface = SkSurfaces::WrapPixels(image_info, bitmap.begin(), bitmap.width() * 4);
VERIFY(surface);
m_surface = make<SkiaSurface>(surface);
}
CommandExecutorSkia::~CommandExecutorSkia() = default;
CommandExecutorSkia::SkiaSurface& CommandExecutorSkia::surface() const
{
return static_cast<SkiaSurface&>(*m_surface);
}
CommandResult CommandExecutorSkia::draw_glyph_run(DrawGlyphRun const& command)
{
auto& canvas = surface().canvas();
SkPaint paint;
paint.setColorFilter(SkColorFilters::Blend(gfx_color_to_skia_color(command.color), SkBlendMode::kSrcIn));
auto const& glyphs = command.glyph_run->glyphs();
for (auto const& glyph_or_emoji : glyphs) {
auto transformed_glyph = glyph_or_emoji;
transformed_glyph.visit([&](auto& glyph) {
glyph.position = glyph.position.scaled(command.scale).translated(command.translation);
glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast<float>(command.scale));
});
if (transformed_glyph.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
auto const& point = glyph.position;
auto const& code_point = glyph.code_point;
auto top_left = point + Gfx::FloatPoint(glyph.font->glyph_left_bearing(code_point), 0);
auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left);
auto font_glyph = glyph.font->glyph(code_point, glyph_position.subpixel_offset);
if (font_glyph->is_color_bitmap()) {
TODO();
} else {
SkBitmap sk_bitmap = gfx_bitmap_to_skia_bitmap(*font_glyph->bitmap());
auto sk_image = SkImages::RasterFromBitmap(sk_bitmap);
auto const& blit_position = glyph_position.blit_position;
canvas.drawImage(sk_image, blit_position.x(), blit_position.y(), SkSamplingOptions(), &paint);
}
}
}
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_text(DrawText const&)
{
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::fill_rect(FillRect const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setColor(gfx_color_to_skia_color(command.color));
canvas.drawRect(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_scaled_bitmap(DrawScaledBitmap const& command)
{
auto src_rect = gfx_rect_to_skia_rect(command.src_rect);
auto dst_rect = gfx_rect_to_skia_rect(command.dst_rect);
auto bitmap = gfx_bitmap_to_skia_bitmap(command.bitmap);
auto image = SkImages::RasterFromBitmap(bitmap);
auto& canvas = surface().canvas();
SkPaint paint;
// FIXME: Account for scaling_mode
canvas.drawImageRect(image, src_rect, dst_rect, SkSamplingOptions(), &paint, SkCanvas::kStrict_SrcRectConstraint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto src_rect = gfx_rect_to_skia_rect(command.src_rect);
auto dst_rect = gfx_rect_to_skia_rect(command.dst_rect);
auto bitmap = gfx_bitmap_to_skia_bitmap(command.bitmap->bitmap());
auto image = SkImages::RasterFromBitmap(bitmap);
auto& canvas = surface().canvas();
SkPaint paint;
// FIXME: Account for scaling_mode
canvas.drawImageRect(image, src_rect, dst_rect, SkSamplingOptions(), &paint, SkCanvas::kStrict_SrcRectConstraint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::add_clip_rect(AddClipRect const& command)
{
auto& canvas = surface().canvas();
auto const& rect = command.rect;
canvas.clipRect(gfx_rect_to_skia_rect(rect));
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::save(Save const&)
{
auto& canvas = surface().canvas();
canvas.save();
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::restore(Restore const&)
{
auto& canvas = surface().canvas();
canvas.restore();
return CommandResult::Continue;
}
static SkBitmap alpha_mask_from_bitmap(Gfx::Bitmap const& bitmap, Gfx::Bitmap::MaskKind kind)
{
SkBitmap alpha_mask;
alpha_mask.allocPixels(SkImageInfo::MakeA8(bitmap.width(), bitmap.height()));
for (int y = 0; y < bitmap.height(); y++) {
for (int x = 0; x < bitmap.width(); x++) {
if (kind == Gfx::Bitmap::MaskKind::Luminance) {
auto color = bitmap.get_pixel(x, y);
*alpha_mask.getAddr8(x, y) = color.alpha() * color.luminosity() / 255;
} else {
VERIFY(kind == Gfx::Bitmap::MaskKind::Alpha);
auto color = bitmap.get_pixel(x, y);
*alpha_mask.getAddr8(x, y) = color.alpha();
}
}
}
return alpha_mask;
}
CommandResult CommandExecutorSkia::push_stacking_context(PushStackingContext const& command)
{
auto& canvas = surface().canvas();
auto affine_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
auto new_transform = Gfx::AffineTransform {}
.set_translation(command.post_transform_translation.to_type<float>())
.translate(command.transform.origin)
.multiply(affine_transform)
.translate(-command.transform.origin);
auto matrix = gfx_affine_transform_to_skia_matrix(new_transform);
if (command.opacity < 1) {
auto source_paintable_rect = gfx_rect_to_skia_rect(command.source_paintable_rect);
SkRect dest;
matrix.mapRect(&dest, source_paintable_rect);
canvas.saveLayerAlphaf(&dest, command.opacity);
} else {
canvas.save();
}
if (command.mask.has_value()) {
auto alpha_mask = alpha_mask_from_bitmap(*command.mask.value().mask_bitmap, command.mask.value().mask_kind);
SkMatrix mask_matrix;
auto mask_position = command.source_paintable_rect.location();
mask_matrix.setTranslate(mask_position.x(), mask_position.y());
auto shader = alpha_mask.makeShader(SkSamplingOptions(), mask_matrix);
canvas.clipShader(shader);
}
if (command.is_fixed_position) {
// FIXME: Resetting matrix is not correct when element is nested in a transformed stacking context
canvas.resetMatrix();
}
canvas.concat(matrix);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::pop_stacking_context(PopStackingContext const&)
{
surface().canvas().restore();
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::paint_linear_gradient(PaintLinearGradient const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto const& linear_gradient_data = command.linear_gradient_data;
// FIXME: Account for repeat length
Vector<SkColor> colors;
colors.ensure_capacity(linear_gradient_data.color_stops.list.size());
Vector<SkScalar> positions;
positions.ensure_capacity(linear_gradient_data.color_stops.list.size());
auto const& list = linear_gradient_data.color_stops.list;
for (auto const& color_stop : linear_gradient_data.color_stops.list) {
// FIXME: Account for ColorStop::transition_hint
colors.append(gfx_color_to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
auto const& rect = command.gradient_rect;
auto length = calculate_gradient_length<int>(rect.size(), linear_gradient_data.gradient_angle);
auto bottom = rect.center().translated(0, -length / 2);
auto top = rect.center().translated(0, length / 2);
Array<SkPoint, 2> points;
points[0] = SkPoint::Make(top.x(), top.y());
points[1] = SkPoint::Make(bottom.x(), bottom.y());
auto center = gfx_rect_to_skia_rect(rect).center();
SkMatrix matrix;
matrix.setRotate(linear_gradient_data.gradient_angle, center.x(), center.y());
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), list.size(), SkTileMode::kClamp, 0, &matrix);
SkPaint paint;
paint.setShader(shader);
surface().canvas().drawRect(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
static void add_spread_distance_to_border_radius(int& border_radius, int spread_distance)
{
if (border_radius == 0 || spread_distance == 0)
return;
// https://drafts.csswg.org/css-backgrounds/#shadow-shape
// To preserve the boxs shape when spread is applied, the corner radii of the shadow are also increased (decreased,
// for inner shadows) from the border-box (padding-box) radii by adding (subtracting) the spread distance (and flooring
// at zero). However, in order to create a sharper corner when the border radius is small (and thus ensure continuity
// between round and sharp corners), when the border radius is less than the spread distance (or in the case of an inner
// shadow, less than the absolute value of a negative spread distance), the spread distance is first multiplied by the
// proportion 1 + (r-1)^3, where r is the ratio of the border radius to the spread distance, in calculating the corner
// radii of the spread shadow shape.
if (border_radius > AK::abs(spread_distance)) {
border_radius += spread_distance;
} else {
auto r = (float)border_radius / AK::abs(spread_distance);
border_radius += spread_distance * (1 + AK::pow(r - 1, 3.0f));
}
}
CommandResult CommandExecutorSkia::paint_outer_box_shadow(PaintOuterBoxShadow const& command)
{
auto const& outer_box_shadow_params = command.box_shadow_params;
auto const& color = outer_box_shadow_params.color;
auto const& spread_distance = outer_box_shadow_params.spread_distance;
auto const& blur_radius = outer_box_shadow_params.blur_radius;
auto content_rrect = gfx_rrect_to_skia_rrect(outer_box_shadow_params.device_content_rect, outer_box_shadow_params.corner_radii);
auto shadow_rect = outer_box_shadow_params.device_content_rect;
shadow_rect.inflate(spread_distance, spread_distance, spread_distance, spread_distance);
auto offset_x = outer_box_shadow_params.offset_x;
auto offset_y = outer_box_shadow_params.offset_y;
shadow_rect.translate_by(offset_x, offset_y);
auto add_spread_distance_to_corner_radius = [&](auto& corner_radius) {
add_spread_distance_to_border_radius(corner_radius.horizontal_radius, spread_distance);
add_spread_distance_to_border_radius(corner_radius.vertical_radius, spread_distance);
};
auto corner_radii = outer_box_shadow_params.corner_radii;
add_spread_distance_to_corner_radius(corner_radii.top_left);
add_spread_distance_to_corner_radius(corner_radii.top_right);
add_spread_distance_to_corner_radius(corner_radii.bottom_right);
add_spread_distance_to_corner_radius(corner_radii.bottom_left);
auto& canvas = surface().canvas();
canvas.save();
canvas.clipRRect(content_rrect, SkClipOp::kDifference, true);
SkPaint paint;
paint.setColor(gfx_color_to_skia_color(color));
paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, blur_radius / 2));
auto shadow_rounded_rect = gfx_rrect_to_skia_rrect(shadow_rect, corner_radii);
canvas.drawRRect(shadow_rounded_rect, paint);
canvas.restore();
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::paint_inner_box_shadow(PaintInnerBoxShadow const& command)
{
auto const& outer_box_shadow_params = command.box_shadow_params;
auto color = outer_box_shadow_params.color;
auto device_content_rect = outer_box_shadow_params.device_content_rect;
auto offset_x = outer_box_shadow_params.offset_x;
auto offset_y = outer_box_shadow_params.offset_y;
auto blur_radius = outer_box_shadow_params.blur_radius;
auto spread_distance = outer_box_shadow_params.spread_distance;
auto const& corner_radii = outer_box_shadow_params.corner_radii;
auto outer_shadow_rect = device_content_rect.translated({ offset_x, offset_y });
auto inner_shadow_rect = outer_shadow_rect.inflated(-spread_distance, -spread_distance, -spread_distance, -spread_distance);
outer_shadow_rect.inflate(
blur_radius + offset_y,
blur_radius + abs(offset_x),
blur_radius + abs(offset_y),
blur_radius + offset_x);
auto inner_rect_corner_radii = corner_radii;
auto add_spread_distance_to_corner_radius = [&](auto& corner_radius) {
add_spread_distance_to_border_radius(corner_radius.horizontal_radius, -spread_distance);
add_spread_distance_to_border_radius(corner_radius.vertical_radius, -spread_distance);
};
add_spread_distance_to_corner_radius(inner_rect_corner_radii.top_left);
add_spread_distance_to_corner_radius(inner_rect_corner_radii.top_right);
add_spread_distance_to_corner_radius(inner_rect_corner_radii.bottom_right);
add_spread_distance_to_corner_radius(inner_rect_corner_radii.bottom_left);
auto outer_rect = gfx_rrect_to_skia_rrect(outer_shadow_rect, corner_radii);
auto inner_rect = gfx_rrect_to_skia_rrect(inner_shadow_rect, inner_rect_corner_radii);
SkPath outer_path;
outer_path.addRRect(outer_rect);
SkPath inner_path;
inner_path.addRRect(inner_rect);
SkPath result_path;
if (!Op(outer_path, inner_path, SkPathOp::kDifference_SkPathOp, &result_path)) {
VERIFY_NOT_REACHED();
}
auto& canvas = surface().canvas();
SkPaint path_paint;
path_paint.setColor(gfx_color_to_skia_color(color));
path_paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, blur_radius / 2));
canvas.save();
canvas.clipRRect(gfx_rrect_to_skia_rrect(device_content_rect, corner_radii), true);
canvas.drawPath(result_path, path_paint);
canvas.restore();
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::paint_text_shadow(PaintTextShadow const&)
{
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::fill_rect_with_rounded_corners(FillRectWithRoundedCorners const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setColor(gfx_color_to_skia_color(command.color));
SkRRect rounded_rect;
SkVector radii[4];
radii[0].set(command.top_left_radius.horizontal_radius, command.top_left_radius.vertical_radius);
radii[1].set(command.top_right_radius.horizontal_radius, command.top_right_radius.vertical_radius);
radii[2].set(command.bottom_right_radius.horizontal_radius, command.bottom_right_radius.vertical_radius);
radii[3].set(command.bottom_left_radius.horizontal_radius, command.bottom_left_radius.vertical_radius);
rounded_rect.setRectRadii(gfx_rect_to_skia_rect(rect), radii);
canvas.drawRRect(rounded_rect, paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::fill_path_using_color(FillPathUsingColor const& command)
{
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(gfx_color_to_skia_color(command.color));
auto path = gfx_path_to_skia_path(command.path);
path.offset(command.aa_translation.x(), command.aa_translation.y());
canvas.drawPath(path, paint);
return CommandResult::Continue;
}
SkPaint paint_style_to_skia_paint(Painting::SVGGradientPaintStyle const& paint_style, Gfx::FloatRect bounding_rect)
{
SkPaint paint;
if (is<SVGLinearGradientPaintStyle>(paint_style)) {
auto const& linear_gradient_paint_style = static_cast<SVGLinearGradientPaintStyle const&>(paint_style);
SkMatrix matrix;
auto scale = linear_gradient_paint_style.scale();
auto start_point = linear_gradient_paint_style.start_point().scaled(scale);
auto end_point = linear_gradient_paint_style.end_point().scaled(scale);
start_point.translate_by(bounding_rect.location());
end_point.translate_by(bounding_rect.location());
Array<SkPoint, 2> points;
points[0] = SkPoint::Make(start_point.x(), start_point.y());
points[1] = SkPoint::Make(end_point.x(), end_point.y());
auto const& color_stops = linear_gradient_paint_style.color_stops();
Vector<SkColor> colors;
colors.ensure_capacity(color_stops.size());
Vector<SkScalar> positions;
positions.ensure_capacity(color_stops.size());
for (auto const& color_stop : linear_gradient_paint_style.color_stops()) {
colors.append(gfx_color_to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
paint.setShader(shader);
} else if (is<SVGRadialGradientPaintStyle>(paint_style)) {
// TODO:
}
return paint;
}
CommandResult CommandExecutorSkia::fill_path_using_paint_style(FillPathUsingPaintStyle const& command)
{
auto path = gfx_path_to_skia_path(command.path);
path.offset(command.aa_translation.x(), command.aa_translation.y());
auto paint = paint_style_to_skia_paint(*command.paint_style, command.bounding_rect().to_type<float>());
paint.setAntiAlias(true);
paint.setAlphaf(command.opacity);
surface().canvas().drawPath(path, paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::stroke_path_using_color(StrokePathUsingColor const& command)
{
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(command.thickness);
paint.setColor(gfx_color_to_skia_color(command.color));
auto path = gfx_path_to_skia_path(command.path);
path.offset(command.aa_translation.x(), command.aa_translation.y());
canvas.drawPath(path, paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::stroke_path_using_paint_style(StrokePathUsingPaintStyle const& command)
{
auto path = gfx_path_to_skia_path(command.path);
path.offset(command.aa_translation.x(), command.aa_translation.y());
auto paint = paint_style_to_skia_paint(*command.paint_style, command.bounding_rect().to_type<float>());
paint.setAntiAlias(true);
paint.setAlphaf(command.opacity);
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setStrokeWidth(command.thickness);
surface().canvas().drawPath(path, paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_ellipse(DrawEllipse const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(command.thickness);
paint.setColor(gfx_color_to_skia_color(command.color));
canvas.drawOval(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::fill_ellipse(FillEllipse const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(gfx_color_to_skia_color(command.color));
canvas.drawOval(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_line(DrawLine const& command)
{
auto from = SkPoint::Make(command.from.x(), command.from.y());
auto to = SkPoint::Make(command.to.x(), command.to.y());
auto& canvas = surface().canvas();
SkPaint paint;
paint.setStrokeWidth(command.thickness);
paint.setColor(gfx_color_to_skia_color(command.color));
canvas.drawLine(from, to, paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::apply_backdrop_filter(ApplyBackdropFilter const& command)
{
auto& canvas = surface().canvas();
auto rect = gfx_rect_to_skia_rect(command.backdrop_region);
canvas.save();
canvas.clipRect(rect);
ScopeGuard guard = [&] { canvas.restore(); };
for (auto const& filter_function : command.backdrop_filter.filters) {
// See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
filter_function.visit(
[&](CSS::ResolvedBackdropFilter::Blur const& blur_filter) {
auto blur_image_filter = SkImageFilters::Blur(blur_filter.radius, blur_filter.radius, nullptr);
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, blur_image_filter.get(), 0));
canvas.restore();
},
[&](CSS::ResolvedBackdropFilter::ColorOperation const& color) {
auto amount = clamp(color.amount, 0.0f, 1.0f);
// Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
sk_sp<SkColorFilter> color_filter;
switch (color.operation) {
case CSS::Filter::Color::Operation::Grayscale: {
float matrix[20] = {
0.2126f + 0.7874f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f + 0.2848f * (1 - amount), 0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f - 0.7152f * (1 - amount), 0.0722f + 0.9278f * (1 - amount), 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Brightness: {
float matrix[20] = {
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Contrast: {
float intercept = -(0.5f * amount) + 0.5f;
float matrix[20] = {
amount, 0, 0, 0, intercept,
0, amount, 0, 0, intercept,
0, 0, amount, 0, intercept,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Invert: {
float matrix[20] = {
1 - 2 * amount, 0, 0, 0, amount,
0, 1 - 2 * amount, 0, 0, amount,
0, 0, 1 - 2 * amount, 0, amount,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Opacity: {
float matrix[20] = {
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, amount, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Sepia: {
float matrix[20] = {
0.393f + 0.607f * (1 - amount), 0.769f - 0.769f * (1 - amount), 0.189f - 0.189f * (1 - amount), 0, 0,
0.349f - 0.349f * (1 - amount), 0.686f + 0.314f * (1 - amount), 0.168f - 0.168f * (1 - amount), 0, 0,
0.272f - 0.272f * (1 - amount), 0.534f - 0.534f * (1 - amount), 0.131f + 0.869f * (1 - amount), 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
case CSS::Filter::Color::Operation::Saturate: {
float matrix[20] = {
0.213f + 0.787f * amount, 0.715f - 0.715f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f + 0.285f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f - 0.715f * amount, 0.072f + 0.928f * amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix);
break;
}
default:
VERIFY_NOT_REACHED();
}
auto image_filter = SkImageFilters::ColorFilter(color_filter, nullptr);
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
canvas.restore();
},
[&](CSS::ResolvedBackdropFilter::HueRotate const& hue_rotate) {
float radians = AK::to_radians(hue_rotate.angle_degrees);
auto cosA = cos(radians);
auto sinA = sin(radians);
auto a00 = 0.213f + cosA * 0.787f - sinA * 0.213f;
auto a01 = 0.715f - cosA * 0.715f - sinA * 0.715f;
auto a02 = 0.072f - cosA * 0.072f + sinA * 0.928f;
auto a10 = 0.213f - cosA * 0.213f + sinA * 0.143f;
auto a11 = 0.715f + cosA * 0.285f + sinA * 0.140f;
auto a12 = 0.072f - cosA * 0.072f - sinA * 0.283f;
auto a20 = 0.213f - cosA * 0.213f - sinA * 0.787f;
auto a21 = 0.715f - cosA * 0.715f + sinA * 0.715f;
auto a22 = 0.072f + cosA * 0.928f + sinA * 0.072f;
float matrix[20] = {
a00, a01, a02, 0, 0,
a10, a11, a12, 0, 0,
a20, a21, a22, 0, 0,
0, 0, 0, 1, 0
};
auto color_filter = SkColorFilters::Matrix(matrix);
auto image_filter = SkImageFilters::ColorFilter(color_filter, nullptr);
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
canvas.restore();
},
[&](CSS::ResolvedBackdropFilter::DropShadow const&) {
dbgln("TODO: Implement drop-shadow() filter function!");
});
}
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_rect(DrawRect const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(1);
paint.setColor(gfx_color_to_skia_color(command.color));
canvas.drawRect(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::paint_radial_gradient(PaintRadialGradient const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
auto const& linear_gradient_data = command.radial_gradient_data;
// FIXME: Account for repeat length
Vector<SkColor> colors;
colors.ensure_capacity(linear_gradient_data.color_stops.list.size());
Vector<SkScalar> positions;
positions.ensure_capacity(linear_gradient_data.color_stops.list.size());
auto const& list = linear_gradient_data.color_stops.list;
for (auto const& color_stop : linear_gradient_data.color_stops.list) {
// FIXME: Account for ColorStop::transition_hint
colors.append(gfx_color_to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
auto const& rect = command.rect;
auto center = SkPoint::Make(command.center.x(), command.center.y());
auto radius = command.size.height();
auto shader = SkGradientShader::MakeRadial(center, radius, colors.data(), positions.data(), list.size(), SkTileMode::kClamp, 0);
SkPaint paint;
paint.setShader(shader);
surface().canvas().drawRect(gfx_rect_to_skia_rect(rect), paint);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::paint_conic_gradient(PaintConicGradient const& command)
{
APPLY_PATH_CLIP_IF_NEEDED
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::draw_triangle_wave(DrawTriangleWave const&)
{
return CommandResult::Continue;
}
void CommandExecutorSkia::prepare_to_execute(size_t)
{
}
CommandResult CommandExecutorSkia::sample_under_corners(SampleUnderCorners const& command)
{
auto rounded_rect = gfx_rrect_to_skia_rrect(command.border_rect, command.corner_radii);
auto& canvas = surface().canvas();
canvas.save();
auto clip_op = command.corner_clip == CornerClip::Inside ? SkClipOp::kDifference : SkClipOp::kIntersect;
canvas.clipRRect(rounded_rect, clip_op, true);
return CommandResult::Continue;
}
CommandResult CommandExecutorSkia::blit_corner_clipping(BlitCornerClipping const&)
{
auto& canvas = surface().canvas();
canvas.restore();
return CommandResult::Continue;
}
bool CommandExecutorSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{
return surface().canvas().quickReject(gfx_rect_to_skia_rect(rect));
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Bitmap.h>
#include <LibWeb/Painting/RecordingPainter.h>
namespace Web::Painting {
class CommandExecutorSkia : public CommandExecutor {
public:
CommandResult draw_glyph_run(DrawGlyphRun const&) override;
CommandResult draw_text(DrawText const&) override;
CommandResult fill_rect(FillRect const&) override;
CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) override;
CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) override;
CommandResult add_clip_rect(AddClipRect const&) override;
CommandResult save(Save const&) override;
CommandResult restore(Restore const&) override;
CommandResult push_stacking_context(PushStackingContext const&) override;
CommandResult pop_stacking_context(PopStackingContext const&) override;
CommandResult paint_linear_gradient(PaintLinearGradient const&) override;
CommandResult paint_outer_box_shadow(PaintOuterBoxShadow const&) override;
CommandResult paint_inner_box_shadow(PaintInnerBoxShadow const&) override;
CommandResult paint_text_shadow(PaintTextShadow const&) override;
CommandResult fill_rect_with_rounded_corners(FillRectWithRoundedCorners const&) override;
CommandResult fill_path_using_color(FillPathUsingColor const&) override;
CommandResult fill_path_using_paint_style(FillPathUsingPaintStyle const&) override;
CommandResult stroke_path_using_color(StrokePathUsingColor const&) override;
CommandResult stroke_path_using_paint_style(StrokePathUsingPaintStyle const&) override;
CommandResult draw_ellipse(DrawEllipse const&) override;
CommandResult fill_ellipse(FillEllipse const&) override;
CommandResult draw_line(DrawLine const&) override;
CommandResult apply_backdrop_filter(ApplyBackdropFilter const&) override;
CommandResult draw_rect(DrawRect const&) override;
CommandResult paint_radial_gradient(PaintRadialGradient const&) override;
CommandResult paint_conic_gradient(PaintConicGradient const&) override;
CommandResult draw_triangle_wave(DrawTriangleWave const&) override;
CommandResult sample_under_corners(SampleUnderCorners const&) override;
CommandResult blit_corner_clipping(BlitCornerClipping const&) override;
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
bool needs_prepare_glyphs_texture() const override { return false; }
void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const&) override {};
virtual void prepare_to_execute(size_t corner_clip_max_depth) override;
bool needs_update_immutable_bitmap_texture_cache() const override { return false; }
void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&) override {};
CommandExecutorSkia(Gfx::Bitmap& bitmap);
virtual ~CommandExecutorSkia() override;
private:
class SkiaSurface;
SkiaSurface& surface() const;
OwnPtr<SkiaSurface> m_surface;
};
}

View file

@ -51,6 +51,8 @@ public:
ReadonlySpan<ColorStop> color_stops() const { return m_color_stops; }
Optional<float> repeat_length() const { return m_repeat_length; }
float scale() const { return m_scale; }
virtual ~SVGGradientPaintStyle() {};
protected:
@ -71,6 +73,9 @@ public:
NonnullRefPtr<Gfx::SVGGradientPaintStyle> create_gfx_paint_style() const override;
Gfx::FloatPoint start_point() const { return m_start_point; }
Gfx::FloatPoint end_point() const { return m_end_point; }
void set_start_point(Gfx::FloatPoint start_point) { m_start_point = start_point; }
void set_end_point(Gfx::FloatPoint end_point) { m_end_point = end_point; }

View file

@ -7,6 +7,7 @@
},
"icu",
"libjpeg-turbo",
"skia",
"sqlite3",
"woff2"
],
@ -23,6 +24,10 @@
"name": "libjpeg-turbo",
"version": "3.0.2"
},
{
"name": "skia",
"version": "124#0"
},
{
"name": "sqlite3",
"version": "3.45.3"