CanvasRenderingContext2D.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/ExtraMathConstants.h>
  7. #include <AK/OwnPtr.h>
  8. #include <LibGfx/Painter.h>
  9. #include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
  10. #include <LibWeb/HTML/CanvasRenderingContext2D.h>
  11. #include <LibWeb/HTML/HTMLCanvasElement.h>
  12. #include <LibWeb/HTML/HTMLImageElement.h>
  13. #include <LibWeb/HTML/ImageData.h>
  14. namespace Web::HTML {
  15. CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement& element)
  16. : m_element(element)
  17. {
  18. }
  19. CanvasRenderingContext2D::~CanvasRenderingContext2D()
  20. {
  21. }
  22. void CanvasRenderingContext2D::set_fill_style(String style)
  23. {
  24. m_fill_style = Gfx::Color::from_string(style).value_or(Color::Black);
  25. }
  26. String CanvasRenderingContext2D::fill_style() const
  27. {
  28. return m_fill_style.to_string();
  29. }
  30. void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height)
  31. {
  32. auto painter = this->painter();
  33. if (!painter)
  34. return;
  35. auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
  36. painter->fill_rect(enclosing_int_rect(rect), m_fill_style);
  37. did_draw(rect);
  38. }
  39. void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float height)
  40. {
  41. auto painter = this->painter();
  42. if (!painter)
  43. return;
  44. auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
  45. painter->clear_rect(enclosing_int_rect(rect), Color());
  46. did_draw(rect);
  47. }
  48. void CanvasRenderingContext2D::set_stroke_style(String style)
  49. {
  50. m_stroke_style = Gfx::Color::from_string(style).value_or(Color::Black);
  51. }
  52. String CanvasRenderingContext2D::stroke_style() const
  53. {
  54. return m_stroke_style.to_string();
  55. }
  56. void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height)
  57. {
  58. auto painter = this->painter();
  59. if (!painter)
  60. return;
  61. auto rect = m_transform.map(Gfx::FloatRect(x, y, width, height));
  62. auto top_left = m_transform.map(Gfx::FloatPoint(x, y)).to_type<int>();
  63. auto top_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y)).to_type<int>();
  64. auto bottom_left = m_transform.map(Gfx::FloatPoint(x, y + height - 1)).to_type<int>();
  65. auto bottom_right = m_transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)).to_type<int>();
  66. painter->draw_line(top_left, top_right, m_stroke_style, m_line_width);
  67. painter->draw_line(top_right, bottom_right, m_stroke_style, m_line_width);
  68. painter->draw_line(bottom_right, bottom_left, m_stroke_style, m_line_width);
  69. painter->draw_line(bottom_left, top_left, m_stroke_style, m_line_width);
  70. did_draw(rect);
  71. }
  72. void CanvasRenderingContext2D::draw_image(const HTMLImageElement& image_element, float x, float y)
  73. {
  74. if (!image_element.bitmap())
  75. return;
  76. auto painter = this->painter();
  77. if (!painter)
  78. return;
  79. auto src_rect = image_element.bitmap()->rect();
  80. Gfx::FloatRect dst_rect = { x, y, (float)image_element.bitmap()->width(), (float)image_element.bitmap()->height() };
  81. auto rect = m_transform.map(dst_rect);
  82. painter->draw_scaled_bitmap(rounded_int_rect(rect), *image_element.bitmap(), src_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend);
  83. }
  84. void CanvasRenderingContext2D::scale(float sx, float sy)
  85. {
  86. dbgln("CanvasRenderingContext2D::scale({}, {})", sx, sy);
  87. m_transform.scale(sx, sy);
  88. }
  89. void CanvasRenderingContext2D::translate(float tx, float ty)
  90. {
  91. dbgln("CanvasRenderingContext2D::translate({}, {})", tx, ty);
  92. m_transform.translate(tx, ty);
  93. }
  94. void CanvasRenderingContext2D::rotate(float radians)
  95. {
  96. dbgln("CanvasRenderingContext2D::rotate({})", radians);
  97. m_transform.rotate_radians(radians);
  98. }
  99. void CanvasRenderingContext2D::did_draw(const Gfx::FloatRect&)
  100. {
  101. // FIXME: Make use of the rect to reduce the invalidated area when possible.
  102. if (!m_element)
  103. return;
  104. if (!m_element->layout_node())
  105. return;
  106. m_element->layout_node()->set_needs_display();
  107. }
  108. OwnPtr<Gfx::Painter> CanvasRenderingContext2D::painter()
  109. {
  110. if (!m_element)
  111. return {};
  112. if (!m_element->bitmap()) {
  113. if (!m_element->create_bitmap())
  114. return {};
  115. }
  116. return make<Gfx::Painter>(*m_element->bitmap());
  117. }
  118. void CanvasRenderingContext2D::fill_text(const String& text, float x, float y, Optional<double> max_width)
  119. {
  120. if (max_width.has_value() && max_width.value() <= 0)
  121. return;
  122. auto painter = this->painter();
  123. if (!painter)
  124. return;
  125. // FIXME: painter only supports integer rects for text right now, so this effectively chops off any fractional position
  126. auto text_rect = Gfx::IntRect(x, y, max_width.has_value() ? max_width.value() : painter->font().width(text), painter->font().glyph_height());
  127. auto transformed_rect = m_transform.map(text_rect);
  128. painter->draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, m_fill_style);
  129. did_draw(transformed_rect.to_type<float>());
  130. }
  131. void CanvasRenderingContext2D::begin_path()
  132. {
  133. m_path = Gfx::Path();
  134. }
  135. void CanvasRenderingContext2D::close_path()
  136. {
  137. m_path.close();
  138. }
  139. void CanvasRenderingContext2D::move_to(float x, float y)
  140. {
  141. m_path.move_to({ x, y });
  142. }
  143. void CanvasRenderingContext2D::line_to(float x, float y)
  144. {
  145. m_path.line_to({ x, y });
  146. }
  147. void CanvasRenderingContext2D::quadratic_curve_to(float cx, float cy, float x, float y)
  148. {
  149. m_path.quadratic_bezier_curve_to({ cx, cy }, { x, y });
  150. }
  151. DOM::ExceptionOr<void> CanvasRenderingContext2D::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
  152. {
  153. if (radius < 0)
  154. return DOM::IndexSizeError::create(String::formatted("The radius provided ({}) is negative.", radius));
  155. return ellipse(x, y, radius, radius, 0, start_angle, end_angle, counter_clockwise);
  156. }
  157. 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)
  158. {
  159. if (radius_x < 0)
  160. return DOM::IndexSizeError::create(String::formatted("The major-axis radius provided ({}) is negative.", radius_x));
  161. if (radius_y < 0)
  162. return DOM::IndexSizeError::create(String::formatted("The minor-axis radius provided ({}) is negative.", radius_y));
  163. if (constexpr float tau = M_TAU; (!counter_clockwise && (end_angle - start_angle) >= tau)
  164. || (counter_clockwise && (start_angle - end_angle) >= tau)) {
  165. start_angle = 0;
  166. end_angle = tau;
  167. } else {
  168. start_angle = fmodf(start_angle, tau);
  169. end_angle = fmodf(end_angle, tau);
  170. }
  171. // Then, figure out where the ends of the arc are.
  172. // To do so, we can pretend that the center of this ellipse is at (0, 0),
  173. // and the whole coordinate system is rotated `rotation` radians around the x axis, centered on `center`.
  174. // The sign of the resulting relative positions is just whether our angle is on one of the left quadrants.
  175. auto sin_rotation = sinf(rotation);
  176. auto cos_rotation = cosf(rotation);
  177. auto resolve_point_with_angle = [&](float angle) {
  178. auto tan_relative = tanf(angle);
  179. auto tan2 = tan_relative * tan_relative;
  180. auto ab = radius_x * radius_y;
  181. auto a2 = radius_x * radius_x;
  182. auto b2 = radius_y * radius_y;
  183. auto sqrt = sqrtf(b2 + a2 * tan2);
  184. auto relative_x_position = ab / sqrt;
  185. auto relative_y_position = ab * tan_relative / sqrt;
  186. // Make sure to set the correct sign
  187. float sn = sinf(angle) >= 0 ? 1 : -1;
  188. relative_x_position *= sn;
  189. relative_y_position *= sn;
  190. // Now rotate it (back) around the center point by 'rotation' radians, then move it back to our actual origin.
  191. auto relative_rotated_x_position = relative_x_position * cos_rotation - relative_y_position * sin_rotation;
  192. auto relative_rotated_y_position = relative_x_position * sin_rotation + relative_y_position * cos_rotation;
  193. return Gfx::FloatPoint { relative_rotated_x_position + x, relative_rotated_y_position + y };
  194. };
  195. auto start_point = resolve_point_with_angle(start_angle);
  196. auto end_point = resolve_point_with_angle(end_angle);
  197. m_path.move_to(start_point);
  198. double delta_theta = end_angle - start_angle;
  199. // FIXME: This is still goofy for some values.
  200. m_path.elliptical_arc_to(end_point, { radius_x, radius_y }, rotation, delta_theta > M_PI, !counter_clockwise);
  201. m_path.close();
  202. return {};
  203. }
  204. void CanvasRenderingContext2D::rect(float x, float y, float width, float height)
  205. {
  206. m_path.move_to({ x, y });
  207. if (width == 0 || height == 0)
  208. return;
  209. m_path.line_to({ x + width, y });
  210. m_path.line_to({ x + width, y + height });
  211. m_path.line_to({ x, y + height });
  212. m_path.close();
  213. }
  214. void CanvasRenderingContext2D::stroke()
  215. {
  216. auto painter = this->painter();
  217. if (!painter)
  218. return;
  219. painter->stroke_path(m_path, m_stroke_style, m_line_width);
  220. did_draw(m_path.bounding_box());
  221. }
  222. void CanvasRenderingContext2D::fill(Gfx::Painter::WindingRule winding)
  223. {
  224. auto painter = this->painter();
  225. if (!painter)
  226. return;
  227. auto path = m_path;
  228. path.close_all_subpaths();
  229. painter->fill_path(path, m_fill_style, winding);
  230. did_draw(m_path.bounding_box());
  231. }
  232. void CanvasRenderingContext2D::fill(const String& fill_rule)
  233. {
  234. if (fill_rule == "evenodd")
  235. return fill(Gfx::Painter::WindingRule::EvenOdd);
  236. return fill(Gfx::Painter::WindingRule::Nonzero);
  237. }
  238. RefPtr<ImageData> CanvasRenderingContext2D::create_image_data(int width, int height) const
  239. {
  240. if (!wrapper()) {
  241. dbgln("Hmm! Attempted to create ImageData for wrapper-less CRC2D.");
  242. return {};
  243. }
  244. return ImageData::create_with_size(wrapper()->global_object(), width, height);
  245. }
  246. void CanvasRenderingContext2D::put_image_data(const ImageData& image_data, float x, float y)
  247. {
  248. auto painter = this->painter();
  249. if (!painter)
  250. return;
  251. painter->blit(Gfx::IntPoint(x, y), image_data.bitmap(), image_data.bitmap().rect());
  252. did_draw(Gfx::FloatRect(x, y, image_data.width(), image_data.height()));
  253. }
  254. }