Serenity Operating System
at portability 958 lines 36 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "Painter.h" 28#include "Emoji.h" 29#include "Font.h" 30#include "Bitmap.h" 31#include <AK/Assertions.h> 32#include <AK/StdLibExtras.h> 33#include <AK/StringBuilder.h> 34#include <AK/Utf8View.h> 35#include <LibGfx/CharacterBitmap.h> 36#include <math.h> 37#include <stdio.h> 38#include <unistd.h> 39 40#if defined(__GNUC__) && !defined(__clang__) 41#pragma GCC optimize("O3") 42#endif 43 44#ifndef ALWAYS_INLINE 45#if __has_attribute(always_inline) 46#define ALWAYS_INLINE __attribute__((always_inline)) 47#else 48#define ALWAYS_INLINE inline 49#endif 50#endif 51 52namespace Gfx { 53 54template<BitmapFormat format = BitmapFormat::Invalid> 55static ALWAYS_INLINE Color get_pixel(const Gfx::Bitmap& bitmap, int x, int y) 56{ 57 if constexpr (format == BitmapFormat::Indexed8) 58 return bitmap.palette_color(bitmap.bits(y)[x]); 59 if constexpr (format == BitmapFormat::RGB32) 60 return Color::from_rgb(bitmap.scanline(y)[x]); 61 if constexpr (format == BitmapFormat::RGBA32) 62 return Color::from_rgba(bitmap.scanline(y)[x]); 63 return bitmap.get_pixel(x, y); 64} 65 66Painter::Painter(Gfx::Bitmap& bitmap) 67 : m_target(bitmap) 68{ 69 m_state_stack.append(State()); 70 state().font = &Font::default_font(); 71 state().clip_rect = { { 0, 0 }, bitmap.size() }; 72 m_clip_origin = state().clip_rect; 73} 74 75Painter::~Painter() 76{ 77} 78 79void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color) 80{ 81 auto rect = a_rect.translated(translation()).intersected(clip_rect()); 82 if (rect.is_empty()) 83 return; 84 85 RGBA32* dst = m_target->scanline(rect.top()) + rect.left(); 86 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 87 88 for (int i = rect.height() - 1; i >= 0; --i) { 89 for (int j = 0; j < rect.width(); ++j) 90 set_pixel_with_draw_op(dst[j], color); 91 dst += dst_skip; 92 } 93} 94 95void Painter::clear_rect(const Rect& a_rect, Color color) 96{ 97 auto rect = a_rect.translated(translation()).intersected(clip_rect()); 98 if (rect.is_empty()) 99 return; 100 101 ASSERT(m_target->rect().contains(rect)); 102 103 RGBA32* dst = m_target->scanline(rect.top()) + rect.left(); 104 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 105 106 for (int i = rect.height() - 1; i >= 0; --i) { 107 fast_u32_fill(dst, color.value(), rect.width()); 108 dst += dst_skip; 109 } 110} 111 112void Painter::fill_rect(const Rect& a_rect, Color color) 113{ 114 if (color.alpha() == 0) 115 return; 116 117 if (draw_op() != DrawOp::Copy) { 118 fill_rect_with_draw_op(a_rect, color); 119 return; 120 } 121 122 if (color.alpha() == 0xff) { 123 clear_rect(a_rect, color); 124 return; 125 } 126 127 auto rect = a_rect.translated(translation()).intersected(clip_rect()); 128 if (rect.is_empty()) 129 return; 130 131 ASSERT(m_target->rect().contains(rect)); 132 133 RGBA32* dst = m_target->scanline(rect.top()) + rect.left(); 134 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 135 136 for (int i = rect.height() - 1; i >= 0; --i) { 137 for (int j = 0; j < rect.width(); ++j) 138 dst[j] = Color::from_rgba(dst[j]).blend(color).value(); 139 dst += dst_skip; 140 } 141} 142 143void Painter::fill_rect_with_gradient(const Rect& a_rect, Color gradient_start, Color gradient_end) 144{ 145#ifdef NO_FPU 146 return fill_rect(a_rect, gradient_start); 147#endif 148 auto rect = a_rect.translated(translation()); 149 auto clipped_rect = Rect::intersection(rect, clip_rect()); 150 if (clipped_rect.is_empty()) 151 return; 152 153 int x_offset = clipped_rect.x() - rect.x(); 154 155 RGBA32* dst = m_target->scanline(clipped_rect.top()) + clipped_rect.left(); 156 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 157 158 float increment = (1.0 / ((rect.width()) / 255.0)); 159 160 int r2 = gradient_start.red(); 161 int g2 = gradient_start.green(); 162 int b2 = gradient_start.blue(); 163 int r1 = gradient_end.red(); 164 int g1 = gradient_end.green(); 165 int b1 = gradient_end.blue(); 166 167 for (int i = clipped_rect.height() - 1; i >= 0; --i) { 168 float c = x_offset * increment; 169 for (int j = 0; j < clipped_rect.width(); ++j) { 170 dst[j] = Color( 171 r1 / 255.0 * c + r2 / 255.0 * (255 - c), 172 g1 / 255.0 * c + g2 / 255.0 * (255 - c), 173 b1 / 255.0 * c + b2 / 255.0 * (255 - c)) 174 .value(); 175 c += increment; 176 } 177 dst += dst_skip; 178 } 179} 180 181void Painter::draw_ellipse_intersecting(const Rect& rect, Color color, int thickness) 182{ 183 constexpr int number_samples = 100; // FIXME: dynamically work out the number of samples based upon the rect size 184 double increment = M_PI / number_samples; 185 186 auto ellipse_x = [&](double theta) -> int { 187 return (cos(theta) * rect.width() / sqrt(2)) + rect.center().x(); 188 }; 189 190 auto ellipse_y = [&](double theta) -> int { 191 return (sin(theta) * rect.height() / sqrt(2)) + rect.center().y(); 192 }; 193 194 for (float theta = 0; theta < 2 * M_PI; theta += increment) { 195 draw_line({ ellipse_x(theta), ellipse_y(theta) }, { ellipse_x(theta + increment), ellipse_y(theta + increment) }, color, thickness); 196 } 197} 198 199void Painter::draw_rect(const Rect& a_rect, Color color, bool rough) 200{ 201 Rect rect = a_rect.translated(translation()); 202 auto clipped_rect = rect.intersected(clip_rect()); 203 if (clipped_rect.is_empty()) 204 return; 205 206 int min_y = clipped_rect.top(); 207 int max_y = clipped_rect.bottom(); 208 209 if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) { 210 int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x(); 211 int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width(); 212 fast_u32_fill(m_target->scanline(rect.top()) + start_x, color.value(), width); 213 ++min_y; 214 } 215 if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) { 216 int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x(); 217 int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width(); 218 fast_u32_fill(m_target->scanline(rect.bottom()) + start_x, color.value(), width); 219 --max_y; 220 } 221 222 bool draw_left_side = rect.left() >= clipped_rect.left(); 223 bool draw_right_side = rect.right() == clipped_rect.right(); 224 225 if (draw_left_side && draw_right_side) { 226 // Specialized loop when drawing both sides. 227 for (int y = min_y; y <= max_y; ++y) { 228 auto* bits = m_target->scanline(y); 229 bits[rect.left()] = color.value(); 230 bits[rect.right()] = color.value(); 231 } 232 } else { 233 for (int y = min_y; y <= max_y; ++y) { 234 auto* bits = m_target->scanline(y); 235 if (draw_left_side) 236 bits[rect.left()] = color.value(); 237 if (draw_right_side) 238 bits[rect.right()] = color.value(); 239 } 240 } 241} 242 243void Painter::draw_bitmap(const Point& p, const CharacterBitmap& bitmap, Color color) 244{ 245 auto rect = Rect(p, bitmap.size()).translated(translation()); 246 auto clipped_rect = rect.intersected(clip_rect()); 247 if (clipped_rect.is_empty()) 248 return; 249 const int first_row = clipped_rect.top() - rect.top(); 250 const int last_row = clipped_rect.bottom() - rect.top(); 251 const int first_column = clipped_rect.left() - rect.left(); 252 const int last_column = clipped_rect.right() - rect.left(); 253 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 254 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 255 const char* bitmap_row = &bitmap.bits()[first_row * bitmap.width() + first_column]; 256 const size_t bitmap_skip = bitmap.width(); 257 258 for (int row = first_row; row <= last_row; ++row) { 259 for (int j = 0; j <= (last_column - first_column); ++j) { 260 char fc = bitmap_row[j]; 261 if (fc == '#') 262 dst[j] = color.value(); 263 } 264 bitmap_row += bitmap_skip; 265 dst += dst_skip; 266 } 267} 268 269void Painter::draw_bitmap(const Point& p, const GlyphBitmap& bitmap, Color color) 270{ 271 auto dst_rect = Rect(p, bitmap.size()).translated(translation()); 272 auto clipped_rect = dst_rect.intersected(clip_rect()); 273 if (clipped_rect.is_empty()) 274 return; 275 const int first_row = clipped_rect.top() - dst_rect.top(); 276 const int last_row = clipped_rect.bottom() - dst_rect.top(); 277 const int first_column = clipped_rect.left() - dst_rect.left(); 278 const int last_column = clipped_rect.right() - dst_rect.left(); 279 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 280 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 281 282 for (int row = first_row; row <= last_row; ++row) { 283 for (int j = 0; j <= (last_column - first_column); ++j) { 284 if (bitmap.bit_at(j + first_column, row)) 285 dst[j] = color.value(); 286 } 287 dst += dst_skip; 288 } 289} 290 291void Painter::blit_scaled(const Rect& dst_rect_raw, const Gfx::Bitmap& source, const Rect& src_rect, float hscale, float vscale) 292{ 293 auto dst_rect = Rect(dst_rect_raw.location(), dst_rect_raw.size()).translated(translation()); 294 auto clipped_rect = dst_rect.intersected(clip_rect()); 295 if (clipped_rect.is_empty()) 296 return; 297 const int first_row = (clipped_rect.top() - dst_rect.top()); 298 const int last_row = (clipped_rect.bottom() - dst_rect.top()); 299 const int first_column = (clipped_rect.left() - dst_rect.left()); 300 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 301 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 302 303 int x_start = first_column + src_rect.left(); 304 for (int row = first_row; row <= last_row; ++row) { 305 int sr = (row + src_rect.top()) * vscale; 306 if (sr >= source.size().height() || sr < 0) { 307 dst += dst_skip; 308 continue; 309 } 310 const RGBA32* sl = source.scanline(sr); 311 for (int x = x_start; x < clipped_rect.width() + x_start; ++x) { 312 int sx = x * hscale; 313 if (sx < source.size().width() && sx >= 0) 314 dst[x - x_start] = sl[sx]; 315 } 316 dst += dst_skip; 317 } 318 return; 319} 320 321void Painter::blit_with_opacity(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect, float opacity) 322{ 323 ASSERT(!m_target->has_alpha_channel()); 324 325 if (!opacity) 326 return; 327 if (opacity >= 1.0f) 328 return blit(position, source, src_rect); 329 330 u8 alpha = 255 * opacity; 331 332 Rect safe_src_rect = Rect::intersection(src_rect, source.rect()); 333 Rect dst_rect(position, safe_src_rect.size()); 334 dst_rect.move_by(state().translation); 335 auto clipped_rect = Rect::intersection(dst_rect, clip_rect()); 336 if (clipped_rect.is_empty()) 337 return; 338 const int first_row = clipped_rect.top() - dst_rect.top(); 339 const int last_row = clipped_rect.bottom() - dst_rect.top(); 340 const int first_column = clipped_rect.left() - dst_rect.left(); 341 const int last_column = clipped_rect.right() - dst_rect.left(); 342 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 343 const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; 344 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 345 const unsigned src_skip = source.pitch() / sizeof(RGBA32); 346 347 for (int row = first_row; row <= last_row; ++row) { 348 for (int x = 0; x <= (last_column - first_column); ++x) { 349 Color src_color_with_alpha = Color::from_rgb(src[x]); 350 src_color_with_alpha.set_alpha(alpha); 351 Color dst_color = Color::from_rgb(dst[x]); 352 dst[x] = dst_color.blend(src_color_with_alpha).value(); 353 } 354 dst += dst_skip; 355 src += src_skip; 356 } 357} 358 359void Painter::blit_dimmed(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect) 360{ 361 Rect safe_src_rect = src_rect.intersected(source.rect()); 362 auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation()); 363 auto clipped_rect = dst_rect.intersected(clip_rect()); 364 if (clipped_rect.is_empty()) 365 return; 366 const int first_row = clipped_rect.top() - dst_rect.top(); 367 const int last_row = clipped_rect.bottom() - dst_rect.top(); 368 const int first_column = clipped_rect.left() - dst_rect.left(); 369 const int last_column = clipped_rect.right() - dst_rect.left(); 370 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 371 const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; 372 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 373 const size_t src_skip = source.pitch() / sizeof(RGBA32); 374 375 for (int row = first_row; row <= last_row; ++row) { 376 for (int x = 0; x <= (last_column - first_column); ++x) { 377 u8 alpha = Color::from_rgba(src[x]).alpha(); 378 if (alpha == 0xff) 379 dst[x] = Color::from_rgba(src[x]).to_grayscale().lightened().value(); 380 else if (!alpha) 381 continue; 382 else 383 dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x]).to_grayscale().lightened()).value(); 384 } 385 dst += dst_skip; 386 src += src_skip; 387 } 388} 389 390void Painter::draw_tiled_bitmap(const Rect& a_dst_rect, const Gfx::Bitmap& source) 391{ 392 auto dst_rect = a_dst_rect.translated(translation()); 393 auto clipped_rect = dst_rect.intersected(clip_rect()); 394 if (clipped_rect.is_empty()) 395 return; 396 const int first_row = (clipped_rect.top() - dst_rect.top()); 397 const int last_row = (clipped_rect.bottom() - dst_rect.top()); 398 const int first_column = (clipped_rect.left() - dst_rect.left()); 399 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 400 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 401 402 if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) { 403 int x_start = first_column + a_dst_rect.left(); 404 for (int row = first_row; row <= last_row; ++row) { 405 const RGBA32* sl = source.scanline((row + a_dst_rect.top()) 406 % source.size().height()); 407 for (int x = x_start; x < clipped_rect.width() + x_start; ++x) { 408 dst[x - x_start] = sl[x % source.size().width()]; 409 } 410 dst += dst_skip; 411 } 412 return; 413 } 414 415 ASSERT_NOT_REACHED(); 416} 417 418void Painter::blit_offset(const Point& position, 419 const Gfx::Bitmap& source, 420 const Rect& src_rect, 421 const Point& offset) 422{ 423 auto dst_rect = Rect(position, src_rect.size()).translated(translation()); 424 auto clipped_rect = dst_rect.intersected(clip_rect()); 425 if (clipped_rect.is_empty()) 426 return; 427 const int first_row = (clipped_rect.top() - dst_rect.top()); 428 const int last_row = (clipped_rect.bottom() - dst_rect.top()); 429 const int first_column = (clipped_rect.left() - dst_rect.left()); 430 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 431 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 432 433 if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) { 434 int x_start = first_column + src_rect.left(); 435 for (int row = first_row; row <= last_row; ++row) { 436 int sr = row - offset.y() + src_rect.top(); 437 if (sr >= source.size().height() || sr < 0) { 438 dst += dst_skip; 439 continue; 440 } 441 const RGBA32* sl = source.scanline(sr); 442 for (int x = x_start; x < clipped_rect.width() + x_start; ++x) { 443 int sx = x - offset.x(); 444 if (sx < source.size().width() && sx >= 0) 445 dst[x - x_start] = sl[sx]; 446 } 447 dst += dst_skip; 448 } 449 return; 450 } 451 452 ASSERT_NOT_REACHED(); 453} 454 455void Painter::blit_with_alpha(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect) 456{ 457 ASSERT(source.has_alpha_channel()); 458 Rect safe_src_rect = src_rect.intersected(source.rect()); 459 auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation()); 460 auto clipped_rect = dst_rect.intersected(clip_rect()); 461 if (clipped_rect.is_empty()) 462 return; 463 const int first_row = clipped_rect.top() - dst_rect.top(); 464 const int last_row = clipped_rect.bottom() - dst_rect.top(); 465 const int first_column = clipped_rect.left() - dst_rect.left(); 466 const int last_column = clipped_rect.right() - dst_rect.left(); 467 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 468 const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; 469 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 470 const size_t src_skip = source.pitch() / sizeof(RGBA32); 471 472 for (int row = first_row; row <= last_row; ++row) { 473 for (int x = 0; x <= (last_column - first_column); ++x) { 474 u8 alpha = Color::from_rgba(src[x]).alpha(); 475 if (alpha == 0xff) 476 dst[x] = src[x]; 477 else if (!alpha) 478 continue; 479 else 480 dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x])).value(); 481 } 482 dst += dst_skip; 483 src += src_skip; 484 } 485} 486 487void Painter::blit(const Point& position, const Gfx::Bitmap& source, const Rect& src_rect, float opacity) 488{ 489 if (opacity < 1.0f) 490 return blit_with_opacity(position, source, src_rect, opacity); 491 if (source.has_alpha_channel()) 492 return blit_with_alpha(position, source, src_rect); 493 auto safe_src_rect = src_rect.intersected(source.rect()); 494 ASSERT(source.rect().contains(safe_src_rect)); 495 auto dst_rect = Rect(position, safe_src_rect.size()).translated(translation()); 496 auto clipped_rect = dst_rect.intersected(clip_rect()); 497 if (clipped_rect.is_empty()) 498 return; 499 const int first_row = clipped_rect.top() - dst_rect.top(); 500 const int last_row = clipped_rect.bottom() - dst_rect.top(); 501 const int first_column = clipped_rect.left() - dst_rect.left(); 502 RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); 503 const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); 504 505 if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) { 506 const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; 507 const size_t src_skip = source.pitch() / sizeof(RGBA32); 508 for (int row = first_row; row <= last_row; ++row) { 509 fast_u32_copy(dst, src, clipped_rect.width()); 510 dst += dst_skip; 511 src += src_skip; 512 } 513 return; 514 } 515 516 if (source.format() == BitmapFormat::Indexed8) { 517 const u8* src = source.bits(src_rect.top() + first_row) + src_rect.left() + first_column; 518 const size_t src_skip = source.pitch(); 519 for (int row = first_row; row <= last_row; ++row) { 520 for (int i = 0; i < clipped_rect.width(); ++i) 521 dst[i] = source.palette_color(src[i]).value(); 522 dst += dst_skip; 523 src += src_skip; 524 } 525 return; 526 } 527 528 ASSERT_NOT_REACHED(); 529} 530 531template<bool has_alpha_channel, typename GetPixel> 532ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, const Rect& dst_rect, const Gfx::Bitmap& source, int hfactor, int vfactor, GetPixel get_pixel) 533{ 534 for (int y = source.rect().top(); y <= source.rect().bottom(); ++y) { 535 int dst_y = dst_rect.y() + y * vfactor; 536 for (int x = source.rect().left(); x <= source.rect().right(); ++x) { 537 auto src_pixel = get_pixel(source, x, y); 538 for (int yo = 0; yo < vfactor; ++yo) { 539 auto* scanline = (Color*)target.scanline(dst_y + yo); 540 int dst_x = dst_rect.x() + x * hfactor; 541 for (int xo = 0; xo < hfactor; ++xo) { 542 if constexpr (has_alpha_channel) 543 scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel); 544 else 545 scanline[dst_x + xo] = src_pixel; 546 } 547 } 548 } 549 } 550} 551 552template<bool has_alpha_channel, typename GetPixel> 553ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, const Rect& dst_rect, const Rect& clipped_rect, const Gfx::Bitmap& source, const Rect& src_rect, int hscale, int vscale, GetPixel get_pixel) 554{ 555 if (dst_rect == clipped_rect && !(dst_rect.width() % src_rect.width()) && !(dst_rect.height() % src_rect.height())) { 556 int hfactor = dst_rect.width() / src_rect.width(); 557 int vfactor = dst_rect.height() / src_rect.height(); 558 if (hfactor == 2 && vfactor == 2) 559 return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 2, 2, get_pixel); 560 if (hfactor == 3 && vfactor == 3) 561 return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 3, 3, get_pixel); 562 if (hfactor == 4 && vfactor == 4) 563 return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 4, 4, get_pixel); 564 return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, hfactor, vfactor, get_pixel); 565 } 566 567 for (int y = clipped_rect.top(); y <= clipped_rect.bottom(); ++y) { 568 auto* scanline = (Color*)target.scanline(y); 569 for (int x = clipped_rect.left(); x <= clipped_rect.right(); ++x) { 570 auto scaled_x = ((x - dst_rect.x()) * hscale) >> 16; 571 auto scaled_y = ((y - dst_rect.y()) * vscale) >> 16; 572 auto src_pixel = get_pixel(source, scaled_x, scaled_y); 573 574 if constexpr (has_alpha_channel) { 575 scanline[x] = scanline[x].blend(src_pixel); 576 } else 577 scanline[x] = src_pixel; 578 } 579 } 580} 581 582void Painter::draw_scaled_bitmap(const Rect& a_dst_rect, const Gfx::Bitmap& source, const Rect& src_rect) 583{ 584 auto dst_rect = a_dst_rect; 585 if (dst_rect.size() == src_rect.size()) 586 return blit(dst_rect.location(), source, src_rect); 587 588 auto safe_src_rect = src_rect.intersected(source.rect()); 589 ASSERT(source.rect().contains(safe_src_rect)); 590 dst_rect.move_by(state().translation); 591 auto clipped_rect = dst_rect.intersected(clip_rect()); 592 if (clipped_rect.is_empty()) 593 return; 594 595 int hscale = (src_rect.width() << 16) / dst_rect.width(); 596 int vscale = (src_rect.height() << 16) / dst_rect.height(); 597 598 if (source.has_alpha_channel()) { 599 switch (source.format()) { 600 case BitmapFormat::RGB32: 601 do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>); 602 break; 603 case BitmapFormat::RGBA32: 604 do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>); 605 break; 606 case BitmapFormat::Indexed8: 607 do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>); 608 break; 609 default: 610 do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>); 611 break; 612 } 613 } else { 614 switch (source.format()) { 615 case BitmapFormat::RGB32: 616 do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>); 617 break; 618 case BitmapFormat::RGBA32: 619 do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>); 620 break; 621 case BitmapFormat::Indexed8: 622 do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>); 623 break; 624 default: 625 do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>); 626 break; 627 } 628 } 629} 630 631[[gnu::flatten]] void Painter::draw_glyph(const Point& point, char ch, Color color) 632{ 633 draw_glyph(point, ch, font(), color); 634} 635 636[[gnu::flatten]] void Painter::draw_glyph(const Point& point, char ch, const Font& font, Color color) 637{ 638 draw_bitmap(point, font.glyph_bitmap(ch), color); 639} 640 641void Painter::draw_emoji(const Point& point, const Gfx::Bitmap& emoji, const Font& font) 642{ 643 if (!font.is_fixed_width()) 644 blit(point, emoji, emoji.rect()); 645 else { 646 Rect dst_rect { 647 point.x(), 648 point.y(), 649 font.glyph_width('x'), 650 font.glyph_height() 651 }; 652 draw_scaled_bitmap(dst_rect, emoji, emoji.rect()); 653 } 654} 655 656void Painter::draw_glyph_or_emoji(const Point& point, u32 codepoint, const Font& font, Color color) 657{ 658 if (codepoint < 256) { 659 // This looks like a regular character. 660 draw_glyph(point, (char)codepoint, font, color); 661 return; 662 } 663 664 // Perhaps it's an emoji? 665 auto* emoji = Emoji::emoji_for_codepoint(codepoint); 666 if (emoji == nullptr) { 667#ifdef EMOJI_DEBUG 668 dbg() << "Failed to find an emoji for codepoint " << codepoint; 669#endif 670 draw_glyph(point, '?', font, color); 671 return; 672 } 673 674 draw_emoji(point, *emoji, font); 675} 676 677void Painter::draw_text_line(const Rect& a_rect, const Utf8View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision) 678{ 679 auto rect = a_rect; 680 Utf8View final_text(text); 681 String elided_text; 682 if (elision == TextElision::Right) { 683 int text_width = font.width(final_text); 684 if (font.width(final_text) > rect.width()) { 685 int glyph_spacing = font.glyph_spacing(); 686 int byte_offset = 0; 687 int new_width = font.width("..."); 688 if (new_width < text_width) { 689 for (auto it = final_text.begin(); it != final_text.end(); ++it) { 690 u32 codepoint = *it; 691 int glyph_width = font.glyph_or_emoji_width(codepoint); 692 // NOTE: Glyph spacing should not be added after the last glyph on the line, 693 // but since we are here because the last glyph does not actually fit on the line, 694 // we don't have to worry about spacing. 695 int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing; 696 if (width_with_this_glyph_included > rect.width()) 697 break; 698 byte_offset = final_text.byte_offset_of(it); 699 new_width += glyph_width + glyph_spacing; 700 } 701 StringBuilder builder; 702 builder.append(final_text.substring_view(0, byte_offset).as_string()); 703 builder.append("..."); 704 elided_text = builder.to_string(); 705 final_text = Utf8View { elided_text }; 706 } 707 } 708 } 709 710 switch (alignment) { 711 case TextAlignment::TopLeft: 712 case TextAlignment::CenterLeft: 713 break; 714 case TextAlignment::TopRight: 715 case TextAlignment::CenterRight: 716 rect.set_x(rect.right() - font.width(final_text)); 717 break; 718 case TextAlignment::Center: { 719 auto shrunken_rect = rect; 720 shrunken_rect.set_width(font.width(final_text)); 721 shrunken_rect.center_within(rect); 722 rect = shrunken_rect; 723 break; 724 } 725 default: 726 ASSERT_NOT_REACHED(); 727 } 728 729 auto point = rect.location(); 730 int space_width = font.glyph_width(' ') + font.glyph_spacing(); 731 732 for (u32 codepoint : final_text) { 733 if (codepoint == ' ') { 734 point.move_by(space_width, 0); 735 continue; 736 } 737 draw_glyph_or_emoji(point, codepoint, font, color); 738 point.move_by(font.glyph_or_emoji_width(codepoint) + font.glyph_spacing(), 0); 739 } 740} 741 742void Painter::draw_text(const Rect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision) 743{ 744 draw_text(rect, text, font(), alignment, color, elision); 745} 746 747void Painter::draw_text(const Rect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision) 748{ 749 Utf8View text { raw_text }; 750 Vector<Utf8View, 32> lines; 751 752 int start_of_current_line = 0; 753 for (auto it = text.begin(); it != text.end(); ++it) { 754 u32 codepoint = *it; 755 if (codepoint == '\n') { 756 int byte_offset = text.byte_offset_of(it); 757 Utf8View line = text.substring_view(start_of_current_line, byte_offset - start_of_current_line); 758 lines.append(line); 759 start_of_current_line = byte_offset + 1; 760 } 761 } 762 763 if (start_of_current_line != text.byte_length()) { 764 Utf8View line = text.substring_view(start_of_current_line, text.byte_length() - start_of_current_line); 765 lines.append(line); 766 } 767 768 static const int line_spacing = 4; 769 int line_height = font.glyph_height() + line_spacing; 770 Rect bounding_rect { 0, 0, 0, (static_cast<int>(lines.size()) * line_height) - line_spacing }; 771 772 for (auto& line : lines) { 773 auto line_width = font.width(line); 774 if (line_width > bounding_rect.width()) 775 bounding_rect.set_width(line_width); 776 } 777 778 switch (alignment) { 779 case TextAlignment::TopLeft: 780 bounding_rect.set_location(rect.location()); 781 break; 782 case TextAlignment::TopRight: 783 bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() }); 784 break; 785 case TextAlignment::CenterLeft: 786 bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) }); 787 break; 788 case TextAlignment::CenterRight: 789 bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) }); 790 break; 791 case TextAlignment::Center: 792 bounding_rect.center_within(rect); 793 break; 794 default: 795 ASSERT_NOT_REACHED(); 796 } 797 798 for (size_t i = 0; i < lines.size(); ++i) { 799 auto& line = lines[i]; 800 Rect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height }; 801 line_rect.intersect(rect); 802 draw_text_line(line_rect, line, font, alignment, color, elision); 803 } 804} 805 806void Painter::set_pixel(const Point& p, Color color) 807{ 808 auto point = p; 809 point.move_by(state().translation); 810 if (!clip_rect().contains(point)) 811 return; 812 m_target->scanline(point.y())[point.x()] = color.value(); 813} 814 815[[gnu::always_inline]] inline void Painter::set_pixel_with_draw_op(u32& pixel, const Color& color) 816{ 817 if (draw_op() == DrawOp::Copy) 818 pixel = color.value(); 819 else if (draw_op() == DrawOp::Xor) 820 pixel ^= color.value(); 821} 822 823void Painter::draw_pixel(const Point& position, Color color, int thickness) 824{ 825 ASSERT(draw_op() == DrawOp::Copy); 826 if (thickness == 1) 827 return set_pixel_with_draw_op(m_target->scanline(position.y())[position.x()], color); 828 Rect rect { position.translated(-(thickness / 2), -(thickness / 2)), { thickness, thickness } }; 829 fill_rect(rect.translated(-state().translation), color); 830} 831 832void Painter::draw_line(const Point& p1, const Point& p2, Color color, int thickness, bool dotted) 833{ 834 auto clip_rect = this->clip_rect(); 835 836 auto point1 = p1; 837 point1.move_by(state().translation); 838 839 auto point2 = p2; 840 point2.move_by(state().translation); 841 842 // Special case: vertical line. 843 if (point1.x() == point2.x()) { 844 const int x = point1.x(); 845 if (x < clip_rect.left() || x > clip_rect.right()) 846 return; 847 if (point1.y() > point2.y()) 848 swap(point1, point2); 849 if (point1.y() > clip_rect.bottom()) 850 return; 851 if (point2.y() < clip_rect.top()) 852 return; 853 int min_y = max(point1.y(), clip_rect.top()); 854 int max_y = min(point2.y(), clip_rect.bottom()); 855 if (dotted) { 856 for (int y = min_y; y <= max_y; y += 2) 857 draw_pixel({ x, y }, color, thickness); 858 } else { 859 for (int y = min_y; y <= max_y; ++y) 860 draw_pixel({ x, y }, color, thickness); 861 } 862 return; 863 } 864 865 // Special case: horizontal line. 866 if (point1.y() == point2.y()) { 867 const int y = point1.y(); 868 if (y < clip_rect.top() || y > clip_rect.bottom()) 869 return; 870 if (point1.x() > point2.x()) 871 swap(point1, point2); 872 if (point1.x() > clip_rect.right()) 873 return; 874 if (point2.x() < clip_rect.left()) 875 return; 876 int min_x = max(point1.x(), clip_rect.left()); 877 int max_x = min(point2.x(), clip_rect.right()); 878 if (dotted) { 879 for (int x = min_x; x <= max_x; x += 2) 880 draw_pixel({ x, y }, color, thickness); 881 } else { 882 for (int x = min_x; x <= max_x; ++x) 883 draw_pixel({ x, y }, color, thickness); 884 } 885 return; 886 } 887 888 // FIXME: Implement dotted diagonal lines. 889 ASSERT(!dotted); 890 891 const double adx = abs(point2.x() - point1.x()); 892 const double ady = abs(point2.y() - point1.y()); 893 894 if (adx > ady) { 895 if (point1.x() > point2.x()) 896 swap(point1, point2); 897 } else { 898 if (point1.y() > point2.y()) 899 swap(point1, point2); 900 } 901 902 // FIXME: Implement clipping below. 903 const double dx = point2.x() - point1.x(); 904 const double dy = point2.y() - point1.y(); 905 double error = 0; 906 907 if (dx > dy) { 908 const double y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1); 909 const double delta_error = fabs(dy / dx); 910 int y = point1.y(); 911 for (int x = point1.x(); x <= point2.x(); ++x) { 912 if (clip_rect.contains(x, y)) 913 draw_pixel({ x, y }, color, thickness); 914 error += delta_error; 915 if (error >= 0.5) { 916 y = (double)y + y_step; 917 error -= 1.0; 918 } 919 } 920 } else { 921 const double x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1); 922 const double delta_error = fabs(dx / dy); 923 int x = point1.x(); 924 for (int y = point1.y(); y <= point2.y(); ++y) { 925 if (clip_rect.contains(x, y)) 926 draw_pixel({ x, y }, color, thickness); 927 error += delta_error; 928 if (error >= 0.5) { 929 x = (double)x + x_step; 930 error -= 1.0; 931 } 932 } 933 } 934} 935 936void Painter::add_clip_rect(const Rect& rect) 937{ 938 state().clip_rect.intersect(rect.translated(m_clip_origin.location())); 939 state().clip_rect.intersect(m_target->rect()); 940} 941 942void Painter::clear_clip_rect() 943{ 944 state().clip_rect = m_clip_origin; 945} 946 947PainterStateSaver::PainterStateSaver(Painter& painter) 948 : m_painter(painter) 949{ 950 m_painter.save(); 951} 952 953PainterStateSaver::~PainterStateSaver() 954{ 955 m_painter.restore(); 956} 957 958}