LibWeb+LibGfx: Migrate (most of) the CSS gradient painting to LibGfx

This moves the CSS gradient painting to the painter creating:

 - Painter::fill_rect_with_linear_gradient()
 - Painter::fill_rect_with_conic_gradient()
 - Painter::fill_rect_with_radial_gradient()

This has a few benefits:
 - The gradients can now easily respect the painter scale
 - The Painter::fill_pixels() escape hatch can be removed
 - We can remove the old fixed color stop gradient code
    - The old functions are  now just a shim
 - Anywhere can now easily use this gradient painting code!

This only leaves the color stop resolution in LibWeb (which is fine).
Just means in LibGfx you have to actually specify color stop positions.

(Also while here add a small optimization to avoid generating
excessively long gradient lines)
This commit is contained in:
MacDue 2023-01-10 01:07:06 +00:00 committed by Andreas Kling
parent 668204041b
commit c8c065b6b0
7 changed files with 254 additions and 241 deletions

View file

@ -25,6 +25,7 @@ set(SOURCES
Font/ScaledFont.cpp
Font/Typeface.cpp
Font/WOFF/Font.cpp
GradientPainting.cpp
GIFLoader.cpp
ICCProfile.cpp
ICOLoader.cpp

View file

@ -0,0 +1,192 @@
/*
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Math.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/Painter.h>
#if defined(AK_COMPILER_GCC)
# pragma GCC optimize("O3")
#endif
namespace Gfx {
// Note: This file implements the CSS gradients for LibWeb according to the spec.
// Please do not make ad-hoc changes that may break spec compliance!
static float color_stop_step(ColorStop const& previous_stop, ColorStop const& next_stop, float position)
{
if (position < previous_stop.position)
return 0;
if (position > next_stop.position)
return 1;
// For any given point between the two color stops,
// determine the points location as a percentage of the distance between the two color stops.
// Let this percentage be P.
auto stop_length = next_stop.position - previous_stop.position;
// FIXME: Avoids NaNs... Still not quite correct?
if (stop_length <= 0)
return 1;
auto p = (position - previous_stop.position) / stop_length;
if (!next_stop.transition_hint.has_value())
return p;
if (*next_stop.transition_hint >= 1)
return 0;
if (*next_stop.transition_hint <= 0)
return 1;
// Let C, the color weighting at that point, be equal to P^(logH(.5)).
auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint));
// The color at that point is then a linear blend between the colors of the two color stops,
// blending (1 - C) of the first stop and C of the second stop.
return c;
}
class GradientLine {
public:
GradientLine(int gradient_length, Span<ColorStop const> color_stops, Optional<float> repeat_length)
: m_repeating { repeat_length.has_value() }
, m_start_offset { round_to<int>((m_repeating ? color_stops.first().position : 0.0f) * gradient_length) }
{
// Avoid generating excessive amounts of colors when the not enough shades to fill that length.
auto necessary_length = min<int>((color_stops.size() - 1) * 255, gradient_length);
m_sample_scale = float(necessary_length) / gradient_length;
// Note: color_count will be < gradient_length for repeating gradients.
auto color_count = round_to<int>(repeat_length.value_or(1.0f) * necessary_length);
m_gradient_line_colors.resize(color_count);
// Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in:
// https://drafts.csswg.org/css-images/#coloring-gradient-line
for (int loc = 0; loc < color_count; loc++) {
auto relative_loc = float(loc + m_start_offset) / necessary_length;
Color gradient_color = color_stops[0].color.mixed_with(
color_stops[1].color,
color_stop_step(color_stops[0], color_stops[1], relative_loc));
for (size_t i = 1; i < color_stops.size() - 1; i++) {
gradient_color = gradient_color.mixed_with(
color_stops[i + 1].color,
color_stop_step(color_stops[i], color_stops[i + 1], relative_loc));
}
m_gradient_line_colors[loc] = gradient_color;
if (gradient_color.alpha() < 255)
m_requires_blending = true;
}
}
Color get_color(i64 index) const
{
return m_gradient_line_colors[clamp(index, 0, m_gradient_line_colors.size() - 1)];
}
Color sample_color(float loc) const
{
if (m_sample_scale != 1.0f)
loc *= m_sample_scale;
auto repeat_wrap_if_required = [&](i64 loc) {
if (m_repeating)
return (loc + m_start_offset) % static_cast<i64>(m_gradient_line_colors.size());
return loc;
};
auto int_loc = static_cast<i64>(floor(loc));
auto blend = loc - int_loc;
auto color = get_color(repeat_wrap_if_required(int_loc));
// Blend between the two neighbouring colors (this fixes some nasty aliasing issues at small angles)
if (blend >= 0.004f)
color = color.mixed_with(get_color(repeat_wrap_if_required(int_loc + 1)), blend);
return color;
}
void paint_into_physical_rect(Painter& painter, IntRect rect, auto location_transform)
{
auto clipped_rect = rect.intersected(painter.clip_rect() * painter.scale());
auto start_offset = clipped_rect.location() - rect.location();
for (int y = 0; y < clipped_rect.height(); y++) {
for (int x = 0; x < clipped_rect.width(); x++) {
auto pixel = sample_color(location_transform(x + start_offset.x(), y + start_offset.y()));
painter.set_physical_pixel(clipped_rect.location().translated(x, y), pixel, m_requires_blending);
}
}
}
private:
bool m_repeating;
int m_start_offset;
float m_sample_scale { 1 };
Vector<Color, 1024> m_gradient_line_colors;
bool m_requires_blending = false;
};
void Painter::fill_rect_with_linear_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, float angle, Optional<float> repeat_length)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
return;
float normalized_angle = normalized_gradient_angle_radians(angle);
float sin_angle, cos_angle;
AK::sincos(normalized_angle, sin_angle, cos_angle);
// Full length of the gradient
auto gradient_length = calculate_gradient_length(a_rect.size(), sin_angle, cos_angle);
IntPoint offset { cos_angle * (gradient_length / 2), sin_angle * (gradient_length / 2) };
auto center = a_rect.translated(-a_rect.location()).center();
auto start_point = center - offset;
// Rotate gradient line to be horizontal
auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
GradientLine gradient_line(gradient_length, color_stops, repeat_length);
gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
return (x * cos_angle - (a_rect.height() - y) * -sin_angle) - rotated_start_point_x;
});
}
void Painter::fill_rect_with_conic_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, IntPoint center, float start_angle, Optional<float> repeat_length)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
return;
// FIXME: Do we need/want sub-degree accuracy for the gradient line?
GradientLine gradient_line(360, color_stops, repeat_length);
float normalized_start_angle = (360.0f - start_angle) + 90.0f;
// Translate position/center to the center of the pixel (avoids some funky painting)
auto center_point = FloatPoint { center * scale() }.translated(0.5, 0.5);
// The flooring can make gradients that want soft edges look worse, so only floor if we have hard edges.
// Which makes sure the hard edge stay hard edges :^)
bool should_floor_angles = false;
for (size_t i = 0; i < color_stops.size() - 1; i++) {
if (color_stops[i + 1].position - color_stops[i].position <= 0.01f) {
should_floor_angles = true;
break;
}
}
gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
auto point = FloatPoint { x, y } - center_point;
// FIXME: We could probably get away with some approximation here:
auto loc = fmod((AK::atan2(point.y(), point.x()) * 180.0f / AK::Pi<float> + 360.0f + normalized_start_angle), 360.0f);
return should_floor_angles ? floor(loc) : loc;
});
}
void Painter::fill_rect_with_radial_gradient(IntRect const& rect, Span<ColorStop const> const& color_stops, IntPoint center, IntSize size, Optional<float> repeat_length)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
return;
// A conservative guesstimate on how many colors we need to generate:
auto max_dimension = max(a_rect.width(), a_rect.height());
auto max_visible_gradient = max(max_dimension / 2, min(size.width(), max_dimension));
GradientLine gradient_line(max_visible_gradient, color_stops, repeat_length);
auto center_point = FloatPoint { center * scale() }.translated(0.5, 0.5);
gradient_line.paint_into_physical_rect(*this, a_rect, [&](int x, int y) {
// FIXME: See if there's a more efficient calculation we do there :^)
auto point = FloatPoint(x, y) - center_point;
auto gradient_x = point.x() / size.width();
auto gradient_y = point.y() / size.height();
return AK::sqrt(gradient_x * gradient_x + gradient_y * gradient_y) * max_visible_gradient;
});
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Math.h>
#include <LibGfx/Color.h>
#include <LibGfx/Size.h>
namespace Gfx {
struct ColorStop {
Color color;
float position = AK::NaN<float>;
Optional<float> transition_hint = {};
};
class GradientLine;
inline float normalized_gradient_angle_radians(float gradient_angle)
{
// Adjust angle so 0 degrees is bottom
float real_angle = 90 - gradient_angle;
return real_angle * (AK::Pi<float> / 180);
}
template<typename T>
inline float calculate_gradient_length(Size<T> gradient_size, float sin_angle, float cos_angle)
{
return AK::fabs(static_cast<float>(gradient_size.height()) * sin_angle) + AK::fabs(static_cast<float>(gradient_size.width()) * cos_angle);
}
template<typename T>
inline float calculate_gradient_length(Size<T> gradient_size, float gradient_angle)
{
float angle = normalized_gradient_angle_radians(gradient_angle);
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
return calculate_gradient_length(gradient_size, sin_angle, cos_angle);
}
}

View file

@ -235,47 +235,7 @@ void Painter::fill_rect_with_gradient(Orientation orientation, IntRect const& a_
fill_rect(a_rect, gradient_start);
return;
}
auto rect = to_physical(a_rect);
auto clipped_rect = IntRect::intersection(rect, clip_rect() * scale());
if (clipped_rect.is_empty())
return;
int offset = clipped_rect.primary_offset_for_orientation(orientation) - rect.primary_offset_for_orientation(orientation);
ARGB32* dst = m_target->scanline(clipped_rect.top()) + clipped_rect.left();
size_t const dst_skip = m_target->pitch() / sizeof(ARGB32);
float increment = (1.0 / ((rect.primary_size_for_orientation(orientation))));
float alpha_increment = increment * ((float)gradient_end.alpha() - (float)gradient_start.alpha());
if (orientation == Orientation::Horizontal) {
for (int i = clipped_rect.height() - 1; i >= 0; --i) {
float c = offset * increment;
float c_alpha = gradient_start.alpha() + offset * alpha_increment;
for (int j = 0; j < clipped_rect.width(); ++j) {
auto color = gamma_accurate_blend(gradient_start, gradient_end, c);
color.set_alpha(c_alpha);
dst[j] = Color::from_argb(dst[j]).blend(color).value();
c_alpha += alpha_increment;
c += increment;
}
dst += dst_skip;
}
} else {
float c = offset * increment;
float c_alpha = gradient_start.alpha() + offset * alpha_increment;
for (int i = clipped_rect.height() - 1; i >= 0; --i) {
auto color = gamma_accurate_blend(gradient_start, gradient_end, c);
color.set_alpha(c_alpha);
for (int j = 0; j < clipped_rect.width(); ++j) {
dst[j] = Color::from_argb(dst[j]).blend(color).value();
}
c_alpha += alpha_increment;
c += increment;
dst += dst_skip;
}
}
return fill_rect_with_linear_gradient(a_rect, Array { ColorStop { gradient_start, 0 }, ColorStop { gradient_end, 1 } }, orientation == Orientation::Horizontal ? 90.0f : 0.0f);
}
void Painter::fill_rect_with_gradient(IntRect const& a_rect, Color gradient_start, Color gradient_end)

View file

@ -13,6 +13,7 @@
#include <LibGfx/Color.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
@ -20,7 +21,6 @@
#include <LibGfx/TextDirection.h>
#include <LibGfx/TextElision.h>
#include <LibGfx/TextWrapping.h>
namespace Gfx {
class Painter {
@ -50,6 +50,9 @@ public:
void fill_rect_with_checkerboard(IntRect const&, IntSize, Color color_dark, Color color_light);
void fill_rect_with_gradient(Orientation, IntRect const&, Color gradient_start, Color gradient_end);
void fill_rect_with_gradient(IntRect const&, Color gradient_start, Color gradient_end);
void fill_rect_with_linear_gradient(IntRect const&, Span<ColorStop const> const&, float angle, Optional<float> repeat_length = {});
void fill_rect_with_conic_gradient(IntRect const&, Span<ColorStop const> const&, IntPoint center, float start_angle, Optional<float> repeat_length = {});
void fill_rect_with_radial_gradient(IntRect const&, Span<ColorStop const> const&, IntPoint center, IntSize size, Optional<float> repeat_length = {});
void fill_rect_with_rounded_corners(IntRect const&, Color, int radius);
void fill_rect_with_rounded_corners(IntRect const&, Color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius);
void fill_ellipse(IntRect const&, Color);
@ -173,28 +176,9 @@ public:
int scale() const { return state().scale; }
template<typename TGetPixelCallback>
void fill_pixels(Gfx::IntRect const& region, TGetPixelCallback callback, bool blend = false)
{
// Note: This function paints physical pixels and therefore does not make sense when
// called on a scaled painter. Scaling the region painted may break the caller (this
// would be the case for gradients in LibWeb), and if you scale the pixels (to squares)
// then this is no longer filling pixels.
VERIFY(scale() == 1);
auto paint_region = region.translated(translation());
auto clipped_region = paint_region.intersected(clip_rect());
if (clipped_region.is_empty())
return;
auto start_offset = clipped_region.location() - paint_region.location();
for (int y = 0; y < clipped_region.height(); y++) {
for (int x = 0; x < clipped_region.width(); x++) {
auto pixel = callback(IntPoint(x, y).translated(start_offset));
set_physical_pixel(clipped_region.location().translated(x, y), pixel, blend);
}
}
}
protected:
friend GradientLine;
IntRect to_physical(IntRect const& r) const { return r.translated(translation()) * scale(); }
IntPoint to_physical(IntPoint p) const { return p.translated(translation()) * scale(); }
void set_physical_pixel_with_draw_op(u32& pixel, Color);

View file

@ -1,40 +1,16 @@
/*
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/Math.h>
#include <LibGfx/Gamma.h>
#include <LibGfx/Line.h>
#include <LibGfx/Gradients.h>
#include <LibWeb/CSS/StyleValue.h>
#include <LibWeb/Painting/GradientPainting.h>
namespace Web::Painting {
static float normalized_gradient_angle_radians(float gradient_angle)
{
// Adjust angle so 0 degrees is bottom
float real_angle = 90 - gradient_angle;
return real_angle * (AK::Pi<float> / 180);
}
template<typename T>
static float calculate_gradient_length(Gfx::Size<T> gradient_size, float sin_angle, float cos_angle)
{
return AK::fabs(gradient_size.height().value() * sin_angle) + AK::fabs(gradient_size.width().value() * cos_angle);
}
template<typename T>
static float calculate_gradient_length(Gfx::Size<T> gradient_size, float gradient_angle)
{
float angle = normalized_gradient_angle_radians(gradient_angle);
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
return calculate_gradient_length(gradient_size, sin_angle, cos_angle);
}
static ColorStopData resolve_color_stop_positions(auto const& color_stop_list, auto resolve_position_to_float, bool repeating)
{
VERIFY(color_stop_list.size() >= 2);
@ -50,7 +26,7 @@ static ColorStopData resolve_color_stop_positions(auto const& color_stop_list, a
resolved_color_stops.ensure_capacity(expanded_size);
for (auto& stop : color_stop_list) {
auto resolved_stop = ColorStop { .color = stop.color_stop.color };
auto resolved_stop = Gfx::ColorStop { .color = stop.color_stop.color };
for (int i = 0; i < color_stop_length(stop); i++)
resolved_color_stops.append(resolved_stop);
}
@ -133,7 +109,7 @@ static ColorStopData resolve_color_stop_positions(auto const& color_stop_list, a
LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, CSSPixelSize gradient_size, CSS::LinearGradientStyleValue const& linear_gradient)
{
auto gradient_angle = linear_gradient.angle_degrees(gradient_size);
auto gradient_length_px = calculate_gradient_length(gradient_size, gradient_angle);
auto gradient_length_px = Gfx::calculate_gradient_length(gradient_size, gradient_angle);
auto gradient_length = CSS::Length::make_px(gradient_length_px);
auto resolved_color_stops = resolve_color_stop_positions(
@ -168,158 +144,19 @@ RadialGradientData resolve_radial_gradient_data(Layout::Node const& node, CSSPix
return { resolved_color_stops };
}
static float color_stop_step(ColorStop const& previous_stop, ColorStop const& next_stop, float position)
{
if (position < previous_stop.position)
return 0;
if (position > next_stop.position)
return 1;
// For any given point between the two color stops,
// determine the points location as a percentage of the distance between the two color stops.
// Let this percentage be P.
auto stop_length = next_stop.position - previous_stop.position;
// FIXME: Avoids NaNs... Still not quite correct?
if (stop_length <= 0)
return 1;
auto p = (position - previous_stop.position) / stop_length;
if (!next_stop.transition_hint.has_value())
return p;
if (*next_stop.transition_hint >= 1)
return 0;
if (*next_stop.transition_hint <= 0)
return 1;
// Let C, the color weighting at that point, be equal to P^(logH(.5)).
auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint));
// The color at that point is then a linear blend between the colors of the two color stops,
// blending (1 - C) of the first stop and C of the second stop.
return c;
}
class GradientLine {
public:
GradientLine(int gradient_length, ColorStopData const& color_stops)
: m_repeating { color_stops.repeat_length.has_value() }
, m_start_offset { round_to<int>((m_repeating ? color_stops.list.first().position : 0.0f) * gradient_length) }
{
// Note: color_count will be < gradient_length for repeating gradients.
auto color_count = round_to<int>(color_stops.repeat_length.value_or(1.0f) * gradient_length);
m_gradient_line_colors.resize(color_count);
// Note: color.mixed_with() performs premultiplied alpha mixing when necessary as defined in:
// https://drafts.csswg.org/css-images/#coloring-gradient-line
auto& stop_list = color_stops.list;
for (int loc = 0; loc < color_count; loc++) {
auto relative_loc = float(loc + m_start_offset) / gradient_length;
Gfx::Color gradient_color = stop_list[0].color.mixed_with(
stop_list[1].color,
color_stop_step(stop_list[0], stop_list[1], relative_loc));
for (size_t i = 1; i < stop_list.size() - 1; i++) {
gradient_color = gradient_color.mixed_with(
stop_list[i + 1].color,
color_stop_step(stop_list[i], stop_list[i + 1], relative_loc));
}
m_gradient_line_colors[loc] = gradient_color;
if (gradient_color.alpha() < 255)
m_requires_blending = true;
}
}
Gfx::Color get_color(i64 index) const
{
return m_gradient_line_colors[clamp(index, 0, m_gradient_line_colors.size() - 1)];
}
Gfx::Color sample_color(float loc) const
{
auto repeat_wrap_if_required = [&](i64 loc) {
if (m_repeating)
return (loc + m_start_offset) % static_cast<i64>(m_gradient_line_colors.size());
return loc;
};
auto int_loc = static_cast<i64>(floor(loc));
auto blend = loc - int_loc;
auto color = get_color(repeat_wrap_if_required(int_loc));
// Blend between the two neighbouring colors (this fixes some nasty aliasing issues at small angles)
if (blend >= 0.004f)
color = color.mixed_with(get_color(repeat_wrap_if_required(int_loc + 1)), blend);
return color;
}
void paint_into_rect(Gfx::Painter& painter, DevicePixelRect rect, auto location_transform)
{
painter.fill_pixels(
rect.to_type<int>(), [&](auto point) {
return sample_color(location_transform(point.x(), point.y()));
},
m_requires_blending);
}
private:
bool m_repeating;
int m_start_offset;
Vector<Gfx::Color, 1024> m_gradient_line_colors;
bool m_requires_blending = false;
};
void paint_linear_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, LinearGradientData const& data)
{
float angle = normalized_gradient_angle_radians(data.gradient_angle);
float sin_angle, cos_angle;
AK::sincos(angle, sin_angle, cos_angle);
// Full length of the gradient
auto gradient_length = calculate_gradient_length(gradient_rect.size(), sin_angle, cos_angle);
DevicePixelPoint offset { cos_angle * (gradient_length / 2), sin_angle * (gradient_length / 2) };
auto center = gradient_rect.translated(-gradient_rect.location()).center();
auto start_point = center.to_type<int>() - offset.to_type<int>();
// Rotate gradient line to be horizontal
auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle;
GradientLine gradient_line(gradient_length, data.color_stops);
gradient_line.paint_into_rect(context.painter(), gradient_rect, [&](DevicePixels x, DevicePixels y) {
return (x.value() * cos_angle - (gradient_rect.height() - y).value() * -sin_angle) - rotated_start_point_x;
});
context.painter().fill_rect_with_linear_gradient(gradient_rect.to_type<int>(), data.color_stops.list, data.gradient_angle, data.color_stops.repeat_length);
}
void paint_conic_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, ConicGradientData const& data, DevicePixelPoint position)
{
// FIXME: Do we need/want sub-degree accuracy for the gradient line?
GradientLine gradient_line(360, data.color_stops);
float start_angle = (360.0f - data.start_angle) + 90.0f;
// Translate position/center to the center of the pixel (avoids some funky painting)
auto center_point = Gfx::FloatPoint { position.to_type<int>() }.translated(0.5, 0.5);
// The flooring can make gradients that want soft edges look worse, so only floor if we have hard edges.
// Which makes sure the hard edge stay hard edges :^)
bool should_floor_angles = false;
auto& color_stops = data.color_stops.list;
for (size_t i = 0; i < color_stops.size() - 1; i++) {
if (color_stops[i + 1].position - color_stops[i].position <= 0.01f) {
should_floor_angles = true;
break;
}
}
gradient_line.paint_into_rect(context.painter(), gradient_rect, [&](DevicePixels x, DevicePixels y) {
auto point = Gfx::FloatPoint { x.value(), y.value() } - center_point;
// FIXME: We could probably get away with some approximation here:
auto loc = fmod((AK::atan2(point.y(), point.x()) * 180.0f / AK::Pi<float> + 360.0f + start_angle), 360.0f);
return should_floor_angles ? floor(loc) : loc;
});
context.painter().fill_rect_with_conic_gradient(gradient_rect.to_type<int>(), data.color_stops.list, position.to_type<int>(), data.start_angle, data.color_stops.repeat_length);
}
void paint_radial_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, RadialGradientData const& data, DevicePixelPoint center, DevicePixelSize size)
{
// A conservative guesstimate on how many colors we need to generate:
auto max_dimension = max(gradient_rect.width(), gradient_rect.height());
auto max_visible_gradient = max(max_dimension / 2, min(size.width(), max_dimension.value())).value();
GradientLine gradient_line(max_visible_gradient, data.color_stops);
auto center_point = Gfx::FloatPoint { center.to_type<int>() }.translated(0.5, 0.5);
gradient_line.paint_into_rect(context.painter(), gradient_rect, [&](DevicePixels x, DevicePixels y) {
// FIXME: See if there's a more efficient calculation we do there :^)
auto point = Gfx::FloatPoint(x.value(), y.value()) - center_point;
auto gradient_x = point.x() / size.width().value();
auto gradient_y = point.y() / size.height().value();
return AK::sqrt(gradient_x * gradient_x + gradient_y * gradient_y) * max_visible_gradient;
});
context.painter().fill_rect_with_radial_gradient(gradient_rect.to_type<int>(), data.color_stops.list, center.to_type<int>(), size.to_type<int>(), data.color_stops.repeat_length);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -14,13 +14,7 @@
namespace Web::Painting {
struct ColorStop {
Gfx::Color color;
float position = AK::NaN<float>;
Optional<float> transition_hint = {};
};
using ColorStopList = Vector<ColorStop, 4>;
using ColorStopList = Vector<Gfx::ColorStop, 4>;
struct ColorStopData {
ColorStopList list;