mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-08 01:00:05 +00:00
LibWeb+LibGfx: Refactor CSS filters into LibGfx
CSS filters work similarly to canvas filters, so it makes sense to have Gfx::Filter that can be used by both libraries in an analogous way as Gfx::Color.
This commit is contained in:
parent
bc971a4ccc
commit
9fd1223992
Notes:
github-actions[bot]
2024-12-18 17:55:46 +00:00
Author: https://github.com/ananas-dev
Commit: 9fd1223992
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2876
Reviewed-by: https://github.com/kalenikaliaksandr
Reviewed-by: https://github.com/shlyakpavel
18 changed files with 365 additions and 367 deletions
|
@ -41,6 +41,7 @@ set(SOURCES
|
||||||
ImageFormats/WebPWriterLossless.cpp
|
ImageFormats/WebPWriterLossless.cpp
|
||||||
ImageFormats/AVIFLoader.cpp
|
ImageFormats/AVIFLoader.cpp
|
||||||
ImmutableBitmap.cpp
|
ImmutableBitmap.cpp
|
||||||
|
SkiaUtils.cpp
|
||||||
PaintingSurface.cpp
|
PaintingSurface.cpp
|
||||||
Palette.cpp
|
Palette.cpp
|
||||||
Path.cpp
|
Path.cpp
|
||||||
|
|
43
Libraries/LibGfx/Filter.h
Normal file
43
Libraries/LibGfx/Filter.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Variant.h>
|
||||||
|
#include <LibGfx/Color.h>
|
||||||
|
|
||||||
|
namespace Gfx {
|
||||||
|
|
||||||
|
struct BlurFilter {
|
||||||
|
float radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DropShadowFilter {
|
||||||
|
float offset_x;
|
||||||
|
float offset_y;
|
||||||
|
float radius;
|
||||||
|
Gfx::Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HueRotateFilter {
|
||||||
|
float angle_degrees;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ColorFilter {
|
||||||
|
enum class Type {
|
||||||
|
Brightness,
|
||||||
|
Contrast,
|
||||||
|
Grayscale,
|
||||||
|
Invert,
|
||||||
|
Opacity,
|
||||||
|
Saturate,
|
||||||
|
Sepia
|
||||||
|
} type;
|
||||||
|
float amount;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Filter = Variant<BlurFilter, DropShadowFilter, HueRotateFilter, ColorFilter>;
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
#include <LibGfx/ImmutableBitmap.h>
|
#include <LibGfx/ImmutableBitmap.h>
|
||||||
#include <LibGfx/PainterSkia.h>
|
#include <LibGfx/PainterSkia.h>
|
||||||
#include <LibGfx/PathSkia.h>
|
#include <LibGfx/PathSkia.h>
|
||||||
|
#include <LibGfx/SkiaUtils.h>
|
||||||
|
|
||||||
#include <AK/TypeCasts.h>
|
#include <AK/TypeCasts.h>
|
||||||
#include <core/SkCanvas.h>
|
#include <core/SkCanvas.h>
|
||||||
|
@ -34,30 +35,75 @@ struct PainterSkia::Impl {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr SkRect to_skia_rect(auto const& rect)
|
static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
|
||||||
{
|
{
|
||||||
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
|
if (is<Gfx::SolidColorPaintStyle>(style)) {
|
||||||
}
|
auto const& solid_color = static_cast<Gfx::SolidColorPaintStyle const&>(style);
|
||||||
|
auto color = solid_color.sample_color(Gfx::IntPoint(0, 0));
|
||||||
|
|
||||||
static constexpr SkColor to_skia_color(Gfx::Color const& color)
|
paint.setColor(to_skia_color(color));
|
||||||
{
|
} else if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
|
||||||
return SkColorSetARGB(color.alpha(), color.red(), color.green(), color.blue());
|
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
|
||||||
}
|
auto const& color_stops = linear_gradient.color_stops();
|
||||||
|
|
||||||
static SkPath to_skia_path(Gfx::Path const& path)
|
Vector<SkColor> colors;
|
||||||
{
|
colors.ensure_capacity(color_stops.size());
|
||||||
return static_cast<PathImplSkia const&>(path.impl()).sk_path();
|
Vector<SkScalar> positions;
|
||||||
}
|
positions.ensure_capacity(color_stops.size());
|
||||||
|
for (auto const& color_stop : color_stops) {
|
||||||
static SkPathFillType to_skia_path_fill_type(Gfx::WindingRule winding_rule)
|
colors.append(to_skia_color(color_stop.color));
|
||||||
{
|
positions.append(color_stop.position);
|
||||||
switch (winding_rule) {
|
|
||||||
case Gfx::WindingRule::Nonzero:
|
|
||||||
return SkPathFillType::kWinding;
|
|
||||||
case Gfx::WindingRule::EvenOdd:
|
|
||||||
return SkPathFillType::kEvenOdd;
|
|
||||||
}
|
}
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
|
Array<SkPoint, 2> 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<Gfx::CanvasRadialGradientPaintStyle>(style)) {
|
||||||
|
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
|
||||||
|
auto const& color_stops = radial_gradient.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Gfx::Filter> filters)
|
||||||
|
{
|
||||||
|
for (auto const& filter : filters) {
|
||||||
|
paint.setImageFilter(to_skia_image_filter(filter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkPaint to_skia_paint(Gfx::PaintStyle const& style, ReadonlySpan<Gfx::Filter> filters)
|
||||||
|
{
|
||||||
|
SkPaint paint;
|
||||||
|
|
||||||
|
apply_paint_style(paint, style);
|
||||||
|
apply_filters(paint, filters);
|
||||||
|
|
||||||
|
return paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
PainterSkia::PainterSkia(NonnullRefPtr<Gfx::PaintingSurface> painting_surface)
|
PainterSkia::PainterSkia(NonnullRefPtr<Gfx::PaintingSurface> painting_surface)
|
||||||
|
@ -82,21 +128,6 @@ void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color)
|
||||||
impl().canvas()->drawRect(to_skia_rect(rect), paint);
|
impl().canvas()->drawRect(to_skia_rect(rect), paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mode)
|
|
||||||
{
|
|
||||||
switch (scaling_mode) {
|
|
||||||
case Gfx::ScalingMode::NearestNeighbor:
|
|
||||||
return SkSamplingOptions(SkFilterMode::kNearest);
|
|
||||||
case Gfx::ScalingMode::BilinearBlend:
|
|
||||||
case Gfx::ScalingMode::SmoothPixels:
|
|
||||||
return SkSamplingOptions(SkFilterMode::kLinear);
|
|
||||||
case Gfx::ScalingMode::BoxSampling:
|
|
||||||
return SkSamplingOptions(SkCubicResampler::Mitchell());
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, float global_alpha)
|
void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, float global_alpha)
|
||||||
{
|
{
|
||||||
SkPaint paint;
|
SkPaint paint;
|
||||||
|
@ -121,67 +152,6 @@ void PainterSkia::set_transform(Gfx::AffineTransform const& transform)
|
||||||
impl().canvas()->setMatrix(matrix);
|
impl().canvas()->setMatrix(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkPoint to_skia_point(auto const& point)
|
|
||||||
{
|
|
||||||
return SkPoint::Make(point.x(), point.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkPaint to_skia_paint(Gfx::PaintStyle const& style)
|
|
||||||
{
|
|
||||||
if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
|
|
||||||
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
|
|
||||||
auto const& color_stops = linear_gradient.color_stops();
|
|
||||||
|
|
||||||
SkPaint paint;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Array<SkPoint, 2> 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);
|
|
||||||
return paint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is<Gfx::CanvasRadialGradientPaintStyle>(style)) {
|
|
||||||
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
|
|
||||||
auto const& color_stops = radial_gradient.color_stops();
|
|
||||||
|
|
||||||
SkPaint paint;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
return paint;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness)
|
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.
|
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
|
||||||
|
@ -220,7 +190,7 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::PaintStyle const& pain
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto sk_path = to_skia_path(path);
|
auto sk_path = to_skia_path(path);
|
||||||
auto paint = to_skia_paint(paint_style);
|
auto paint = to_skia_paint(paint_style, {});
|
||||||
paint.setAntiAlias(true);
|
paint.setAntiAlias(true);
|
||||||
paint.setAlphaf(global_alpha);
|
paint.setAlphaf(global_alpha);
|
||||||
paint.setStyle(SkPaint::Style::kStroke_Style);
|
paint.setStyle(SkPaint::Style::kStroke_Style);
|
||||||
|
@ -253,7 +223,7 @@ void PainterSkia::fill_path(Gfx::Path const& path, Gfx::PaintStyle const& paint_
|
||||||
{
|
{
|
||||||
auto sk_path = to_skia_path(path);
|
auto sk_path = to_skia_path(path);
|
||||||
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
|
sk_path.setFillType(to_skia_path_fill_type(winding_rule));
|
||||||
auto paint = to_skia_paint(paint_style);
|
auto paint = to_skia_paint(paint_style, {});
|
||||||
paint.setAntiAlias(true);
|
paint.setAntiAlias(true);
|
||||||
paint.setAlphaf(global_alpha);
|
paint.setAlphaf(global_alpha);
|
||||||
impl().canvas()->drawPath(sk_path, paint);
|
impl().canvas()->drawPath(sk_path, paint);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibGfx/PathSkia.h>
|
#include <LibGfx/PathSkia.h>
|
||||||
#include <LibGfx/Rect.h>
|
#include <LibGfx/Rect.h>
|
||||||
|
#include <LibGfx/SkiaUtils.h>
|
||||||
#include <core/SkFont.h>
|
#include <core/SkFont.h>
|
||||||
#include <core/SkPath.h>
|
#include <core/SkPath.h>
|
||||||
#include <core/SkPathMeasure.h>
|
#include <core/SkPathMeasure.h>
|
||||||
|
@ -205,17 +206,6 @@ Gfx::FloatRect PathImplSkia::bounding_box() const
|
||||||
return { bounds.fLeft, bounds.fTop, bounds.fRight - bounds.fLeft, bounds.fBottom - bounds.fTop };
|
return { bounds.fLeft, bounds.fTop, bounds.fRight - bounds.fLeft, bounds.fBottom - bounds.fTop };
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkPathFillType to_skia_path_fill_type(Gfx::WindingRule winding_rule)
|
|
||||||
{
|
|
||||||
switch (winding_rule) {
|
|
||||||
case Gfx::WindingRule::Nonzero:
|
|
||||||
return SkPathFillType::kWinding;
|
|
||||||
case Gfx::WindingRule::EvenOdd:
|
|
||||||
return SkPathFillType::kEvenOdd;
|
|
||||||
}
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PathImplSkia::contains(FloatPoint point, Gfx::WindingRule winding_rule) const
|
bool PathImplSkia::contains(FloatPoint point, Gfx::WindingRule winding_rule) const
|
||||||
{
|
{
|
||||||
SkPath temp_path = *m_path;
|
SkPath temp_path = *m_path;
|
||||||
|
|
143
Libraries/LibGfx/SkiaUtils.cpp
Normal file
143
Libraries/LibGfx/SkiaUtils.cpp
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/Assertions.h>
|
||||||
|
#include <LibGfx/Filter.h>
|
||||||
|
#include <LibGfx/SkiaUtils.h>
|
||||||
|
#include <core/SkColorFilter.h>
|
||||||
|
#include <core/SkImageFilter.h>
|
||||||
|
#include <effects/SkImageFilters.h>
|
||||||
|
|
||||||
|
namespace Gfx {
|
||||||
|
|
||||||
|
SkPath to_skia_path(Path const& path)
|
||||||
|
{
|
||||||
|
return static_cast<PathImplSkia const&>(path.impl()).sk_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
sk_sp<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter)
|
||||||
|
{
|
||||||
|
// See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
|
||||||
|
return filter.visit(
|
||||||
|
[&](Gfx::BlurFilter blur_filter) {
|
||||||
|
return SkImageFilters::Blur(blur_filter.radius, blur_filter.radius, nullptr);
|
||||||
|
},
|
||||||
|
[&](Gfx::ColorFilter color_filter) {
|
||||||
|
sk_sp<SkColorFilter> skia_color_filter;
|
||||||
|
float amount = color_filter.amount;
|
||||||
|
|
||||||
|
// Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
|
||||||
|
switch (color_filter.type) {
|
||||||
|
case ColorFilter::Type::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
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::Brightness: {
|
||||||
|
float matrix[20] = {
|
||||||
|
amount, 0, 0, 0, 0,
|
||||||
|
0, amount, 0, 0, 0,
|
||||||
|
0, 0, amount, 0, 0,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::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
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::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
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::Opacity: {
|
||||||
|
float matrix[20] = {
|
||||||
|
1, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, amount, 0
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::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
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Gfx::ColorFilter::Type::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
|
||||||
|
};
|
||||||
|
skia_color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SkImageFilters::ColorFilter(skia_color_filter, nullptr);
|
||||||
|
},
|
||||||
|
[&](Gfx::HueRotateFilter hue_rotate_filter) {
|
||||||
|
float radians = AK::to_radians(hue_rotate_filter.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 filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
|
||||||
|
return SkImageFilters::ColorFilter(filter, nullptr);
|
||||||
|
},
|
||||||
|
[&](Gfx::DropShadowFilter drop_shadow_filter) {
|
||||||
|
auto shadow_color = to_skia_color(drop_shadow_filter.color);
|
||||||
|
return SkImageFilters::DropShadow(drop_shadow_filter.offset_x, drop_shadow_filter.offset_y, drop_shadow_filter.radius, drop_shadow_filter.radius, shadow_color, nullptr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
|
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
|
||||||
|
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -8,11 +9,19 @@
|
||||||
|
|
||||||
#include <AK/Assertions.h>
|
#include <AK/Assertions.h>
|
||||||
#include <LibGfx/Bitmap.h>
|
#include <LibGfx/Bitmap.h>
|
||||||
|
#include <LibGfx/Filter.h>
|
||||||
|
#include <LibGfx/PathSkia.h>
|
||||||
|
#include <LibGfx/ScalingMode.h>
|
||||||
|
#include <LibGfx/WindingRule.h>
|
||||||
|
#include <core/SkColor.h>
|
||||||
#include <core/SkColorType.h>
|
#include <core/SkColorType.h>
|
||||||
|
#include <core/SkImageFilter.h>
|
||||||
|
#include <core/SkPath.h>
|
||||||
|
#include <core/SkSamplingOptions.h>
|
||||||
|
|
||||||
namespace Gfx {
|
namespace Gfx {
|
||||||
|
|
||||||
static SkColorType to_skia_color_type(Gfx::BitmapFormat format)
|
constexpr SkColorType to_skia_color_type(Gfx::BitmapFormat format)
|
||||||
{
|
{
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Gfx::BitmapFormat::Invalid:
|
case Gfx::BitmapFormat::Invalid:
|
||||||
|
@ -28,4 +37,58 @@ static SkColorType to_skia_color_type(Gfx::BitmapFormat format)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr SkRect to_skia_rect(auto const& rect)
|
||||||
|
{
|
||||||
|
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SkColor to_skia_color(Gfx::Color const& color)
|
||||||
|
{
|
||||||
|
return SkColorSetARGB(color.alpha(), color.red(), color.green(), color.blue());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SkColor4f to_skia_color4f(Color const& color)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.fR = color.red() / 255.0f,
|
||||||
|
.fG = color.green() / 255.0f,
|
||||||
|
.fB = color.blue() / 255.0f,
|
||||||
|
.fA = color.alpha() / 255.0f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SkPoint to_skia_point(auto const& point)
|
||||||
|
{
|
||||||
|
return SkPoint::Make(point.x(), point.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SkPathFillType to_skia_path_fill_type(Gfx::WindingRule winding_rule)
|
||||||
|
{
|
||||||
|
switch (winding_rule) {
|
||||||
|
case Gfx::WindingRule::Nonzero:
|
||||||
|
return SkPathFillType::kWinding;
|
||||||
|
case Gfx::WindingRule::EvenOdd:
|
||||||
|
return SkPathFillType::kEvenOdd;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mode)
|
||||||
|
{
|
||||||
|
switch (scaling_mode) {
|
||||||
|
case Gfx::ScalingMode::NearestNeighbor:
|
||||||
|
return SkSamplingOptions(SkFilterMode::kNearest);
|
||||||
|
case Gfx::ScalingMode::BilinearBlend:
|
||||||
|
case Gfx::ScalingMode::SmoothPixels:
|
||||||
|
return SkSamplingOptions(SkFilterMode::kLinear);
|
||||||
|
case Gfx::ScalingMode::BoxSampling:
|
||||||
|
return SkSamplingOptions(SkCubicResampler::Mitchell());
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SkPath to_skia_path(Path const& path);
|
||||||
|
sk_sp<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <AK/FlyString.h>
|
#include <AK/FlyString.h>
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
|
#include <LibGfx/Filter.h>
|
||||||
#include <LibGfx/FontCascadeList.h>
|
#include <LibGfx/FontCascadeList.h>
|
||||||
#include <LibGfx/ScalingMode.h>
|
#include <LibGfx/ScalingMode.h>
|
||||||
#include <LibWeb/CSS/CalculatedOr.h>
|
#include <LibWeb/CSS/CalculatedOr.h>
|
||||||
|
@ -57,34 +58,6 @@ struct QuotesData {
|
||||||
Vector<Array<FlyString, 2>> strings {};
|
Vector<Array<FlyString, 2>> strings {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResolvedFilter {
|
|
||||||
struct Blur {
|
|
||||||
float radius;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DropShadow {
|
|
||||||
double offset_x;
|
|
||||||
double offset_y;
|
|
||||||
double radius;
|
|
||||||
Gfx::Color color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HueRotate {
|
|
||||||
float angle_degrees;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Color {
|
|
||||||
FilterOperation::Color::Type type;
|
|
||||||
float amount;
|
|
||||||
};
|
|
||||||
|
|
||||||
using FilterFunction = Variant<Blur, DropShadow, HueRotate, Color>;
|
|
||||||
|
|
||||||
bool is_none() const { return filters.size() == 0; }
|
|
||||||
|
|
||||||
Vector<FilterFunction> filters;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ObjectPosition {
|
struct ObjectPosition {
|
||||||
PositionEdge edge_x { PositionEdge::Left };
|
PositionEdge edge_x { PositionEdge::Left };
|
||||||
CSS::LengthPercentage offset_x { Percentage(50) };
|
CSS::LengthPercentage offset_x { Percentage(50) };
|
||||||
|
@ -122,8 +95,8 @@ public:
|
||||||
static CSS::Display display() { return CSS::Display { CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow }; }
|
static CSS::Display display() { return CSS::Display { CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow }; }
|
||||||
static Color color() { return Color::Black; }
|
static Color color() { return Color::Black; }
|
||||||
static Color stop_color() { return Color::Black; }
|
static Color stop_color() { return Color::Black; }
|
||||||
static CSS::ResolvedFilter backdrop_filter() { return ResolvedFilter { .filters = {} }; }
|
static Vector<Gfx::Filter> backdrop_filter() { return {}; }
|
||||||
static CSS::ResolvedFilter filter() { return ResolvedFilter { .filters = {} }; }
|
static Vector<Gfx::Filter> filter() { return {}; }
|
||||||
static Color background_color() { return Color::Transparent; }
|
static Color background_color() { return Color::Transparent; }
|
||||||
static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; }
|
static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; }
|
||||||
static CSS::ListStylePosition list_style_position() { return CSS::ListStylePosition::Outside; }
|
static CSS::ListStylePosition list_style_position() { return CSS::ListStylePosition::Outside; }
|
||||||
|
@ -416,8 +389,8 @@ public:
|
||||||
CSS::JustifyContent justify_content() const { return m_noninherited.justify_content; }
|
CSS::JustifyContent justify_content() const { return m_noninherited.justify_content; }
|
||||||
CSS::JustifySelf justify_self() const { return m_noninherited.justify_self; }
|
CSS::JustifySelf justify_self() const { return m_noninherited.justify_self; }
|
||||||
CSS::JustifyItems justify_items() const { return m_noninherited.justify_items; }
|
CSS::JustifyItems justify_items() const { return m_noninherited.justify_items; }
|
||||||
CSS::ResolvedFilter const& backdrop_filter() const { return m_noninherited.backdrop_filter; }
|
Vector<Gfx::Filter> const& backdrop_filter() const { return m_noninherited.backdrop_filter; }
|
||||||
CSS::ResolvedFilter const& filter() const { return m_noninherited.filter; }
|
Vector<Gfx::Filter> const& filter() const { return m_noninherited.filter; }
|
||||||
Vector<ShadowData> const& box_shadow() const { return m_noninherited.box_shadow; }
|
Vector<ShadowData> const& box_shadow() const { return m_noninherited.box_shadow; }
|
||||||
CSS::BoxSizing box_sizing() const { return m_noninherited.box_sizing; }
|
CSS::BoxSizing box_sizing() const { return m_noninherited.box_sizing; }
|
||||||
CSS::Size const& width() const { return m_noninherited.width; }
|
CSS::Size const& width() const { return m_noninherited.width; }
|
||||||
|
@ -636,8 +609,8 @@ protected:
|
||||||
CSS::LengthBox inset { InitialValues::inset() };
|
CSS::LengthBox inset { InitialValues::inset() };
|
||||||
CSS::LengthBox margin { InitialValues::margin() };
|
CSS::LengthBox margin { InitialValues::margin() };
|
||||||
CSS::LengthBox padding { InitialValues::padding() };
|
CSS::LengthBox padding { InitialValues::padding() };
|
||||||
CSS::ResolvedFilter backdrop_filter { InitialValues::backdrop_filter() };
|
Vector<Gfx::Filter> backdrop_filter { InitialValues::backdrop_filter() };
|
||||||
CSS::ResolvedFilter filter { InitialValues::filter() };
|
Vector<Gfx::Filter> filter { InitialValues::filter() };
|
||||||
BorderData border_left;
|
BorderData border_left;
|
||||||
BorderData border_top;
|
BorderData border_top;
|
||||||
BorderData border_right;
|
BorderData border_right;
|
||||||
|
@ -793,8 +766,8 @@ public:
|
||||||
void set_list_style_type(CSS::ListStyleType value) { m_inherited.list_style_type = value; }
|
void set_list_style_type(CSS::ListStyleType value) { m_inherited.list_style_type = value; }
|
||||||
void set_list_style_position(CSS::ListStylePosition value) { m_inherited.list_style_position = value; }
|
void set_list_style_position(CSS::ListStylePosition value) { m_inherited.list_style_position = value; }
|
||||||
void set_display(CSS::Display value) { m_noninherited.display = value; }
|
void set_display(CSS::Display value) { m_noninherited.display = value; }
|
||||||
void set_backdrop_filter(CSS::ResolvedFilter backdrop_filter) { m_noninherited.backdrop_filter = move(backdrop_filter); }
|
void set_backdrop_filter(Vector<Gfx::Filter> backdrop_filter) { m_noninherited.backdrop_filter = move(backdrop_filter); }
|
||||||
void set_filter(CSS::ResolvedFilter filter) { m_noninherited.filter = move(filter); }
|
void set_filter(Vector<Gfx::Filter> filter) { m_noninherited.filter = move(filter); }
|
||||||
void set_border_bottom_left_radius(CSS::BorderRadiusData value)
|
void set_border_bottom_left_radius(CSS::BorderRadiusData value)
|
||||||
{
|
{
|
||||||
m_noninherited.has_noninitial_border_radii = true;
|
m_noninherited.has_noninitial_border_radii = true;
|
||||||
|
|
|
@ -5425,7 +5425,7 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
|
||||||
|
|
||||||
auto filter_token_to_operation = [&](auto filter) {
|
auto filter_token_to_operation = [&](auto filter) {
|
||||||
VERIFY(to_underlying(filter) < to_underlying(FilterToken::Blur));
|
VERIFY(to_underlying(filter) < to_underlying(FilterToken::Blur));
|
||||||
return static_cast<FilterOperation::Color::Type>(filter);
|
return static_cast<Gfx::ColorFilter::Type>(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
|
auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
|
||||||
|
|
|
@ -92,19 +92,19 @@ String FilterValueListStyleValue::to_string(SerializationMode) const
|
||||||
builder.appendff("{}(",
|
builder.appendff("{}(",
|
||||||
[&] {
|
[&] {
|
||||||
switch (color.operation) {
|
switch (color.operation) {
|
||||||
case FilterOperation::Color::Type::Brightness:
|
case Gfx::ColorFilter::Type::Brightness:
|
||||||
return "brightness"sv;
|
return "brightness"sv;
|
||||||
case FilterOperation::Color::Type::Contrast:
|
case Gfx::ColorFilter::Type::Contrast:
|
||||||
return "contrast"sv;
|
return "contrast"sv;
|
||||||
case FilterOperation::Color::Type::Grayscale:
|
case Gfx::ColorFilter::Type::Grayscale:
|
||||||
return "grayscale"sv;
|
return "grayscale"sv;
|
||||||
case FilterOperation::Color::Type::Invert:
|
case Gfx::ColorFilter::Type::Invert:
|
||||||
return "invert"sv;
|
return "invert"sv;
|
||||||
case FilterOperation::Color::Type::Opacity:
|
case Gfx::ColorFilter::Type::Opacity:
|
||||||
return "opacity"sv;
|
return "opacity"sv;
|
||||||
case FilterOperation::Color::Type::Saturate:
|
case Gfx::ColorFilter::Type::Saturate:
|
||||||
return "saturate"sv;
|
return "saturate"sv;
|
||||||
case FilterOperation::Color::Type::Sepia:
|
case Gfx::ColorFilter::Type::Sepia:
|
||||||
return "sepia"sv;
|
return "sepia"sv;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGfx/Filter.h>
|
||||||
#include <LibWeb/CSS/Angle.h>
|
#include <LibWeb/CSS/Angle.h>
|
||||||
#include <LibWeb/CSS/CalculatedOr.h>
|
#include <LibWeb/CSS/CalculatedOr.h>
|
||||||
#include <LibWeb/CSS/Length.h>
|
#include <LibWeb/CSS/Length.h>
|
||||||
|
@ -44,15 +45,7 @@ struct HueRotate {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Color {
|
struct Color {
|
||||||
enum class Type {
|
Gfx::ColorFilter::Type operation;
|
||||||
Brightness,
|
|
||||||
Contrast,
|
|
||||||
Grayscale,
|
|
||||||
Invert,
|
|
||||||
Opacity,
|
|
||||||
Saturate,
|
|
||||||
Sepia
|
|
||||||
} operation;
|
|
||||||
Optional<NumberPercentage> amount {};
|
Optional<NumberPercentage> amount {};
|
||||||
float resolved_amount() const;
|
float resolved_amount() const;
|
||||||
bool operator==(Color const&) const = default;
|
bool operator==(Color const&) const = default;
|
||||||
|
|
|
@ -204,7 +204,7 @@ bool Node::establishes_stacking_context() const
|
||||||
// [CSS21] and a Containing Block for absolute and fixed position descendants, unless the
|
// [CSS21] and a Containing Block for absolute and fixed position descendants, unless the
|
||||||
// element it applies to is a document root element in the current browsing context.
|
// element it applies to is a document root element in the current browsing context.
|
||||||
// Spec Note: This rule works in the same way as for the filter property.
|
// Spec Note: This rule works in the same way as for the filter property.
|
||||||
if (!computed_values().backdrop_filter().is_none() || !computed_values().filter().is_none())
|
if (!computed_values().backdrop_filter().is_empty() || !computed_values().filter().is_empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Element with any of the following properties with value other than none:
|
// Element with any of the following properties with value other than none:
|
||||||
|
@ -541,31 +541,31 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
|
||||||
computed_values.set_order(computed_style.order());
|
computed_values.set_order(computed_style.order());
|
||||||
computed_values.set_clip(computed_style.clip());
|
computed_values.set_clip(computed_style.clip());
|
||||||
|
|
||||||
auto resolve_filter = [this](CSS::Filter const& computed_filter) -> CSS::ResolvedFilter {
|
auto resolve_filter = [this](CSS::Filter const& computed_filter) -> Vector<Gfx::Filter> {
|
||||||
CSS::ResolvedFilter resolved_filter;
|
Vector<Gfx::Filter> resolved_filter;
|
||||||
for (auto const& filter : computed_filter.filters()) {
|
for (auto const& filter : computed_filter.filters()) {
|
||||||
filter.visit(
|
filter.visit(
|
||||||
[&](CSS::FilterOperation::Blur const& blur) {
|
[&](CSS::FilterOperation::Blur const& blur) {
|
||||||
resolved_filter.filters.append(CSS::ResolvedFilter::Blur {
|
resolved_filter.append(Gfx::BlurFilter {
|
||||||
.radius = blur.resolved_radius(*this) });
|
.radius = blur.resolved_radius(*this) });
|
||||||
},
|
},
|
||||||
[&](CSS::FilterOperation::DropShadow const& drop_shadow) {
|
[&](CSS::FilterOperation::DropShadow const& drop_shadow) {
|
||||||
auto context = CSS::Length::ResolutionContext::for_layout_node(*this);
|
auto context = CSS::Length::ResolutionContext::for_layout_node(*this);
|
||||||
// The default value for omitted values is missing length values set to 0
|
// The default value for omitted values is missing length values set to 0
|
||||||
// and the missing used color is taken from the color property.
|
// and the missing used color is taken from the color property.
|
||||||
resolved_filter.filters.append(CSS::ResolvedFilter::DropShadow {
|
resolved_filter.append(Gfx::DropShadowFilter {
|
||||||
.offset_x = drop_shadow.offset_x.resolved(context).to_px(*this).to_double(),
|
.offset_x = static_cast<float>(drop_shadow.offset_x.resolved(context).to_px(*this).to_double()),
|
||||||
.offset_y = drop_shadow.offset_y.resolved(context).to_px(*this).to_double(),
|
.offset_y = static_cast<float>(drop_shadow.offset_y.resolved(context).to_px(*this).to_double()),
|
||||||
.radius = drop_shadow.radius.has_value() ? drop_shadow.radius->resolved(context).to_px(*this).to_double() : 0.0,
|
.radius = static_cast<float>(drop_shadow.radius.has_value() ? drop_shadow.radius->resolved(context).to_px(*this).to_double() : 0.0),
|
||||||
.color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() });
|
.color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() });
|
||||||
},
|
},
|
||||||
[&](CSS::FilterOperation::Color const& color_operation) {
|
[&](CSS::FilterOperation::Color const& color_operation) {
|
||||||
resolved_filter.filters.append(CSS::ResolvedFilter::Color {
|
resolved_filter.append(Gfx::ColorFilter {
|
||||||
.type = color_operation.operation,
|
.type = color_operation.operation,
|
||||||
.amount = color_operation.resolved_amount() });
|
.amount = color_operation.resolved_amount() });
|
||||||
},
|
},
|
||||||
[&](CSS::FilterOperation::HueRotate const& hue_rotate) {
|
[&](CSS::FilterOperation::HueRotate const& hue_rotate) {
|
||||||
resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees(*this) });
|
resolved_filter.append(Gfx::HueRotateFilter { .angle_degrees = hue_rotate.angle_degrees(*this) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return resolved_filter;
|
return resolved_filter;
|
||||||
|
|
|
@ -300,7 +300,7 @@ struct DrawLine {
|
||||||
struct ApplyBackdropFilter {
|
struct ApplyBackdropFilter {
|
||||||
Gfx::IntRect backdrop_region;
|
Gfx::IntRect backdrop_region;
|
||||||
BorderRadiiData border_radii_data;
|
BorderRadiiData border_radii_data;
|
||||||
CSS::ResolvedFilter backdrop_filter;
|
Vector<Gfx::Filter> backdrop_filter;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
|
||||||
|
|
||||||
|
@ -408,7 +408,7 @@ struct ApplyOpacity {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApplyFilters {
|
struct ApplyFilters {
|
||||||
CSS::ResolvedFilter filter;
|
Vector<Gfx::Filter> filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApplyTransform {
|
struct ApplyTransform {
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibGfx/PathSkia.h>
|
#include <LibGfx/PathSkia.h>
|
||||||
|
#include <LibGfx/SkiaUtils.h>
|
||||||
#include <LibWeb/CSS/ComputedValues.h>
|
#include <LibWeb/CSS/ComputedValues.h>
|
||||||
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
||||||
#include <LibWeb/Painting/ShadowPainting.h>
|
#include <LibWeb/Painting/ShadowPainting.h>
|
||||||
|
@ -64,169 +65,6 @@ DisplayListPlayerSkia::~DisplayListPlayerSkia()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkPoint to_skia_point(auto const& point)
|
|
||||||
{
|
|
||||||
return SkPoint::Make(point.x(), point.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkRect to_skia_rect(auto const& rect)
|
|
||||||
{
|
|
||||||
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkColor to_skia_color(Gfx::Color const& color)
|
|
||||||
{
|
|
||||||
return SkColorSetARGB(color.alpha(), color.red(), color.green(), color.blue());
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkColor4f to_skia_color4f(Gfx::Color const& color)
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
.fR = color.red() / 255.0f,
|
|
||||||
.fG = color.green() / 255.0f,
|
|
||||||
.fB = color.blue() / 255.0f,
|
|
||||||
.fA = color.alpha() / 255.0f,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static sk_sp<SkImageFilter> to_skia_image_filter(CSS::ResolvedFilter::FilterFunction const& function)
|
|
||||||
{
|
|
||||||
// See: https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions
|
|
||||||
return function.visit(
|
|
||||||
[&](CSS::ResolvedFilter::Blur const& blur_filter) {
|
|
||||||
return SkImageFilters::Blur(blur_filter.radius, blur_filter.radius, nullptr);
|
|
||||||
},
|
|
||||||
[&](CSS::ResolvedFilter::Color const& color) {
|
|
||||||
auto amount = color.amount;
|
|
||||||
|
|
||||||
// Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
|
|
||||||
sk_sp<SkColorFilter> color_filter;
|
|
||||||
switch (color.type) {
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kYes);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kNo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kNo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kYes);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kYes);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kYes);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CSS::FilterOperation::Color::Type::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, SkColorFilters::Clamp::kNo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkImageFilters::ColorFilter(color_filter, nullptr);
|
|
||||||
},
|
|
||||||
[&](CSS::ResolvedFilter::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, SkColorFilters::Clamp::kNo);
|
|
||||||
return SkImageFilters::ColorFilter(color_filter, nullptr);
|
|
||||||
},
|
|
||||||
[&](CSS::ResolvedFilter::DropShadow const& command) {
|
|
||||||
auto shadow_color = to_skia_color(command.color);
|
|
||||||
return SkImageFilters::DropShadow(command.offset_x, command.offset_y, command.radius, command.radius, shadow_color, nullptr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkPath to_skia_path(Gfx::Path const& path)
|
|
||||||
{
|
|
||||||
return static_cast<Gfx::PathImplSkia const&>(path.impl()).sk_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkPathFillType to_skia_path_fill_type(Gfx::WindingRule winding_rule)
|
|
||||||
{
|
|
||||||
switch (winding_rule) {
|
|
||||||
case Gfx::WindingRule::Nonzero:
|
|
||||||
return SkPathFillType::kWinding;
|
|
||||||
case Gfx::WindingRule::EvenOdd:
|
|
||||||
return SkPathFillType::kEvenOdd;
|
|
||||||
}
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkRRect to_skia_rrect(auto const& rect, CornerRadii const& corner_radii)
|
static SkRRect to_skia_rrect(auto const& rect, CornerRadii const& corner_radii)
|
||||||
{
|
{
|
||||||
SkRRect rrect;
|
SkRRect rrect;
|
||||||
|
@ -254,21 +92,6 @@ static SkMatrix to_skia_matrix(Gfx::AffineTransform const& affine_transform)
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkSamplingOptions to_skia_sampling_options(Gfx::ScalingMode scaling_mode)
|
|
||||||
{
|
|
||||||
switch (scaling_mode) {
|
|
||||||
case Gfx::ScalingMode::NearestNeighbor:
|
|
||||||
case Gfx::ScalingMode::SmoothPixels:
|
|
||||||
return SkSamplingOptions(SkFilterMode::kNearest);
|
|
||||||
case Gfx::ScalingMode::BilinearBlend:
|
|
||||||
return SkSamplingOptions(SkFilterMode::kLinear);
|
|
||||||
case Gfx::ScalingMode::BoxSampling:
|
|
||||||
return SkSamplingOptions(SkCubicResampler::Mitchell());
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gfx::PaintingSurface& DisplayListPlayerSkia::surface() const
|
Gfx::PaintingSurface& DisplayListPlayerSkia::surface() const
|
||||||
{
|
{
|
||||||
return *m_surface;
|
return *m_surface;
|
||||||
|
@ -916,8 +739,8 @@ void DisplayListPlayerSkia::apply_backdrop_filter(ApplyBackdropFilter const& com
|
||||||
canvas.clipRect(rect);
|
canvas.clipRect(rect);
|
||||||
ScopeGuard guard = [&] { canvas.restore(); };
|
ScopeGuard guard = [&] { canvas.restore(); };
|
||||||
|
|
||||||
for (auto const& filter_function : command.backdrop_filter.filters) {
|
for (auto const& filter : command.backdrop_filter) {
|
||||||
auto image_filter = to_skia_image_filter(filter_function);
|
auto image_filter = to_skia_image_filter(filter);
|
||||||
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
|
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
@ -1091,7 +914,7 @@ void DisplayListPlayerSkia::apply_opacity(ApplyOpacity const& command)
|
||||||
|
|
||||||
void DisplayListPlayerSkia::apply_filters(ApplyFilters const& command)
|
void DisplayListPlayerSkia::apply_filters(ApplyFilters const& command)
|
||||||
{
|
{
|
||||||
if (command.filter.is_none()) {
|
if (command.filter.is_empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sk_sp<SkImageFilter> image_filter;
|
sk_sp<SkImageFilter> image_filter;
|
||||||
|
@ -1103,7 +926,7 @@ void DisplayListPlayerSkia::apply_filters(ApplyFilters const& command)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply filters in order
|
// Apply filters in order
|
||||||
for (auto filter : command.filter.filters) {
|
for (auto filter : command.filter) {
|
||||||
append_filter(to_skia_image_filter(filter));
|
append_filter(to_skia_image_filter(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,7 +934,6 @@ void DisplayListPlayerSkia::apply_filters(ApplyFilters const& command)
|
||||||
paint.setImageFilter(image_filter);
|
paint.setImageFilter(image_filter);
|
||||||
auto& canvas = surface().canvas();
|
auto& canvas = surface().canvas();
|
||||||
canvas.saveLayer(nullptr, &paint);
|
canvas.saveLayer(nullptr, &paint);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListPlayerSkia::apply_transform(ApplyTransform const& command)
|
void DisplayListPlayerSkia::apply_transform(ApplyTransform const& command)
|
||||||
|
|
|
@ -310,7 +310,7 @@ void DisplayListRecorder::pop_stacking_context()
|
||||||
append(PopStackingContext {});
|
append(PopStackingContext {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, CSS::ResolvedFilter const& backdrop_filter)
|
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Vector<Gfx::Filter> const& backdrop_filter)
|
||||||
{
|
{
|
||||||
if (backdrop_region.is_empty())
|
if (backdrop_region.is_empty())
|
||||||
return;
|
return;
|
||||||
|
@ -407,9 +407,9 @@ void DisplayListRecorder::apply_opacity(float opacity)
|
||||||
append(ApplyOpacity { .opacity = opacity });
|
append(ApplyOpacity { .opacity = opacity });
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListRecorder::apply_filters(CSS::ResolvedFilter filter)
|
void DisplayListRecorder::apply_filters(Vector<Gfx::Filter> filter)
|
||||||
{
|
{
|
||||||
append(ApplyFilters { .filter = filter });
|
append(ApplyFilters { .filter = move(filter) });
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayListRecorder::apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix)
|
void DisplayListRecorder::apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix)
|
||||||
|
|
|
@ -133,7 +133,7 @@ public:
|
||||||
void add_rounded_rect_clip(CornerRadii corner_radii, Gfx::IntRect border_rect, CornerClip corner_clip);
|
void add_rounded_rect_clip(CornerRadii corner_radii, Gfx::IntRect border_rect, CornerClip corner_clip);
|
||||||
void add_mask(RefPtr<DisplayList> display_list, Gfx::IntRect rect);
|
void add_mask(RefPtr<DisplayList> display_list, Gfx::IntRect rect);
|
||||||
|
|
||||||
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, CSS::ResolvedFilter const& backdrop_filter);
|
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Vector<Gfx::Filter> const& backdrop_filter);
|
||||||
|
|
||||||
void paint_outer_box_shadow_params(PaintBoxShadowParams params);
|
void paint_outer_box_shadow_params(PaintBoxShadowParams params);
|
||||||
void paint_inner_box_shadow_params(PaintBoxShadowParams params);
|
void paint_inner_box_shadow_params(PaintBoxShadowParams params);
|
||||||
|
@ -148,7 +148,7 @@ public:
|
||||||
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical);
|
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical);
|
||||||
|
|
||||||
void apply_opacity(float opacity);
|
void apply_opacity(float opacity);
|
||||||
void apply_filters(CSS::ResolvedFilter filter);
|
void apply_filters(Vector<Gfx::Filter> filter);
|
||||||
void apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4);
|
void apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4);
|
||||||
void apply_mask_bitmap(Gfx::IntPoint origin, Gfx::ImmutableBitmap const&, Gfx::Bitmap::MaskKind);
|
void apply_mask_bitmap(Gfx::IntPoint origin, Gfx::ImmutableBitmap const&, Gfx::Bitmap::MaskKind);
|
||||||
|
|
||||||
|
|
|
@ -470,7 +470,7 @@ void PaintableBox::paint_border(PaintContext& context) const
|
||||||
void PaintableBox::paint_backdrop_filter(PaintContext& context) const
|
void PaintableBox::paint_backdrop_filter(PaintContext& context) const
|
||||||
{
|
{
|
||||||
auto const& backdrop_filter = computed_values().backdrop_filter();
|
auto const& backdrop_filter = computed_values().backdrop_filter();
|
||||||
if (backdrop_filter.is_none()) {
|
if (backdrop_filter.is_empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ void SVGSVGPaintable::paint_svg_box(PaintContext& context, PaintableBox const& s
|
||||||
context.display_list_recorder().apply_opacity(computed_values.opacity());
|
context.display_list_recorder().apply_opacity(computed_values.opacity());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filter.is_none()) {
|
if (!filter.is_empty()) {
|
||||||
context.display_list_recorder().apply_filters(filter);
|
context.display_list_recorder().apply_filters(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ void SVGSVGPaintable::paint_svg_box(PaintContext& context, PaintableBox const& s
|
||||||
|
|
||||||
paint_descendants(context, svg_box, phase);
|
paint_descendants(context, svg_box, phase);
|
||||||
|
|
||||||
if (!filter.is_none()) {
|
if (!filter.is_empty()) {
|
||||||
context.display_list_recorder().restore();
|
context.display_list_recorder().restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -329,7 +329,7 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
||||||
|
|
||||||
auto const& filter = computed_values.filter();
|
auto const& filter = computed_values.filter();
|
||||||
if (!filter.is_none()) {
|
if (!filter.is_empty()) {
|
||||||
context.display_list_recorder().apply_filters(paintable_box().computed_values().filter());
|
context.display_list_recorder().apply_filters(paintable_box().computed_values().filter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +354,7 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
|
|
||||||
paint_internal(context);
|
paint_internal(context);
|
||||||
|
|
||||||
if (!filter.is_none()) {
|
if (!filter.is_empty()) {
|
||||||
context.display_list_recorder().restore();
|
context.display_list_recorder().restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue