Files
ladybird/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp
Aliaksandr Kalenik d9eec0d204 LibWeb: Replace ScrollStateSnapshotByDisplayList with a plain snapshot
Cleanup following the per-Navigable rasterization split: since each
Navigable now rasterizes its own display list independently, the HashMap
keyed on display list was always populated with exactly one entry. Pass
the ScrollStateSnapshot directly through the display list player and
rendering thread instead.
2026-04-07 15:09:43 +02:00

809 lines
31 KiB
C++

/*
* Copyright (c) 2024-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define SK_SUPPORT_UNSPANNED_APIS
#include <core/SkBitmap.h>
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkFont.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h>
#include <core/SkPathEffect.h>
#include <core/SkRRect.h>
#include <core/SkSurface.h>
#include <effects/SkDashPathEffect.h>
#include <effects/SkGradientShader.h>
#include <effects/SkImageFilters.h>
#include <effects/SkLumaColorFilter.h>
#include <gpu/ganesh/GrDirectContext.h>
#include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibGfx/SkiaUtils.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/PaintStyle.h>
namespace Web::Painting {
DisplayListPlayerSkia::DisplayListPlayerSkia()
{
}
DisplayListPlayerSkia::~DisplayListPlayerSkia()
{
}
static SkRRect to_skia_rrect(auto const& rect, CornerRadii const& 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(to_skia_rect(rect), radii);
return rrect;
}
static SkMatrix 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;
}
static SkM44 to_skia_matrix4x4(Gfx::FloatMatrix4x4 const& matrix)
{
return SkM44(
matrix[0, 0],
matrix[0, 1],
matrix[0, 2],
matrix[0, 3],
matrix[1, 0],
matrix[1, 1],
matrix[1, 2],
matrix[1, 3],
matrix[2, 0],
matrix[2, 1],
matrix[2, 2],
matrix[2, 3],
matrix[3, 0],
matrix[3, 1],
matrix[3, 2],
matrix[3, 3]);
}
void DisplayListPlayerSkia::flush()
{
if (auto context = Gfx::SkiaBackendContext::the())
context->flush_and_submit(&surface().sk_surface());
surface().flush();
}
void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command)
{
auto* blob = command.glyph_run->cached_skia_text_blob();
if (!blob)
return;
SkPaint paint;
paint.setColor(to_skia_color(command.color));
auto& canvas = surface().canvas();
auto const& translation = command.translation;
switch (command.orientation) {
case Gfx::Orientation::Horizontal:
canvas.drawTextBlob(blob, translation.x(), translation.y(), paint);
break;
case Gfx::Orientation::Vertical:
canvas.save();
canvas.translate(command.rect.width(), 0);
canvas.rotate(90, command.rect.top_left().x(), command.rect.top_left().y());
canvas.drawTextBlob(blob, translation.x(), translation.y(), paint);
canvas.restore();
break;
}
}
void DisplayListPlayerSkia::fill_rect(FillRect const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(to_skia_color(command.color));
canvas.drawRect(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::draw_external_content(DrawExternalContent const& command)
{
auto bitmap = command.source->current_bitmap();
if (!bitmap)
return;
if (Gfx::SkiaBackendContext::the() && !bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
auto dst_rect = to_skia_rect(command.dst_rect);
SkRect src_rect = SkRect::MakeIWH(bitmap->width(), bitmap->height());
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
canvas.drawImageRect(bitmap->sk_image(), src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint);
}
void DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
if (Gfx::SkiaBackendContext::the() && !command.bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
auto dst_rect = to_skia_rect(command.dst_rect);
auto clip_rect = to_skia_rect(command.clip_rect);
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
canvas.save();
canvas.clipRect(clip_rect, true);
canvas.drawImageRect(command.bitmap->sk_image(), dst_rect, to_skia_sampling_options(command.scaling_mode), &paint);
canvas.restore();
}
void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const& command)
{
if (Gfx::SkiaBackendContext::the() && !command.bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
SkMatrix matrix;
auto dst_rect = command.dst_rect.to_type<float>();
auto src_size = command.bitmap->size().to_type<float>();
matrix.setScale(dst_rect.width() / src_size.width(), dst_rect.height() / src_size.height());
matrix.postTranslate(dst_rect.x(), dst_rect.y());
auto sampling_options = to_skia_sampling_options(command.scaling_mode);
auto tile_mode_x = command.repeat.x ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto tile_mode_y = command.repeat.y ? SkTileMode::kRepeat : SkTileMode::kDecal;
auto shader = command.bitmap->sk_image()->makeShader(tile_mode_x, tile_mode_y, sampling_options, matrix);
SkPaint paint;
paint.setAntiAlias(true);
paint.setShader(shader);
auto& canvas = surface().canvas();
canvas.drawPaint(paint);
}
void DisplayListPlayerSkia::add_clip_rect(AddClipRect const& command)
{
auto& canvas = surface().canvas();
auto const& rect = command.rect;
canvas.clipRect(to_skia_rect(rect), true);
}
void DisplayListPlayerSkia::save(Save const&)
{
auto& canvas = surface().canvas();
canvas.save();
}
void DisplayListPlayerSkia::save_layer(SaveLayer const&)
{
auto& canvas = surface().canvas();
canvas.saveLayer(nullptr, nullptr);
}
void DisplayListPlayerSkia::restore(Restore const&)
{
auto& canvas = surface().canvas();
canvas.restore();
}
void DisplayListPlayerSkia::translate(Translate const& command)
{
auto& canvas = surface().canvas();
canvas.translate(command.delta.x(), command.delta.y());
}
static SkGradientShader::Interpolation to_skia_interpolation(CSS::ColorInterpolationMethodStyleValue::ColorInterpolationMethod interpolation_method)
{
SkGradientShader::Interpolation interpolation;
interpolation_method.visit(
[&](CSS::RectangularColorSpace color_space) {
switch (color_space) {
case CSS::RectangularColorSpace::Srgb:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB;
break;
case CSS::RectangularColorSpace::SrgbLinear:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGBLinear;
break;
case CSS::RectangularColorSpace::Lab:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLab;
break;
case CSS::RectangularColorSpace::Oklab:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab;
break;
case CSS::RectangularColorSpace::DisplayP3:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kDisplayP3;
break;
case CSS::RectangularColorSpace::A98Rgb:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kA98RGB;
break;
case CSS::RectangularColorSpace::ProphotoRgb:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kProphotoRGB;
break;
case CSS::RectangularColorSpace::Rec2020:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kRec2020;
break;
case CSS::RectangularColorSpace::DisplayP3Linear:
case CSS::RectangularColorSpace::XyzD50:
case CSS::RectangularColorSpace::XyzD65:
dbgln("FIXME: Unsupported gradient color space");
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab;
break;
case CSS::RectangularColorSpace::Xyz:
// NB: Xyz should have been canonicalized to XyzD65 at parse time
VERIFY_NOT_REACHED();
}
},
[&](CSS::ColorInterpolationMethodStyleValue::PolarColorInterpolationMethod const& color_space) {
switch (color_space.color_space) {
case CSS::PolarColorSpace::Hsl:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHSL;
break;
case CSS::PolarColorSpace::Hwb:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHWB;
break;
case CSS::PolarColorSpace::Lch:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLCH;
break;
case CSS::PolarColorSpace::Oklch:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLCH;
break;
}
switch (color_space.hue_interpolation_method) {
case CSS::HueInterpolationMethod::Shorter:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kShorter;
break;
case CSS::HueInterpolationMethod::Longer:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kLonger;
break;
case CSS::HueInterpolationMethod::Increasing:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kIncreasing;
break;
case CSS::HueInterpolationMethod::Decreasing:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kDecreasing;
break;
}
});
interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes;
return interpolation;
}
void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command)
{
auto const& linear_gradient_data = command.linear_gradient_data;
auto const& color_stop_list = linear_gradient_data.color_stops.list;
auto const& repeat_length = linear_gradient_data.color_stops.repeat_length;
VERIFY(!color_stop_list.is_empty());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
auto const first_position = repeat_length.has_value() ? color_stop_list.first().position : 0.f;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append((stop.position - first_position) / repeat_length.value_or(1));
}
auto rect = command.gradient_rect.to_type<float>();
auto length = calculate_gradient_length<float>(rect.size(), linear_gradient_data.gradient_angle);
// Starting and ending points before rotation (0deg / "to top")
auto rect_center = rect.center();
auto start = rect_center.translated(0, (.5f - first_position) * length);
auto end = start.translated(0, repeat_length.value_or(1) * -length);
Array const points { to_skia_point(start), to_skia_point(end) };
SkMatrix matrix;
matrix.setRotate(linear_gradient_data.gradient_angle, rect_center.x(), rect_center.y());
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(linear_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);
paint.setShader(shader);
surface().canvas().drawRect(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::paint_outer_box_shadow(PaintOuterBoxShadow const& command)
{
auto content_rrect = to_skia_rrect(command.device_content_rect, command.content_corner_radii);
auto& canvas = surface().canvas();
canvas.save();
canvas.clipRRect(content_rrect, SkClipOp::kDifference, true);
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(to_skia_color(command.color));
paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, command.blur_radius / 2));
auto shadow_rounded_rect = to_skia_rrect(command.shadow_rect, command.shadow_corner_radii);
canvas.drawRRect(shadow_rounded_rect, paint);
canvas.restore();
}
void DisplayListPlayerSkia::paint_inner_box_shadow(PaintInnerBoxShadow const& command)
{
auto outer_rect = to_skia_rrect(command.outer_shadow_rect, command.content_corner_radii);
auto inner_rect = to_skia_rrect(command.inner_shadow_rect, command.inner_shadow_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.setAntiAlias(true);
path_paint.setColor(to_skia_color(command.color));
path_paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, command.blur_radius / 2));
canvas.save();
canvas.clipRRect(to_skia_rrect(command.device_content_rect, command.content_corner_radii), true);
canvas.drawPath(result_path, path_paint);
canvas.restore();
}
void DisplayListPlayerSkia::paint_text_shadow(PaintTextShadow const& command)
{
auto& canvas = surface().canvas();
auto blur_image_filter = SkImageFilters::Blur(command.blur_radius / 2, command.blur_radius / 2, nullptr);
SkPaint blur_paint;
blur_paint.setImageFilter(blur_image_filter);
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, &blur_paint, nullptr, 0));
draw_glyph_run({ .glyph_run = command.glyph_run,
.rect = command.text_rect,
.translation = command.draw_location + command.text_rect.location().to_type<float>(),
.color = command.color });
canvas.restore();
}
void DisplayListPlayerSkia::fill_rect_with_rounded_corners(FillRectWithRoundedCorners const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setColor(to_skia_color(command.color));
paint.setAntiAlias(true);
auto rounded_rect = to_skia_rrect(rect, command.corner_radii);
canvas.drawRRect(rounded_rect, paint);
}
static SkTileMode to_skia_tile_mode(SVGLinearGradientPaintStyle::SpreadMethod spread_method)
{
switch (spread_method) {
case SVGLinearGradientPaintStyle::SpreadMethod::Pad:
return SkTileMode::kClamp;
case SVGLinearGradientPaintStyle::SpreadMethod::Reflect:
return SkTileMode::kMirror;
case SVGLinearGradientPaintStyle::SpreadMethod::Repeat:
return SkTileMode::kRepeat;
default:
VERIFY_NOT_REACHED();
}
}
static SkPaint gradient_paint_style_to_skia_paint(Painting::SVGGradientPaintStyle const& paint_style, Gfx::FloatRect const& bounding_rect)
{
SkPaint paint;
auto const& color_stops = 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 : color_stops) {
colors.append(to_skia_color(color_stop.color));
positions.append(color_stop.position);
}
SkMatrix matrix;
matrix.setTranslate(bounding_rect.x(), bounding_rect.y());
if (auto gradient_transform = paint_style.gradient_transform(); gradient_transform.has_value())
matrix = matrix * to_skia_matrix(gradient_transform.value());
auto tile_mode = to_skia_tile_mode(paint_style.spread_method());
sk_sp<SkShader> shader;
if (is<SVGLinearGradientPaintStyle>(paint_style)) {
auto const& linear_gradient_paint_style = static_cast<SVGLinearGradientPaintStyle const&>(paint_style);
Array points {
to_skia_point(linear_gradient_paint_style.start_point()),
to_skia_point(linear_gradient_paint_style.end_point()),
};
shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), tile_mode, 0, &matrix);
} else if (is<SVGRadialGradientPaintStyle>(paint_style)) {
auto const& radial_gradient_paint_style = static_cast<SVGRadialGradientPaintStyle const&>(paint_style);
auto start_center = to_skia_point(radial_gradient_paint_style.start_center());
auto end_center = to_skia_point(radial_gradient_paint_style.end_center());
auto start_radius = radial_gradient_paint_style.start_radius();
auto end_radius = radial_gradient_paint_style.end_radius();
shader = SkGradientShader::MakeTwoPointConical(start_center, start_radius, end_center, end_radius, colors.data(), positions.data(), color_stops.size(), tile_mode, 0, &matrix);
}
paint.setShader(shader);
if (paint_style.color_space() == Gfx::InterpolationColorSpace::LinearRGB) {
paint.setColorFilter(SkColorFilters::LinearToSRGBGamma());
}
return paint;
}
SkPaint DisplayListPlayerSkia::paint_style_to_skia_paint(Painting::SVGPaintServerPaintStyle const& paint_style, Gfx::FloatRect const& bounding_rect)
{
if (auto const* gradient = as_if<SVGGradientPaintStyle>(paint_style))
return gradient_paint_style_to_skia_paint(*gradient, bounding_rect);
if (auto const* pattern = as_if<SVGPatternPaintStyle>(paint_style)) {
auto const& tile_rect = pattern->tile_rect();
auto tile_size = Gfx::IntSize(ceilf(tile_rect.width()), ceilf(tile_rect.height()));
if (tile_size.is_empty())
return {};
auto tile_surface = Gfx::PaintingSurface::create_with_size(tile_size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
execute_display_list_into_surface(*pattern->tile_display_list(), *tile_surface);
auto image = tile_surface->sk_surface().makeImageSnapshot();
SkMatrix matrix;
matrix.setTranslate(tile_rect.x(), tile_rect.y());
matrix.preScale(tile_rect.width() / tile_size.width(), tile_rect.height() / tile_size.height());
if (auto transform = pattern->pattern_transform(); transform.has_value())
matrix = matrix * to_skia_matrix(transform.value());
auto sampling = SkSamplingOptions(SkFilterMode::kLinear);
auto shader = image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, sampling, &matrix);
SkPaint paint;
paint.setShader(shader);
return paint;
}
return {};
}
void DisplayListPlayerSkia::fill_path(FillPath const& command)
{
auto path = to_skia_path(command.path);
path.setFillType(to_skia_path_fill_type(command.winding_rule));
SkPaint paint;
if (command.paint_style_or_color.has<PaintStyle>()) {
auto const& paint_style = command.paint_style_or_color.get<PaintStyle>();
paint = paint_style_to_skia_paint(*paint_style, command.bounding_rect().to_type<float>());
paint.setAlphaf(command.opacity);
} else {
auto const& color = command.paint_style_or_color.get<Color>();
paint.setColor(to_skia_color(color));
}
paint.setAntiAlias(command.should_anti_alias == ShouldAntiAlias::Yes);
surface().canvas().drawPath(path, paint);
}
void DisplayListPlayerSkia::stroke_path(StrokePath const& command)
{
auto path = to_skia_path(command.path);
SkPaint paint;
if (command.paint_style_or_color.has<PaintStyle>()) {
auto const& paint_style = command.paint_style_or_color.get<PaintStyle>();
paint = paint_style_to_skia_paint(*paint_style, command.bounding_rect().to_type<float>());
paint.setAlphaf(command.opacity);
} else {
auto const& color = command.paint_style_or_color.get<Color>();
paint.setColor(to_skia_color(color));
}
paint.setAntiAlias(command.should_anti_alias == ShouldAntiAlias::Yes);
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setStrokeWidth(command.thickness);
paint.setStrokeCap(to_skia_cap(command.cap_style));
paint.setStrokeJoin(to_skia_join(command.join_style));
paint.setStrokeMiter(command.miter_limit);
paint.setPathEffect(SkDashPathEffect::Make(command.dash_array.data(), command.dash_array.size(), command.dash_offset));
surface().canvas().drawPath(path, paint);
}
void DisplayListPlayerSkia::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(to_skia_color(command.color));
canvas.drawOval(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::fill_ellipse(FillEllipse const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(to_skia_color(command.color));
canvas.drawOval(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::draw_line(DrawLine const& command)
{
auto from = to_skia_point(command.from);
auto to = to_skia_point(command.to);
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setStrokeWidth(command.thickness);
paint.setColor(to_skia_color(command.color));
switch (command.style) {
case Gfx::LineStyle::Solid:
break;
case Gfx::LineStyle::Dotted: {
auto length = command.to.distance_from(command.from);
auto dot_count = floor(length / (static_cast<float>(command.thickness) * 2));
auto interval = length / dot_count;
SkScalar intervals[] = { 0, interval };
paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
paint.setStrokeCap(SkPaint::Cap::kRound_Cap);
// NOTE: As Skia doesn't render a dot exactly at the end of a line, we need
// to extend it by less then an interval.
auto direction = to - from;
direction.normalize();
to += direction * (interval / 2.0f);
break;
}
case Gfx::LineStyle::Dashed: {
auto length = command.to.distance_from(command.from) + command.thickness;
auto dash_count = floor(length / static_cast<float>(command.thickness) / 4) * 2 + 1;
auto interval = length / dash_count;
SkScalar intervals[] = { interval, interval };
paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
auto direction = to - from;
direction.normalize();
from -= direction * (command.thickness / 2.0f);
to += direction * (command.thickness / 2.0f);
break;
}
}
canvas.drawLine(from, to, paint);
}
void DisplayListPlayerSkia::apply_backdrop_filter(ApplyBackdropFilter const& command)
{
auto& canvas = surface().canvas();
auto rect = to_skia_rect(command.backdrop_region);
canvas.save();
canvas.clipRect(rect, true);
ScopeGuard guard = [&] { canvas.restore(); };
if (command.backdrop_filter.has_value()) {
auto image_filter = to_skia_image_filter(command.backdrop_filter.value());
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
canvas.restore();
}
}
void DisplayListPlayerSkia::draw_rect(DrawRect const& command)
{
auto const& rect = command.rect;
auto& canvas = surface().canvas();
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(1);
paint.setColor(to_skia_color(command.color));
canvas.drawRect(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& command)
{
auto const& radial_gradient_data = command.radial_gradient_data;
auto const& color_stop_list = radial_gradient_data.color_stops.list;
VERIFY(!color_stop_list.is_empty());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append(stop.position);
}
auto const& rect = command.rect;
auto center = to_skia_point(command.center.translated(command.rect.location()));
auto const size = command.size.to_type<float>();
SkMatrix matrix;
// Skia does not support specifying of horizontal and vertical radius's separately,
// so instead we apply scale matrix
auto const aspect_ratio = size.width() / size.height();
auto const sx = isinf(aspect_ratio) ? 1.0f : aspect_ratio;
matrix.setScale(sx, 1.0f, center.x(), center.y());
SkTileMode tile_mode = radial_gradient_data.color_stops.repeating ? SkTileMode::kRepeat : SkTileMode::kClamp;
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(radial_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeRadial(center, size.height(), colors.data(), color_space, positions.data(), positions.size(), tile_mode, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);
paint.setAntiAlias(true);
paint.setShader(shader);
surface().canvas().drawRect(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::paint_conic_gradient(PaintConicGradient const& command)
{
auto const& conic_gradient_data = command.conic_gradient_data;
auto const& color_stop_list = conic_gradient_data.color_stops.list;
VERIFY(!color_stop_list.is_empty());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append(stop.position);
}
auto const& rect = command.rect;
auto center = command.position.translated(rect.location()).to_type<float>();
SkMatrix matrix;
matrix.setRotate(-90 + conic_gradient_data.start_angle, center.x(), center.y());
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(conic_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeSweep(center.x(), center.y(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, 0, 360, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);
paint.setAntiAlias(true);
paint.setShader(shader);
surface().canvas().drawRect(to_skia_rect(rect), paint);
}
void DisplayListPlayerSkia::add_rounded_rect_clip(AddRoundedRectClip const& command)
{
auto rounded_rect = to_skia_rrect(command.border_rect, command.corner_radii);
auto& canvas = surface().canvas();
auto clip_op = command.corner_clip == CornerClip::Inside ? SkClipOp::kDifference : SkClipOp::kIntersect;
canvas.clipRRect(rounded_rect, clip_op, true);
}
void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList const& command)
{
auto& canvas = surface().canvas();
canvas.translate(command.rect.x(), command.rect.y());
ScrollStateSnapshot scroll_state_snapshot;
execute_impl(*command.display_list, scroll_state_snapshot);
}
void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command)
{
auto gutter_rect = to_skia_rect(command.gutter_rect);
auto thumb_rect = to_skia_rect(command.thumb_rect);
auto radius = thumb_rect.width() / 2;
auto thumb_rrect = SkRRect::MakeRectXY(thumb_rect, radius, radius);
auto& canvas = surface().canvas();
auto gutter_fill_color = command.track_color;
SkPaint gutter_fill_paint;
gutter_fill_paint.setColor(to_skia_color(gutter_fill_color));
canvas.drawRect(gutter_rect, gutter_fill_paint);
auto thumb_fill_color = command.thumb_color;
if (command.gutter_rect.is_empty() && thumb_fill_color == CSS::InitialValues::scrollbar_color().thumb_color)
thumb_fill_color = thumb_fill_color.with_alpha(128);
SkPaint thumb_fill_paint;
thumb_fill_paint.setColor(to_skia_color(thumb_fill_color));
canvas.drawRRect(thumb_rrect, thumb_fill_paint);
auto stroke_color = thumb_fill_color.lightened();
SkPaint stroke_paint;
stroke_paint.setStroke(true);
stroke_paint.setStrokeWidth(1);
stroke_paint.setAntiAlias(true);
stroke_paint.setColor(to_skia_color(stroke_color));
canvas.drawRRect(thumb_rrect, stroke_paint);
}
void DisplayListPlayerSkia::apply_effects(ApplyEffects const& command)
{
auto& canvas = surface().canvas();
SkPaint paint;
if (command.opacity < 1.0f)
paint.setAlphaf(command.opacity);
if (command.compositing_and_blending_operator != Gfx::CompositingAndBlendingOperator::Normal)
paint.setBlender(Gfx::to_skia_blender(command.compositing_and_blending_operator));
if (command.filter.has_value())
paint.setImageFilter(to_skia_image_filter(command.filter.value()));
if (command.mask_kind.has_value() && command.mask_kind.value() == Gfx::MaskKind::Luminance)
paint.setColorFilter(SkLumaColorFilter::Make());
canvas.saveLayer(nullptr, &paint);
}
void DisplayListPlayerSkia::apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 const& matrix)
{
auto new_transform = Gfx::translation_matrix(Vector3<float>(origin.x(), origin.y(), 0));
new_transform = new_transform * matrix;
new_transform = new_transform * Gfx::translation_matrix(Vector3<float>(-origin.x(), -origin.y(), 0));
auto skia_matrix = to_skia_matrix4x4(new_transform);
surface().canvas().concat(skia_matrix);
}
void DisplayListPlayerSkia::add_clip_path(Gfx::Path const& path)
{
auto& canvas = surface().canvas();
canvas.clipPath(to_skia_path(path), true);
}
bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{
return surface().canvas().quickReject(to_skia_rect(rect));
}
}