LibGfx: Update fill_path() to support taking a PaintStyle

This means fill_path() now paints the scanlines its self rather than
calling draw_line() which easily allows each pixel along the scanline
to have a different color.
This commit is contained in:
MacDue 2023-01-15 22:15:24 +00:00 committed by Andreas Kling
parent b31d768e95
commit 223cedc896
Notes: sideshowbarker 2024-07-17 08:55:54 +09:00
5 changed files with 64 additions and 29 deletions

View file

@ -50,11 +50,6 @@ void AntiAliasingPainter::draw_anti_aliased_line(FloatPoint actual_from, FloatPo
// Axis-aligned lines:
if (mapped_from.y() == mapped_to.y()) {
auto start_point = (mapped_from.x() < mapped_to.x() ? mapped_from : mapped_to).translated(0, -int_thickness / 2);
if constexpr (path_hacks == FixmeEnableHacksForBetterPathPainting::Yes) {
// FIXME: SVG fill_path() hack:
// SVG asks for 1px scanlines at floating point y values, if they're not snapped to a pixel they look faint.
start_point.set_y(floorf(start_point.y()));
}
return fill_rect(Gfx::FloatRect(start_point, { length, thickness }), color);
}
if (mapped_from.x() == mapped_to.x()) {
@ -213,9 +208,20 @@ void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to
draw_anti_aliased_line<FixmeEnableHacksForBetterPathPainting::No>(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode);
}
void AntiAliasingPainter::fill_path(Path& path, Color color, Painter::WindingRule rule)
// FIXME: In the fill_paths() m_transform.translation() throws away any other transforms
// this currently does not matter -- but may in future.
void AntiAliasingPainter::fill_path(Path const& path, Color color, Painter::WindingRule rule)
{
Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(*this, path, color, rule);
Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(
m_underlying_painter, path, [=](IntPoint) { return color; }, rule, m_transform.translation());
}
void AntiAliasingPainter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)
{
paint_style.paint(enclosing_int_rect(path.bounding_box()), [&](PaintStyle::SamplerFunction sampler) {
Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(m_underlying_painter, path, move(sampler), rule, m_transform.translation());
});
}
void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)

View file

@ -29,12 +29,10 @@ public:
void draw_line(IntPoint, IntPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid, Color alternate_color = Color::Transparent, LineLengthMode line_length_mode = LineLengthMode::PointToPoint);
void draw_line(FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid, Color alternate_color = Color::Transparent, LineLengthMode line_length_mode = LineLengthMode::PointToPoint);
void draw_line_for_path(FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid, Color alternate_color = Color::Transparent, LineLengthMode line_length_mode = LineLengthMode::PointToPoint);
void draw_line_for_fill_path(FloatPoint from, FloatPoint to, Color color, float thickness = 1)
{
draw_line_for_path(from, to, color, thickness, Painter::LineStyle::Solid, Color {}, LineLengthMode::Distance);
}
void fill_path(Path&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
void fill_path(Path const&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
void fill_path(Path const&, PaintStyle const& paint_style, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
void stroke_path(Path const&, Color, float thickness);
void draw_quadratic_bezier_curve(FloatPoint control_point, FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
void draw_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint, FloatPoint, Color, float thickness = 1, Painter::LineStyle style = Painter::LineStyle::Solid);
@ -75,6 +73,8 @@ public:
void fill_rect_with_rounded_corners(IntRect const&, Color, CornerRadius top_left, CornerRadius top_right, CornerRadius bottom_right, CornerRadius bottom_left, BlendMode blend_mode = BlendMode::Normal);
Gfx::Painter& underlying_painter() { return m_underlying_painter; }
private:
struct Range {
int min;

View file

@ -41,16 +41,40 @@ enum class FillPathMode {
AllowFloatingPoints,
};
template<FillPathMode fill_path_mode, typename Painter>
void fill_path(Painter& painter, Path const& path, Color color, Gfx::Painter::WindingRule winding_rule)
template<FillPathMode fill_path_mode, typename ColorFunction>
void fill_path(Painter& painter, Path const& path, ColorFunction color_function, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
{
using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
using PointType = Point<GridCoordinateType>;
auto draw_line = [&](auto... args) {
if constexpr (requires { painter.draw_line_for_fill_path(args...); })
painter.draw_line_for_fill_path(args...);
else
painter.draw_line(args...);
auto draw_scanline = [&](int y, float x1, float x2) {
const auto draw_offset = offset.value_or({ 0, 0 });
const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type<int>();
// FIMXE: Offset is added here to handle floating point translations in the AA painter,
// really this should be done there but this function is a bit too specialised.
y = floorf(y + draw_offset.y());
x1 += draw_offset.x();
x2 += draw_offset.x();
if (x1 > x2)
swap(x1, x2);
auto set_pixel = [&](int x, int y, Color color) {
painter.set_pixel(x, y, color, true);
};
if constexpr (fill_path_mode == FillPathMode::AllowFloatingPoints) {
int int_x1 = ceilf(x1);
int int_x2 = floorf(x2);
float left_subpixel = int_x1 - x1;
float right_subpixel = x2 - int_x2;
auto left_color = color_function(IntPoint(int_x1 - 1, y) - draw_origin);
auto right_color = color_function(IntPoint(int_x2, y) - draw_origin);
set_pixel(int_x1 - 1, y, left_color.with_alpha(left_color.alpha() * left_subpixel));
set_pixel(int_x2, y, right_color.with_alpha(right_color.alpha() * right_subpixel));
for (int x = int_x1; x < int_x2; x++)
set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
} else {
for (int x = x1; x < int(x2); x++)
set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
}
};
auto const& segments = path.split_lines();
@ -136,7 +160,7 @@ void fill_path(Painter& painter, Path const& path, Color color, Gfx::Painter::Wi
// inside the shape
dbgln_if(FILL_PATH_DEBUG, "y={}: {} at {}: {} -- {}", scanline, winding_number, i, from, to);
draw_line(from, to, color, 1);
draw_scanline(floorf(scanline), from.x(), to.x());
}
auto is_passing_through_maxima = scanline == previous.maximum_y
@ -159,7 +183,7 @@ void fill_path(Painter& painter, Path const& path, Color color, Gfx::Painter::Wi
active_list.last().x -= active_list.last().inverse_slope;
} else {
auto point = PointType(active_list[0].x, scanline);
draw_line(point, point, color);
draw_scanline(floorf(scanline), point.x(), point.x());
// update the x coord
active_list.first().x -= active_list.first().inverse_slope;
@ -185,12 +209,5 @@ void fill_path(Painter& painter, Path const& path, Color color, Gfx::Painter::Wi
active_list.append(segment);
}
}
if constexpr (FILL_PATH_DEBUG) {
size_t i { 0 };
for (auto& segment : segments) {
draw_line(PointType(segment.from), PointType(segment.to), Color::from_hsv(i++ * 360.0 / segments.size(), 1.0, 1.0), 1);
}
}
}
}

View file

@ -2373,7 +2373,16 @@ void Painter::stroke_path(Path const& path, Color color, int thickness)
void Painter::fill_path(Path const& path, Color color, WindingRule winding_rule)
{
VERIFY(scale() == 1); // FIXME: Add scaling support.
Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(*this, path, color, winding_rule);
Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(
*this, path, [=](IntPoint) { return color; }, winding_rule);
}
void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)
{
VERIFY(scale() == 1); // FIXME: Add scaling support.
paint_style.paint(enclosing_int_rect(path.bounding_box()), [&](PaintStyle::SamplerFunction sampler) {
Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(*this, path, move(sampler), rule);
});
}
void Painter::blit_disabled(IntPoint location, Gfx::Bitmap const& bitmap, IntRect const& rect, Palette const& palette)

View file

@ -14,6 +14,7 @@
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
@ -21,6 +22,7 @@
#include <LibGfx/TextDirection.h>
#include <LibGfx/TextElision.h>
#include <LibGfx/TextWrapping.h>
namespace Gfx {
class Painter {
@ -138,6 +140,7 @@ public:
EvenOdd,
};
void fill_path(Path const&, Color, WindingRule rule = WindingRule::Nonzero);
void fill_path(Path const&, PaintStyle const& paint_style, WindingRule rule = WindingRule::Nonzero);
Font const& font() const
{