2020-01-18 08:38:21 +00:00
|
|
|
/*
|
2024-10-04 11:19:50 +00:00
|
|
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
2021-04-24 13:20:51 +00:00
|
|
|
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
2021-09-13 20:48:22 +00:00
|
|
|
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
2021-09-20 18:48:56 +00:00
|
|
|
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
2022-01-20 18:55:14 +00:00
|
|
|
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
2022-02-22 21:55:01 +00:00
|
|
|
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
2024-11-01 11:14:53 +00:00
|
|
|
* Copyright (c) 2022, Jelle Raaijmakers <jelle@ladybird.org>
|
2020-01-18 08:38:21 +00:00
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 08:38:21 +00:00
|
|
|
*/
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
#include "DeprecatedPainter.h"
|
2020-03-08 11:05:14 +00:00
|
|
|
#include "Bitmap.h"
|
2018-10-10 14:49:36 +00:00
|
|
|
#include <AK/Assertions.h>
|
2020-03-30 17:39:37 +00:00
|
|
|
#include <AK/Function.h>
|
2021-07-17 16:29:28 +00:00
|
|
|
#include <AK/Math.h>
|
2020-03-08 11:05:14 +00:00
|
|
|
#include <AK/Memory.h>
|
2018-12-21 01:18:16 +00:00
|
|
|
#include <AK/StdLibExtras.h>
|
2024-08-08 08:22:03 +00:00
|
|
|
#include <LibGfx/DeprecatedPath.h>
|
2024-11-20 13:37:15 +00:00
|
|
|
#include <LibGfx/ScalingMode.h>
|
2019-06-05 16:22:11 +00:00
|
|
|
#include <stdio.h>
|
2019-02-28 17:57:36 +00:00
|
|
|
|
2022-10-04 19:04:13 +00:00
|
|
|
#if defined(AK_COMPILER_GCC)
|
2020-03-08 11:05:14 +00:00
|
|
|
# pragma GCC optimize("O3")
|
2020-01-30 23:59:03 +00:00
|
|
|
#endif
|
|
|
|
|
2020-02-06 10:56:38 +00:00
|
|
|
namespace Gfx {
|
|
|
|
|
2020-02-14 22:02:47 +00:00
|
|
|
template<BitmapFormat format = BitmapFormat::Invalid>
|
2021-09-17 13:06:28 +00:00
|
|
|
ALWAYS_INLINE Color get_pixel(Gfx::Bitmap const& bitmap, int x, int y)
|
2019-05-11 01:53:28 +00:00
|
|
|
{
|
2021-03-16 10:48:42 +00:00
|
|
|
if constexpr (format == BitmapFormat::BGRx8888)
|
2019-05-11 01:53:28 +00:00
|
|
|
return Color::from_rgb(bitmap.scanline(y)[x]);
|
2021-03-16 10:48:42 +00:00
|
|
|
if constexpr (format == BitmapFormat::BGRA8888)
|
2022-03-04 21:28:59 +00:00
|
|
|
return Color::from_argb(bitmap.scanline(y)[x]);
|
2019-05-11 01:53:28 +00:00
|
|
|
return bitmap.get_pixel(x, y);
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
DeprecatedPainter::DeprecatedPainter(Gfx::Bitmap& bitmap)
|
2019-02-25 15:04:08 +00:00
|
|
|
: m_target(bitmap)
|
2019-01-12 02:42:50 +00:00
|
|
|
{
|
2021-03-16 10:48:42 +00:00
|
|
|
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888);
|
2019-03-09 15:48:02 +00:00
|
|
|
m_state_stack.append(State());
|
2021-05-03 14:37:05 +00:00
|
|
|
state().clip_rect = { { 0, 0 }, bitmap.size() };
|
2019-01-12 02:42:50 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::clear_rect(IntRect const& a_rect, Color color)
|
2019-11-25 10:34:55 +00:00
|
|
|
{
|
2021-05-03 14:37:05 +00:00
|
|
|
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
2019-11-25 10:34:55 +00:00
|
|
|
if (rect.is_empty())
|
|
|
|
return;
|
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
VERIFY(target().rect().contains(rect));
|
2021-05-03 14:37:05 +00:00
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
ARGB32* dst = target().scanline(rect.top()) + rect.left();
|
|
|
|
size_t const dst_skip = target().pitch() / sizeof(ARGB32);
|
2019-11-25 10:34:55 +00:00
|
|
|
|
|
|
|
for (int i = rect.height() - 1; i >= 0; --i) {
|
|
|
|
fast_u32_fill(dst, color.value(), rect.width());
|
|
|
|
dst += dst_skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_physical_rect(IntRect const& physical_rect, Color color)
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
{
|
2021-01-20 14:59:22 +00:00
|
|
|
// Callers must do clipping.
|
2024-06-05 08:43:00 +00:00
|
|
|
ARGB32* dst = target().scanline(physical_rect.top()) + physical_rect.left();
|
|
|
|
size_t const dst_skip = target().pitch() / sizeof(ARGB32);
|
2021-05-03 14:37:05 +00:00
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
auto dst_format = target().format();
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int i = physical_rect.height() - 1; i >= 0; --i) {
|
|
|
|
for (int j = 0; j < physical_rect.width(); ++j)
|
2023-05-14 16:22:05 +00:00
|
|
|
dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
|
2021-05-03 14:37:05 +00:00
|
|
|
dst += dst_skip;
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_rect(IntRect const& a_rect, Color color)
|
2018-10-10 18:06:58 +00:00
|
|
|
{
|
2019-11-16 18:26:17 +00:00
|
|
|
if (color.alpha() == 0)
|
|
|
|
return;
|
|
|
|
|
2019-11-25 10:34:55 +00:00
|
|
|
if (color.alpha() == 0xff) {
|
|
|
|
clear_rect(a_rect, color);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-03 14:37:05 +00:00
|
|
|
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
2021-01-20 14:59:22 +00:00
|
|
|
if (rect.is_empty())
|
|
|
|
return;
|
2024-06-05 08:43:00 +00:00
|
|
|
VERIFY(target().rect().contains(rect));
|
2021-01-20 14:59:22 +00:00
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
fill_physical_rect(rect, color);
|
2018-10-10 14:49:36 +00:00
|
|
|
}
|
2018-10-10 18:06:58 +00:00
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_rect(IntRect const& rect, PaintStyle const& paint_style)
|
2023-01-15 22:18:46 +00:00
|
|
|
{
|
|
|
|
auto a_rect = rect.translated(translation());
|
|
|
|
auto clipped_rect = a_rect.intersected(clip_rect());
|
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
return;
|
|
|
|
auto start_offset = clipped_rect.location() - a_rect.location();
|
|
|
|
paint_style.paint(a_rect, [&](PaintStyle::SamplerFunction sample) {
|
|
|
|
for (int y = 0; y < clipped_rect.height(); ++y) {
|
|
|
|
for (int x = 0; x < clipped_rect.width(); ++x) {
|
|
|
|
IntPoint point(x, y);
|
|
|
|
set_physical_pixel(point + clipped_rect.location(), sample(point + start_offset), true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
|
2021-06-04 11:38:30 +00:00
|
|
|
{
|
|
|
|
return fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius)
|
2021-05-15 21:33:21 +00:00
|
|
|
{
|
|
|
|
// Fasttrack for rects without any border radii
|
|
|
|
if (!top_left_radius && !top_right_radius && !bottom_right_radius && !bottom_left_radius)
|
|
|
|
return fill_rect(a_rect, color);
|
|
|
|
|
|
|
|
// Fully transparent, dont care.
|
|
|
|
if (color.alpha() == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// FIXME: Allow for elliptically rounded corners
|
|
|
|
IntRect top_left_corner = {
|
|
|
|
a_rect.x(),
|
|
|
|
a_rect.y(),
|
|
|
|
top_left_radius,
|
|
|
|
top_left_radius
|
|
|
|
};
|
|
|
|
IntRect top_right_corner = {
|
|
|
|
a_rect.x() + a_rect.width() - top_right_radius,
|
|
|
|
a_rect.y(),
|
|
|
|
top_right_radius,
|
|
|
|
top_right_radius
|
|
|
|
};
|
|
|
|
IntRect bottom_right_corner = {
|
|
|
|
a_rect.x() + a_rect.width() - bottom_right_radius,
|
|
|
|
a_rect.y() + a_rect.height() - bottom_right_radius,
|
|
|
|
bottom_right_radius,
|
|
|
|
bottom_right_radius
|
|
|
|
};
|
|
|
|
IntRect bottom_left_corner = {
|
|
|
|
a_rect.x(),
|
|
|
|
a_rect.y() + a_rect.height() - bottom_left_radius,
|
|
|
|
bottom_left_radius,
|
|
|
|
bottom_left_radius
|
|
|
|
};
|
|
|
|
|
|
|
|
IntRect top_rect = {
|
|
|
|
a_rect.x() + top_left_radius,
|
|
|
|
a_rect.y(),
|
|
|
|
a_rect.width() - top_left_radius - top_right_radius, top_left_radius
|
|
|
|
};
|
|
|
|
IntRect right_rect = {
|
|
|
|
a_rect.x() + a_rect.width() - top_right_radius,
|
|
|
|
a_rect.y() + top_right_radius,
|
|
|
|
top_right_radius,
|
|
|
|
a_rect.height() - top_right_radius - bottom_right_radius
|
|
|
|
};
|
|
|
|
IntRect bottom_rect = {
|
|
|
|
a_rect.x() + bottom_left_radius,
|
|
|
|
a_rect.y() + a_rect.height() - bottom_right_radius,
|
|
|
|
a_rect.width() - bottom_left_radius - bottom_right_radius,
|
|
|
|
bottom_right_radius
|
|
|
|
};
|
|
|
|
IntRect left_rect = {
|
|
|
|
a_rect.x(),
|
|
|
|
a_rect.y() + top_left_radius,
|
|
|
|
bottom_left_radius,
|
|
|
|
a_rect.height() - top_left_radius - bottom_left_radius
|
|
|
|
};
|
|
|
|
|
|
|
|
IntRect inner = {
|
|
|
|
left_rect.x() + left_rect.width(),
|
|
|
|
left_rect.y(),
|
|
|
|
a_rect.width() - left_rect.width() - right_rect.width(),
|
|
|
|
a_rect.height() - top_rect.height() - bottom_rect.height()
|
|
|
|
};
|
|
|
|
|
|
|
|
fill_rect(top_rect, color);
|
|
|
|
fill_rect(right_rect, color);
|
|
|
|
fill_rect(bottom_rect, color);
|
|
|
|
fill_rect(left_rect, color);
|
|
|
|
|
|
|
|
fill_rect(inner, color);
|
|
|
|
|
|
|
|
if (top_left_radius)
|
|
|
|
fill_rounded_corner(top_left_corner, top_left_radius, color, CornerOrientation::TopLeft);
|
|
|
|
if (top_right_radius)
|
|
|
|
fill_rounded_corner(top_right_corner, top_right_radius, color, CornerOrientation::TopRight);
|
|
|
|
if (bottom_left_radius)
|
|
|
|
fill_rounded_corner(bottom_left_corner, bottom_left_radius, color, CornerOrientation::BottomLeft);
|
|
|
|
if (bottom_right_radius)
|
|
|
|
fill_rounded_corner(bottom_right_corner, bottom_right_radius, color, CornerOrientation::BottomRight);
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_rounded_corner(IntRect const& a_rect, int radius, Color color, CornerOrientation orientation)
|
2021-05-15 21:33:21 +00:00
|
|
|
{
|
|
|
|
// Care about clipping
|
|
|
|
auto translated_a_rect = a_rect.translated(translation());
|
|
|
|
auto rect = translated_a_rect.intersected(clip_rect());
|
|
|
|
|
|
|
|
if (rect.is_empty())
|
|
|
|
return;
|
2024-06-05 08:43:00 +00:00
|
|
|
VERIFY(target().rect().contains(rect));
|
2021-05-15 21:33:21 +00:00
|
|
|
|
|
|
|
// We got cut on the top!
|
|
|
|
// FIXME: Also account for clipping on the x-axis
|
|
|
|
int clip_offset = 0;
|
|
|
|
if (translated_a_rect.y() < rect.y())
|
|
|
|
clip_offset = rect.y() - translated_a_rect.y();
|
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
ARGB32* dst = target().scanline(rect.top()) + rect.left();
|
|
|
|
size_t const dst_skip = target().pitch() / sizeof(ARGB32);
|
2021-05-15 21:33:21 +00:00
|
|
|
|
|
|
|
IntPoint circle_center;
|
|
|
|
switch (orientation) {
|
|
|
|
case CornerOrientation::TopLeft:
|
|
|
|
circle_center = { radius, radius + 1 };
|
|
|
|
break;
|
|
|
|
case CornerOrientation::TopRight:
|
|
|
|
circle_center = { -1, radius + 1 };
|
|
|
|
break;
|
|
|
|
case CornerOrientation::BottomRight:
|
|
|
|
circle_center = { -1, 0 };
|
|
|
|
break;
|
|
|
|
case CornerOrientation::BottomLeft:
|
|
|
|
circle_center = { radius, 0 };
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
|
|
|
|
int radius2 = radius * radius;
|
|
|
|
auto is_in_circle = [&](int x, int y) {
|
|
|
|
int distance2 = (circle_center.x() - x) * (circle_center.x() - x) + (circle_center.y() - y) * (circle_center.y() - y);
|
|
|
|
// To reflect the grid and be compatible with the draw_circle_arc_intersecting algorithm
|
|
|
|
// add 1/2 to the radius
|
|
|
|
return distance2 <= (radius2 + radius + 0.25);
|
|
|
|
};
|
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
auto dst_format = target().format();
|
2021-05-15 21:33:21 +00:00
|
|
|
for (int i = rect.height() - 1; i >= 0; --i) {
|
|
|
|
for (int j = 0; j < rect.width(); ++j)
|
|
|
|
if (is_in_circle(j, rect.height() - i + clip_offset))
|
2023-05-14 16:22:05 +00:00
|
|
|
dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
|
2021-05-15 21:33:21 +00:00
|
|
|
dst += dst_skip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-01 14:55:57 +00:00
|
|
|
// The callback will only be called for a quarter of the ellipse, the user is intended to deduce other points.
|
|
|
|
// As the coordinate space is relative to the center of the rectangle, it's simply (x, y), (x, -y), (-x, y) and (-x, -y).
|
|
|
|
static void on_each_ellipse_point(IntRect const& rect, Function<void(IntPoint)>&& callback)
|
2020-05-24 18:03:48 +00:00
|
|
|
{
|
2023-01-01 14:55:57 +00:00
|
|
|
// Note: This is an implementation of the Midpoint Ellipse Algorithm.
|
2022-11-08 16:07:14 +00:00
|
|
|
double const a = rect.width() / 2;
|
|
|
|
double const a_square = a * a;
|
|
|
|
double const b = rect.height() / 2;
|
|
|
|
double const b_square = b * b;
|
|
|
|
|
|
|
|
int x = 0;
|
|
|
|
auto y = static_cast<int>(b);
|
|
|
|
|
|
|
|
double dx = 2 * b_square * x;
|
|
|
|
double dy = 2 * a_square * y;
|
|
|
|
|
|
|
|
// For region 1:
|
|
|
|
auto decision_parameter = b_square - a_square * b + .25 * a_square;
|
|
|
|
|
|
|
|
while (dx < dy) {
|
2023-01-01 14:55:57 +00:00
|
|
|
callback({ x, y });
|
2022-11-08 16:07:14 +00:00
|
|
|
|
|
|
|
if (decision_parameter >= 0) {
|
|
|
|
y--;
|
|
|
|
dy -= 2 * a_square;
|
|
|
|
decision_parameter -= dy;
|
|
|
|
}
|
|
|
|
x++;
|
|
|
|
dx += 2 * b_square;
|
|
|
|
decision_parameter += dx + b_square;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For region 2:
|
|
|
|
decision_parameter = b_square * ((x + 0.5) * (x + 0.5)) + a_square * ((y - 1) * (y - 1)) - a_square * b_square;
|
|
|
|
|
|
|
|
while (y >= 0) {
|
2023-01-01 14:55:57 +00:00
|
|
|
callback({ x, y });
|
2022-11-08 16:07:14 +00:00
|
|
|
|
|
|
|
if (decision_parameter <= 0) {
|
|
|
|
x++;
|
|
|
|
dx += 2 * b_square;
|
|
|
|
decision_parameter += dx;
|
|
|
|
}
|
|
|
|
y--;
|
|
|
|
dy -= 2 * a_square;
|
|
|
|
decision_parameter += a_square - dy;
|
2019-12-26 22:19:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::fill_ellipse(IntRect const& a_rect, Color color)
|
2023-01-01 14:55:57 +00:00
|
|
|
{
|
|
|
|
auto rect = a_rect.translated(translation()).intersected(clip_rect());
|
|
|
|
if (rect.is_empty())
|
|
|
|
return;
|
|
|
|
|
2024-06-05 08:43:00 +00:00
|
|
|
VERIFY(target().rect().contains(rect));
|
2023-01-01 14:55:57 +00:00
|
|
|
|
2023-01-01 15:42:46 +00:00
|
|
|
auto const center = a_rect.center();
|
|
|
|
|
|
|
|
on_each_ellipse_point(rect, [this, &color, center](IntPoint position) {
|
|
|
|
IntPoint const directions[4] = { { position.x(), position.y() }, { -position.x(), position.y() }, { position.x(), -position.y() }, { -position.x(), -position.y() } };
|
|
|
|
|
|
|
|
draw_line(center + directions[0], center + directions[1], color);
|
|
|
|
draw_line(center + directions[2], center + directions[3], color);
|
|
|
|
});
|
2023-01-01 14:55:57 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 14:37:05 +00:00
|
|
|
template<typename RectType, typename Callback>
|
2021-09-17 13:06:28 +00:00
|
|
|
static void for_each_pixel_around_rect_clockwise(RectType const& rect, Callback callback)
|
2020-10-26 19:43:59 +00:00
|
|
|
{
|
|
|
|
if (rect.is_empty())
|
|
|
|
return;
|
2023-05-21 22:41:18 +00:00
|
|
|
for (auto x = rect.left(); x < rect.right(); ++x)
|
2020-10-26 19:43:59 +00:00
|
|
|
callback(x, rect.top());
|
2023-05-21 22:41:18 +00:00
|
|
|
for (auto y = rect.top() + 1; y < rect.bottom(); ++y)
|
|
|
|
callback(rect.right() - 1, y);
|
|
|
|
for (auto x = rect.right() - 2; x >= rect.left(); --x)
|
|
|
|
callback(x, rect.bottom() - 1);
|
|
|
|
for (auto y = rect.bottom() - 2; y > rect.top(); --y)
|
2020-10-26 19:43:59 +00:00
|
|
|
callback(rect.left(), y);
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::draw_rect(IntRect const& a_rect, Color color, bool rough)
|
2018-10-11 21:14:51 +00:00
|
|
|
{
|
2021-05-03 14:37:05 +00:00
|
|
|
IntRect rect = a_rect.translated(translation());
|
|
|
|
auto clipped_rect = rect.intersected(clip_rect());
|
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int min_y = clipped_rect.top();
|
2023-05-21 22:41:18 +00:00
|
|
|
int max_y = clipped_rect.bottom() - 1;
|
2021-01-13 01:22:15 +00:00
|
|
|
|
2023-05-21 22:41:18 +00:00
|
|
|
if (rect.top() >= clipped_rect.top() && rect.top() < clipped_rect.bottom()) {
|
2024-04-14 11:31:19 +00:00
|
|
|
int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
|
|
|
|
if (width > 0) {
|
|
|
|
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
|
2024-06-05 06:17:28 +00:00
|
|
|
fill_physical_scanline(rect.top(), start_x, width, color);
|
2024-04-14 11:31:19 +00:00
|
|
|
}
|
2021-05-03 14:37:05 +00:00
|
|
|
++min_y;
|
|
|
|
}
|
2023-05-21 22:41:18 +00:00
|
|
|
if (rect.bottom() > clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
|
2024-04-14 11:31:19 +00:00
|
|
|
int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
|
|
|
|
if (width > 0) {
|
|
|
|
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
|
2024-06-05 06:17:28 +00:00
|
|
|
fill_physical_scanline(max_y, start_x, width, color);
|
2024-04-14 11:31:19 +00:00
|
|
|
}
|
2021-05-03 14:37:05 +00:00
|
|
|
--max_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool draw_left_side = rect.left() >= clipped_rect.left();
|
|
|
|
bool draw_right_side = rect.right() == clipped_rect.right();
|
|
|
|
|
|
|
|
if (draw_left_side && draw_right_side) {
|
|
|
|
// Specialized loop when drawing both sides.
|
2024-06-05 06:17:28 +00:00
|
|
|
for (int y = min_y; y <= max_y; ++y) {
|
2024-06-05 08:43:00 +00:00
|
|
|
auto* bits = target().scanline(y);
|
2024-06-05 06:17:28 +00:00
|
|
|
set_physical_pixel(bits[rect.left()], color);
|
|
|
|
set_physical_pixel(bits[(rect.right() - 1)], color);
|
2021-05-03 14:37:05 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-06-05 06:17:28 +00:00
|
|
|
for (int y = min_y; y <= max_y; ++y) {
|
2024-06-05 08:43:00 +00:00
|
|
|
auto* bits = target().scanline(y);
|
2021-05-03 14:37:05 +00:00
|
|
|
if (draw_left_side)
|
2024-06-05 06:17:28 +00:00
|
|
|
set_physical_pixel(bits[rect.left()], color);
|
2021-05-03 14:37:05 +00:00
|
|
|
if (draw_right_side)
|
2024-06-05 06:17:28 +00:00
|
|
|
set_physical_pixel(bits[(rect.right() - 1)], color);
|
2021-05-03 14:37:05 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-11 21:14:51 +00:00
|
|
|
}
|
|
|
|
|
2021-03-21 08:40:14 +00:00
|
|
|
struct BlitState {
|
|
|
|
enum AlphaState {
|
|
|
|
NoAlpha = 0,
|
|
|
|
SrcAlpha = 1,
|
|
|
|
DstAlpha = 2,
|
|
|
|
BothAlpha = SrcAlpha | DstAlpha
|
|
|
|
};
|
|
|
|
|
2022-03-04 21:05:20 +00:00
|
|
|
ARGB32 const* src;
|
|
|
|
ARGB32* dst;
|
2021-03-21 08:40:14 +00:00
|
|
|
size_t src_pitch;
|
|
|
|
size_t dst_pitch;
|
|
|
|
int row_count;
|
|
|
|
int column_count;
|
|
|
|
float opacity;
|
2022-03-05 23:14:34 +00:00
|
|
|
BitmapFormat src_format;
|
2021-03-21 08:40:14 +00:00
|
|
|
};
|
|
|
|
|
2022-03-05 23:14:34 +00:00
|
|
|
// FIXME: This is a hack to support blit_with_opacity() with RGBA8888 source.
|
|
|
|
// Ideally we'd have a more generic solution that allows any source format.
|
|
|
|
static void swap_red_and_blue_channels(Color& color)
|
|
|
|
{
|
|
|
|
u32 rgba = color.value();
|
|
|
|
u32 bgra = (rgba & 0xff00ff00)
|
|
|
|
| ((rgba & 0x000000ff) << 16)
|
|
|
|
| ((rgba & 0x00ff0000) >> 16);
|
|
|
|
color = Color::from_argb(bgra);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: This function is very unoptimized.
|
2021-03-21 08:40:14 +00:00
|
|
|
template<BlitState::AlphaState has_alpha>
|
|
|
|
static void do_blit_with_opacity(BlitState& state)
|
|
|
|
{
|
|
|
|
for (int row = 0; row < state.row_count; ++row) {
|
|
|
|
for (int x = 0; x < state.column_count; ++x) {
|
2022-03-04 21:28:59 +00:00
|
|
|
Color dest_color = (has_alpha & BlitState::DstAlpha) ? Color::from_argb(state.dst[x]) : Color::from_rgb(state.dst[x]);
|
2021-03-21 08:40:14 +00:00
|
|
|
if constexpr (has_alpha & BlitState::SrcAlpha) {
|
2022-03-04 21:28:59 +00:00
|
|
|
Color src_color_with_alpha = Color::from_argb(state.src[x]);
|
2022-03-05 23:14:34 +00:00
|
|
|
if (state.src_format == BitmapFormat::RGBA8888)
|
|
|
|
swap_red_and_blue_channels(src_color_with_alpha);
|
2021-03-21 08:40:14 +00:00
|
|
|
float pixel_opacity = src_color_with_alpha.alpha() / 255.0;
|
|
|
|
src_color_with_alpha.set_alpha(255 * (state.opacity * pixel_opacity));
|
|
|
|
state.dst[x] = dest_color.blend(src_color_with_alpha).value();
|
|
|
|
} else {
|
|
|
|
Color src_color_with_alpha = Color::from_rgb(state.src[x]);
|
2022-03-05 23:14:34 +00:00
|
|
|
if (state.src_format == BitmapFormat::RGBA8888)
|
|
|
|
swap_red_and_blue_channels(src_color_with_alpha);
|
2021-03-21 08:40:14 +00:00
|
|
|
src_color_with_alpha.set_alpha(state.opacity * 255);
|
|
|
|
state.dst[x] = dest_color.blend(src_color_with_alpha).value();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.dst += state.dst_pitch;
|
|
|
|
state.src += state.src_pitch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::blit_with_opacity(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, float opacity, bool apply_alpha)
|
2019-02-19 00:42:53 +00:00
|
|
|
{
|
2021-02-13 18:10:27 +00:00
|
|
|
if (opacity >= 1.0f && !(source.has_alpha_channel() && apply_alpha))
|
2024-06-05 06:17:28 +00:00
|
|
|
return blit(position, source, src_rect);
|
2019-02-19 00:42:53 +00:00
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
IntRect safe_src_rect = IntRect::intersection(src_rect, source.rect());
|
2021-01-25 02:41:38 +00:00
|
|
|
|
|
|
|
auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
|
2021-01-25 17:25:02 +00:00
|
|
|
auto clipped_rect = dst_rect.intersected(clip_rect());
|
2019-02-19 00:42:53 +00:00
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
return;
|
2021-01-25 02:41:38 +00:00
|
|
|
|
2021-09-17 13:06:28 +00:00
|
|
|
int const first_row = clipped_rect.top() - dst_rect.top();
|
|
|
|
int const last_row = clipped_rect.bottom() - dst_rect.top();
|
|
|
|
int const first_column = clipped_rect.left() - dst_rect.left();
|
|
|
|
int const last_column = clipped_rect.right() - dst_rect.left();
|
2019-02-19 00:42:53 +00:00
|
|
|
|
2021-03-21 08:40:14 +00:00
|
|
|
BlitState blit_state {
|
|
|
|
.src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column,
|
2024-06-05 08:43:00 +00:00
|
|
|
.dst = target().scanline(clipped_rect.y()) + clipped_rect.x(),
|
2022-03-04 21:05:20 +00:00
|
|
|
.src_pitch = source.pitch() / sizeof(ARGB32),
|
2024-06-05 08:43:00 +00:00
|
|
|
.dst_pitch = target().pitch() / sizeof(ARGB32),
|
2023-05-21 22:41:18 +00:00
|
|
|
.row_count = last_row - first_row,
|
|
|
|
.column_count = last_column - first_column,
|
2022-03-05 23:14:34 +00:00
|
|
|
.opacity = opacity,
|
|
|
|
.src_format = source.format(),
|
2021-02-13 18:10:27 +00:00
|
|
|
};
|
2021-03-21 08:40:14 +00:00
|
|
|
|
|
|
|
if (source.has_alpha_channel() && apply_alpha) {
|
2024-06-05 08:43:00 +00:00
|
|
|
if (target().has_alpha_channel())
|
2021-03-21 08:40:14 +00:00
|
|
|
do_blit_with_opacity<BlitState::BothAlpha>(blit_state);
|
|
|
|
else
|
|
|
|
do_blit_with_opacity<BlitState::SrcAlpha>(blit_state);
|
|
|
|
} else {
|
2024-06-05 08:43:00 +00:00
|
|
|
if (target().has_alpha_channel())
|
2021-03-21 08:40:14 +00:00
|
|
|
do_blit_with_opacity<BlitState::DstAlpha>(blit_state);
|
|
|
|
else
|
|
|
|
do_blit_with_opacity<BlitState::NoAlpha>(blit_state);
|
|
|
|
}
|
2019-02-19 00:42:53 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::blit_filtered(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, Function<Color(Color)> const& filter, bool apply_alpha)
|
2019-04-10 14:14:44 +00:00
|
|
|
{
|
2020-06-10 08:57:59 +00:00
|
|
|
IntRect safe_src_rect = src_rect.intersected(source.rect());
|
|
|
|
auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
|
2019-04-16 11:58:02 +00:00
|
|
|
auto clipped_rect = dst_rect.intersected(clip_rect());
|
2019-04-10 14:14:44 +00:00
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
return;
|
2021-01-25 19:54:38 +00:00
|
|
|
|
2021-09-17 13:06:28 +00:00
|
|
|
int const first_row = clipped_rect.top() - dst_rect.top();
|
|
|
|
int const last_row = clipped_rect.bottom() - dst_rect.top();
|
|
|
|
int const first_column = clipped_rect.left() - dst_rect.left();
|
|
|
|
int const last_column = clipped_rect.right() - dst_rect.left();
|
2024-06-05 08:43:00 +00:00
|
|
|
ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
|
|
|
|
size_t const dst_skip = target().pitch() / sizeof(ARGB32);
|
|
|
|
auto dst_format = target().format();
|
2023-05-14 16:22:05 +00:00
|
|
|
auto src_format = source.format();
|
2019-04-10 14:14:44 +00:00
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
ARGB32 const* src = source.scanline(safe_src_rect.top() + first_row) + safe_src_rect.left() + first_column;
|
|
|
|
size_t const src_skip = source.pitch() / sizeof(ARGB32);
|
2021-01-25 19:54:38 +00:00
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
for (int row = first_row; row < last_row; ++row) {
|
|
|
|
for (int x = 0; x < (last_column - first_column); ++x) {
|
|
|
|
auto source_color = color_for_format(src_format, src[x]);
|
|
|
|
if (source_color.alpha() == 0)
|
|
|
|
continue;
|
|
|
|
auto filtered_color = filter(source_color);
|
|
|
|
if (!apply_alpha || filtered_color.alpha() == 0xff)
|
|
|
|
dst[x] = filtered_color.value();
|
|
|
|
else
|
|
|
|
dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
|
2019-04-10 14:14:44 +00:00
|
|
|
}
|
2024-06-05 06:17:28 +00:00
|
|
|
dst += dst_skip;
|
|
|
|
src += src_skip;
|
2019-04-10 14:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::blit(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, float opacity, bool apply_alpha)
|
2019-02-10 06:46:29 +00:00
|
|
|
{
|
2021-02-13 18:10:27 +00:00
|
|
|
if (opacity < 1.0f || (source.has_alpha_channel() && apply_alpha))
|
2024-06-05 06:17:28 +00:00
|
|
|
return blit_with_opacity(position, source, src_rect, opacity, apply_alpha);
|
2021-01-22 23:56:01 +00:00
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
auto safe_src_rect = src_rect.intersected(source.rect());
|
2021-01-19 17:10:47 +00:00
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
// If we get here, the DeprecatedPainter might have a scale factor, but the source bitmap has the same scale factor.
|
2021-01-19 17:10:47 +00:00
|
|
|
// We need to transform from logical to physical coordinates, but we can just copy pixels without resampling.
|
2021-01-20 14:59:22 +00:00
|
|
|
auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
|
2019-04-16 11:58:02 +00:00
|
|
|
auto clipped_rect = dst_rect.intersected(clip_rect());
|
2019-02-10 06:46:29 +00:00
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
return;
|
2021-01-20 14:59:22 +00:00
|
|
|
|
2021-09-17 13:06:28 +00:00
|
|
|
int const first_row = clipped_rect.top() - dst_rect.top();
|
|
|
|
int const last_row = clipped_rect.bottom() - dst_rect.top();
|
|
|
|
int const first_column = clipped_rect.left() - dst_rect.left();
|
2024-06-05 08:43:00 +00:00
|
|
|
ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
|
|
|
|
size_t const dst_skip = target().pitch() / sizeof(ARGB32);
|
2019-02-10 06:46:29 +00:00
|
|
|
|
2021-03-16 10:48:42 +00:00
|
|
|
if (source.format() == BitmapFormat::BGRx8888 || source.format() == BitmapFormat::BGRA8888) {
|
2022-03-04 21:05:20 +00:00
|
|
|
ARGB32 const* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
|
|
|
size_t const src_skip = source.pitch() / sizeof(ARGB32);
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int row = first_row; row < last_row; ++row) {
|
2022-09-14 12:29:27 +00:00
|
|
|
memcpy(dst, src, sizeof(ARGB32) * clipped_rect.width());
|
2019-05-06 17:32:56 +00:00
|
|
|
dst += dst_skip;
|
|
|
|
src += src_skip;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-16 11:09:15 +00:00
|
|
|
if (source.format() == BitmapFormat::RGBA8888) {
|
2021-09-17 13:06:28 +00:00
|
|
|
u32 const* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
|
|
|
|
size_t const src_skip = source.pitch() / sizeof(u32);
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int row = first_row; row < last_row; ++row) {
|
2021-03-16 11:09:15 +00:00
|
|
|
for (int i = 0; i < clipped_rect.width(); ++i) {
|
|
|
|
u32 rgba = src[i];
|
|
|
|
u32 bgra = (rgba & 0xff00ff00)
|
|
|
|
| ((rgba & 0x000000ff) << 16)
|
|
|
|
| ((rgba & 0x00ff0000) >> 16);
|
|
|
|
dst[i] = bgra;
|
|
|
|
}
|
|
|
|
dst += dst_skip;
|
|
|
|
src += src_skip;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-23 19:42:32 +00:00
|
|
|
VERIFY_NOT_REACHED();
|
2019-02-10 06:46:29 +00:00
|
|
|
}
|
|
|
|
|
2019-05-11 01:53:28 +00:00
|
|
|
template<bool has_alpha_channel, typename GetPixel>
|
2021-09-17 13:06:28 +00:00
|
|
|
ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& src_rect, Gfx::Bitmap const& source, int hfactor, int vfactor, GetPixel get_pixel, float opacity)
|
2019-05-11 01:53:28 +00:00
|
|
|
{
|
2020-08-05 12:40:11 +00:00
|
|
|
bool has_opacity = opacity != 1.0f;
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
for (int y = 0; y < src_rect.height(); ++y) {
|
2019-05-11 14:53:53 +00:00
|
|
|
int dst_y = dst_rect.y() + y * vfactor;
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
for (int x = 0; x < src_rect.width(); ++x) {
|
|
|
|
auto src_pixel = get_pixel(source, x + src_rect.left(), y + src_rect.top());
|
2020-08-05 12:40:11 +00:00
|
|
|
if (has_opacity)
|
|
|
|
src_pixel.set_alpha(src_pixel.alpha() * opacity);
|
2023-05-21 19:54:05 +00:00
|
|
|
for (int yo = 0; yo < vfactor; ++yo) {
|
2019-05-11 14:53:53 +00:00
|
|
|
auto* scanline = (Color*)target.scanline(dst_y + yo);
|
|
|
|
int dst_x = dst_rect.x() + x * hfactor;
|
2023-05-21 19:54:05 +00:00
|
|
|
for (int xo = 0; xo < hfactor; ++xo) {
|
2019-05-11 03:34:20 +00:00
|
|
|
if constexpr (has_alpha_channel)
|
2019-05-11 14:53:53 +00:00
|
|
|
scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel);
|
2019-05-11 03:34:20 +00:00
|
|
|
else
|
2019-05-11 14:53:53 +00:00
|
|
|
scanline[dst_x + xo] = src_pixel;
|
2019-05-11 03:34:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-18 22:26:34 +00:00
|
|
|
template<bool has_alpha_channel, typename GetPixel>
|
|
|
|
ALWAYS_INLINE static void do_draw_box_sampled_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& clipped_rect, Gfx::Bitmap const& source, FloatRect const& src_rect, GetPixel get_pixel, float opacity)
|
|
|
|
{
|
|
|
|
float source_pixel_width = src_rect.width() / dst_rect.width();
|
|
|
|
float source_pixel_height = src_rect.height() / dst_rect.height();
|
|
|
|
float source_pixel_area = source_pixel_width * source_pixel_height;
|
|
|
|
FloatRect const pixel_box = { 0.f, 0.f, 1.f, 1.f };
|
|
|
|
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int y = clipped_rect.top(); y < clipped_rect.bottom(); ++y) {
|
2023-05-18 22:26:34 +00:00
|
|
|
auto* scanline = reinterpret_cast<Color*>(target.scanline(y));
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int x = clipped_rect.left(); x < clipped_rect.right(); ++x) {
|
2023-05-18 22:26:34 +00:00
|
|
|
// Project the destination pixel in the source image
|
|
|
|
FloatRect const source_box = {
|
|
|
|
src_rect.left() + (x - dst_rect.x()) * source_pixel_width,
|
|
|
|
src_rect.top() + (y - dst_rect.y()) * source_pixel_height,
|
|
|
|
source_pixel_width,
|
|
|
|
source_pixel_height,
|
|
|
|
};
|
|
|
|
IntRect enclosing_source_box = enclosing_int_rect(source_box).intersected(source.rect());
|
|
|
|
|
|
|
|
// Sum the contribution of all source pixels inside the projected pixel
|
|
|
|
float red_accumulator = 0.f;
|
|
|
|
float green_accumulator = 0.f;
|
|
|
|
float blue_accumulator = 0.f;
|
|
|
|
float total_area = 0.f;
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int sy = enclosing_source_box.y(); sy < enclosing_source_box.bottom(); ++sy) {
|
|
|
|
for (int sx = enclosing_source_box.x(); sx < enclosing_source_box.right(); ++sx) {
|
2023-05-21 22:58:42 +00:00
|
|
|
float area = source_box.intersected(pixel_box.translated(sx, sy)).size().area();
|
2023-05-18 22:26:34 +00:00
|
|
|
|
|
|
|
auto pixel = get_pixel(source, sx, sy);
|
|
|
|
area *= pixel.alpha() / 255.f;
|
|
|
|
|
|
|
|
red_accumulator += pixel.red() * area;
|
|
|
|
green_accumulator += pixel.green() * area;
|
|
|
|
blue_accumulator += pixel.blue() * area;
|
|
|
|
total_area += area;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Color src_pixel = {
|
|
|
|
round_to<u8>(min(red_accumulator / total_area, 255.f)),
|
|
|
|
round_to<u8>(min(green_accumulator / total_area, 255.f)),
|
|
|
|
round_to<u8>(min(blue_accumulator / total_area, 255.f)),
|
|
|
|
round_to<u8>(min(total_area * 255.f / source_pixel_area * opacity, 255.f)),
|
|
|
|
};
|
|
|
|
|
|
|
|
if constexpr (has_alpha_channel)
|
|
|
|
scanline[x] = scanline[x].blend(src_pixel);
|
|
|
|
else
|
|
|
|
scanline[x] = src_pixel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-05 08:10:38 +00:00
|
|
|
template<bool has_alpha_channel, ScalingMode scaling_mode, typename GetPixel>
|
2021-09-17 13:06:28 +00:00
|
|
|
ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& clipped_rect, Gfx::Bitmap const& source, FloatRect const& src_rect, GetPixel get_pixel, float opacity)
|
2019-05-11 03:34:20 +00:00
|
|
|
{
|
2022-03-23 08:05:37 +00:00
|
|
|
auto int_src_rect = enclosing_int_rect(src_rect);
|
|
|
|
auto clipped_src_rect = int_src_rect.intersected(source.rect());
|
2022-03-21 21:03:15 +00:00
|
|
|
if (clipped_src_rect.is_empty())
|
|
|
|
return;
|
|
|
|
|
2024-06-05 08:10:38 +00:00
|
|
|
if constexpr (scaling_mode == ScalingMode::NearestNeighbor || scaling_mode == ScalingMode::SmoothPixels) {
|
2021-09-20 18:48:56 +00:00
|
|
|
if (dst_rect == clipped_rect && int_src_rect == src_rect && !(dst_rect.width() % int_src_rect.width()) && !(dst_rect.height() % int_src_rect.height())) {
|
|
|
|
int hfactor = dst_rect.width() / int_src_rect.width();
|
|
|
|
int vfactor = dst_rect.height() / int_src_rect.height();
|
|
|
|
if (hfactor == 2 && vfactor == 2)
|
|
|
|
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 2, 2, get_pixel, opacity);
|
|
|
|
if (hfactor == 3 && vfactor == 3)
|
|
|
|
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 3, 3, get_pixel, opacity);
|
|
|
|
if (hfactor == 4 && vfactor == 4)
|
|
|
|
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 4, 4, get_pixel, opacity);
|
|
|
|
return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, hfactor, vfactor, get_pixel, opacity);
|
|
|
|
}
|
2019-05-11 03:34:20 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 08:10:38 +00:00
|
|
|
if constexpr (scaling_mode == ScalingMode::BoxSampling)
|
2023-05-18 22:26:34 +00:00
|
|
|
return do_draw_box_sampled_scaled_bitmap<has_alpha_channel>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
|
|
|
|
2023-05-18 22:26:09 +00:00
|
|
|
bool has_opacity = opacity != 1.f;
|
|
|
|
i64 shift = 1ll << 32;
|
|
|
|
i64 fractional_mask = shift - 1;
|
2022-09-06 12:06:10 +00:00
|
|
|
i64 bilinear_offset_x = (1ll << 31) * (src_rect.width() / dst_rect.width() - 1);
|
|
|
|
i64 bilinear_offset_y = (1ll << 31) * (src_rect.height() / dst_rect.height() - 1);
|
2023-05-18 22:26:09 +00:00
|
|
|
i64 hscale = src_rect.width() * shift / dst_rect.width();
|
|
|
|
i64 vscale = src_rect.height() * shift / dst_rect.height();
|
2021-09-10 17:18:49 +00:00
|
|
|
i64 src_left = src_rect.left() * shift;
|
|
|
|
i64 src_top = src_rect.top() * shift;
|
2020-07-23 18:32:39 +00:00
|
|
|
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int y = clipped_rect.top(); y < clipped_rect.bottom(); ++y) {
|
2023-05-18 22:26:09 +00:00
|
|
|
auto* scanline = reinterpret_cast<Color*>(target.scanline(y));
|
|
|
|
auto desired_y = (y - dst_rect.y()) * vscale + src_top;
|
2022-03-23 08:02:14 +00:00
|
|
|
|
2023-05-21 22:41:18 +00:00
|
|
|
for (int x = clipped_rect.left(); x < clipped_rect.right(); ++x) {
|
2023-05-18 22:26:09 +00:00
|
|
|
auto desired_x = (x - dst_rect.x()) * hscale + src_left;
|
2021-09-20 18:48:56 +00:00
|
|
|
|
|
|
|
Color src_pixel;
|
2024-06-05 08:10:38 +00:00
|
|
|
if constexpr (scaling_mode == ScalingMode::BilinearBlend) {
|
2022-09-06 12:06:10 +00:00
|
|
|
auto shifted_x = desired_x + bilinear_offset_x;
|
|
|
|
auto shifted_y = desired_y + bilinear_offset_y;
|
2021-09-20 18:48:56 +00:00
|
|
|
|
2023-05-21 22:41:18 +00:00
|
|
|
auto scaled_x0 = clamp(shifted_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
|
|
|
|
auto scaled_x1 = clamp((shifted_x >> 32) + 1, clipped_src_rect.left(), clipped_src_rect.right() - 1);
|
|
|
|
auto scaled_y0 = clamp(shifted_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
|
|
|
|
auto scaled_y1 = clamp((shifted_y >> 32) + 1, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
|
2022-09-06 12:06:10 +00:00
|
|
|
|
|
|
|
float x_ratio = (shifted_x & fractional_mask) / static_cast<float>(shift);
|
|
|
|
float y_ratio = (shifted_y & fractional_mask) / static_cast<float>(shift);
|
2021-09-20 18:48:56 +00:00
|
|
|
|
2022-03-20 23:08:52 +00:00
|
|
|
auto top_left = get_pixel(source, scaled_x0, scaled_y0);
|
|
|
|
auto top_right = get_pixel(source, scaled_x1, scaled_y0);
|
|
|
|
auto bottom_left = get_pixel(source, scaled_x0, scaled_y1);
|
|
|
|
auto bottom_right = get_pixel(source, scaled_x1, scaled_y1);
|
|
|
|
|
2023-02-16 22:22:21 +00:00
|
|
|
auto top = top_left.mixed_with(top_right, x_ratio);
|
|
|
|
auto bottom = bottom_left.mixed_with(bottom_right, x_ratio);
|
2022-03-20 23:08:52 +00:00
|
|
|
|
2023-02-16 22:22:21 +00:00
|
|
|
src_pixel = top.mixed_with(bottom, y_ratio);
|
2024-06-05 08:10:38 +00:00
|
|
|
} else if constexpr (scaling_mode == ScalingMode::SmoothPixels) {
|
2023-05-21 22:41:18 +00:00
|
|
|
auto scaled_x1 = clamp(desired_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
|
|
|
|
auto scaled_x0 = clamp(scaled_x1 - 1, clipped_src_rect.left(), clipped_src_rect.right() - 1);
|
|
|
|
auto scaled_y1 = clamp(desired_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
|
|
|
|
auto scaled_y0 = clamp(scaled_y1 - 1, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
|
2022-06-03 15:16:38 +00:00
|
|
|
|
|
|
|
float x_ratio = (desired_x & fractional_mask) / (float)shift;
|
|
|
|
float y_ratio = (desired_y & fractional_mask) / (float)shift;
|
|
|
|
|
2023-05-18 22:26:09 +00:00
|
|
|
float scaled_x_ratio = clamp(x_ratio * dst_rect.width() / (float)src_rect.width(), 0.f, 1.f);
|
|
|
|
float scaled_y_ratio = clamp(y_ratio * dst_rect.height() / (float)src_rect.height(), 0.f, 1.f);
|
2022-06-03 15:16:38 +00:00
|
|
|
|
|
|
|
auto top_left = get_pixel(source, scaled_x0, scaled_y0);
|
|
|
|
auto top_right = get_pixel(source, scaled_x1, scaled_y0);
|
|
|
|
auto bottom_left = get_pixel(source, scaled_x0, scaled_y1);
|
|
|
|
auto bottom_right = get_pixel(source, scaled_x1, scaled_y1);
|
|
|
|
|
2023-02-16 22:22:21 +00:00
|
|
|
auto top = top_left.mixed_with(top_right, scaled_x_ratio);
|
|
|
|
auto bottom = bottom_left.mixed_with(bottom_right, scaled_x_ratio);
|
2022-06-03 15:16:38 +00:00
|
|
|
|
2023-02-16 22:22:21 +00:00
|
|
|
src_pixel = top.mixed_with(bottom, scaled_y_ratio);
|
2021-09-20 18:48:56 +00:00
|
|
|
} else {
|
2023-05-21 22:41:18 +00:00
|
|
|
auto scaled_x = clamp(desired_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
|
|
|
|
auto scaled_y = clamp(desired_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
|
2021-09-20 18:48:56 +00:00
|
|
|
src_pixel = get_pixel(source, scaled_x, scaled_y);
|
|
|
|
}
|
|
|
|
|
2020-08-05 12:40:11 +00:00
|
|
|
if (has_opacity)
|
|
|
|
src_pixel.set_alpha(src_pixel.alpha() * opacity);
|
2023-05-18 22:26:09 +00:00
|
|
|
|
|
|
|
if constexpr (has_alpha_channel)
|
2019-05-11 01:53:28 +00:00
|
|
|
scanline[x] = scanline[x].blend(src_pixel);
|
2023-05-18 22:26:09 +00:00
|
|
|
else
|
2019-05-11 01:53:28 +00:00
|
|
|
scanline[x] = src_pixel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:48:56 +00:00
|
|
|
template<bool has_alpha_channel, typename GetPixel>
|
2024-06-05 08:10:38 +00:00
|
|
|
ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& clipped_rect, Gfx::Bitmap const& source, FloatRect const& src_rect, GetPixel get_pixel, float opacity, ScalingMode scaling_mode)
|
2021-09-20 18:48:56 +00:00
|
|
|
{
|
|
|
|
switch (scaling_mode) {
|
2024-06-05 08:10:38 +00:00
|
|
|
case ScalingMode::NearestNeighbor:
|
|
|
|
do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::NearestNeighbor>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
2021-09-20 18:48:56 +00:00
|
|
|
break;
|
2024-06-05 08:10:38 +00:00
|
|
|
case ScalingMode::SmoothPixels:
|
|
|
|
do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::SmoothPixels>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
2022-06-03 15:16:38 +00:00
|
|
|
break;
|
2024-06-05 08:10:38 +00:00
|
|
|
case ScalingMode::BilinearBlend:
|
|
|
|
do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::BilinearBlend>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
2023-05-18 22:26:34 +00:00
|
|
|
break;
|
2024-06-05 08:10:38 +00:00
|
|
|
case ScalingMode::BoxSampling:
|
|
|
|
do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::BoxSampling>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
2021-09-20 18:48:56 +00:00
|
|
|
break;
|
2024-06-05 08:10:38 +00:00
|
|
|
case ScalingMode::None:
|
|
|
|
do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::None>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
|
2022-10-10 19:54:06 +00:00
|
|
|
break;
|
2021-09-20 18:48:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::set_physical_pixel(IntPoint physical_point, Color color, bool blend)
|
2022-12-24 14:18:53 +00:00
|
|
|
{
|
|
|
|
// This function should only be called after translation, clipping, etc has been handled elsewhere
|
|
|
|
// if not use set_pixel().
|
2024-06-05 08:43:00 +00:00
|
|
|
auto& dst = target().scanline(physical_point.y())[physical_point.x()];
|
2022-12-17 19:55:44 +00:00
|
|
|
if (!blend || color.alpha() == 255)
|
2022-03-10 02:21:02 +00:00
|
|
|
dst = color.value();
|
2022-11-28 22:26:46 +00:00
|
|
|
else if (color.alpha())
|
2024-06-05 08:43:00 +00:00
|
|
|
dst = color_for_format(target().format(), dst).blend(color).value();
|
2018-10-12 12:58:16 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
Optional<Color> DeprecatedPainter::get_pixel(IntPoint p)
|
2022-06-15 20:05:58 +00:00
|
|
|
{
|
|
|
|
auto point = p;
|
|
|
|
point.translate_by(state().translation);
|
2024-06-05 06:17:28 +00:00
|
|
|
if (!clip_rect().contains(point))
|
2022-06-15 20:05:58 +00:00
|
|
|
return {};
|
2024-06-05 08:43:00 +00:00
|
|
|
return target().get_pixel(point);
|
2022-06-15 20:05:58 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
ALWAYS_INLINE void DeprecatedPainter::set_physical_pixel(u32& pixel, Color color)
|
2019-01-11 02:52:09 +00:00
|
|
|
{
|
2021-01-13 01:22:15 +00:00
|
|
|
// This always sets a single physical pixel, independent of scale().
|
|
|
|
// This should only be called by routines that already handle scale.
|
2024-06-04 12:58:01 +00:00
|
|
|
pixel = color.value();
|
2020-09-08 04:25:30 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
ALWAYS_INLINE void DeprecatedPainter::fill_physical_scanline(int y, int x, int width, Color color)
|
2020-09-08 04:25:30 +00:00
|
|
|
{
|
2021-01-13 01:22:15 +00:00
|
|
|
// This always draws a single physical scanline, independent of scale().
|
|
|
|
// This should only be called by routines that already handle scale.
|
2024-06-05 08:43:00 +00:00
|
|
|
fast_u32_fill(target().scanline(y) + x, color.value(), width);
|
2019-01-11 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::draw_physical_pixel(IntPoint physical_position, Color color, int thickness)
|
2019-06-23 08:00:02 +00:00
|
|
|
{
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
// This always draws a single physical pixel, independent of scale().
|
|
|
|
// This should only be called by routines that already handle scale
|
|
|
|
// (including scaling thickness).
|
2021-09-16 15:51:45 +00:00
|
|
|
if (thickness <= 0)
|
|
|
|
return;
|
|
|
|
|
2021-05-03 14:37:05 +00:00
|
|
|
if (thickness == 1) { // Implies scale() == 1.
|
2024-06-05 08:43:00 +00:00
|
|
|
auto& pixel = target().scanline(physical_position.y())[physical_position.x()];
|
|
|
|
return set_physical_pixel(pixel, color_for_format(target().format(), pixel).blend(color));
|
2021-01-12 20:00:04 +00:00
|
|
|
}
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
|
2021-05-03 14:37:05 +00:00
|
|
|
IntRect rect { physical_position, { thickness, thickness } };
|
2024-06-05 06:17:28 +00:00
|
|
|
rect.intersect(clip_rect());
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
fill_physical_rect(rect, color);
|
2019-06-23 08:00:02 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::draw_line(IntPoint a_p1, IntPoint a_p2, Color color, int thickness, LineStyle style, Color alternate_color)
|
2018-10-12 12:58:16 +00:00
|
|
|
{
|
2023-06-26 06:05:12 +00:00
|
|
|
if (clip_rect().is_empty())
|
|
|
|
return;
|
|
|
|
|
2021-09-16 15:51:45 +00:00
|
|
|
if (thickness <= 0)
|
|
|
|
return;
|
|
|
|
|
2020-06-13 18:38:36 +00:00
|
|
|
if (color.alpha() == 0)
|
|
|
|
return;
|
|
|
|
|
2024-06-05 06:17:28 +00:00
|
|
|
auto clip_rect = this->clip_rect();
|
2019-05-07 15:01:55 +00:00
|
|
|
|
2021-07-09 16:18:46 +00:00
|
|
|
auto const p1 = thickness > 1 ? a_p1.translated(-(thickness / 2), -(thickness / 2)) : a_p1;
|
|
|
|
auto const p2 = thickness > 1 ? a_p2.translated(-(thickness / 2), -(thickness / 2)) : a_p2;
|
|
|
|
|
2021-05-03 14:36:57 +00:00
|
|
|
auto point1 = to_physical(p1);
|
|
|
|
auto point2 = to_physical(p2);
|
|
|
|
|
2021-08-05 14:34:03 +00:00
|
|
|
auto alternate_color_is_transparent = alternate_color == Color::Transparent;
|
|
|
|
|
2018-10-12 12:58:16 +00:00
|
|
|
// Special case: vertical line.
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.x() == point2.x()) {
|
2021-09-17 13:06:28 +00:00
|
|
|
int const x = point1.x();
|
2023-05-21 22:41:18 +00:00
|
|
|
if (x < clip_rect.left() || x >= clip_rect.right())
|
2018-10-12 20:50:28 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.y() > point2.y())
|
|
|
|
swap(point1, point2);
|
2023-05-21 22:41:18 +00:00
|
|
|
if (point1.y() >= clip_rect.bottom())
|
2019-02-03 02:37:55 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point2.y() < clip_rect.top())
|
2019-02-03 02:37:55 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
int min_y = max(point1.y(), clip_rect.top());
|
2023-05-21 22:41:18 +00:00
|
|
|
int max_y = min(point2.y(), clip_rect.bottom() - 1);
|
2020-05-10 10:58:33 +00:00
|
|
|
if (style == LineStyle::Dotted) {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int y = min_y; y <= max_y; y += thickness * 2)
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2020-05-10 15:17:26 +00:00
|
|
|
} else if (style == LineStyle::Dashed) {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int y = min_y; y <= max_y; y += thickness * 6) {
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2021-05-03 14:37:05 +00:00
|
|
|
draw_physical_pixel({ x, min(y + thickness, max_y) }, color, thickness);
|
|
|
|
draw_physical_pixel({ x, min(y + thickness * 2, max_y) }, color, thickness);
|
2021-08-05 14:34:03 +00:00
|
|
|
if (!alternate_color_is_transparent) {
|
|
|
|
draw_physical_pixel({ x, min(y + thickness * 3, max_y) }, alternate_color, thickness);
|
|
|
|
draw_physical_pixel({ x, min(y + thickness * 4, max_y) }, alternate_color, thickness);
|
|
|
|
draw_physical_pixel({ x, min(y + thickness * 5, max_y) }, alternate_color, thickness);
|
|
|
|
}
|
2020-05-10 15:17:26 +00:00
|
|
|
}
|
2019-11-27 19:52:11 +00:00
|
|
|
} else {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int y = min_y; y <= max_y; y += thickness)
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2021-10-02 16:45:07 +00:00
|
|
|
draw_physical_pixel({ x, max_y }, color, thickness);
|
2019-11-27 19:52:11 +00:00
|
|
|
}
|
2018-10-12 12:58:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case: horizontal line.
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.y() == point2.y()) {
|
2021-09-17 13:06:28 +00:00
|
|
|
int const y = point1.y();
|
2023-05-21 22:41:18 +00:00
|
|
|
if (y < clip_rect.top() || y >= clip_rect.bottom())
|
2018-10-12 20:50:28 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.x() > point2.x())
|
|
|
|
swap(point1, point2);
|
2023-05-21 22:41:18 +00:00
|
|
|
if (point1.x() >= clip_rect.right())
|
2019-02-03 02:37:55 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point2.x() < clip_rect.left())
|
2019-02-03 02:37:55 +00:00
|
|
|
return;
|
2021-05-03 14:36:57 +00:00
|
|
|
int min_x = max(point1.x(), clip_rect.left());
|
2023-05-21 22:41:18 +00:00
|
|
|
int max_x = min(point2.x(), clip_rect.right() - 1);
|
2020-05-10 10:58:33 +00:00
|
|
|
if (style == LineStyle::Dotted) {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int x = min_x; x <= max_x; x += thickness * 2)
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2020-05-10 15:17:26 +00:00
|
|
|
} else if (style == LineStyle::Dashed) {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int x = min_x; x <= max_x; x += thickness * 6) {
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2021-05-03 14:37:05 +00:00
|
|
|
draw_physical_pixel({ min(x + thickness, max_x), y }, color, thickness);
|
|
|
|
draw_physical_pixel({ min(x + thickness * 2, max_x), y }, color, thickness);
|
2021-08-05 14:34:03 +00:00
|
|
|
if (!alternate_color_is_transparent) {
|
|
|
|
draw_physical_pixel({ min(x + thickness * 3, max_x), y }, alternate_color, thickness);
|
|
|
|
draw_physical_pixel({ min(x + thickness * 4, max_x), y }, alternate_color, thickness);
|
|
|
|
draw_physical_pixel({ min(x + thickness * 5, max_x), y }, alternate_color, thickness);
|
|
|
|
}
|
2020-05-10 15:17:26 +00:00
|
|
|
}
|
2019-11-27 19:52:11 +00:00
|
|
|
} else {
|
2021-05-03 14:37:05 +00:00
|
|
|
for (int x = min_x; x <= max_x; x += thickness)
|
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 20:53:07 +00:00
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
2021-10-02 16:45:07 +00:00
|
|
|
draw_physical_pixel({ max_x, y }, color, thickness);
|
2019-11-27 19:52:11 +00:00
|
|
|
}
|
2018-10-12 12:58:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-17 13:06:28 +00:00
|
|
|
int const adx = abs(point2.x() - point1.x());
|
|
|
|
int const ady = abs(point2.y() - point1.y());
|
2019-05-07 15:01:55 +00:00
|
|
|
|
|
|
|
if (adx > ady) {
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.x() > point2.x())
|
|
|
|
swap(point1, point2);
|
2019-05-07 15:01:55 +00:00
|
|
|
} else {
|
2021-05-03 14:36:57 +00:00
|
|
|
if (point1.y() > point2.y())
|
|
|
|
swap(point1, point2);
|
2019-05-07 15:01:55 +00:00
|
|
|
}
|
2018-10-12 20:50:28 +00:00
|
|
|
|
2021-09-17 13:06:28 +00:00
|
|
|
int const dx = point2.x() - point1.x();
|
|
|
|
int const dy = point2.y() - point1.y();
|
2021-04-03 14:11:35 +00:00
|
|
|
int error = 0;
|
2019-05-07 15:01:55 +00:00
|
|
|
|
2023-01-12 11:56:23 +00:00
|
|
|
size_t number_of_pixels_drawn = 0;
|
|
|
|
|
|
|
|
auto draw_pixel_in_line = [&](int x, int y) {
|
|
|
|
bool should_draw_line = true;
|
|
|
|
if (style == LineStyle::Dotted && number_of_pixels_drawn % 2 == 1)
|
|
|
|
should_draw_line = false;
|
|
|
|
else if (style == LineStyle::Dashed && number_of_pixels_drawn % 6 >= 3)
|
|
|
|
should_draw_line = false;
|
|
|
|
|
|
|
|
if (should_draw_line)
|
|
|
|
draw_physical_pixel({ x, y }, color, thickness);
|
|
|
|
else if (!alternate_color_is_transparent)
|
|
|
|
draw_physical_pixel({ x, y }, alternate_color, thickness);
|
|
|
|
|
|
|
|
number_of_pixels_drawn++;
|
|
|
|
};
|
|
|
|
|
2019-05-07 15:01:55 +00:00
|
|
|
if (dx > dy) {
|
2021-09-17 13:06:28 +00:00
|
|
|
int const y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
|
|
|
|
int const delta_error = 2 * abs(dy);
|
2021-05-03 14:36:57 +00:00
|
|
|
int y = point1.y();
|
|
|
|
for (int x = point1.x(); x <= point2.x(); ++x) {
|
2019-05-07 15:01:55 +00:00
|
|
|
if (clip_rect.contains(x, y))
|
2023-01-12 11:56:23 +00:00
|
|
|
draw_pixel_in_line(x, y);
|
2019-05-07 15:01:55 +00:00
|
|
|
error += delta_error;
|
2021-04-03 14:11:35 +00:00
|
|
|
if (error >= dx) {
|
|
|
|
y += y_step;
|
|
|
|
error -= 2 * dx;
|
2019-05-07 15:01:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-09-17 13:06:28 +00:00
|
|
|
int const x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
|
|
|
|
int const delta_error = 2 * abs(dx);
|
2021-05-03 14:36:57 +00:00
|
|
|
int x = point1.x();
|
|
|
|
for (int y = point1.y(); y <= point2.y(); ++y) {
|
2019-05-07 15:01:55 +00:00
|
|
|
if (clip_rect.contains(x, y))
|
2023-01-12 11:56:23 +00:00
|
|
|
draw_pixel_in_line(x, y);
|
2019-05-07 15:01:55 +00:00
|
|
|
error += delta_error;
|
2021-04-03 14:11:35 +00:00
|
|
|
if (error >= dy) {
|
|
|
|
x += x_step;
|
|
|
|
error -= 2 * dy;
|
2019-05-07 15:01:55 +00:00
|
|
|
}
|
2018-10-12 12:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-13 18:59:25 +00:00
|
|
|
|
2022-12-06 20:57:07 +00:00
|
|
|
static bool can_approximate_bezier_curve(FloatPoint p1, FloatPoint p2, FloatPoint control)
|
2020-10-20 22:03:07 +00:00
|
|
|
{
|
2024-01-08 00:14:45 +00:00
|
|
|
// TODO: Somehow calculate the required number of splits based on the curve (and its size).
|
|
|
|
constexpr float tolerance = 0.5f;
|
2020-10-20 22:03:07 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-05-11 21:20:38 +00:00
|
|
|
auto error = max(p1x, p2x) + max(p1y, p2y);
|
|
|
|
VERIFY(isfinite(error));
|
|
|
|
|
|
|
|
return error <= tolerance;
|
2020-10-20 22:03:07 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::for_each_line_segment_on_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>& callback)
|
2020-05-05 01:15:17 +00:00
|
|
|
{
|
2021-04-14 23:25:07 +00:00
|
|
|
struct SegmentDescriptor {
|
|
|
|
FloatPoint control_point;
|
|
|
|
FloatPoint p1;
|
|
|
|
FloatPoint p2;
|
|
|
|
};
|
2020-05-05 01:15:17 +00:00
|
|
|
|
2022-12-06 20:57:07 +00:00
|
|
|
static constexpr auto split_quadratic_bezier_curve = [](FloatPoint original_control, FloatPoint p1, FloatPoint p2, auto& segments) {
|
2021-04-14 23:25:07 +00:00
|
|
|
auto po1_midpoint = original_control + p1;
|
|
|
|
po1_midpoint /= 2;
|
2020-05-06 07:25:12 +00:00
|
|
|
|
2021-04-14 23:25:07 +00:00
|
|
|
auto po2_midpoint = original_control + p2;
|
|
|
|
po2_midpoint /= 2;
|
2020-07-22 06:46:15 +00:00
|
|
|
|
2021-04-14 23:25:07 +00:00
|
|
|
auto new_segment = po1_midpoint + po2_midpoint;
|
|
|
|
new_segment /= 2;
|
2020-07-22 06:46:15 +00:00
|
|
|
|
2023-12-10 18:44:00 +00:00
|
|
|
segments.append({ po2_midpoint, new_segment, p2 });
|
|
|
|
segments.append({ po1_midpoint, p1, new_segment });
|
2021-04-14 23:25:07 +00:00
|
|
|
};
|
|
|
|
|
2023-12-10 18:44:00 +00:00
|
|
|
Vector<SegmentDescriptor> segments;
|
|
|
|
segments.append({ control_point, p1, p2 });
|
2021-04-14 23:25:07 +00:00
|
|
|
while (!segments.is_empty()) {
|
2023-12-10 18:44:00 +00:00
|
|
|
auto segment = segments.take_last();
|
2020-07-22 06:46:15 +00:00
|
|
|
|
2021-04-14 23:25:07 +00:00
|
|
|
if (can_approximate_bezier_curve(segment.p1, segment.p2, segment.control_point))
|
|
|
|
callback(segment.p1, segment.p2);
|
|
|
|
else
|
|
|
|
split_quadratic_bezier_curve(segment.control_point, segment.p1, segment.p2, segments);
|
|
|
|
}
|
|
|
|
}
|
2020-07-22 06:46:15 +00:00
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::for_each_line_segment_on_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>&& callback)
|
2021-04-14 23:25:07 +00:00
|
|
|
{
|
|
|
|
for_each_line_segment_on_bezier_curve(control_point, p1, p2, callback);
|
2020-07-22 06:46:15 +00:00
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>&& callback)
|
2021-09-17 14:12:30 +00:00
|
|
|
{
|
|
|
|
for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, callback);
|
|
|
|
}
|
|
|
|
|
2022-12-06 20:57:07 +00:00
|
|
|
static bool can_approximate_cubic_bezier_curve(FloatPoint p1, FloatPoint p2, FloatPoint control_0, FloatPoint control_1)
|
2021-09-17 14:12:30 +00:00
|
|
|
{
|
2024-01-08 00:14:45 +00:00
|
|
|
// TODO: Somehow calculate the required number of splits based on the curve (and its size).
|
|
|
|
constexpr float tolerance = 0.5f;
|
2021-09-17 14:12:30 +00:00
|
|
|
|
|
|
|
auto ax = 3 * control_0.x() - 2 * p1.x() - p2.x();
|
|
|
|
auto ay = 3 * control_0.y() - 2 * p1.y() - p2.y();
|
|
|
|
auto bx = 3 * control_1.x() - p1.x() - 2 * p2.x();
|
|
|
|
auto by = 3 * control_1.y() - p1.y() - 2 * p2.y();
|
|
|
|
|
|
|
|
ax *= ax;
|
|
|
|
ay *= ay;
|
|
|
|
bx *= bx;
|
|
|
|
by *= by;
|
|
|
|
|
2023-05-11 21:20:38 +00:00
|
|
|
auto error = max(ax, bx) + max(ay, by);
|
|
|
|
VERIFY(isfinite(error));
|
|
|
|
|
|
|
|
return error <= tolerance;
|
2021-09-17 14:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::for_each_line_segment_on_cubic_bezier_curve(FloatPoint control_point_0, FloatPoint control_point_1, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>& callback)
|
2021-09-17 14:12:30 +00:00
|
|
|
{
|
|
|
|
struct ControlPair {
|
|
|
|
FloatPoint control_point_0;
|
|
|
|
FloatPoint control_point_1;
|
|
|
|
};
|
|
|
|
struct SegmentDescriptor {
|
|
|
|
ControlPair control_points;
|
|
|
|
FloatPoint p1;
|
|
|
|
FloatPoint p2;
|
|
|
|
};
|
|
|
|
|
2022-12-06 20:57:07 +00:00
|
|
|
static constexpr auto split_cubic_bezier_curve = [](ControlPair const& original_controls, FloatPoint p1, FloatPoint p2, auto& segments) {
|
2021-09-17 14:12:30 +00:00
|
|
|
Array level_1_midpoints {
|
|
|
|
(p1 + original_controls.control_point_0) / 2,
|
|
|
|
(original_controls.control_point_0 + original_controls.control_point_1) / 2,
|
|
|
|
(original_controls.control_point_1 + p2) / 2,
|
|
|
|
};
|
|
|
|
Array level_2_midpoints {
|
|
|
|
(level_1_midpoints[0] + level_1_midpoints[1]) / 2,
|
|
|
|
(level_1_midpoints[1] + level_1_midpoints[2]) / 2,
|
|
|
|
};
|
|
|
|
auto level_3_midpoint = (level_2_midpoints[0] + level_2_midpoints[1]) / 2;
|
|
|
|
|
2023-12-10 18:44:00 +00:00
|
|
|
segments.append({ { level_2_midpoints[1], level_1_midpoints[2] }, level_3_midpoint, p2 });
|
|
|
|
segments.append({ { level_1_midpoints[0], level_2_midpoints[0] }, p1, level_3_midpoint });
|
2021-09-17 14:12:30 +00:00
|
|
|
};
|
|
|
|
|
2023-12-10 18:44:00 +00:00
|
|
|
Vector<SegmentDescriptor> segments;
|
|
|
|
segments.append({ { control_point_0, control_point_1 }, p1, p2 });
|
2021-09-17 14:12:30 +00:00
|
|
|
while (!segments.is_empty()) {
|
2023-12-10 18:44:00 +00:00
|
|
|
auto segment = segments.take_last();
|
2021-09-17 14:12:30 +00:00
|
|
|
|
|
|
|
if (can_approximate_cubic_bezier_curve(segment.p1, segment.p2, segment.control_points.control_point_0, segment.control_points.control_point_1))
|
|
|
|
callback(segment.p1, segment.p2);
|
|
|
|
else
|
|
|
|
split_cubic_bezier_curve(segment.control_points, segment.p1, segment.p2, segments);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 08:40:29 +00:00
|
|
|
void DeprecatedPainter::add_clip_rect(IntRect const& rect)
|
2019-01-24 22:40:12 +00:00
|
|
|
{
|
2021-05-03 14:37:05 +00:00
|
|
|
state().clip_rect.intersect(rect.translated(translation()));
|
2024-06-05 08:43:00 +00:00
|
|
|
state().clip_rect.intersect(target().rect()); // FIXME: This shouldn't be necessary?
|
2019-01-24 22:40:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-08 08:22:03 +00:00
|
|
|
void DeprecatedPainter::stroke_path(DeprecatedPath const& path, Color color, int thickness)
|
2020-04-16 19:03:17 +00:00
|
|
|
{
|
2021-09-16 15:51:45 +00:00
|
|
|
if (thickness <= 0)
|
|
|
|
return;
|
2023-07-16 15:27:30 +00:00
|
|
|
fill_path(path.stroke_to_fill(thickness), color);
|
2020-04-16 19:03:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 10:56:38 +00:00
|
|
|
}
|