LibGfx+LibWeb: Delete unused Line class and Rect methods

This commit is contained in:
Aliaksandr Kalenik 2025-11-04 21:51:40 +01:00 committed by Alexander Kalenik
parent aa5b3ff95a
commit ed921b66f5
Notes: github-actions[bot] 2025-11-04 22:17:01 +00:00
8 changed files with 4 additions and 597 deletions

View file

@ -19,9 +19,6 @@ class GlyphRun;
class ImageDecoder;
struct FontPixelMetrics;
template<typename T>
class Line;
class Painter;
class PaintingSurface;
class Palette;
@ -46,9 +43,6 @@ class Rect;
template<typename T>
class Quad;
using IntLine = Line<int>;
using FloatLine = Line<float>;
using IntRect = Rect<int>;
using FloatRect = Rect<float>;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Vector.h>
#include <LibGfx/Bitmap.h>

View file

@ -11,7 +11,6 @@
#include <AK/MemoryStream.h>
#include <AK/Variant.h>
#include <LibGfx/ImageFormats/TinyVGLoader.h>
#include <LibGfx/Line.h>
#include <LibGfx/Painter.h>
#include <LibGfx/Path.h>
#include <LibGfx/Point.h>
@ -249,11 +248,6 @@ public:
return FloatRect { TRY(read_unit()), TRY(read_unit()), TRY(read_unit()), TRY(read_unit()) };
}
ErrorOr<FloatLine> read_line()
{
return FloatLine { TRY(read_point()), TRY(read_point()) };
}
ErrorOr<Path> read_path(u32 segment_count)
{
Path path;
@ -414,9 +408,8 @@ ErrorOr<NonnullRefPtr<TinyVGDecodedImageData>> TinyVGDecodedImageData::decode(St
auto header = TRY(reader.read_draw_command_header(style_type));
Path path;
for (u32 i = 0; i < header.count; i++) {
auto line = TRY(reader.read_line());
path.move_to(line.a());
path.line_to(line.b());
path.move_to(TRY(reader.read_point()));
path.line_to(TRY(reader.read_point()));
}
TRY(draw_commands.try_append(DrawCommand { move(path), {}, move(header.line_style), header.line_width }));
break;

View file

@ -1,199 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Format.h>
#include <AK/Optional.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Point.h>
namespace Gfx {
template<typename T>
class Line {
public:
Line() = default;
Line(Point<T> a, Point<T> b)
: m_a(a)
, m_b(b)
{
}
template<typename U>
Line(U a, U b)
: m_a(a)
, m_b(b)
{
}
template<typename U>
explicit Line(Line<U> const& other)
: m_a(other.a())
, m_b(other.b())
{
}
bool intersects(Line const& other) const
{
return intersected(other).has_value();
}
Optional<Point<T>> intersected(Line const& other) const
{
auto cross_product = [](Point<T> const& p1, Point<T> const& p2) {
return p1.x() * p2.y() - p1.y() * p2.x();
};
auto r = m_b - m_a;
auto s = other.m_b - other.m_a;
auto delta_a = other.m_a - m_a;
auto num = cross_product(delta_a, r);
auto denom = cross_product(r, s);
if (denom == 0) {
if (num == 0) {
// Lines are collinear, check if line ends are touching
if (m_a == other.m_a || m_a == other.m_b)
return m_a;
if (m_b == other.m_a || m_b == other.m_b)
return m_b;
// Check if they're overlapping
if (!(m_b.x() - m_a.x() < 0 && m_b.x() - other.m_a.x() < 0 && other.m_b.x() - m_a.x() && other.m_b.x() - other.m_a.x())) {
// Overlapping
// TODO find center point?
}
if (!(m_b.y() - m_a.y() < 0 && m_b.y() - other.m_a.y() < 0 && other.m_b.y() - m_a.y() && other.m_b.y() - other.m_a.y())) {
// Overlapping
// TODO find center point?
}
return {};
} else {
// Lines are parallel and not intersecting
return {};
}
}
auto u = static_cast<float>(num) / static_cast<float>(denom);
if (u < 0.0f || u > 1.0f) {
// Lines are not parallel and don't intersect
return {};
}
auto t = static_cast<float>(cross_product(delta_a, s)) / static_cast<float>(denom);
if (t < 0.0f || t > 1.0f) {
// Lines are not parallel and don't intersect
return {};
}
// TODO: round if we're dealing with int
return Point<T> { m_a.x() + static_cast<T>(t * r.x()), m_a.y() + static_cast<T>(t * r.y()) };
}
float length() const
{
return m_a.distance_from(m_b);
}
Point<T> closest_to(Point<T> const& point) const
{
if (m_a == m_b)
return m_a;
auto delta_a = point.x() - m_a.x();
auto delta_b = point.y() - m_a.y();
auto delta_c = m_b.x() - m_a.x();
auto delta_d = m_b.y() - m_a.y();
auto len_sq = delta_c * delta_c + delta_d * delta_d;
float param = -1.0;
if (len_sq != 0)
param = static_cast<float>(delta_a * delta_c + delta_b * delta_d) / static_cast<float>(len_sq);
if (param < 0)
return m_a;
if (param > 1)
return m_b;
// TODO: round if we're dealing with int
return { static_cast<T>(m_a.x() + param * delta_c), static_cast<T>(m_a.y() + param * delta_d) };
}
Line<T> shortest_line_to(Point<T> const& point) const
{
return { closest_to(point), point };
}
float distance_to(Point<T> const& point) const
{
return shortest_line_to(point).length();
}
Point<T> const& a() const { return m_a; }
Point<T> const& b() const { return m_b; }
Line<T> rotated(float radians)
{
Gfx::AffineTransform rotation_transform;
rotation_transform.rotate_radians(radians);
Line<T> line = *this;
line.set_a(line.a().transformed(rotation_transform));
line.set_b(line.b().transformed(rotation_transform));
return line;
}
void set_a(Point<T> const& a) { m_a = a; }
void set_b(Point<T> const& b) { m_b = b; }
Line<T> scaled(T sx, T sy) const
{
Line<T> line = *this;
line.set_a(line.a().scaled(sx, sy));
line.set_b(line.b().scaled(sx, sy));
return line;
}
Line<T> translated(Point<T> const& delta) const
{
Line<T> line = *this;
line.set_a(line.a().translated(delta));
line.set_b(line.b().translated(delta));
return line;
}
template<typename U>
requires(!IsSame<T, U>)
[[nodiscard]] ALWAYS_INLINE constexpr Line<U> to_type() const
{
return Line<U>(*this);
}
ByteString to_byte_string() const;
private:
Point<T> m_a;
Point<T> m_b;
};
template<>
inline ByteString IntLine::to_byte_string() const
{
return ByteString::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
}
template<>
inline ByteString FloatLine::to_byte_string() const
{
return ByteString::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
}
}
namespace AK {
template<typename T>
struct Formatter<Gfx::Line<T>> : Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, Gfx::Line<T> const& value)
{
return Formatter<FormatString>::format(builder, "[{},{} -> {},{}]"sv, value.a().x(), value.a().y(), value.b().x(), value.b().y());
}
};
}

View file

@ -11,7 +11,6 @@
#include <AK/Format.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Line.h>
#include <LibGfx/Orientation.h>
#include <LibGfx/Point.h>
#include <LibGfx/Size.h>
@ -411,65 +410,6 @@ public:
return false;
}
template<typename Container, typename Function>
IterationDecision for_each_intersected(Container const& others, Function f) const
{
if (is_empty())
return IterationDecision::Continue;
for (auto const& other : others) {
auto intersected_rect = intersected(other);
if (!intersected_rect.is_empty()) {
IterationDecision decision = f(intersected_rect);
if (decision != IterationDecision::Continue)
return decision;
}
}
return IterationDecision::Continue;
}
[[nodiscard]] Vector<Rect<T>, 4> shatter(Rect<T> const& hammer) const
{
Vector<Rect<T>, 4> pieces;
if (!intersects(hammer)) {
pieces.unchecked_append(*this);
return pieces;
}
Rect<T> top_shard {
x(),
y(),
width(),
hammer.y() - y(),
};
Rect<T> bottom_shard {
x(),
hammer.bottom(),
width(),
bottom() - hammer.bottom(),
};
Rect<T> left_shard {
x(),
max(hammer.y(), y()),
hammer.x() - x(),
min(hammer.bottom(), bottom()) - max(hammer.y(), y()),
};
Rect<T> right_shard {
hammer.right(),
max(hammer.y(), y()),
right() - hammer.right(),
min(hammer.bottom(), bottom()) - max(hammer.y(), y()),
};
if (!top_shard.is_empty())
pieces.unchecked_append(top_shard);
if (!bottom_shard.is_empty())
pieces.unchecked_append(bottom_shard);
if (!left_shard.is_empty())
pieces.unchecked_append(left_shard);
if (!right_shard.is_empty())
pieces.unchecked_append(right_shard);
return pieces;
}
template<class U>
[[nodiscard]] bool operator==(Rect<U> const& other) const
{
@ -526,30 +466,6 @@ public:
return intersection(*this, other);
}
[[nodiscard]] Vector<Point<T>, 2> intersected(Line<T> const& line) const
{
if (is_empty())
return {};
Vector<Point<T>, 2> points;
if (auto point = line.intersected({ top_left(), top_right() }); point.has_value())
points.append({ point.value().x(), y() });
if (auto point = line.intersected({ bottom_left(), bottom_right() }); point.has_value()) {
points.append({ point.value().x(), bottom() - 1 });
if (points.size() == 2)
return points;
}
if (height() > 2) {
if (auto point = line.intersected({ { x(), y() + 1 }, { x(), bottom() - 2 } }); point.has_value()) {
points.append({ x(), point.value().y() });
if (points.size() == 2)
return points;
}
if (auto point = line.intersected({ { right() - 1, y() + 1 }, { right() - 1, bottom() - 2 } }); point.has_value())
points.append({ right() - 1, point.value().y() });
}
return points;
}
template<typename U = T>
[[nodiscard]] Gfx::Rect<U> interpolated_to(Gfx::Rect<T> const& to, float factor) const
{
@ -568,274 +484,6 @@ public:
return { interpolated_left, interpolated_top, interpolated_right - interpolated_left, interpolated_bottom - interpolated_top };
}
[[nodiscard]] float center_point_distance_to(Rect<T> const& other) const
{
return Line { center(), other.center() }.length();
}
[[nodiscard]] Vector<Point<T>, 2> closest_outside_center_points(Rect<T> const& other) const
{
if (intersects(other))
return {};
Line centers_line { center(), other.center() };
auto points_this = intersected(centers_line);
VERIFY(points_this.size() == 1);
auto points_other = other.intersected(centers_line);
VERIFY(points_other.size() == 1);
return { points_this[0], points_other[0] };
}
[[nodiscard]] float outside_center_point_distance_to(Rect<T> const& other) const
{
auto points = closest_outside_center_points(other);
if (points.is_empty())
return 0.f;
return Line { points[0], points[0] }.length();
}
[[nodiscard]] Rect<T> constrained_to(Rect<T> const& constrain_rect) const
{
if (constrain_rect.contains(*this))
return *this;
T move_x = 0, move_y = 0;
if (right() > constrain_rect.right())
move_x = constrain_rect.right() - right();
if (bottom() > constrain_rect.bottom())
move_y = constrain_rect.bottom() - bottom();
if (x() < constrain_rect.x())
move_x = constrain_rect.x() - x();
if (y() < constrain_rect.y())
move_y = constrain_rect.y() - y();
auto rect = *this;
if (move_x != 0 || move_y != 0)
rect.translate_by(move_x, move_y);
return rect;
}
[[nodiscard]] Point<T> closest_to(Point<T> const& point) const
{
if (is_empty())
return {};
Optional<Point<T>> closest_point;
float closest_distance = 0.0;
auto check_distance = [&](Line<T> const& line) {
auto point_on_line = line.closest_to(point);
auto distance = Line { point_on_line, point }.length();
if (!closest_point.has_value() || distance < closest_distance) {
closest_point = point_on_line;
closest_distance = distance;
}
};
check_distance({ top_left(), top_right().moved_left(1) });
check_distance({ bottom_left().moved_up(1), bottom_right().translated(-1) });
if (height() > 2) {
check_distance({ { x(), y() + 1 }, { x(), bottom() - 2 } });
check_distance({ { right() - 1, y() + 1 }, { right() - 1, bottom() - 2 } });
}
VERIFY(closest_point.has_value());
VERIFY(side(closest_point.value()) != Side::None);
return closest_point.value();
}
class RelativeLocation {
friend class Rect<T>;
RelativeLocation(Rect<T> const& base_rect, Rect<T> const& other_rect)
{
if (base_rect.is_empty() || other_rect.is_empty())
return;
auto parts = base_rect.shatter(other_rect);
for (auto& part : parts) {
if (part.x() < other_rect.x()) {
if (part.y() < other_rect.y())
m_top_left = true;
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom() - 1) || (part.y() < other_rect.bottom() && part.bottom() - 1 > other_rect.y()))
m_left = true;
if (part.y() >= other_rect.bottom() - 1 || part.bottom() - 1 > other_rect.y())
m_bottom_left = true;
}
if (part.x() >= other_rect.x() || part.right() - 1 > other_rect.x()) {
if (part.y() < other_rect.y())
m_top = true;
if (part.y() >= other_rect.bottom() - 1 || part.bottom() > other_rect.bottom())
m_bottom = true;
}
if (part.x() >= other_rect.right() - 1 || part.right() > other_rect.right()) {
if (part.y() < other_rect.y())
m_top_right = true;
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom() - 1) || (part.y() < other_rect.bottom() && part.bottom() - 1 > other_rect.y()))
m_right = true;
if (part.y() >= other_rect.bottom() - 1 || part.bottom() - 1 > other_rect.y())
m_bottom_right = true;
}
}
}
public:
RelativeLocation() = default;
bool top_left() const { return m_top_left; }
bool top() const { return m_top; }
bool top_right() const { return m_top_right; }
bool left() const { return m_left; }
bool right() const { return m_right; }
bool bottom_left() const { return m_bottom_left; }
bool bottom() const { return m_bottom; }
bool bottom_right() const { return m_bottom_right; }
bool anywhere_above() const { return m_top_left || m_top || m_top_right; }
bool anywhere_below() const { return m_bottom_left || m_bottom || m_bottom_right; }
bool anywhere_left() const { return m_top_left || m_left || m_bottom_left; }
bool anywhere_right() const { return m_top_right || m_right || m_bottom_right; }
private:
bool m_top_left : 1 { false };
bool m_top : 1 { false };
bool m_top_right : 1 { false };
bool m_left : 1 { false };
bool m_right : 1 { false };
bool m_bottom_left : 1 { false };
bool m_bottom : 1 { false };
bool m_bottom_right : 1 { false };
};
[[nodiscard]] RelativeLocation relative_location_to(Rect<T> const& other) const
{
return RelativeLocation(*this, other);
}
enum class Side {
None = 0,
Left,
Top,
Right,
Bottom
};
[[nodiscard]] Side side(Point<T> const& point) const
{
if (is_empty())
return Side::None;
if (point.y() == y() || point.y() == bottom() - 1)
return (point.x() >= x() && point.x() < right()) ? (point.y() == y() ? Side::Top : Side::Bottom) : Side::None;
if (point.x() == x() || point.x() == right() - 1)
return (point.y() > y() && point.y() < bottom()) ? (point.x() == x() ? Side::Left : Side::Right) : Side::None;
return Side::None;
}
[[nodiscard]] Rect<T> rect_on_side(Side side, Rect<T> const& other) const
{
switch (side) {
case Side::None:
break;
case Side::Left:
// Return the area in other that is to the left of this rect
if (other.x() < x()) {
if (other.right() > x())
return { other.location(), { x() - other.x(), other.height() } };
else
return other;
}
break;
case Side::Top:
// Return the area in other that is above this rect
if (other.y() < y()) {
if (other.bottom() > y())
return { other.location(), { other.width(), y() - other.y() } };
else
return other;
}
break;
case Side::Right:
// Return the area in other that is to the right of this rect
if (other.right() > x()) {
if (other.x() < right())
return { { right(), other.y() }, { other.width() - (right() - 1 - other.x()), other.height() } };
else
return other;
}
break;
case Side::Bottom:
// Return the area in other that is below this rect
if (other.bottom() > y()) {
if (other.y() < bottom())
return { { other.x(), bottom() }, { other.width(), other.height() - (bottom() - 1 - other.y()) } };
else
return other;
}
break;
}
return {};
}
template<typename Container>
static bool disperse(Container& rects)
{
auto has_intersecting = [&]() {
for (auto& rect : rects) {
for (auto& other_rect : rects) {
if (&rect == &other_rect)
continue;
if (rect.intersects(other_rect))
return true;
}
}
return false;
};
if (!has_intersecting())
return false;
auto calc_delta = [&](Rect<T> const& rect) -> Point<T> {
auto rect_center = rect.center();
Point<T> center_sum;
for (auto& other_rect : rects) {
if (&other_rect == &rect)
continue;
if (rect.intersects(other_rect))
center_sum += rect_center - other_rect.center();
}
double m = sqrt((double)center_sum.x() * (double)center_sum.x() + (double)center_sum.y() * (double)center_sum.y());
if (m != 0.0)
return { (double)center_sum.x() / m + 0.5, (double)center_sum.y() / m + 0.5 };
return {};
};
Vector<Point<T>, 8> deltas;
do {
bool changes = false;
deltas.clear_with_capacity();
for (auto& rect : rects) {
auto delta = calc_delta(rect);
if (!delta.is_zero())
changes = true;
deltas.append(delta);
}
// TODO: If we have no changes we would loop infinitely!
// Figure out some way to resolve this. Maybe randomly moving an intersecting rect?
VERIFY(changes);
size_t i = 0;
for (auto& rect : rects)
rect.translate_by(deltas[i++]);
} while (has_intersecting());
return true;
}
[[nodiscard]] bool is_adjacent(Rect<T> const& other) const
{
if (is_empty() || other.is_empty())
return false;
if (intersects(other))
return false;
if (other.right() == x() || other.x() == right())
return max(top(), other.top()) < min(bottom(), other.bottom());
if (other.bottom() == y() || other.y() == bottom())
return max(left(), other.left()) < min(right(), other.right());
return false;
}
[[nodiscard]] static Rect<T> centered_at(Point<T> const& point, Size<T> const& size)
{
return { { point.x() - size.width() / 2, point.y() - size.height() / 2 }, size };

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/RefCounted.h>
#include <LibGC/Weak.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>

View file

@ -415,12 +415,10 @@ constexpr CSSPixelFraction operator/(CSSPixels left, T right) { return left / CS
inline float operator/(CSSPixels left, float right) { return left.to_float() / right; }
inline double operator/(CSSPixels left, double right) { return left.to_double() / right; }
using CSSPixelLine = Gfx::Line<CSSPixels>;
using CSSPixelPoint = Gfx::Point<CSSPixels>;
using CSSPixelRect = Gfx::Rect<CSSPixels>;
using CSSPixelSize = Gfx::Size<CSSPixels>;
using DevicePixelLine = Gfx::Line<DevicePixels>;
using DevicePixelPoint = Gfx::Point<DevicePixels>;
using DevicePixelRect = Gfx::Rect<DevicePixels>;
using DevicePixelSize = Gfx::Size<DevicePixels>;

View file

@ -28,35 +28,6 @@ TEST_CASE(rect_contains_vertically)
EXPECT(!rect.contains_vertically(100.f));
}
TEST_CASE(rect_shatter)
{
Gfx::IntRect glass_plate = { 0, 0, 100, 100 };
Gfx::IntRect hammer = { 30, 40, 40, 10 };
auto shards = glass_plate.shatter(hammer);
EXPECT(!shards.is_empty());
int total_shard_area = 0;
for (auto shard : shards) {
EXPECT(glass_plate.contains(shard));
EXPECT(!hammer.intersects(shard));
total_shard_area += shard.size().area();
}
EXPECT_EQ(glass_plate.size().area() - hammer.size().area(), total_shard_area);
}
TEST_CASE(rect_closest_to)
{
Gfx::IntRect const screen_rect = { 0, 0, 960, 540 };
Gfx::Point<int> p = { 460, 592 }; // point is below the rect
Gfx::Point<int> closest = screen_rect.closest_to(p);
EXPECT_EQ(screen_rect.side(closest), Gfx::IntRect::Side::Bottom);
p = { 960, 0 }; // point exactly on top right corner
closest = screen_rect.closest_to(p);
EXPECT_EQ(screen_rect.side(closest), Gfx::IntRect::Side::Top);
}
TEST_CASE(rect_unite)
{
Gfx::IntRect rect_a { 10, 10, 100, 100 };