/* * Copyright (c) 2024, Andreas Kling * Copyright (c) 2024, Aliaksandr Kalenik * Copyright (c) 2024, Lucien Fiorini * * SPDX-License-Identifier: BSD-2-Clause */ #define AK_DONT_REPLACE_STD #include #include #include #include #include #include #include #include #include #include #include namespace Gfx { struct PainterSkia::Impl { RefPtr painting_surface; Impl(Gfx::PaintingSurface& surface) : painting_surface(surface) { } SkCanvas* canvas() const { return &painting_surface->canvas(); } }; static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style) { if (is(style)) { auto const& solid_color = static_cast(style); auto color = solid_color.sample_color(Gfx::IntPoint(0, 0)); paint.setColor(to_skia_color(color)); } else if (is(style)) { auto const& linear_gradient = static_cast(style); auto const& color_stops = linear_gradient.color_stops(); Vector colors; colors.ensure_capacity(color_stops.size()); Vector 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); } Array points; points[0] = to_skia_point(linear_gradient.start_point()); points[1] = to_skia_point(linear_gradient.end_point()); SkMatrix matrix; auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix); paint.setShader(shader); } else if (is(style)) { auto const& radial_gradient = static_cast(style); auto const& color_stops = radial_gradient.color_stops(); Vector colors; colors.ensure_capacity(color_stops.size()); Vector 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); } auto start_center = radial_gradient.start_center(); auto end_center = radial_gradient.end_center(); auto start_radius = radial_gradient.start_radius(); auto end_radius = radial_gradient.end_radius(); auto start_sk_point = to_skia_point(start_center); auto end_sk_point = to_skia_point(end_center); SkMatrix matrix; auto shader = SkGradientShader::MakeTwoPointConical(start_sk_point, start_radius, end_sk_point, end_radius, colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix); paint.setShader(shader); } } static void apply_filters(SkPaint& paint, ReadonlySpan filters) { for (auto const& filter : filters) { paint.setImageFilter(to_skia_image_filter(filter)); } } static SkPaint to_skia_paint(Gfx::PaintStyle const& style, ReadonlySpan filters) { SkPaint paint; apply_paint_style(paint, style); apply_filters(paint, filters); return paint; } PainterSkia::PainterSkia(NonnullRefPtr painting_surface) : m_impl(adopt_own(*new Impl { move(painting_surface) })) { } PainterSkia::~PainterSkia() = default; void PainterSkia::clear_rect(Gfx::FloatRect const& rect, Gfx::Color color) { SkPaint paint; paint.setColor(to_skia_color(color)); paint.setBlendMode(SkBlendMode::kClear); impl().canvas()->drawRect(to_skia_rect(rect), paint); } void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color) { SkPaint paint; paint.setColor(to_skia_color(color)); impl().canvas()->drawRect(to_skia_rect(rect), paint); } void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan filters, float global_alpha) { SkPaint paint; apply_filters(paint, filters); paint.setAlpha(static_cast(global_alpha * 255)); impl().canvas()->drawImageRect( src_bitmap.sk_image(), to_skia_rect(src_rect), to_skia_rect(dst_rect), to_skia_sampling_options(scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint); } void PainterSkia::set_transform(Gfx::AffineTransform const& transform) { auto matrix = SkMatrix::MakeAll( transform.a(), transform.c(), transform.e(), transform.b(), transform.d(), transform.f(), 0, 0, 1); impl().canvas()->setMatrix(matrix); } void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness) { // Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing. if (thickness <= 0) return; SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(thickness); paint.setColor(to_skia_color(color)); auto sk_path = to_skia_path(path); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius) { // Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing. if (thickness <= 0) return; SkPaint paint; paint.setAntiAlias(true); paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, blur_radius / 2)); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(thickness); paint.setColor(to_skia_color(color)); auto sk_path = to_skia_path(path); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float thickness, float global_alpha) { // Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing. if (thickness <= 0) return; auto sk_path = to_skia_path(path); auto paint = to_skia_paint(paint_style, filters); paint.setAntiAlias(true); float alpha = paint.getAlphaf(); paint.setAlphaf(alpha * global_alpha); paint.setStyle(SkPaint::Style::kStroke_Style); paint.setStrokeWidth(thickness); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule) { SkPaint paint; paint.setAntiAlias(true); paint.setColor(to_skia_color(color)); auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::fill_path(Gfx::Path const& path, Gfx::Color color, Gfx::WindingRule winding_rule, float blur_radius) { SkPaint paint; paint.setAntiAlias(true); paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, blur_radius / 2)); paint.setColor(to_skia_color(color)); auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_style, ReadonlySpan filters, float global_alpha, Gfx::WindingRule winding_rule) { auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); auto paint = to_skia_paint(paint_style, filters); paint.setAntiAlias(true); float alpha = paint.getAlphaf(); paint.setAlphaf(alpha * global_alpha); impl().canvas()->drawPath(sk_path, paint); } void PainterSkia::save() { impl().canvas()->save(); } void PainterSkia::restore() { impl().canvas()->restore(); } void PainterSkia::clip(Gfx::Path const& path, Gfx::WindingRule winding_rule) { auto sk_path = to_skia_path(path); sk_path.setFillType(to_skia_path_fill_type(winding_rule)); impl().canvas()->clipPath(sk_path, SkClipOp::kIntersect, true); } }