2020-04-06 11:09:01 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-04-06 11:09:01 +02:00
|
|
|
*/
|
|
|
|
|
2021-04-25 21:10:55 +02:00
|
|
|
#include <AK/ExtraMathConstants.h>
|
2020-03-19 19:07:56 +01:00
|
|
|
#include <AK/OwnPtr.h>
|
|
|
|
#include <LibGfx/Painter.h>
|
2020-06-22 18:39:22 +02:00
|
|
|
#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
|
2020-07-26 15:08:16 +02:00
|
|
|
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
|
|
|
|
#include <LibWeb/HTML/HTMLCanvasElement.h>
|
|
|
|
#include <LibWeb/HTML/HTMLImageElement.h>
|
|
|
|
#include <LibWeb/HTML/ImageData.h>
|
2020-03-19 19:07:56 +01:00
|
|
|
|
2020-07-28 18:20:36 +02:00
|
|
|
namespace Web::HTML {
|
2020-03-19 19:07:56 +01:00
|
|
|
|
|
|
|
CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement& element)
|
AK: Make RefPtr, NonnullRefPtr, WeakPtr thread safe
This makes most operations thread safe, especially so that they
can safely be used in the Kernel. This includes obtaining a strong
reference from a weak reference, which now requires an explicit
call to WeakPtr::strong_ref(). Another major change is that
Weakable::make_weak_ref() may require the explicit target type.
Previously we used reinterpret_cast in WeakPtr, assuming that it
can be properly converted. But WeakPtr does not necessarily have
the knowledge to be able to do this. Instead, we now ask the class
itself to deliver a WeakPtr to the type that we want.
Also, WeakLink is no longer specific to a target type. The reason
for this is that we want to be able to safely convert e.g. WeakPtr<T>
to WeakPtr<U>, and before this we just reinterpret_cast the internal
WeakLink<T> to WeakLink<U>, which is a bold assumption that it would
actually produce the correct code. Instead, WeakLink now operates
on just a raw pointer and we only make those constructors/operators
available if we can verify that it can be safely cast.
In order to guarantee thread safety, we now use the least significant
bit in the pointer for locking purposes. This also means that only
properly aligned pointers can be used.
2020-09-29 16:26:13 -06:00
|
|
|
: m_element(element)
|
2020-03-19 19:07:56 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
CanvasRenderingContext2D::~CanvasRenderingContext2D()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasRenderingContext2D::set_fill_style(String style)
|
|
|
|
{
|
|
|
|
m_fill_style = Gfx::Color::from_string(style).value_or(Color::Black);
|
|
|
|
}
|
|
|
|
|
|
|
|
String CanvasRenderingContext2D::fill_style() const
|
|
|
|
{
|
|
|
|
return m_fill_style.to_string();
|
|
|
|
}
|
|
|
|
|
2020-04-08 11:22:40 +02:00
|
|
|
void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height)
|
2020-03-19 19:07:56 +01:00
|
|
|
{
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
2020-04-12 19:22:42 +02:00
|
|
|
auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
|
2020-04-08 11:22:40 +02:00
|
|
|
painter->fill_rect(enclosing_int_rect(rect), m_fill_style);
|
2020-03-22 21:15:49 +01:00
|
|
|
did_draw(rect);
|
|
|
|
}
|
|
|
|
|
2021-03-15 19:40:42 +01:00
|
|
|
void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float height)
|
|
|
|
{
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
|
|
|
|
painter->clear_rect(enclosing_int_rect(rect), Color());
|
|
|
|
did_draw(rect);
|
|
|
|
}
|
|
|
|
|
2020-04-07 01:09:17 -07:00
|
|
|
void CanvasRenderingContext2D::set_stroke_style(String style)
|
|
|
|
{
|
|
|
|
m_stroke_style = Gfx::Color::from_string(style).value_or(Color::Black);
|
|
|
|
}
|
|
|
|
|
|
|
|
String CanvasRenderingContext2D::stroke_style() const
|
|
|
|
{
|
|
|
|
return m_fill_style.to_string();
|
|
|
|
}
|
|
|
|
|
2020-04-08 11:22:40 +02:00
|
|
|
void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height)
|
2020-04-07 01:09:17 -07:00
|
|
|
{
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
2020-04-12 19:22:42 +02:00
|
|
|
auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
|
2020-04-16 21:12:14 +02:00
|
|
|
|
2020-07-25 21:31:47 -07:00
|
|
|
auto top_left = m_transform.map(Gfx::FloatPoint(x, y)).to_type<int>();
|
|
|
|
auto top_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y)).to_type<int>();
|
|
|
|
auto bottom_left = m_transform.map(Gfx::FloatPoint(x, y + height - 1)).to_type<int>();
|
|
|
|
auto bottom_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)).to_type<int>();
|
2020-04-16 21:12:14 +02:00
|
|
|
|
|
|
|
painter->draw_line(top_left, top_right, m_stroke_style, m_line_width);
|
|
|
|
painter->draw_line(top_right, bottom_right, m_stroke_style, m_line_width);
|
|
|
|
painter->draw_line(bottom_right, bottom_left, m_stroke_style, m_line_width);
|
|
|
|
painter->draw_line(bottom_left, top_left, m_stroke_style, m_line_width);
|
|
|
|
|
2020-04-07 01:09:17 -07:00
|
|
|
did_draw(rect);
|
|
|
|
}
|
|
|
|
|
2020-04-14 20:38:44 +02:00
|
|
|
void CanvasRenderingContext2D::draw_image(const HTMLImageElement& image_element, float x, float y)
|
|
|
|
{
|
|
|
|
if (!image_element.bitmap())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto src_rect = image_element.bitmap()->rect();
|
|
|
|
Gfx::FloatRect dst_rect = { x, y, (float)image_element.bitmap()->width(), (float)image_element.bitmap()->height() };
|
|
|
|
auto rect = m_transform.map(dst_rect);
|
|
|
|
|
2021-09-21 15:36:32 +01:00
|
|
|
painter->draw_scaled_bitmap(rounded_int_rect(rect), *image_element.bitmap(), src_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend);
|
2020-04-14 20:38:44 +02:00
|
|
|
}
|
|
|
|
|
2020-04-08 11:22:40 +02:00
|
|
|
void CanvasRenderingContext2D::scale(float sx, float sy)
|
2020-04-04 23:54:58 +02:00
|
|
|
{
|
2021-01-12 14:11:44 +01:00
|
|
|
dbgln("CanvasRenderingContext2D::scale({}, {})", sx, sy);
|
2020-04-12 19:22:42 +02:00
|
|
|
m_transform.scale(sx, sy);
|
2020-04-04 23:54:58 +02:00
|
|
|
}
|
|
|
|
|
2020-04-12 19:22:42 +02:00
|
|
|
void CanvasRenderingContext2D::translate(float tx, float ty)
|
2020-04-04 23:54:58 +02:00
|
|
|
{
|
2021-01-12 14:11:44 +01:00
|
|
|
dbgln("CanvasRenderingContext2D::translate({}, {})", tx, ty);
|
2020-04-12 19:22:42 +02:00
|
|
|
m_transform.translate(tx, ty);
|
2020-04-07 01:09:17 -07:00
|
|
|
}
|
|
|
|
|
2020-06-26 18:26:18 +02:00
|
|
|
void CanvasRenderingContext2D::rotate(float radians)
|
|
|
|
{
|
2021-01-12 14:11:44 +01:00
|
|
|
dbgln("CanvasRenderingContext2D::rotate({})", radians);
|
2020-06-26 18:26:18 +02:00
|
|
|
m_transform.rotate_radians(radians);
|
|
|
|
}
|
|
|
|
|
2020-04-08 11:22:40 +02:00
|
|
|
void CanvasRenderingContext2D::did_draw(const Gfx::FloatRect&)
|
2020-03-22 21:15:49 +01:00
|
|
|
{
|
|
|
|
// FIXME: Make use of the rect to reduce the invalidated area when possible.
|
|
|
|
if (!m_element)
|
|
|
|
return;
|
|
|
|
if (!m_element->layout_node())
|
|
|
|
return;
|
|
|
|
m_element->layout_node()->set_needs_display();
|
2020-03-19 19:07:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
OwnPtr<Gfx::Painter> CanvasRenderingContext2D::painter()
|
|
|
|
{
|
|
|
|
if (!m_element)
|
2021-01-10 16:29:28 -07:00
|
|
|
return {};
|
2020-03-19 19:07:56 +01:00
|
|
|
|
2020-04-15 12:12:19 +02:00
|
|
|
if (!m_element->bitmap()) {
|
|
|
|
if (!m_element->create_bitmap())
|
2021-01-10 16:29:28 -07:00
|
|
|
return {};
|
2020-04-15 12:12:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return make<Gfx::Painter>(*m_element->bitmap());
|
2020-03-19 19:07:56 +01:00
|
|
|
}
|
|
|
|
|
2021-04-15 20:36:10 +03:00
|
|
|
void CanvasRenderingContext2D::fill_text(const String& text, float x, float y, Optional<double> max_width)
|
|
|
|
{
|
|
|
|
if (max_width.has_value() && max_width.value() <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// FIXME: painter only supports integer rects for text right now, so this effectively chops off any fractional position
|
|
|
|
auto text_rect = Gfx::IntRect(x, y, max_width.has_value() ? max_width.value() : painter->font().width(text), painter->font().glyph_height());
|
|
|
|
auto transformed_rect = m_transform.map(text_rect);
|
|
|
|
painter->draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, m_fill_style);
|
2021-04-12 11:47:09 -07:00
|
|
|
did_draw(transformed_rect.to_type<float>());
|
2021-04-15 20:36:10 +03:00
|
|
|
}
|
|
|
|
|
2020-04-16 21:06:03 +02:00
|
|
|
void CanvasRenderingContext2D::begin_path()
|
|
|
|
{
|
|
|
|
m_path = Gfx::Path();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasRenderingContext2D::close_path()
|
|
|
|
{
|
|
|
|
m_path.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasRenderingContext2D::move_to(float x, float y)
|
|
|
|
{
|
|
|
|
m_path.move_to({ x, y });
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasRenderingContext2D::line_to(float x, float y)
|
|
|
|
{
|
|
|
|
m_path.line_to({ x, y });
|
|
|
|
}
|
|
|
|
|
2020-05-05 06:54:26 +04:30
|
|
|
void CanvasRenderingContext2D::quadratic_curve_to(float cx, float cy, float x, float y)
|
2020-04-16 21:06:03 +02:00
|
|
|
{
|
2020-05-05 06:54:26 +04:30
|
|
|
m_path.quadratic_bezier_curve_to({ cx, cy }, { x, y });
|
|
|
|
}
|
2020-04-16 21:06:03 +02:00
|
|
|
|
2021-04-15 20:38:43 +03:00
|
|
|
DOM::ExceptionOr<void> CanvasRenderingContext2D::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
|
2021-04-15 18:41:13 +04:30
|
|
|
{
|
2021-04-15 20:38:43 +03:00
|
|
|
if (radius < 0)
|
|
|
|
return DOM::IndexSizeError::create(String::formatted("The radius provided ({}) is negative.", radius));
|
|
|
|
return ellipse(x, y, radius, radius, 0, start_angle, end_angle, counter_clockwise);
|
2021-04-15 18:41:13 +04:30
|
|
|
}
|
|
|
|
|
2021-04-15 20:38:43 +03:00
|
|
|
DOM::ExceptionOr<void> CanvasRenderingContext2D::ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise)
|
2021-04-15 18:41:13 +04:30
|
|
|
{
|
2021-04-15 20:38:43 +03:00
|
|
|
if (radius_x < 0)
|
|
|
|
return DOM::IndexSizeError::create(String::formatted("The major-axis radius provided ({}) is negative.", radius_x));
|
|
|
|
|
|
|
|
if (radius_y < 0)
|
|
|
|
return DOM::IndexSizeError::create(String::formatted("The minor-axis radius provided ({}) is negative.", radius_y));
|
|
|
|
|
2021-04-15 00:36:14 -07:00
|
|
|
if (constexpr float tau = M_TAU; (!counter_clockwise && (end_angle - start_angle) >= tau)
|
|
|
|
|| (counter_clockwise && (start_angle - end_angle) >= tau)) {
|
2021-04-15 18:41:13 +04:30
|
|
|
start_angle = 0;
|
2021-04-15 00:36:14 -07:00
|
|
|
end_angle = tau;
|
2021-04-15 18:41:13 +04:30
|
|
|
} else {
|
2021-04-15 00:36:14 -07:00
|
|
|
start_angle = fmodf(start_angle, tau);
|
|
|
|
end_angle = fmodf(end_angle, tau);
|
2021-04-15 18:41:13 +04:30
|
|
|
}
|
|
|
|
|
|
|
|
// Then, figure out where the ends of the arc are.
|
|
|
|
// To do so, we can pretend that the center of this ellipse is at (0, 0),
|
|
|
|
// and the whole coordinate system is rotated `rotation` radians around the x axis, centered on `center`.
|
|
|
|
// The sign of the resulting relative positions is just whether our angle is on one of the left quadrants.
|
|
|
|
auto sin_rotation = sinf(rotation);
|
|
|
|
auto cos_rotation = cosf(rotation);
|
|
|
|
|
|
|
|
auto resolve_point_with_angle = [&](float angle) {
|
|
|
|
auto tan_relative = tanf(angle);
|
|
|
|
auto tan2 = tan_relative * tan_relative;
|
|
|
|
|
|
|
|
auto ab = radius_x * radius_y;
|
|
|
|
auto a2 = radius_x * radius_x;
|
|
|
|
auto b2 = radius_y * radius_y;
|
|
|
|
auto sqrt = sqrtf(b2 + a2 * tan2);
|
|
|
|
|
|
|
|
auto relative_x_position = ab / sqrt;
|
|
|
|
auto relative_y_position = ab * tan_relative / sqrt;
|
|
|
|
|
|
|
|
// Make sure to set the correct sign
|
|
|
|
float sn = sinf(angle) >= 0 ? 1 : -1;
|
|
|
|
relative_x_position *= sn;
|
|
|
|
relative_y_position *= sn;
|
|
|
|
|
|
|
|
// Now rotate it (back) around the center point by 'rotation' radians, then move it back to our actual origin.
|
|
|
|
auto relative_rotated_x_position = relative_x_position * cos_rotation - relative_y_position * sin_rotation;
|
|
|
|
auto relative_rotated_y_position = relative_x_position * sin_rotation + relative_y_position * cos_rotation;
|
|
|
|
return Gfx::FloatPoint { relative_rotated_x_position + x, relative_rotated_y_position + y };
|
|
|
|
};
|
|
|
|
|
|
|
|
auto start_point = resolve_point_with_angle(start_angle);
|
|
|
|
auto end_point = resolve_point_with_angle(end_angle);
|
|
|
|
|
|
|
|
m_path.move_to(start_point);
|
|
|
|
|
2021-04-15 00:36:14 -07:00
|
|
|
double delta_theta = end_angle - start_angle;
|
2021-04-15 18:41:13 +04:30
|
|
|
|
|
|
|
// FIXME: This is still goofy for some values.
|
|
|
|
m_path.elliptical_arc_to(end_point, { radius_x, radius_y }, rotation, delta_theta > M_PI, !counter_clockwise);
|
|
|
|
|
|
|
|
m_path.close();
|
2021-04-15 20:38:43 +03:00
|
|
|
return {};
|
2021-04-15 18:41:13 +04:30
|
|
|
}
|
|
|
|
|
2021-04-14 23:29:14 +03:00
|
|
|
void CanvasRenderingContext2D::rect(float x, float y, float width, float height)
|
|
|
|
{
|
|
|
|
m_path.move_to({ x, y });
|
|
|
|
if (width == 0 || height == 0)
|
|
|
|
return;
|
|
|
|
m_path.line_to({ x + width, y });
|
|
|
|
m_path.line_to({ x + width, y + height });
|
|
|
|
m_path.line_to({ x, y + height });
|
|
|
|
m_path.close();
|
|
|
|
}
|
|
|
|
|
2020-05-05 06:54:26 +04:30
|
|
|
void CanvasRenderingContext2D::stroke()
|
|
|
|
{
|
2020-04-16 21:06:03 +02:00
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
painter->stroke_path(m_path, m_stroke_style, m_line_width);
|
2021-04-14 23:22:45 +03:00
|
|
|
did_draw(m_path.bounding_box());
|
2020-04-16 21:06:03 +02:00
|
|
|
}
|
|
|
|
|
2020-05-06 11:56:47 +04:30
|
|
|
void CanvasRenderingContext2D::fill(Gfx::Painter::WindingRule winding)
|
|
|
|
{
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
2020-05-10 01:47:43 +04:30
|
|
|
auto path = m_path;
|
|
|
|
path.close_all_subpaths();
|
|
|
|
painter->fill_path(path, m_fill_style, winding);
|
2021-04-14 23:22:45 +03:00
|
|
|
did_draw(m_path.bounding_box());
|
2020-05-06 11:56:47 +04:30
|
|
|
}
|
|
|
|
|
2020-06-22 18:39:22 +02:00
|
|
|
void CanvasRenderingContext2D::fill(const String& fill_rule)
|
2020-04-21 23:49:51 +02:00
|
|
|
{
|
2020-06-22 18:39:22 +02:00
|
|
|
if (fill_rule == "evenodd")
|
|
|
|
return fill(Gfx::Painter::WindingRule::EvenOdd);
|
|
|
|
return fill(Gfx::Painter::WindingRule::Nonzero);
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<ImageData> CanvasRenderingContext2D::create_image_data(int width, int height) const
|
|
|
|
{
|
|
|
|
if (!wrapper()) {
|
2021-01-09 18:51:44 +01:00
|
|
|
dbgln("Hmm! Attempted to create ImageData for wrapper-less CRC2D.");
|
2021-01-10 16:29:28 -07:00
|
|
|
return {};
|
2020-06-22 18:39:22 +02:00
|
|
|
}
|
|
|
|
return ImageData::create_with_size(wrapper()->global_object(), width, height);
|
2020-04-21 23:49:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasRenderingContext2D::put_image_data(const ImageData& image_data, float x, float y)
|
|
|
|
{
|
|
|
|
auto painter = this->painter();
|
|
|
|
if (!painter)
|
|
|
|
return;
|
|
|
|
|
2020-06-10 10:57:59 +02:00
|
|
|
painter->blit(Gfx::IntPoint(x, y), image_data.bitmap(), image_data.bitmap().rect());
|
2020-04-22 00:09:23 +02:00
|
|
|
|
|
|
|
did_draw(Gfx::FloatRect(x, y, image_data.width(), image_data.height()));
|
2020-04-21 23:49:51 +02:00
|
|
|
}
|
|
|
|
|
2020-03-19 19:07:56 +01:00
|
|
|
}
|