diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 35a181da67..5e903b5ef4 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -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}) diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp index f9cfb02379..02bbc832a3 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #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); diff --git a/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.cpp b/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.cpp new file mode 100644 index 0000000000..8f9477b47a --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.cpp @@ -0,0 +1,876 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Web::Painting { + +class CommandExecutorSkia::SkiaSurface { +public: + SkCanvas& canvas() const { return *surface->getCanvas(); } + + SkiaSurface(sk_sp surface) + : surface(move(surface)) + { + } + +private: + sk_sp 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 subpath_start_point; + Optional 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(surface); +} + +CommandExecutorSkia::~CommandExecutorSkia() = default; + +CommandExecutorSkia::SkiaSurface& CommandExecutorSkia::surface() const +{ + return static_cast(*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(command.scale)); + }); + if (transformed_glyph.has()) { + auto& glyph = transformed_glyph.get(); + 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()) + .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 colors; + colors.ensure_capacity(linear_gradient_data.color_stops.list.size()); + Vector 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(rect.size(), linear_gradient_data.gradient_angle); + auto bottom = rect.center().translated(0, -length / 2); + auto top = rect.center().translated(0, length / 2); + + Array 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 box’s 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(paint_style)) { + auto const& linear_gradient_paint_style = static_cast(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 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 colors; + colors.ensure_capacity(color_stops.size()); + Vector 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(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()); + 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()); + 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 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 colors; + colors.ensure_capacity(linear_gradient_data.color_stops.list.size()); + Vector 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)); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.h b/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.h new file mode 100644 index 0000000000..fd350175f1 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CommandExecutorSkia.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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> 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&) override {}; + + CommandExecutorSkia(Gfx::Bitmap& bitmap); + virtual ~CommandExecutorSkia() override; + +private: + class SkiaSurface; + SkiaSurface& surface() const; + + OwnPtr m_surface; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/PaintStyle.h b/Userland/Libraries/LibWeb/Painting/PaintStyle.h index dde375a958..ab4df88687 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintStyle.h +++ b/Userland/Libraries/LibWeb/Painting/PaintStyle.h @@ -51,6 +51,8 @@ public: ReadonlySpan color_stops() const { return m_color_stops; } Optional repeat_length() const { return m_repeat_length; } + float scale() const { return m_scale; } + virtual ~SVGGradientPaintStyle() {}; protected: @@ -71,6 +73,9 @@ public: NonnullRefPtr 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; } diff --git a/vcpkg.json b/vcpkg.json index ca6d0202f1..5d580b0911 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -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"