1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 20:47:45 +00:00

LibGfx: Update Painter's bezier curve drawing algorithm.

The new algorithm is an iterative one with an arbitrary threshold for splitting
curves. It splits curves evenly. This should theoretically be less accurate
than the existing recursive approach, but seems to give subjectively better
results in practice.
This commit is contained in:
Srimanta Barua 2020-06-06 15:31:34 +05:30 committed by Andreas Kling
parent 0e9fb803c8
commit 186499cc25
3 changed files with 69 additions and 164 deletions

View file

@ -1282,45 +1282,22 @@ void Painter::draw_line(const IntPoint& p1, const IntPoint& p2, Color color, int
} }
} }
static void split_quadratic_bezier_curve(const FloatPoint& original_control, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
{
auto po1_midpoint = original_control + p1;
po1_midpoint /= 2;
auto po2_midpoint = original_control + p2;
po2_midpoint /= 2;
auto new_segment = po1_midpoint + po2_midpoint;
new_segment /= 2;
Painter::for_each_line_segment_on_bezier_curve(po1_midpoint, p1, new_segment, callback);
Painter::for_each_line_segment_on_bezier_curve(po2_midpoint, new_segment, p2, callback);
}
static bool can_approximate_bezier_curve(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& control)
{
constexpr static int tolerance = 15;
auto p1x = 3 * control.x() - 2 * p1.x() - p2.x();
auto p1y = 3 * control.y() - 2 * p1.y() - p2.y();
auto p2x = 3 * control.x() - 2 * p2.x() - p1.x();
auto p2y = 3 * control.y() - 2 * p2.y() - p1.y();
p1x = p1x * p1x;
p1y = p1y * p1y;
p2x = p2x * p2x;
p2y = p2y * p2y;
return max(p1x, p2x) + max(p1y, p2y) <= tolerance;
}
void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback) void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
{ {
if (can_approximate_bezier_curve(p1, p2, control_point)) { float arbitrary = 15.0;
callback(p1, p2); auto mid_point = FloatPoint::interpolate(p1, p2, 0.5);
} else { float squared_distance = FloatPoint::squared_distance(control_point, mid_point);
split_quadratic_bezier_curve(control_point, p1, p2, callback); size_t num_sections = 1 + floorf(sqrtf(arbitrary * squared_distance));
float delta = 1.0 / num_sections;
float t = 0.0;
FloatPoint p_cur = p1;
for (size_t i = 0; i < num_sections - 1; i++) {
t += delta;
FloatPoint pn = FloatPoint::interpolate(FloatPoint::interpolate(p1, control_point, t), FloatPoint::interpolate(control_point, p2, t), t);
callback(p_cur, pn);
p_cur = pn;
} }
callback(p_cur, p2);
} }
void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&& callback) void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)

View file

@ -30,21 +30,13 @@
#include <AK/Utf32View.h> #include <AK/Utf32View.h>
#include <bits/stdint.h> #include <bits/stdint.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibGfx/FloatPoint.h>
#include <LibGfx/Path.h>
#include <math.h> #include <math.h>
namespace Gfx { namespace Gfx {
namespace TTF { namespace TTF {
static float min(float x, float y)
{
return x < y ? x : y;
}
static float max(float x, float y)
{
return x > y ? x : y;
}
static u16 be_u16(const u8* ptr) static u16 be_u16(const u8* ptr)
{ {
return (((u16) ptr[0]) << 8) | ((u16) ptr[1]); return (((u16) ptr[0]) << 8) | ((u16) ptr[1]);
@ -138,34 +130,11 @@ u16 Font::Maxp::num_glyphs() const
return be_u16(m_slice.offset_pointer(4)); return be_u16(m_slice.offset_pointer(4));
} }
struct Point {
float x;
float y;
Point(float x, float y)
: x(x)
, y(y)
{
}
static Point interpolate(const Point& a, const Point &b, float t)
{
return Point(a.x * (1.0f - t) + b.x * t, a.y * (1.0f - t) + b.y * t);
}
static float squared_distance(const Point& a, const Point& b)
{
float x_diff = a.x - b.x;
float y_diff = a.y - b.y;
return x_diff * x_diff + y_diff * y_diff;
}
};
class PointIterator { class PointIterator {
public: public:
struct Item { struct Item {
bool on_curve; bool on_curve;
Point point; FloatPoint point;
}; };
PointIterator(const ByteBuffer& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, float x_translate, float y_translate, float x_scale, float y_scale) PointIterator(const ByteBuffer& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, float x_translate, float y_translate, float x_scale, float y_scale)
@ -196,28 +165,28 @@ public:
} }
switch (m_flag & 0x12) { switch (m_flag & 0x12) {
case 0x00: case 0x00:
m_last_point.x += be_i16(m_slice.offset_pointer(m_x_offset)); m_last_point.set_x(m_last_point.x() + be_i16(m_slice.offset_pointer(m_x_offset)));
m_x_offset += 2; m_x_offset += 2;
break; break;
case 0x02: case 0x02:
m_last_point.x -= m_slice[m_x_offset++]; m_last_point.set_x(m_last_point.x() - m_slice[m_x_offset++]);
break; break;
case 0x12: case 0x12:
m_last_point.x += m_slice[m_x_offset++]; m_last_point.set_x(m_last_point.x() + m_slice[m_x_offset++]);
break; break;
default: default:
break; break;
} }
switch (m_flag & 0x24) { switch (m_flag & 0x24) {
case 0x00: case 0x00:
m_last_point.y += be_i16(m_slice.offset_pointer(m_y_offset)); m_last_point.set_y(m_last_point.y() + be_i16(m_slice.offset_pointer(m_y_offset)));
m_y_offset += 2; m_y_offset += 2;
break; break;
case 0x04: case 0x04:
m_last_point.y -= m_slice[m_y_offset++]; m_last_point.set_y(m_last_point.y() - m_slice[m_y_offset++]);
break; break;
case 0x24: case 0x24:
m_last_point.y += m_slice[m_y_offset++]; m_last_point.set_y(m_last_point.y() + m_slice[m_y_offset++]);
break; break;
default: default:
break; break;
@ -227,10 +196,9 @@ public:
.on_curve = (m_flag & 0x01) != 0, .on_curve = (m_flag & 0x01) != 0,
.point = m_last_point, .point = m_last_point,
}; };
ret.point.x += m_x_translate; ret.point.move_by(m_x_translate, m_y_translate);
ret.point.y += m_y_translate; ret.point.set_x(ret.point.x() * m_x_scale);
ret.point.x *= m_x_scale; ret.point.set_y(ret.point.y() * m_y_scale);
ret.point.y *= m_y_scale;
return ret; return ret;
} }
@ -238,7 +206,7 @@ private:
ByteBuffer m_slice; ByteBuffer m_slice;
u16 m_points_remaining; u16 m_points_remaining;
u8 m_flag { 0 }; u8 m_flag { 0 };
Point m_last_point = { 0.0f, 0.0f }; FloatPoint m_last_point = { 0.0f, 0.0f };
u32 m_flags_remaining = { 0 }; u32 m_flags_remaining = { 0 };
u32 m_flags_offset; u32 m_flags_offset;
u32 m_x_offset; u32 m_x_offset;
@ -260,40 +228,19 @@ public:
} }
} }
void move_to(Point& point) RefPtr<Bitmap> draw_path(Path& path)
{ {
m_last_point = point; for (auto& line : path.split_lines()) {
draw_line(line.from, line.to);
}
return accumulate();
} }
void line_to(Point& point) private:
RefPtr<Bitmap> accumulate()
{ {
draw_line(m_last_point, point); auto bitmap = Bitmap::create(BitmapFormat::RGBA32, m_size);
m_last_point = point; Color base_color = Color::from_rgb(0xffffff);
}
// FIXME: Use a better algorithm to split/approximate bezier curve.
void quadratic_bezier_to(Point& control, Point& end_point)
{
float arbitrary = 15.0;
auto mid_point = Point::interpolate(m_last_point, end_point, 0.5);
float squared_distance = Point::squared_distance(mid_point, control);
u32 num_sections = 1 + floor(sqrtf(arbitrary * squared_distance));
float delta = 1.0 / num_sections;
float t = 0.0;
Point p_cur = m_last_point;
for (u32 i = 0; i < num_sections - 1; i++) {
t += delta;
Point pn = Point::interpolate(Point::interpolate(m_last_point, control, t), Point::interpolate(control, end_point, t), t);
draw_line(p_cur, pn);
p_cur = pn;
}
draw_line(p_cur, end_point);
m_last_point = end_point;
}
AABitmap accumulate()
{
AABitmap bitmap(m_size);
for (int y = 0; y < m_size.height(); y++) { for (int y = 0; y < m_size.height(); y++) {
float accumulator = 0.0; float accumulator = 0.0;
for (int x = 0; x < m_size.width(); x++) { for (int x = 0; x < m_size.width(); x++) {
@ -305,39 +252,39 @@ public:
if (value > 1.0) { if (value > 1.0) {
value = 1.0; value = 1.0;
} }
bitmap.set_byte_at(x, y, value * 255.0); u8 alpha = value * 255.0;
bitmap->set_pixel(x, y, base_color.with_alpha(alpha));
} }
} }
return bitmap; return bitmap;
} }
private: void draw_line(FloatPoint p0, FloatPoint p1)
void draw_line(Point p0, Point p1)
{ {
ASSERT(p0.x >= 0.0 && p0.y >= 0.0 && p0.x <= m_size.width() && p0.y <= m_size.height()); ASSERT(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height());
ASSERT(p1.x >= 0.0 && p1.y >= 0.0 && p1.x <= m_size.width() && p1.y <= m_size.height()); ASSERT(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height());
// If we're on the same Y, there's no need to draw // If we're on the same Y, there's no need to draw
if (p0.y == p1.y) { if (p0.y() == p1.y()) {
return; return;
} }
float direction = -1.0; float direction = -1.0;
if (p1.y < p0.y) { if (p1.y() < p0.y()) {
direction = 1.0; direction = 1.0;
auto tmp = p0; auto tmp = p0;
p0 = p1; p0 = p1;
p1 = tmp; p1 = tmp;
} }
float dxdy = (p1.x - p0.x) / (p1.y - p0.y); float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y());
u32 y0 = floor(p0.y); u32 y0 = floor(p0.y());
u32 y1 = ceil(p1.y); u32 y1 = ceil(p1.y());
float x_cur = p0.x; float x_cur = p0.x();
for (u32 y = y0; y < y1; y++) { for (u32 y = y0; y < y1; y++) {
u32 line_offset = m_size.width() * y; u32 line_offset = m_size.width() * y;
float dy = min(y + 1, p1.y) - max(y, p0.y); float dy = min(y + 1.0f, p1.y()) - max((float) y, p0.y());
float directed_dy = dy * direction; float directed_dy = dy * direction;
float x_next = x_cur + dy * dxdy; float x_next = x_cur + dy * dxdy;
if (x_next < 0.0) { if (x_next < 0.0) {
@ -378,7 +325,6 @@ private:
} }
Size m_size; Size m_size;
Point m_last_point { 0.0, 0.0 };
OwnPtr<float> m_data; OwnPtr<float> m_data;
}; };
@ -554,7 +500,7 @@ Font::Glyf::Glyph Font::Glyf::Glyph::composite(const ByteBuffer& slice)
return ret; return ret;
} }
AABitmap Font::Glyf::Glyph::raster(float x_scale, float y_scale) const RefPtr<Bitmap> Font::Glyf::Glyph::raster(float x_scale, float y_scale) const
{ {
switch (m_type) { switch (m_type) {
case Type::Simple: case Type::Simple:
@ -597,7 +543,7 @@ static void get_ttglyph_offsets(const ByteBuffer& slice, u32 num_points, u32 fla
*y_offset = *x_offset + x_size; *y_offset = *x_offset + x_size;
} }
AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const RefPtr<Bitmap> Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
{ {
auto simple = m_meta.simple; auto simple = m_meta.simple;
// Get offets for flags, x, and y. // Get offets for flags, x, and y.
@ -611,14 +557,14 @@ AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
// Prepare to render glyph. // Prepare to render glyph.
u32 width = (u32) (ceil((simple.xmax - simple.xmin) * x_scale)) + 1; u32 width = (u32) (ceil((simple.xmax - simple.xmin) * x_scale)) + 1;
u32 height = (u32) (ceil((simple.ymax - simple.ymin) * y_scale)) + 1; u32 height = (u32) (ceil((simple.ymax - simple.ymin) * y_scale)) + 1;
Rasterizer rasterizer(Size(width, height)); Path path;
PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, -simple.xmin, -simple.ymax, x_scale, -y_scale); PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, -simple.xmin, -simple.ymax, x_scale, -y_scale);
int last_contour_end = -1; int last_contour_end = -1;
u32 contour_index = 0; u32 contour_index = 0;
u32 contour_size = 0; u32 contour_size = 0;
Optional<Point> contour_start = {}; Optional<FloatPoint> contour_start = {};
Optional<Point> last_offcurve_point = {}; Optional<FloatPoint> last_offcurve_point = {};
// Render glyph // Render glyph
while (true) { while (true) {
@ -634,7 +580,7 @@ AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
contour_start = opt_item.value().point; contour_start = opt_item.value().point;
rasterizer.move_to(contour_start.value()); path.move_to(contour_start.value());
contour_size--; contour_size--;
} else if (!last_offcurve_point.has_value()) { } else if (!last_offcurve_point.has_value()) {
if (contour_size > 0) { if (contour_size > 0) {
@ -646,7 +592,7 @@ AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
auto item = opt_item.value(); auto item = opt_item.value();
contour_size--; contour_size--;
if (item.on_curve) { if (item.on_curve) {
rasterizer.line_to(item.point); path.line_to(item.point);
} else if (contour_size > 0) { } else if (contour_size > 0) {
auto opt_next_item = point_iterator.next(); auto opt_next_item = point_iterator.next();
// FIXME: Should we draw a quadratic bezier to the first point here? // FIXME: Should we draw a quadratic bezier to the first point here?
@ -656,18 +602,18 @@ AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
auto next_item = opt_next_item.value(); auto next_item = opt_next_item.value();
contour_size--; contour_size--;
if (next_item.on_curve) { if (next_item.on_curve) {
rasterizer.quadratic_bezier_to(item.point, next_item.point); path.quadratic_bezier_curve_to(item.point, next_item.point);
} else { } else {
auto mid_point = Point::interpolate(item.point, next_item.point, 0.5); auto mid_point = FloatPoint::interpolate(item.point, next_item.point, 0.5);
rasterizer.quadratic_bezier_to(item.point, mid_point); path.quadratic_bezier_curve_to(item.point, mid_point);
last_offcurve_point = next_item.point; last_offcurve_point = next_item.point;
} }
} else { } else {
rasterizer.quadratic_bezier_to(item.point, contour_start.value()); path.quadratic_bezier_curve_to(item.point, contour_start.value());
contour_start = {}; contour_start = {};
} }
} else { } else {
rasterizer.line_to(contour_start.value()); path.line_to(contour_start.value());
contour_start = {}; contour_start = {};
} }
} else { } else {
@ -682,20 +628,20 @@ AABitmap Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
auto item = opt_item.value(); auto item = opt_item.value();
contour_size--; contour_size--;
if (item.on_curve) { if (item.on_curve) {
rasterizer.quadratic_bezier_to(point0, item.point); path.quadratic_bezier_curve_to(point0, item.point);
} else { } else {
auto mid_point = Point::interpolate(point0, item.point, 0.5); auto mid_point = FloatPoint::interpolate(point0, item.point, 0.5);
rasterizer.quadratic_bezier_to(point0, mid_point); path.quadratic_bezier_curve_to(point0, mid_point);
last_offcurve_point = item.point; last_offcurve_point = item.point;
} }
} else { } else {
rasterizer.quadratic_bezier_to(point0, contour_start.value()); path.quadratic_bezier_curve_to(point0, contour_start.value());
contour_start = {}; contour_start = {};
} }
} }
} }
return rasterizer.accumulate(); return Rasterizer(Size(width, height)).draw_path(path);
} }
Font::Glyf::Glyph Font::Glyf::glyph(u32 offset) const Font::Glyf::Glyph Font::Glyf::glyph(u32 offset) const
@ -866,7 +812,7 @@ ScaledGlyphMetrics Font::glyph_metrics(u32 glyph_id, float x_scale, float y_scal
} }
// FIXME: "loca" and "glyf" are not available for CFF fonts. // FIXME: "loca" and "glyf" are not available for CFF fonts.
AABitmap Font::raster_glyph(u32 glyph_id, float x_scale, float y_scale) const RefPtr<Bitmap> Font::raster_glyph(u32 glyph_id, float x_scale, float y_scale) const
{ {
if (glyph_id >= m_maxp.num_glyphs()) { if (glyph_id >= m_maxp.num_glyphs()) {
glyph_id = 0; glyph_id = 0;

View file

@ -31,30 +31,12 @@
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h> #include <LibGfx/Size.h>
namespace Gfx { namespace Gfx {
namespace TTF { namespace TTF {
class AABitmap {
public:
AABitmap(Size size)
: m_size(size)
{
m_data = OwnPtr(new u8[size.width() * size.height()]);
}
Size size() const { return m_size; }
u8 byte_at(int x, int y) const { return m_data[y * m_size.width() + x]; }
void set_byte_at(int x, int y, u8 value)
{
m_data[y * m_size.width() + x] = value;
}
private:
Size m_size;
OwnPtr<u8> m_data;
};
class ScaledFont; class ScaledFont;
struct ScaledFontMetrics { struct ScaledFontMetrics {
@ -86,7 +68,7 @@ private:
Font(ByteBuffer&& buffer, u32 offset); Font(ByteBuffer&& buffer, u32 offset);
ScaledFontMetrics metrics(float x_scale, float y_scale) const; ScaledFontMetrics metrics(float x_scale, float y_scale) const;
ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const; ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const;
AABitmap raster_glyph(u32 glyph_id, float x_scale, float y_scale) const; RefPtr<Bitmap> raster_glyph(u32 glyph_id, float x_scale, float y_scale) const;
u32 glyph_count() const { return m_maxp.num_glyphs(); } u32 glyph_count() const { return m_maxp.num_glyphs(); }
enum class IndexToLocFormat { enum class IndexToLocFormat {
@ -262,7 +244,7 @@ private:
public: public:
static Glyph simple(const ByteBuffer& slice, u16 num_contours, i16 xmin, i16 ymin, i16 xmax, i16 ymax); static Glyph simple(const ByteBuffer& slice, u16 num_contours, i16 xmin, i16 ymin, i16 xmax, i16 ymax);
static Glyph composite(const ByteBuffer& slice); // FIXME: This is currently just a dummy. Need to add support for composite glyphs. static Glyph composite(const ByteBuffer& slice); // FIXME: This is currently just a dummy. Need to add support for composite glyphs.
AABitmap raster(float x_scale, float y_scale) const; RefPtr<Bitmap> raster(float x_scale, float y_scale) const;
int ascender() const int ascender() const
{ {
if (m_type == Type::Simple) { if (m_type == Type::Simple) {
@ -302,7 +284,7 @@ private:
, m_slice(move(slice)) , m_slice(move(slice))
{ {
} }
AABitmap raster_simple(float x_scale, float y_scale) const; RefPtr<Bitmap> raster_simple(float x_scale, float y_scale) const;
Type m_type; Type m_type;
ByteBuffer m_slice; ByteBuffer m_slice;
@ -347,7 +329,7 @@ public:
u32 glyph_id_for_codepoint(u32 codepoint) const { return m_font->m_cmap.glyph_id_for_codepoint(codepoint); } u32 glyph_id_for_codepoint(u32 codepoint) const { return m_font->m_cmap.glyph_id_for_codepoint(codepoint); }
ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); } ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); }
ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale); } ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale); }
AABitmap raster_glyph(u32 glyph_id) const { return m_font->raster_glyph(glyph_id, m_x_scale, m_y_scale); } RefPtr<Bitmap> raster_glyph(u32 glyph_id) const { return m_font->raster_glyph(glyph_id, m_x_scale, m_y_scale); }
u32 glyph_count() const { return m_font->glyph_count(); } u32 glyph_count() const { return m_font->glyph_count(); }
int width(const StringView&) const; int width(const StringView&) const;
int width(const Utf8View&) const; int width(const Utf8View&) const;