Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibGfx/Painter.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
4
 * Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
5
 * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
6
 * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
7
 * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
8
 * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
9
 *
10
 * SPDX-License-Identifier: BSD-2-Clause
11
 */
12
13
#include "Painter.h"
14
#include "Bitmap.h"
15
#include "Font/Emoji.h"
16
#include "Font/Font.h"
17
#include <AK/Assertions.h>
18
#include <AK/Debug.h>
19
#include <AK/Function.h>
20
#include <AK/Math.h>
21
#include <AK/Memory.h>
22
#include <AK/Queue.h>
23
#include <AK/QuickSort.h>
24
#include <AK/Stack.h>
25
#include <AK/StdLibExtras.h>
26
#include <AK/StringBuilder.h>
27
#include <AK/Utf32View.h>
28
#include <AK/Utf8View.h>
29
#include <LibGfx/CharacterBitmap.h>
30
#include <LibGfx/Palette.h>
31
#include <LibGfx/Path.h>
32
#include <LibGfx/Quad.h>
33
#include <LibGfx/TextDirection.h>
34
#include <LibGfx/TextLayout.h>
35
#include <LibUnicode/CharacterTypes.h>
36
#include <LibUnicode/Emoji.h>
37
#include <stdio.h>
38
39
#if defined(AK_COMPILER_GCC)
40
#    pragma GCC optimize("O3")
41
#endif
42
43
namespace Gfx {
44
45
template<BitmapFormat format = BitmapFormat::Invalid>
46
ALWAYS_INLINE Color get_pixel(Gfx::Bitmap const& bitmap, int x, int y)
47
0
{
48
    if constexpr (format == BitmapFormat::BGRx8888)
49
0
        return Color::from_rgb(bitmap.scanline(y)[x]);
50
    if constexpr (format == BitmapFormat::BGRA8888)
51
0
        return Color::from_argb(bitmap.scanline(y)[x]);
52
0
    return bitmap.get_pixel(x, y);
53
0
}
Unexecuted instantiation: Gfx::Color Gfx::get_pixel<(Gfx::BitmapFormat)1>(Gfx::Bitmap const&, int, int)
Unexecuted instantiation: Gfx::Color Gfx::get_pixel<(Gfx::BitmapFormat)2>(Gfx::Bitmap const&, int, int)
Unexecuted instantiation: Gfx::Color Gfx::get_pixel<(Gfx::BitmapFormat)0>(Gfx::Bitmap const&, int, int)
54
55
Painter::Painter(Gfx::Bitmap& bitmap)
56
2.77k
    : m_target(bitmap)
57
2.77k
{
58
2.77k
    int scale = bitmap.scale();
59
2.77k
    VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888);
60
2.77k
    VERIFY(bitmap.physical_width() % scale == 0);
61
2.77k
    VERIFY(bitmap.physical_height() % scale == 0);
62
2.77k
    m_state_stack.append(State());
63
2.77k
    state().font = nullptr;
64
2.77k
    state().clip_rect = { { 0, 0 }, bitmap.size() };
65
2.77k
    state().scale = scale;
66
2.77k
    m_clip_origin = state().clip_rect;
67
2.77k
}
68
69
void Painter::fill_rect_with_draw_op(IntRect const& a_rect, Color color)
70
0
{
71
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
72
73
0
    auto rect = a_rect.translated(translation()).intersected(clip_rect());
74
0
    if (rect.is_empty())
75
0
        return;
76
77
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
78
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
79
80
0
    for (int i = rect.height() - 1; i >= 0; --i) {
81
0
        for (int j = 0; j < rect.width(); ++j)
82
0
            set_physical_pixel_with_draw_op(dst[j], color);
83
0
        dst += dst_skip;
84
0
    }
85
0
}
86
87
void Painter::clear_rect(IntRect const& a_rect, Color color)
88
0
{
89
0
    auto rect = a_rect.translated(translation()).intersected(clip_rect());
90
0
    if (rect.is_empty())
91
0
        return;
92
93
0
    VERIFY(target().rect().contains(rect));
94
0
    rect *= scale();
95
96
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
97
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
98
99
0
    for (int i = rect.height() - 1; i >= 0; --i) {
100
0
        fast_u32_fill(dst, color.value(), rect.width());
101
0
        dst += dst_skip;
102
0
    }
103
0
}
104
105
void Painter::fill_physical_rect(IntRect const& physical_rect, Color color)
106
0
{
107
    // Callers must do clipping.
108
0
    ARGB32* dst = target().scanline(physical_rect.top()) + physical_rect.left();
109
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
110
111
0
    auto dst_format = target().format();
112
0
    for (int i = physical_rect.height() - 1; i >= 0; --i) {
113
0
        for (int j = 0; j < physical_rect.width(); ++j)
114
0
            dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
115
0
        dst += dst_skip;
116
0
    }
117
0
}
118
119
void Painter::fill_rect(IntRect const& a_rect, Color color)
120
0
{
121
0
    if (color.alpha() == 0)
122
0
        return;
123
124
0
    if (draw_op() != DrawOp::Copy) {
125
0
        fill_rect_with_draw_op(a_rect, color);
126
0
        return;
127
0
    }
128
129
0
    if (color.alpha() == 0xff) {
130
0
        clear_rect(a_rect, color);
131
0
        return;
132
0
    }
133
134
0
    auto rect = a_rect.translated(translation()).intersected(clip_rect());
135
0
    if (rect.is_empty())
136
0
        return;
137
0
    VERIFY(target().rect().contains(rect));
138
139
0
    fill_physical_rect(rect * scale(), color);
140
0
}
141
142
void Painter::fill_rect(IntRect const& rect, PaintStyle const& paint_style)
143
0
{
144
0
    auto a_rect = rect.translated(translation());
145
0
    auto clipped_rect = a_rect.intersected(clip_rect());
146
0
    if (clipped_rect.is_empty())
147
0
        return;
148
0
    a_rect *= scale();
149
0
    clipped_rect *= scale();
150
0
    auto start_offset = clipped_rect.location() - a_rect.location();
151
0
    paint_style.paint(a_rect, [&](PaintStyle::SamplerFunction sample) {
152
0
        for (int y = 0; y < clipped_rect.height(); ++y) {
153
0
            for (int x = 0; x < clipped_rect.width(); ++x) {
154
0
                IntPoint point(x, y);
155
0
                set_physical_pixel(point + clipped_rect.location(), sample(point + start_offset), true);
156
0
            }
157
0
        }
158
0
    });
159
0
}
160
161
void Painter::fill_rect_with_dither_pattern(IntRect const& a_rect, Color color_a, Color color_b)
162
0
{
163
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
164
165
0
    auto rect = a_rect.translated(translation()).intersected(clip_rect());
166
0
    if (rect.is_empty())
167
0
        return;
168
169
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
170
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
171
172
0
    for (int i = 0; i < rect.height(); ++i) {
173
0
        for (int j = 0; j < rect.width(); ++j) {
174
0
            bool checkboard_use_a = ((rect.left() + i) & 1) ^ ((rect.top() + j) & 1);
175
0
            if (checkboard_use_a && !color_a.alpha())
176
0
                continue;
177
0
            if (!checkboard_use_a && !color_b.alpha())
178
0
                continue;
179
0
            dst[j] = checkboard_use_a ? color_a.value() : color_b.value();
180
0
        }
181
0
        dst += dst_skip;
182
0
    }
183
0
}
184
185
void Painter::fill_rect_with_checkerboard(IntRect const& a_rect, IntSize cell_size, Color color_dark, Color color_light)
186
0
{
187
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
188
189
0
    auto translated_rect = a_rect.translated(translation());
190
0
    auto rect = translated_rect.intersected(clip_rect());
191
0
    if (rect.is_empty())
192
0
        return;
193
194
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
195
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
196
197
0
    int first_cell_column = (rect.x() - translated_rect.x()) / cell_size.width();
198
0
    int prologue_length = min(rect.width(), cell_size.width() - ((rect.x() - translated_rect.x()) % cell_size.width()));
199
0
    int number_of_aligned_strips = (rect.width() - prologue_length) / cell_size.width();
200
201
0
    for (int i = 0; i < rect.height(); ++i) {
202
0
        int y = rect.y() - translated_rect.y() + i;
203
0
        int cell_row = y / cell_size.height();
204
0
        bool odd_row = cell_row & 1;
205
206
        // Prologue: Paint the unaligned part up to the first intersection.
207
0
        int j = 0;
208
0
        int cell_column = first_cell_column;
209
210
0
        {
211
0
            bool odd_cell = cell_column & 1;
212
0
            auto color = (odd_row ^ odd_cell) ? color_light.value() : color_dark.value();
213
0
            fast_u32_fill(&dst[j], color, prologue_length);
214
0
            j += prologue_length;
215
0
        }
216
217
        // Aligned run: Paint the maximum number of aligned cell strips.
218
0
        for (int strip = 0; strip < number_of_aligned_strips; ++strip) {
219
0
            ++cell_column;
220
0
            bool odd_cell = cell_column & 1;
221
0
            auto color = (odd_row ^ odd_cell) ? color_light.value() : color_dark.value();
222
0
            fast_u32_fill(&dst[j], color, cell_size.width());
223
0
            j += cell_size.width();
224
0
        }
225
226
        // Epilogue: Paint the unaligned part until the end of the rect.
227
0
        if (j != rect.width()) {
228
0
            ++cell_column;
229
0
            bool odd_cell = cell_column & 1;
230
0
            auto color = (odd_row ^ odd_cell) ? color_light.value() : color_dark.value();
231
0
            int epilogue_length = rect.width() - j;
232
0
            fast_u32_fill(&dst[j], color, epilogue_length);
233
0
            j += epilogue_length;
234
0
        }
235
236
0
        dst += dst_skip;
237
0
    }
238
0
}
239
240
void Painter::fill_rect_with_gradient(Orientation orientation, IntRect const& a_rect, Color gradient_start, Color gradient_end)
241
0
{
242
0
    if (gradient_start == gradient_end) {
243
0
        fill_rect(a_rect, gradient_start);
244
0
        return;
245
0
    }
246
0
    return fill_rect_with_linear_gradient(a_rect, Array { ColorStop { gradient_start, 0 }, ColorStop { gradient_end, 1 } }, orientation == Orientation::Horizontal ? 90.0f : 0.0f);
247
0
}
248
249
void Painter::fill_rect_with_gradient(IntRect const& a_rect, Color gradient_start, Color gradient_end)
250
0
{
251
0
    return fill_rect_with_gradient(Orientation::Horizontal, a_rect, gradient_start, gradient_end);
252
0
}
253
254
void Painter::fill_rect_with_rounded_corners(IntRect const& a_rect, Color color, int radius)
255
0
{
256
0
    return fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius);
257
0
}
258
259
void Painter::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)
260
0
{
261
    // Fasttrack for rects without any border radii
262
0
    if (!top_left_radius && !top_right_radius && !bottom_right_radius && !bottom_left_radius)
263
0
        return fill_rect(a_rect, color);
264
265
    // Fully transparent, dont care.
266
0
    if (color.alpha() == 0)
267
0
        return;
268
269
    // FIXME: Allow for elliptically rounded corners
270
0
    IntRect top_left_corner = {
271
0
        a_rect.x(),
272
0
        a_rect.y(),
273
0
        top_left_radius,
274
0
        top_left_radius
275
0
    };
276
0
    IntRect top_right_corner = {
277
0
        a_rect.x() + a_rect.width() - top_right_radius,
278
0
        a_rect.y(),
279
0
        top_right_radius,
280
0
        top_right_radius
281
0
    };
282
0
    IntRect bottom_right_corner = {
283
0
        a_rect.x() + a_rect.width() - bottom_right_radius,
284
0
        a_rect.y() + a_rect.height() - bottom_right_radius,
285
0
        bottom_right_radius,
286
0
        bottom_right_radius
287
0
    };
288
0
    IntRect bottom_left_corner = {
289
0
        a_rect.x(),
290
0
        a_rect.y() + a_rect.height() - bottom_left_radius,
291
0
        bottom_left_radius,
292
0
        bottom_left_radius
293
0
    };
294
295
0
    IntRect top_rect = {
296
0
        a_rect.x() + top_left_radius,
297
0
        a_rect.y(),
298
0
        a_rect.width() - top_left_radius - top_right_radius, top_left_radius
299
0
    };
300
0
    IntRect right_rect = {
301
0
        a_rect.x() + a_rect.width() - top_right_radius,
302
0
        a_rect.y() + top_right_radius,
303
0
        top_right_radius,
304
0
        a_rect.height() - top_right_radius - bottom_right_radius
305
0
    };
306
0
    IntRect bottom_rect = {
307
0
        a_rect.x() + bottom_left_radius,
308
0
        a_rect.y() + a_rect.height() - bottom_right_radius,
309
0
        a_rect.width() - bottom_left_radius - bottom_right_radius,
310
0
        bottom_right_radius
311
0
    };
312
0
    IntRect left_rect = {
313
0
        a_rect.x(),
314
0
        a_rect.y() + top_left_radius,
315
0
        bottom_left_radius,
316
0
        a_rect.height() - top_left_radius - bottom_left_radius
317
0
    };
318
319
0
    IntRect inner = {
320
0
        left_rect.x() + left_rect.width(),
321
0
        left_rect.y(),
322
0
        a_rect.width() - left_rect.width() - right_rect.width(),
323
0
        a_rect.height() - top_rect.height() - bottom_rect.height()
324
0
    };
325
326
0
    fill_rect(top_rect, color);
327
0
    fill_rect(right_rect, color);
328
0
    fill_rect(bottom_rect, color);
329
0
    fill_rect(left_rect, color);
330
331
0
    fill_rect(inner, color);
332
333
0
    if (top_left_radius)
334
0
        fill_rounded_corner(top_left_corner, top_left_radius, color, CornerOrientation::TopLeft);
335
0
    if (top_right_radius)
336
0
        fill_rounded_corner(top_right_corner, top_right_radius, color, CornerOrientation::TopRight);
337
0
    if (bottom_left_radius)
338
0
        fill_rounded_corner(bottom_left_corner, bottom_left_radius, color, CornerOrientation::BottomLeft);
339
0
    if (bottom_right_radius)
340
0
        fill_rounded_corner(bottom_right_corner, bottom_right_radius, color, CornerOrientation::BottomRight);
341
0
}
342
343
void Painter::fill_rounded_corner(IntRect const& a_rect, int radius, Color color, CornerOrientation orientation)
344
0
{
345
    // Care about clipping
346
0
    auto translated_a_rect = a_rect.translated(translation());
347
0
    auto rect = translated_a_rect.intersected(clip_rect());
348
349
0
    if (rect.is_empty())
350
0
        return;
351
0
    VERIFY(target().rect().contains(rect));
352
353
    // We got cut on the top!
354
    // FIXME: Also account for clipping on the x-axis
355
0
    int clip_offset = 0;
356
0
    if (translated_a_rect.y() < rect.y())
357
0
        clip_offset = rect.y() - translated_a_rect.y();
358
359
0
    radius *= scale();
360
0
    rect *= scale();
361
0
    clip_offset *= scale();
362
363
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
364
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
365
366
0
    IntPoint circle_center;
367
0
    switch (orientation) {
368
0
    case CornerOrientation::TopLeft:
369
0
        circle_center = { radius, radius + 1 };
370
0
        break;
371
0
    case CornerOrientation::TopRight:
372
0
        circle_center = { -1, radius + 1 };
373
0
        break;
374
0
    case CornerOrientation::BottomRight:
375
0
        circle_center = { -1, 0 };
376
0
        break;
377
0
    case CornerOrientation::BottomLeft:
378
0
        circle_center = { radius, 0 };
379
0
        break;
380
0
    default:
381
0
        VERIFY_NOT_REACHED();
382
0
    }
383
384
0
    int radius2 = radius * radius;
385
0
    auto is_in_circle = [&](int x, int y) {
386
0
        int distance2 = (circle_center.x() - x) * (circle_center.x() - x) + (circle_center.y() - y) * (circle_center.y() - y);
387
        // To reflect the grid and be compatible with the draw_circle_arc_intersecting algorithm
388
        // add 1/2 to the radius
389
0
        return distance2 <= (radius2 + radius + 0.25);
390
0
    };
391
392
0
    auto dst_format = target().format();
393
0
    for (int i = rect.height() - 1; i >= 0; --i) {
394
0
        for (int j = 0; j < rect.width(); ++j)
395
0
            if (is_in_circle(j, rect.height() - i + clip_offset))
396
0
                dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
397
0
        dst += dst_skip;
398
0
    }
399
0
}
400
401
void Painter::draw_circle_arc_intersecting(IntRect const& a_rect, IntPoint center, int radius, Color color, int thickness)
402
0
{
403
0
    if (thickness <= 0 || radius <= 0)
404
0
        return;
405
406
    // Care about clipping
407
0
    auto translated_a_rect = a_rect.translated(translation());
408
0
    auto rect = translated_a_rect.intersected(clip_rect());
409
410
0
    if (rect.is_empty())
411
0
        return;
412
0
    VERIFY(target().rect().contains(rect));
413
414
    // We got cut on the top!
415
    // FIXME: Also account for clipping on the x-axis
416
0
    int clip_offset = 0;
417
0
    if (translated_a_rect.y() < rect.y())
418
0
        clip_offset = rect.y() - translated_a_rect.y();
419
420
0
    if (thickness > radius)
421
0
        thickness = radius;
422
423
0
    int radius2 = radius * radius;
424
0
    auto is_on_arc = [&](int x, int y) {
425
0
        int distance2 = (center.x() - x) * (center.x() - x) + (center.y() - y) * (center.y() - y);
426
        // Is within a circle of radius 1/2 around (x,y), so basically within the current pixel.
427
        // Technically this is angle-dependent and should be between 1/2 and sqrt(2)/2, but this works.
428
0
        return distance2 <= (radius2 + radius + 0.25) && distance2 >= (radius2 - radius + 0.25);
429
0
    };
430
431
0
    ARGB32* dst = target().scanline(rect.top()) + rect.left();
432
0
    auto dst_format = target().format();
433
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
434
435
0
    for (int i = rect.height() - 1; i >= 0; --i) {
436
0
        for (int j = 0; j < rect.width(); ++j)
437
0
            if (is_on_arc(j, rect.height() - i + clip_offset))
438
0
                dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
439
0
        dst += dst_skip;
440
0
    }
441
442
0
    return draw_circle_arc_intersecting(a_rect, center, radius - 1, color, thickness - 1);
443
0
}
444
445
// The callback will only be called for a quarter of the ellipse, the user is intended to deduce other points.
446
// As the coordinate space is relative to the center of the rectangle, it's simply (x, y), (x, -y), (-x, y) and (-x, -y).
447
static void on_each_ellipse_point(IntRect const& rect, Function<void(IntPoint)>&& callback)
448
0
{
449
    // Note: This is an implementation of the Midpoint Ellipse Algorithm.
450
0
    double const a = rect.width() / 2;
451
0
    double const a_square = a * a;
452
0
    double const b = rect.height() / 2;
453
0
    double const b_square = b * b;
454
455
0
    int x = 0;
456
0
    auto y = static_cast<int>(b);
457
458
0
    double dx = 2 * b_square * x;
459
0
    double dy = 2 * a_square * y;
460
461
    // For region 1:
462
0
    auto decision_parameter = b_square - a_square * b + .25 * a_square;
463
464
0
    while (dx < dy) {
465
0
        callback({ x, y });
466
467
0
        if (decision_parameter >= 0) {
468
0
            y--;
469
0
            dy -= 2 * a_square;
470
0
            decision_parameter -= dy;
471
0
        }
472
0
        x++;
473
0
        dx += 2 * b_square;
474
0
        decision_parameter += dx + b_square;
475
0
    }
476
477
    // For region 2:
478
0
    decision_parameter = b_square * ((x + 0.5) * (x + 0.5)) + a_square * ((y - 1) * (y - 1)) - a_square * b_square;
479
480
0
    while (y >= 0) {
481
0
        callback({ x, y });
482
483
0
        if (decision_parameter <= 0) {
484
0
            x++;
485
0
            dx += 2 * b_square;
486
0
            decision_parameter += dx;
487
0
        }
488
0
        y--;
489
0
        dy -= 2 * a_square;
490
0
        decision_parameter += a_square - dy;
491
0
    }
492
0
}
493
494
void Painter::fill_ellipse(IntRect const& a_rect, Color color)
495
0
{
496
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
497
498
0
    auto rect = a_rect.translated(translation()).intersected(clip_rect());
499
0
    if (rect.is_empty())
500
0
        return;
501
502
0
    VERIFY(target().rect().contains(rect));
503
504
0
    auto const center = a_rect.center();
505
506
0
    on_each_ellipse_point(rect, [this, &color, center](IntPoint position) {
507
0
        IntPoint const directions[4] = { { position.x(), position.y() }, { -position.x(), position.y() }, { position.x(), -position.y() }, { -position.x(), -position.y() } };
508
509
0
        draw_line(center + directions[0], center + directions[1], color);
510
0
        draw_line(center + directions[2], center + directions[3], color);
511
0
    });
512
0
}
513
514
void Painter::draw_ellipse_intersecting(IntRect const& rect, Color color, int thickness)
515
0
{
516
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
517
518
0
    if (thickness <= 0)
519
0
        return;
520
521
0
    auto const center = rect.center();
522
523
0
    on_each_ellipse_point(rect, [this, &color, thickness, center](IntPoint position) {
524
0
        IntPoint const directions[4] = { { position.x(), position.y() }, { position.x(), -position.y() }, { -position.x(), position.y() }, { -position.x(), -position.y() } };
525
0
        for (auto const delta : directions) {
526
0
            auto const point = center + delta;
527
0
            draw_line(point, point, color, thickness);
528
0
        }
529
0
    });
530
0
}
531
532
template<typename RectType, typename Callback>
533
static void for_each_pixel_around_rect_clockwise(RectType const& rect, Callback callback)
534
0
{
535
0
    if (rect.is_empty())
536
0
        return;
537
0
    for (auto x = rect.left(); x < rect.right(); ++x)
538
0
        callback(x, rect.top());
539
0
    for (auto y = rect.top() + 1; y < rect.bottom(); ++y)
540
0
        callback(rect.right() - 1, y);
541
0
    for (auto x = rect.right() - 2; x >= rect.left(); --x)
542
0
        callback(x, rect.bottom() - 1);
543
0
    for (auto y = rect.bottom() - 2; y > rect.top(); --y)
544
0
        callback(rect.left(), y);
545
0
}
546
547
void Painter::draw_focus_rect(IntRect const& rect, Color color)
548
0
{
549
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
550
551
0
    if (rect.is_empty())
552
0
        return;
553
0
    bool state = false;
554
0
    for_each_pixel_around_rect_clockwise(rect, [&](auto x, auto y) {
555
0
        if (state)
556
0
            set_pixel(x, y, color);
557
0
        state = !state;
558
0
    });
559
0
}
560
561
void Painter::draw_rect(IntRect const& a_rect, Color color, bool rough)
562
0
{
563
0
    IntRect rect = a_rect.translated(translation());
564
0
    auto clipped_rect = rect.intersected(clip_rect());
565
0
    if (clipped_rect.is_empty())
566
0
        return;
567
568
0
    int min_y = clipped_rect.top();
569
0
    int max_y = clipped_rect.bottom() - 1;
570
0
    int scale = this->scale();
571
572
0
    if (rect.top() >= clipped_rect.top() && rect.top() < clipped_rect.bottom()) {
573
0
        int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
574
0
        if (width > 0) {
575
0
            int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
576
0
            for (int i = 0; i < scale; ++i)
577
0
                fill_physical_scanline_with_draw_op(rect.top() * scale + i, start_x * scale, width * scale, color);
578
0
        }
579
0
        ++min_y;
580
0
    }
581
0
    if (rect.bottom() > clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
582
0
        int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
583
0
        if (width > 0) {
584
0
            int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
585
0
            for (int i = 0; i < scale; ++i)
586
0
                fill_physical_scanline_with_draw_op(max_y * scale + i, start_x * scale, width * scale, color);
587
0
        }
588
0
        --max_y;
589
0
    }
590
591
0
    bool draw_left_side = rect.left() >= clipped_rect.left();
592
0
    bool draw_right_side = rect.right() == clipped_rect.right();
593
594
0
    if (draw_left_side && draw_right_side) {
595
        // Specialized loop when drawing both sides.
596
0
        for (int y = min_y * scale; y <= max_y * scale; ++y) {
597
0
            auto* bits = target().scanline(y);
598
0
            for (int i = 0; i < scale; ++i)
599
0
                set_physical_pixel_with_draw_op(bits[rect.left() * scale + i], color);
600
0
            for (int i = 0; i < scale; ++i)
601
0
                set_physical_pixel_with_draw_op(bits[(rect.right() - 1) * scale + i], color);
602
0
        }
603
0
    } else {
604
0
        for (int y = min_y * scale; y <= max_y * scale; ++y) {
605
0
            auto* bits = target().scanline(y);
606
0
            if (draw_left_side)
607
0
                for (int i = 0; i < scale; ++i)
608
0
                    set_physical_pixel_with_draw_op(bits[rect.left() * scale + i], color);
609
0
            if (draw_right_side)
610
0
                for (int i = 0; i < scale; ++i)
611
0
                    set_physical_pixel_with_draw_op(bits[(rect.right() - 1) * scale + i], color);
612
0
        }
613
0
    }
614
0
}
615
616
void Painter::draw_rect_with_thickness(IntRect const& rect, Color color, int thickness)
617
0
{
618
0
    if (thickness <= 0)
619
0
        return;
620
621
0
    IntPoint p1 = rect.location();
622
0
    IntPoint p2 = { rect.location().x() + rect.width(), rect.location().y() };
623
0
    IntPoint p3 = { rect.location().x() + rect.width(), rect.location().y() + rect.height() };
624
0
    IntPoint p4 = { rect.location().x(), rect.location().y() + rect.height() };
625
626
0
    draw_line(p1.translated(thickness, 0), p2.translated(-thickness, 0), color, thickness);
627
0
    draw_line(p2, p3, color, thickness);
628
0
    draw_line(p4.translated(thickness, 0), p3.translated(-thickness, 0), color, thickness);
629
0
    draw_line(p4, p1, color, thickness);
630
0
}
631
632
void Painter::draw_bitmap(IntPoint p, CharacterBitmap const& bitmap, Color color)
633
0
{
634
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
635
636
0
    auto rect = IntRect(p, bitmap.size()).translated(translation());
637
0
    auto clipped_rect = rect.intersected(clip_rect());
638
0
    if (clipped_rect.is_empty())
639
0
        return;
640
0
    int const first_row = clipped_rect.top() - rect.top();
641
0
    int const last_row = clipped_rect.bottom() - rect.top();
642
0
    int const first_column = clipped_rect.left() - rect.left();
643
0
    int const last_column = clipped_rect.right() - rect.left();
644
0
    ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
645
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
646
0
    char const* bitmap_row = &bitmap.bits()[first_row * bitmap.width() + first_column];
647
0
    size_t const bitmap_skip = bitmap.width();
648
649
0
    for (int row = first_row; row < last_row; ++row) {
650
0
        for (int j = 0; j < (last_column - first_column); ++j) {
651
0
            char fc = bitmap_row[j];
652
0
            if (fc == '#')
653
0
                dst[j] = color.value();
654
0
        }
655
0
        bitmap_row += bitmap_skip;
656
0
        dst += dst_skip;
657
0
    }
658
0
}
659
660
void Painter::draw_bitmap(IntPoint p, GlyphBitmap const& bitmap, Color color)
661
0
{
662
0
    auto dst_rect = IntRect(p, bitmap.size()).translated(translation());
663
0
    auto clipped_rect = dst_rect.intersected(clip_rect());
664
0
    if (clipped_rect.is_empty())
665
0
        return;
666
0
    int const first_row = clipped_rect.top() - dst_rect.top();
667
0
    int const last_row = clipped_rect.bottom() - dst_rect.top();
668
0
    int const first_column = clipped_rect.left() - dst_rect.left();
669
0
    int const last_column = clipped_rect.right() - dst_rect.left();
670
671
0
    int scale = this->scale();
672
0
    ARGB32* dst = target().scanline(clipped_rect.y() * scale) + clipped_rect.x() * scale;
673
0
    auto dst_format = target().format();
674
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
675
676
0
    if (scale == 1) {
677
0
        for (int row = first_row; row < last_row; ++row) {
678
0
            for (int j = 0; j < (last_column - first_column); ++j) {
679
0
                if (bitmap.bit_at(j + first_column, row))
680
0
                    dst[j] = color_for_format(dst_format, dst[j]).blend(color).value();
681
0
            }
682
0
            dst += dst_skip;
683
0
        }
684
0
    } else {
685
0
        for (int row = first_row; row < last_row; ++row) {
686
0
            for (int j = 0; j < (last_column - first_column); ++j) {
687
0
                if (bitmap.bit_at((j + first_column), row)) {
688
0
                    for (int iy = 0; iy < scale; ++iy)
689
0
                        for (int ix = 0; ix < scale; ++ix) {
690
0
                            auto pixel_index = j * scale + ix + iy * dst_skip;
691
0
                            dst[pixel_index] = color_for_format(dst_format, dst[pixel_index]).blend(color).value();
692
0
                        }
693
0
                }
694
0
            }
695
0
            dst += dst_skip * scale;
696
0
        }
697
0
    }
698
0
}
699
700
void Painter::draw_triangle(IntPoint offset, ReadonlySpan<IntPoint> control_points, Color color)
701
0
{
702
0
    VERIFY(control_points.size() == 3);
703
0
    draw_triangle(control_points[0] + offset, control_points[1] + offset, control_points[2] + offset, color);
704
0
}
705
706
void Painter::draw_triangle(IntPoint a, IntPoint b, IntPoint c, Color color)
707
0
{
708
0
    IntPoint p0(to_physical(a));
709
0
    IntPoint p1(to_physical(b));
710
0
    IntPoint p2(to_physical(c));
711
712
    // sort points from top to bottom
713
0
    if (p0.y() > p1.y())
714
0
        swap(p0, p1);
715
0
    if (p0.y() > p2.y())
716
0
        swap(p0, p2);
717
0
    if (p1.y() > p2.y())
718
0
        swap(p1, p2);
719
720
    // return if top and bottom points are on same line
721
0
    if (p0.y() == p2.y())
722
0
        return;
723
724
    // return if all points are on the same line vertically
725
0
    if (p0.x() == p1.x() && p1.x() == p2.x())
726
0
        return;
727
728
    // return if top is below clip rect or bottom is above clip rect
729
0
    auto clip = clip_rect();
730
0
    if (p0.y() >= clip.bottom() - 1)
731
0
        return;
732
0
    if (p2.y() < clip.top())
733
0
        return;
734
735
0
    class BoundaryLine {
736
0
    private:
737
0
        IntPoint m_base {};
738
0
        IntPoint m_path {};
739
740
0
    public:
741
0
        BoundaryLine(IntPoint a, IntPoint b)
742
0
        {
743
0
            VERIFY(a.y() <= b.y());
744
0
            m_base = a;
745
0
            m_path = b - a;
746
0
        }
747
748
0
        int top_y() const { return m_base.y(); }
749
750
0
        int bottom_y() const { return m_base.y() + m_path.y(); }
751
752
0
        bool is_vertical() const { return m_path.x() == 0; }
753
754
0
        bool is_horizontal() const { return m_path.y() == 0; }
755
756
0
        bool in_y_range(int y) const { return y >= top_y() && y <= bottom_y(); }
757
758
0
        Optional<int> intersection_on_x(int y) const
759
0
        {
760
0
            if (!in_y_range(y))
761
0
                return {};
762
0
            if (is_horizontal())
763
0
                return {};
764
0
            if (is_vertical())
765
0
                return m_base.x();
766
767
0
            int y_diff = y - top_y();
768
0
            int x_d = m_path.x() * y_diff, y_d = m_path.y();
769
770
0
            return (x_d / y_d) + m_base.x();
771
0
        }
772
0
    };
773
774
0
    BoundaryLine l0(p0, p1), l1(p0, p2), l2(p1, p2);
775
776
0
    int rgba = color.value();
777
778
0
    for (int y = max(p0.y(), clip.top()); y < min(p2.y() + 1, clip.bottom()); y++) {
779
0
        Optional<int>
780
0
            x0 = l0.intersection_on_x(y),
781
0
            x1 = l1.intersection_on_x(y),
782
0
            x2 = l2.intersection_on_x(y);
783
784
0
        int result_a = 0, result_b = 0;
785
786
0
        if (x0.has_value()) {
787
0
            result_a = x0.value();
788
0
            if (x1.has_value() && ((!x2.has_value()) || (result_a != x1.value()))) {
789
0
                result_b = x1.value();
790
0
            } else {
791
0
                result_b = x2.value();
792
0
            }
793
0
        } else if (x1.has_value()) {
794
0
            result_a = x1.value();
795
0
            result_b = x2.value();
796
0
        }
797
798
0
        if (result_a > result_b)
799
0
            swap(result_a, result_b);
800
801
0
        int left_bound = result_a, right_bound = result_b;
802
803
0
        ARGB32* scanline = target().scanline(y);
804
0
        for (int x = max(left_bound, clip.left()); x <= min(right_bound, clip.right() - 1); x++)
805
0
            scanline[x] = rgba;
806
0
    }
807
0
}
808
809
struct BlitState {
810
    enum AlphaState {
811
        NoAlpha = 0,
812
        SrcAlpha = 1,
813
        DstAlpha = 2,
814
        BothAlpha = SrcAlpha | DstAlpha
815
    };
816
817
    ARGB32 const* src;
818
    ARGB32* dst;
819
    size_t src_pitch;
820
    size_t dst_pitch;
821
    int row_count;
822
    int column_count;
823
    float opacity;
824
    BitmapFormat src_format;
825
};
826
827
// FIXME: This is a hack to support blit_with_opacity() with RGBA8888 source.
828
//        Ideally we'd have a more generic solution that allows any source format.
829
static void swap_red_and_blue_channels(Color& color)
830
0
{
831
0
    u32 rgba = color.value();
832
0
    u32 bgra = (rgba & 0xff00ff00)
833
0
        | ((rgba & 0x000000ff) << 16)
834
0
        | ((rgba & 0x00ff0000) >> 16);
835
0
    color = Color::from_argb(bgra);
836
0
}
837
838
// FIXME: This function is very unoptimized.
839
template<BlitState::AlphaState has_alpha>
840
static void do_blit_with_opacity(BlitState& state)
841
68
{
842
5.40k
    for (int row = 0; row < state.row_count; ++row) {
843
34.0k
        for (int x = 0; x < state.column_count; ++x) {
844
28.6k
            Color dest_color = (has_alpha & BlitState::DstAlpha) ? Color::from_argb(state.dst[x]) : Color::from_rgb(state.dst[x]);
845
28.6k
            if constexpr (has_alpha & BlitState::SrcAlpha) {
846
28.6k
                Color src_color_with_alpha = Color::from_argb(state.src[x]);
847
28.6k
                if (state.src_format == BitmapFormat::RGBA8888)
848
0
                    swap_red_and_blue_channels(src_color_with_alpha);
849
28.6k
                float pixel_opacity = src_color_with_alpha.alpha() / 255.0;
850
28.6k
                src_color_with_alpha.set_alpha(255 * (state.opacity * pixel_opacity));
851
28.6k
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
852
28.6k
            } else {
853
0
                Color src_color_with_alpha = Color::from_rgb(state.src[x]);
854
0
                if (state.src_format == BitmapFormat::RGBA8888)
855
0
                    swap_red_and_blue_channels(src_color_with_alpha);
856
0
                src_color_with_alpha.set_alpha(state.opacity * 255);
857
0
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
858
0
            }
859
28.6k
        }
860
5.33k
        state.dst += state.dst_pitch;
861
5.33k
        state.src += state.src_pitch;
862
5.33k
    }
863
68
}
Painter.cpp:void Gfx::do_blit_with_opacity<(Gfx::BlitState::AlphaState)3>(Gfx::BlitState&)
Line
Count
Source
841
21
{
842
4.41k
    for (int row = 0; row < state.row_count; ++row) {
843
21.9k
        for (int x = 0; x < state.column_count; ++x) {
844
17.5k
            Color dest_color = (has_alpha & BlitState::DstAlpha) ? Color::from_argb(state.dst[x]) : Color::from_rgb(state.dst[x]);
845
17.5k
            if constexpr (has_alpha & BlitState::SrcAlpha) {
846
17.5k
                Color src_color_with_alpha = Color::from_argb(state.src[x]);
847
17.5k
                if (state.src_format == BitmapFormat::RGBA8888)
848
0
                    swap_red_and_blue_channels(src_color_with_alpha);
849
17.5k
                float pixel_opacity = src_color_with_alpha.alpha() / 255.0;
850
17.5k
                src_color_with_alpha.set_alpha(255 * (state.opacity * pixel_opacity));
851
17.5k
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
852
            } else {
853
                Color src_color_with_alpha = Color::from_rgb(state.src[x]);
854
                if (state.src_format == BitmapFormat::RGBA8888)
855
                    swap_red_and_blue_channels(src_color_with_alpha);
856
                src_color_with_alpha.set_alpha(state.opacity * 255);
857
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
858
            }
859
17.5k
        }
860
4.39k
        state.dst += state.dst_pitch;
861
4.39k
        state.src += state.src_pitch;
862
4.39k
    }
863
21
}
Painter.cpp:void Gfx::do_blit_with_opacity<(Gfx::BlitState::AlphaState)1>(Gfx::BlitState&)
Line
Count
Source
841
47
{
842
984
    for (int row = 0; row < state.row_count; ++row) {
843
12.0k
        for (int x = 0; x < state.column_count; ++x) {
844
11.1k
            Color dest_color = (has_alpha & BlitState::DstAlpha) ? Color::from_argb(state.dst[x]) : Color::from_rgb(state.dst[x]);
845
11.1k
            if constexpr (has_alpha & BlitState::SrcAlpha) {
846
11.1k
                Color src_color_with_alpha = Color::from_argb(state.src[x]);
847
11.1k
                if (state.src_format == BitmapFormat::RGBA8888)
848
0
                    swap_red_and_blue_channels(src_color_with_alpha);
849
11.1k
                float pixel_opacity = src_color_with_alpha.alpha() / 255.0;
850
11.1k
                src_color_with_alpha.set_alpha(255 * (state.opacity * pixel_opacity));
851
11.1k
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
852
            } else {
853
                Color src_color_with_alpha = Color::from_rgb(state.src[x]);
854
                if (state.src_format == BitmapFormat::RGBA8888)
855
                    swap_red_and_blue_channels(src_color_with_alpha);
856
                src_color_with_alpha.set_alpha(state.opacity * 255);
857
                state.dst[x] = dest_color.blend(src_color_with_alpha).value();
858
            }
859
11.1k
        }
860
937
        state.dst += state.dst_pitch;
861
937
        state.src += state.src_pitch;
862
937
    }
863
47
}
Unexecuted instantiation: Painter.cpp:void Gfx::do_blit_with_opacity<(Gfx::BlitState::AlphaState)2>(Gfx::BlitState&)
Unexecuted instantiation: Painter.cpp:void Gfx::do_blit_with_opacity<(Gfx::BlitState::AlphaState)0>(Gfx::BlitState&)
864
865
void Painter::blit_with_opacity(IntPoint position, Gfx::Bitmap const& source, IntRect const& a_src_rect, float opacity, bool apply_alpha)
866
68
{
867
68
    VERIFY(scale() >= source.scale() && "painter doesn't support downsampling scale factors");
868
869
68
    if (opacity >= 1.0f && !(source.has_alpha_channel() && apply_alpha))
870
0
        return blit(position, source, a_src_rect);
871
872
68
    IntRect safe_src_rect = IntRect::intersection(a_src_rect, source.rect());
873
68
    if (scale() != source.scale())
874
0
        return draw_scaled_bitmap({ position, safe_src_rect.size() }, source, safe_src_rect, opacity);
875
876
68
    auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
877
68
    auto clipped_rect = dst_rect.intersected(clip_rect());
878
68
    if (clipped_rect.is_empty())
879
0
        return;
880
881
68
    int scale = this->scale();
882
68
    auto src_rect = a_src_rect * scale;
883
68
    clipped_rect *= scale;
884
68
    dst_rect *= scale;
885
886
68
    int const first_row = clipped_rect.top() - dst_rect.top();
887
68
    int const last_row = clipped_rect.bottom() - dst_rect.top();
888
68
    int const first_column = clipped_rect.left() - dst_rect.left();
889
68
    int const last_column = clipped_rect.right() - dst_rect.left();
890
891
68
    BlitState blit_state {
892
68
        .src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column,
893
68
        .dst = target().scanline(clipped_rect.y()) + clipped_rect.x(),
894
68
        .src_pitch = source.pitch() / sizeof(ARGB32),
895
68
        .dst_pitch = target().pitch() / sizeof(ARGB32),
896
68
        .row_count = last_row - first_row,
897
68
        .column_count = last_column - first_column,
898
68
        .opacity = opacity,
899
68
        .src_format = source.format(),
900
68
    };
901
902
68
    if (source.has_alpha_channel() && apply_alpha) {
903
68
        if (target().has_alpha_channel())
904
21
            do_blit_with_opacity<BlitState::BothAlpha>(blit_state);
905
47
        else
906
47
            do_blit_with_opacity<BlitState::SrcAlpha>(blit_state);
907
68
    } else {
908
0
        if (target().has_alpha_channel())
909
0
            do_blit_with_opacity<BlitState::DstAlpha>(blit_state);
910
0
        else
911
0
            do_blit_with_opacity<BlitState::NoAlpha>(blit_state);
912
0
    }
913
68
}
914
915
void Painter::blit_filtered(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, Function<Color(Color)> const& filter, bool apply_alpha)
916
0
{
917
0
    VERIFY((source.scale() == 1 || source.scale() == scale()) && "blit_filtered only supports integer upsampling");
918
919
0
    IntRect safe_src_rect = src_rect.intersected(source.rect());
920
0
    auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
921
0
    auto clipped_rect = dst_rect.intersected(clip_rect());
922
0
    if (clipped_rect.is_empty())
923
0
        return;
924
925
0
    int scale = this->scale();
926
0
    clipped_rect *= scale;
927
0
    dst_rect *= scale;
928
0
    safe_src_rect *= source.scale();
929
930
0
    int const first_row = clipped_rect.top() - dst_rect.top();
931
0
    int const last_row = clipped_rect.bottom() - dst_rect.top();
932
0
    int const first_column = clipped_rect.left() - dst_rect.left();
933
0
    int const last_column = clipped_rect.right() - dst_rect.left();
934
0
    ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
935
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
936
0
    auto dst_format = target().format();
937
0
    auto src_format = source.format();
938
939
0
    int s = scale / source.scale();
940
0
    if (s == 1) {
941
0
        ARGB32 const* src = source.scanline(safe_src_rect.top() + first_row) + safe_src_rect.left() + first_column;
942
0
        size_t const src_skip = source.pitch() / sizeof(ARGB32);
943
944
0
        for (int row = first_row; row < last_row; ++row) {
945
0
            for (int x = 0; x < (last_column - first_column); ++x) {
946
0
                auto source_color = color_for_format(src_format, src[x]);
947
0
                if (source_color.alpha() == 0)
948
0
                    continue;
949
0
                auto filtered_color = filter(source_color);
950
0
                if (!apply_alpha || filtered_color.alpha() == 0xff)
951
0
                    dst[x] = filtered_color.value();
952
0
                else
953
0
                    dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
954
0
            }
955
0
            dst += dst_skip;
956
0
            src += src_skip;
957
0
        }
958
0
    } else {
959
0
        for (int row = first_row; row < last_row; ++row) {
960
0
            ARGB32 const* src = source.scanline(safe_src_rect.top() + row / s) + safe_src_rect.left() + first_column / s;
961
0
            for (int x = 0; x < (last_column - first_column); ++x) {
962
0
                auto source_color = color_for_format(src_format, src[x / s]);
963
0
                if (source_color.alpha() == 0)
964
0
                    continue;
965
0
                auto filtered_color = filter(source_color);
966
0
                if (!apply_alpha || filtered_color.alpha() == 0xff)
967
0
                    dst[x] = filtered_color.value();
968
0
                else
969
0
                    dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
970
0
            }
971
0
            dst += dst_skip;
972
0
        }
973
0
    }
974
0
}
975
976
void Painter::blit_brightened(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect)
977
0
{
978
0
    return blit_filtered(position, source, src_rect, [](Color src) {
979
0
        return src.lightened();
980
0
    });
981
0
}
982
983
void Painter::blit_dimmed(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect)
984
0
{
985
0
    return blit_filtered(position, source, src_rect, [](Color src) {
986
0
        return src.to_grayscale().lightened();
987
0
    });
988
0
}
989
990
void Painter::draw_tiled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& source)
991
0
{
992
0
    VERIFY((source.scale() == 1 || source.scale() == scale()) && "draw_tiled_bitmap only supports integer upsampling");
993
994
0
    auto dst_rect = a_dst_rect.translated(translation());
995
0
    auto clipped_rect = dst_rect.intersected(clip_rect());
996
0
    if (clipped_rect.is_empty())
997
0
        return;
998
999
0
    int scale = this->scale();
1000
0
    clipped_rect *= scale;
1001
0
    dst_rect *= scale;
1002
1003
0
    int const first_row = clipped_rect.top() - dst_rect.top();
1004
0
    int const last_row = clipped_rect.bottom() - dst_rect.top();
1005
0
    int const first_column = clipped_rect.left() - dst_rect.left();
1006
0
    ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
1007
0
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
1008
1009
0
    if (source.format() == BitmapFormat::BGRx8888 || source.format() == BitmapFormat::BGRA8888) {
1010
0
        int s = scale / source.scale();
1011
0
        if (s == 1) {
1012
0
            int x_start = first_column + a_dst_rect.left() * scale;
1013
0
            for (int row = first_row; row < last_row; ++row) {
1014
0
                ARGB32 const* sl = source.scanline((row + a_dst_rect.top() * scale) % source.physical_height());
1015
0
                for (int x = x_start; x < clipped_rect.width() + x_start; ++x)
1016
0
                    dst[x - x_start] = sl[x % source.physical_width()];
1017
0
                dst += dst_skip;
1018
0
            }
1019
0
        } else {
1020
0
            int x_start = first_column + a_dst_rect.left() * scale;
1021
0
            for (int row = first_row; row < last_row; ++row) {
1022
0
                ARGB32 const* sl = source.scanline(((row + a_dst_rect.top() * scale) / s) % source.physical_height());
1023
0
                for (int x = x_start; x < clipped_rect.width() + x_start; ++x)
1024
0
                    dst[x - x_start] = sl[(x / s) % source.physical_width()];
1025
0
                dst += dst_skip;
1026
0
            }
1027
0
        }
1028
0
        return;
1029
0
    }
1030
1031
0
    VERIFY_NOT_REACHED();
1032
0
}
1033
1034
void Painter::blit_offset(IntPoint a_position, Gfx::Bitmap const& source, IntRect const& a_src_rect, IntPoint offset)
1035
0
{
1036
0
    auto src_rect = IntRect { a_src_rect.location() - offset, a_src_rect.size() };
1037
0
    auto position = a_position;
1038
0
    if (src_rect.x() < 0) {
1039
0
        position.set_x(position.x() - src_rect.x());
1040
0
        src_rect.set_x(0);
1041
0
    }
1042
0
    if (src_rect.y() < 0) {
1043
0
        position.set_y(position.y() - src_rect.y());
1044
0
        src_rect.set_y(0);
1045
0
    }
1046
0
    blit(position, source, src_rect);
1047
0
}
1048
1049
void Painter::blit(IntPoint position, Gfx::Bitmap const& source, IntRect const& a_src_rect, float opacity, bool apply_alpha)
1050
114
{
1051
114
    VERIFY(scale() >= source.scale() && "painter doesn't support downsampling scale factors");
1052
1053
114
    if (opacity < 1.0f || (source.has_alpha_channel() && apply_alpha))
1054
68
        return blit_with_opacity(position, source, a_src_rect, opacity, apply_alpha);
1055
1056
46
    auto safe_src_rect = a_src_rect.intersected(source.rect());
1057
46
    if (scale() != source.scale())
1058
0
        return draw_scaled_bitmap({ position, safe_src_rect.size() }, source, safe_src_rect, opacity);
1059
1060
    // If we get here, the Painter might have a scale factor, but the source bitmap has the same scale factor.
1061
    // We need to transform from logical to physical coordinates, but we can just copy pixels without resampling.
1062
46
    auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
1063
46
    auto clipped_rect = dst_rect.intersected(clip_rect());
1064
46
    if (clipped_rect.is_empty())
1065
0
        return;
1066
1067
    // All computations below are in physical coordinates.
1068
46
    int scale = this->scale();
1069
46
    auto src_rect = a_src_rect * scale;
1070
46
    clipped_rect *= scale;
1071
46
    dst_rect *= scale;
1072
1073
46
    int const first_row = clipped_rect.top() - dst_rect.top();
1074
46
    int const last_row = clipped_rect.bottom() - dst_rect.top();
1075
46
    int const first_column = clipped_rect.left() - dst_rect.left();
1076
46
    ARGB32* dst = target().scanline(clipped_rect.y()) + clipped_rect.x();
1077
46
    size_t const dst_skip = target().pitch() / sizeof(ARGB32);
1078
1079
46
    if (source.format() == BitmapFormat::BGRx8888 || source.format() == BitmapFormat::BGRA8888) {
1080
46
        ARGB32 const* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
1081
46
        size_t const src_skip = source.pitch() / sizeof(ARGB32);
1082
4.84k
        for (int row = first_row; row < last_row; ++row) {
1083
4.79k
            memcpy(dst, src, sizeof(ARGB32) * clipped_rect.width());
1084
4.79k
            dst += dst_skip;
1085
4.79k
            src += src_skip;
1086
4.79k
        }
1087
46
        return;
1088
46
    }
1089
1090
0
    if (source.format() == BitmapFormat::RGBA8888) {
1091
0
        u32 const* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
1092
0
        size_t const src_skip = source.pitch() / sizeof(u32);
1093
0
        for (int row = first_row; row < last_row; ++row) {
1094
0
            for (int i = 0; i < clipped_rect.width(); ++i) {
1095
0
                u32 rgba = src[i];
1096
0
                u32 bgra = (rgba & 0xff00ff00)
1097
0
                    | ((rgba & 0x000000ff) << 16)
1098
0
                    | ((rgba & 0x00ff0000) >> 16);
1099
0
                dst[i] = bgra;
1100
0
            }
1101
0
            dst += dst_skip;
1102
0
            src += src_skip;
1103
0
        }
1104
0
        return;
1105
0
    }
1106
1107
0
    VERIFY_NOT_REACHED();
1108
0
}
1109
1110
template<bool has_alpha_channel, typename GetPixel>
1111
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)
1112
0
{
1113
0
    bool has_opacity = opacity != 1.0f;
1114
0
    for (int y = 0; y < src_rect.height(); ++y) {
1115
0
        int dst_y = dst_rect.y() + y * vfactor;
1116
0
        for (int x = 0; x < src_rect.width(); ++x) {
1117
0
            auto src_pixel = get_pixel(source, x + src_rect.left(), y + src_rect.top());
1118
0
            if (has_opacity)
1119
0
                src_pixel.set_alpha(src_pixel.alpha() * opacity);
1120
0
            for (int yo = 0; yo < vfactor; ++yo) {
1121
0
                auto* scanline = (Color*)target.scanline(dst_y + yo);
1122
0
                int dst_x = dst_rect.x() + x * hfactor;
1123
0
                for (int xo = 0; xo < hfactor; ++xo) {
1124
                    if constexpr (has_alpha_channel)
1125
0
                        scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel);
1126
                    else
1127
0
                        scanline[dst_x + xo] = src_pixel;
1128
0
                }
1129
0
            }
1130
0
        }
1131
0
    }
1132
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_integer_scaled_bitmap<true, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, int, int, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_integer_scaled_bitmap<false, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, int, int, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
1133
1134
template<bool has_alpha_channel, typename GetPixel>
1135
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)
1136
0
{
1137
0
    float source_pixel_width = src_rect.width() / dst_rect.width();
1138
0
    float source_pixel_height = src_rect.height() / dst_rect.height();
1139
0
    float source_pixel_area = source_pixel_width * source_pixel_height;
1140
0
    FloatRect const pixel_box = { 0.f, 0.f, 1.f, 1.f };
1141
1142
0
    for (int y = clipped_rect.top(); y < clipped_rect.bottom(); ++y) {
1143
0
        auto* scanline = reinterpret_cast<Color*>(target.scanline(y));
1144
0
        for (int x = clipped_rect.left(); x < clipped_rect.right(); ++x) {
1145
            // Project the destination pixel in the source image
1146
0
            FloatRect const source_box = {
1147
0
                src_rect.left() + (x - dst_rect.x()) * source_pixel_width,
1148
0
                src_rect.top() + (y - dst_rect.y()) * source_pixel_height,
1149
0
                source_pixel_width,
1150
0
                source_pixel_height,
1151
0
            };
1152
0
            IntRect enclosing_source_box = enclosing_int_rect(source_box).intersected(source.rect());
1153
1154
            // Sum the contribution of all source pixels inside the projected pixel
1155
0
            float red_accumulator = 0.f;
1156
0
            float green_accumulator = 0.f;
1157
0
            float blue_accumulator = 0.f;
1158
0
            float total_area = 0.f;
1159
0
            for (int sy = enclosing_source_box.y(); sy < enclosing_source_box.bottom(); ++sy) {
1160
0
                for (int sx = enclosing_source_box.x(); sx < enclosing_source_box.right(); ++sx) {
1161
0
                    float area = source_box.intersected(pixel_box.translated(sx, sy)).size().area();
1162
1163
0
                    auto pixel = get_pixel(source, sx, sy);
1164
0
                    area *= pixel.alpha() / 255.f;
1165
1166
0
                    red_accumulator += pixel.red() * area;
1167
0
                    green_accumulator += pixel.green() * area;
1168
0
                    blue_accumulator += pixel.blue() * area;
1169
0
                    total_area += area;
1170
0
                }
1171
0
            }
1172
1173
0
            Color src_pixel = {
1174
0
                round_to<u8>(min(red_accumulator / total_area, 255.f)),
1175
0
                round_to<u8>(min(green_accumulator / total_area, 255.f)),
1176
0
                round_to<u8>(min(blue_accumulator / total_area, 255.f)),
1177
0
                round_to<u8>(min(total_area * 255.f / source_pixel_area * opacity, 255.f)),
1178
0
            };
1179
1180
            if constexpr (has_alpha_channel)
1181
0
                scanline[x] = scanline[x].blend(src_pixel);
1182
            else
1183
0
                scanline[x] = src_pixel;
1184
0
        }
1185
0
    }
1186
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_box_sampled_scaled_bitmap<true, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_box_sampled_scaled_bitmap<false, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
1187
1188
template<bool has_alpha_channel, ScalingMode scaling_mode, typename GetPixel>
1189
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)
1190
0
{
1191
0
    auto int_src_rect = enclosing_int_rect(src_rect);
1192
0
    auto clipped_src_rect = int_src_rect.intersected(source.rect());
1193
0
    if (clipped_src_rect.is_empty())
1194
0
        return;
1195
1196
0
    if constexpr (scaling_mode == ScalingMode::NearestNeighbor || scaling_mode == ScalingMode::SmoothPixels) {
1197
0
        if (dst_rect == clipped_rect && int_src_rect == src_rect && !(dst_rect.width() % int_src_rect.width()) && !(dst_rect.height() % int_src_rect.height())) {
1198
0
            int hfactor = dst_rect.width() / int_src_rect.width();
1199
0
            int vfactor = dst_rect.height() / int_src_rect.height();
1200
0
            if (hfactor == 2 && vfactor == 2)
1201
0
                return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 2, 2, get_pixel, opacity);
1202
0
            if (hfactor == 3 && vfactor == 3)
1203
0
                return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 3, 3, get_pixel, opacity);
1204
0
            if (hfactor == 4 && vfactor == 4)
1205
0
                return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, 4, 4, get_pixel, opacity);
1206
0
            return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, int_src_rect, source, hfactor, vfactor, get_pixel, opacity);
1207
0
        }
1208
0
    }
1209
1210
    if constexpr (scaling_mode == ScalingMode::BoxSampling)
1211
0
        return do_draw_box_sampled_scaled_bitmap<has_alpha_channel>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1212
1213
0
    bool has_opacity = opacity != 1.f;
1214
0
    i64 shift = 1ll << 32;
1215
0
    i64 fractional_mask = shift - 1;
1216
0
    i64 bilinear_offset_x = (1ll << 31) * (src_rect.width() / dst_rect.width() - 1);
1217
0
    i64 bilinear_offset_y = (1ll << 31) * (src_rect.height() / dst_rect.height() - 1);
1218
0
    i64 hscale = src_rect.width() * shift / dst_rect.width();
1219
0
    i64 vscale = src_rect.height() * shift / dst_rect.height();
1220
0
    i64 src_left = src_rect.left() * shift;
1221
0
    i64 src_top = src_rect.top() * shift;
1222
1223
0
    for (int y = clipped_rect.top(); y < clipped_rect.bottom(); ++y) {
1224
0
        auto* scanline = reinterpret_cast<Color*>(target.scanline(y));
1225
0
        auto desired_y = (y - dst_rect.y()) * vscale + src_top;
1226
1227
0
        for (int x = clipped_rect.left(); x < clipped_rect.right(); ++x) {
1228
0
            auto desired_x = (x - dst_rect.x()) * hscale + src_left;
1229
1230
0
            Color src_pixel;
1231
0
            if constexpr (scaling_mode == ScalingMode::BilinearBlend) {
1232
0
                auto shifted_x = desired_x + bilinear_offset_x;
1233
0
                auto shifted_y = desired_y + bilinear_offset_y;
1234
1235
0
                auto scaled_x0 = clamp(shifted_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
1236
0
                auto scaled_x1 = clamp((shifted_x >> 32) + 1, clipped_src_rect.left(), clipped_src_rect.right() - 1);
1237
0
                auto scaled_y0 = clamp(shifted_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
1238
0
                auto scaled_y1 = clamp((shifted_y >> 32) + 1, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
1239
1240
0
                float x_ratio = (shifted_x & fractional_mask) / static_cast<float>(shift);
1241
0
                float y_ratio = (shifted_y & fractional_mask) / static_cast<float>(shift);
1242
1243
0
                auto top_left = get_pixel(source, scaled_x0, scaled_y0);
1244
0
                auto top_right = get_pixel(source, scaled_x1, scaled_y0);
1245
0
                auto bottom_left = get_pixel(source, scaled_x0, scaled_y1);
1246
0
                auto bottom_right = get_pixel(source, scaled_x1, scaled_y1);
1247
1248
0
                auto top = top_left.mixed_with(top_right, x_ratio);
1249
0
                auto bottom = bottom_left.mixed_with(bottom_right, x_ratio);
1250
1251
0
                src_pixel = top.mixed_with(bottom, y_ratio);
1252
0
            } else if constexpr (scaling_mode == ScalingMode::SmoothPixels) {
1253
0
                auto scaled_x1 = clamp(desired_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
1254
0
                auto scaled_x0 = clamp(scaled_x1 - 1, clipped_src_rect.left(), clipped_src_rect.right() - 1);
1255
0
                auto scaled_y1 = clamp(desired_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
1256
0
                auto scaled_y0 = clamp(scaled_y1 - 1, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
1257
1258
0
                float x_ratio = (desired_x & fractional_mask) / (float)shift;
1259
0
                float y_ratio = (desired_y & fractional_mask) / (float)shift;
1260
1261
0
                float scaled_x_ratio = clamp(x_ratio * dst_rect.width() / (float)src_rect.width(), 0.f, 1.f);
1262
0
                float scaled_y_ratio = clamp(y_ratio * dst_rect.height() / (float)src_rect.height(), 0.f, 1.f);
1263
1264
0
                auto top_left = get_pixel(source, scaled_x0, scaled_y0);
1265
0
                auto top_right = get_pixel(source, scaled_x1, scaled_y0);
1266
0
                auto bottom_left = get_pixel(source, scaled_x0, scaled_y1);
1267
0
                auto bottom_right = get_pixel(source, scaled_x1, scaled_y1);
1268
1269
0
                auto top = top_left.mixed_with(top_right, scaled_x_ratio);
1270
0
                auto bottom = bottom_left.mixed_with(bottom_right, scaled_x_ratio);
1271
1272
0
                src_pixel = top.mixed_with(bottom, scaled_y_ratio);
1273
0
            } else {
1274
0
                auto scaled_x = clamp(desired_x >> 32, clipped_src_rect.left(), clipped_src_rect.right() - 1);
1275
0
                auto scaled_y = clamp(desired_y >> 32, clipped_src_rect.top(), clipped_src_rect.bottom() - 1);
1276
0
                src_pixel = get_pixel(source, scaled_x, scaled_y);
1277
0
            }
1278
1279
0
            if (has_opacity)
1280
0
                src_pixel.set_alpha(src_pixel.alpha() * opacity);
1281
1282
            if constexpr (has_alpha_channel)
1283
0
                scanline[x] = scanline[x].blend(src_pixel);
1284
            else
1285
0
                scanline[x] = src_pixel;
1286
0
        }
1287
0
    }
1288
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, (Gfx::ScalingMode)0, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, (Gfx::ScalingMode)1, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, (Gfx::ScalingMode)2, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, (Gfx::ScalingMode)3, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, (Gfx::ScalingMode)4, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, (Gfx::ScalingMode)0, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, (Gfx::ScalingMode)1, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, (Gfx::ScalingMode)2, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, (Gfx::ScalingMode)3, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, (Gfx::ScalingMode)4, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float)
1289
1290
template<bool has_alpha_channel, typename GetPixel>
1291
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)
1292
0
{
1293
0
    switch (scaling_mode) {
1294
0
    case ScalingMode::NearestNeighbor:
1295
0
        do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::NearestNeighbor>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1296
0
        break;
1297
0
    case ScalingMode::SmoothPixels:
1298
0
        do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::SmoothPixels>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1299
0
        break;
1300
0
    case ScalingMode::BilinearBlend:
1301
0
        do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::BilinearBlend>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1302
0
        break;
1303
0
    case ScalingMode::BoxSampling:
1304
0
        do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::BoxSampling>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1305
0
        break;
1306
0
    case ScalingMode::None:
1307
0
        do_draw_scaled_bitmap<has_alpha_channel, ScalingMode::None>(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity);
1308
0
        break;
1309
0
    }
1310
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<true, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float, Gfx::ScalingMode)
Unexecuted instantiation: Painter.cpp:void Gfx::do_draw_scaled_bitmap<false, Gfx::Color (*)(Gfx::Bitmap const&, int, int)>(Gfx::Bitmap&, Gfx::Rect<int> const&, Gfx::Rect<int> const&, Gfx::Bitmap const&, Gfx::Rect<float> const&, Gfx::Color (*)(Gfx::Bitmap const&, int, int), float, Gfx::ScalingMode)
1311
1312
void Painter::draw_scaled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& source, IntRect const& a_src_rect, float opacity, ScalingMode scaling_mode)
1313
0
{
1314
0
    draw_scaled_bitmap(a_dst_rect, source, FloatRect { a_src_rect }, opacity, scaling_mode);
1315
0
}
1316
1317
void Painter::draw_scaled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& source, FloatRect const& a_src_rect, float opacity, ScalingMode scaling_mode)
1318
0
{
1319
0
    IntRect int_src_rect = enclosing_int_rect(a_src_rect);
1320
0
    if (scale() == source.scale() && a_src_rect == int_src_rect && a_dst_rect.size() == int_src_rect.size())
1321
0
        return blit(a_dst_rect.location(), source, int_src_rect, opacity);
1322
1323
0
    if (scaling_mode == ScalingMode::None) {
1324
0
        IntRect clipped_draw_rect { (int)a_src_rect.location().x(), (int)a_src_rect.location().y(), a_dst_rect.size().width(), a_dst_rect.size().height() };
1325
0
        return blit(a_dst_rect.location(), source, clipped_draw_rect, opacity);
1326
0
    }
1327
1328
0
    auto dst_rect = to_physical(a_dst_rect);
1329
0
    auto src_rect = a_src_rect * source.scale();
1330
0
    auto clipped_rect = dst_rect.intersected(clip_rect() * scale());
1331
0
    if (clipped_rect.is_empty())
1332
0
        return;
1333
1334
0
    if (source.has_alpha_channel() || opacity != 1.0f) {
1335
0
        switch (source.format()) {
1336
0
        case BitmapFormat::BGRx8888:
1337
0
            do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, Gfx::get_pixel<BitmapFormat::BGRx8888>, opacity, scaling_mode);
1338
0
            break;
1339
0
        case BitmapFormat::BGRA8888:
1340
0
            do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, Gfx::get_pixel<BitmapFormat::BGRA8888>, opacity, scaling_mode);
1341
0
            break;
1342
0
        default:
1343
0
            do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, Gfx::get_pixel<BitmapFormat::Invalid>, opacity, scaling_mode);
1344
0
            break;
1345
0
        }
1346
0
    } else {
1347
0
        switch (source.format()) {
1348
0
        case BitmapFormat::BGRx8888:
1349
0
            do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, Gfx::get_pixel<BitmapFormat::BGRx8888>, opacity, scaling_mode);
1350
0
            break;
1351
0
        default:
1352
0
            do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, Gfx::get_pixel<BitmapFormat::Invalid>, opacity, scaling_mode);
1353
0
            break;
1354
0
        }
1355
0
    }
1356
0
}
1357
1358
ALWAYS_INLINE void Painter::draw_glyph(FloatPoint point, u32 code_point, Color color)
1359
0
{
1360
0
    draw_glyph(point, code_point, font(), color);
1361
0
}
1362
1363
FLATTEN void Painter::draw_glyph(FloatPoint point, u32 code_point, Font const& font, Color color)
1364
0
{
1365
0
    auto top_left = point + FloatPoint(font.glyph_left_bearing(code_point), 0);
1366
0
    auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left);
1367
0
    auto glyph = font.glyph(code_point, glyph_position.subpixel_offset);
1368
1369
0
    if (glyph.is_glyph_bitmap()) {
1370
0
        draw_bitmap(top_left.to_type<int>(), glyph.glyph_bitmap(), color);
1371
0
    } else if (glyph.is_color_bitmap()) {
1372
0
        float scaled_width = glyph.advance();
1373
0
        float ratio = static_cast<float>(glyph.bitmap()->height()) / static_cast<float>(glyph.bitmap()->width());
1374
0
        float scaled_height = scaled_width * ratio;
1375
1376
0
        FloatRect rect(point.x(), point.y(), scaled_width, scaled_height);
1377
0
        draw_scaled_bitmap(rect.to_rounded<int>(), *glyph.bitmap(), glyph.bitmap()->rect(), 1.0f, ScalingMode::BilinearBlend);
1378
0
    } else if (color.alpha() != 255) {
1379
0
        blit_filtered(glyph_position.blit_position, *glyph.bitmap(), glyph.bitmap()->rect(), [color](Color pixel) -> Color {
1380
0
            return pixel.multiply(color);
1381
0
        });
1382
0
    } else {
1383
0
        blit_filtered(glyph_position.blit_position, *glyph.bitmap(), glyph.bitmap()->rect(), [color](Color pixel) -> Color {
1384
0
            return color.with_alpha(pixel.alpha());
1385
0
        });
1386
0
    }
1387
0
}
1388
1389
void Painter::draw_emoji(IntPoint point, Gfx::Bitmap const& emoji, Font const& font)
1390
0
{
1391
0
    IntRect dst_rect {
1392
0
        point.x(),
1393
0
        point.y(),
1394
0
        font.pixel_size_rounded_up() * emoji.width() / emoji.height(),
1395
0
        font.pixel_size_rounded_up(),
1396
0
    };
1397
0
    draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
1398
0
}
1399
1400
void Painter::draw_glyph_or_emoji(FloatPoint point, u32 code_point, Font const& font, Color color)
1401
0
{
1402
0
    StringBuilder builder;
1403
0
    builder.append_code_point(code_point);
1404
0
    auto it = Utf8View { builder.string_view() }.begin();
1405
0
    return draw_glyph_or_emoji(point, it, font, color);
1406
0
}
1407
1408
void Painter::draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font, Color color)
1409
0
{
1410
0
    auto draw_glyph_or_emoji = prepare_draw_glyph_or_emoji(point, it, font);
1411
0
    if (draw_glyph_or_emoji.has<DrawGlyph>()) {
1412
0
        auto& glyph = draw_glyph_or_emoji.get<DrawGlyph>();
1413
0
        draw_glyph(glyph.position, glyph.code_point, font, color);
1414
0
    } else {
1415
0
        auto& emoji = draw_glyph_or_emoji.get<DrawEmoji>();
1416
0
        draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, font);
1417
0
    }
1418
0
}
1419
1420
void Painter::draw_glyph(IntPoint point, u32 code_point, Color color)
1421
0
{
1422
0
    draw_glyph(point.to_type<float>(), code_point, font(), color);
1423
0
}
1424
1425
void Painter::draw_glyph(IntPoint point, u32 code_point, Font const& font, Color color)
1426
0
{
1427
0
    draw_glyph(point.to_type<float>(), code_point, font, color);
1428
0
}
1429
1430
void Painter::draw_glyph_or_emoji(IntPoint point, u32 code_point, Font const& font, Color color)
1431
0
{
1432
0
    draw_glyph_or_emoji(point.to_type<float>(), code_point, font, color);
1433
0
}
1434
1435
void Painter::draw_glyph_or_emoji(IntPoint point, Utf8CodePointIterator& it, Font const& font, Color color)
1436
0
{
1437
0
    draw_glyph_or_emoji(point.to_type<float>(), it, font, color);
1438
0
}
1439
1440
template<typename DrawGlyphFunction>
1441
void draw_text_line(FloatRect const& a_rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextDirection direction, DrawGlyphFunction draw_glyph)
1442
0
{
1443
0
    auto rect = a_rect;
1444
1445
0
    switch (alignment) {
1446
0
    case TextAlignment::TopLeft:
1447
0
    case TextAlignment::CenterLeft:
1448
0
    case TextAlignment::BottomLeft:
1449
0
        break;
1450
0
    case TextAlignment::TopRight:
1451
0
    case TextAlignment::CenterRight:
1452
0
    case TextAlignment::BottomRight:
1453
0
        rect.set_x(rect.right() - 1 - font.width(text));
1454
0
        break;
1455
0
    case TextAlignment::TopCenter:
1456
0
    case TextAlignment::BottomCenter:
1457
0
    case TextAlignment::Center: {
1458
0
        auto shrunken_rect = rect;
1459
0
        shrunken_rect.set_width(font.width(text));
1460
0
        shrunken_rect.center_within(rect);
1461
0
        rect = shrunken_rect;
1462
0
        break;
1463
0
    }
1464
0
    default:
1465
0
        VERIFY_NOT_REACHED();
1466
0
    }
1467
1468
0
    auto point = rect.location();
1469
0
    auto space_width = font.glyph_width(' ') + font.glyph_spacing();
1470
1471
0
    if (direction == TextDirection::RTL) {
1472
0
        point.translate_by(rect.width(), 0); // Start drawing from the end
1473
0
        space_width = -space_width;          // Draw spaces backwards
1474
0
    }
1475
1476
0
    u32 last_code_point { 0 };
1477
0
    for (auto it = text.begin(); it != text.end(); ++it) {
1478
0
        auto code_point = *it;
1479
0
        if (should_paint_as_space(code_point)) {
1480
0
            point.translate_by(space_width, 0);
1481
0
            last_code_point = code_point;
1482
0
            continue;
1483
0
        }
1484
1485
0
        auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point);
1486
0
        if (kerning != 0.0f)
1487
0
            point.translate_by(direction == TextDirection::LTR ? kerning : -kerning, 0);
1488
1489
0
        auto it_copy = it; // The callback function will advance the iterator, so create a copy for this lookup.
1490
0
        FloatSize glyph_size(font.glyph_or_emoji_width(it_copy) + font.glyph_spacing(), font.pixel_size());
1491
1492
0
        if (direction == TextDirection::RTL)
1493
0
            point.translate_by(-glyph_size.width(), 0); // If we are drawing right to left, we have to move backwards before drawing the glyph
1494
0
        draw_glyph({ point, glyph_size }, it);
1495
0
        if (direction == TextDirection::LTR)
1496
0
            point.translate_by(glyph_size.width(), 0);
1497
        // The callback function might have exhausted the iterator.
1498
0
        if (it == text.end())
1499
0
            break;
1500
0
        last_code_point = code_point;
1501
0
    }
1502
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::draw_text_line<Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextDirection, Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::draw_text_line<Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextDirection, Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::draw_text_line<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextDirection, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::draw_text_line<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextDirection, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::draw_text_line<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextDirection, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
1503
1504
static inline size_t draw_text_get_length(Utf8View const& text)
1505
0
{
1506
0
    return text.byte_length();
1507
0
}
1508
1509
Vector<DirectionalRun> Painter::split_text_into_directional_runs(Utf8View const& text, TextDirection initial_direction)
1510
0
{
1511
    // FIXME: This is a *very* simplified version of the UNICODE BIDIRECTIONAL ALGORITHM (https://www.unicode.org/reports/tr9/), that can render most bidirectional text
1512
    //  but also produces awkward results in a large amount of edge cases. This should probably be replaced with a fully spec compliant implementation at some point.
1513
1514
    // FIXME: Support HTML "dir" attribute (how?)
1515
0
    u8 paragraph_embedding_level = initial_direction == TextDirection::LTR ? 0 : 1;
1516
0
    Vector<u8> embedding_levels;
1517
0
    embedding_levels.ensure_capacity(text.length());
1518
0
    for (size_t i = 0; i < text.length(); i++)
1519
0
        embedding_levels.unchecked_append(paragraph_embedding_level);
1520
1521
    // FIXME: Support Explicit Directional Formatting Characters
1522
1523
0
    Vector<BidirectionalClass> character_classes;
1524
0
    character_classes.ensure_capacity(text.length());
1525
0
    for (u32 code_point : text)
1526
0
        character_classes.unchecked_append(get_char_bidi_class(code_point));
1527
1528
    // resolving weak types
1529
0
    BidirectionalClass paragraph_class = initial_direction == TextDirection::LTR ? BidirectionalClass::STRONG_LTR : BidirectionalClass::STRONG_RTL;
1530
0
    for (size_t i = 0; i < character_classes.size(); i++) {
1531
0
        if (character_classes[i] != BidirectionalClass::WEAK_SEPARATORS)
1532
0
            continue;
1533
0
        for (ssize_t j = i - 1; j >= 0; j--) {
1534
0
            auto character_class = character_classes[j];
1535
0
            if (character_class != BidirectionalClass::STRONG_RTL && character_class != BidirectionalClass::STRONG_LTR)
1536
0
                continue;
1537
0
            character_classes[i] = character_class;
1538
0
            break;
1539
0
        }
1540
0
        if (character_classes[i] == BidirectionalClass::WEAK_SEPARATORS)
1541
0
            character_classes[i] = paragraph_class;
1542
0
    }
1543
1544
    // resolving neutral types
1545
0
    auto left_side = BidirectionalClass::NEUTRAL;
1546
0
    auto sequence_length = 0;
1547
0
    for (size_t i = 0; i < character_classes.size(); i++) {
1548
0
        auto character_class = character_classes[i];
1549
0
        if (left_side == BidirectionalClass::NEUTRAL) {
1550
0
            if (character_class != BidirectionalClass::NEUTRAL)
1551
0
                left_side = character_class;
1552
0
            else
1553
0
                character_classes[i] = paragraph_class;
1554
0
            continue;
1555
0
        }
1556
0
        if (character_class != BidirectionalClass::NEUTRAL) {
1557
0
            BidirectionalClass sequence_class;
1558
0
            if (bidi_class_to_direction(left_side) == bidi_class_to_direction(character_class)) {
1559
0
                sequence_class = left_side == BidirectionalClass::STRONG_RTL ? BidirectionalClass::STRONG_RTL : BidirectionalClass::STRONG_LTR;
1560
0
            } else {
1561
0
                sequence_class = paragraph_class;
1562
0
            }
1563
0
            for (auto j = 0; j < sequence_length; j++) {
1564
0
                character_classes[i - j - 1] = sequence_class;
1565
0
            }
1566
0
            sequence_length = 0;
1567
0
            left_side = character_class;
1568
0
        } else {
1569
0
            sequence_length++;
1570
0
        }
1571
0
    }
1572
0
    for (auto i = 0; i < sequence_length; i++)
1573
0
        character_classes[character_classes.size() - i - 1] = paragraph_class;
1574
1575
    // resolving implicit levels
1576
0
    for (size_t i = 0; i < character_classes.size(); i++) {
1577
0
        auto character_class = character_classes[i];
1578
0
        if ((embedding_levels[i] % 2) == 0) {
1579
0
            if (character_class == BidirectionalClass::STRONG_RTL)
1580
0
                embedding_levels[i] += 1;
1581
0
            else if (character_class == BidirectionalClass::WEAK_NUMBERS || character_class == BidirectionalClass::WEAK_SEPARATORS)
1582
0
                embedding_levels[i] += 2;
1583
0
        } else {
1584
0
            if (character_class == BidirectionalClass::STRONG_LTR || character_class == BidirectionalClass::WEAK_NUMBERS || character_class == BidirectionalClass::WEAK_SEPARATORS)
1585
0
                embedding_levels[i] += 1;
1586
0
        }
1587
0
    }
1588
1589
    // splitting into runs
1590
0
    auto run_code_points_start = text.begin();
1591
0
    auto next_code_points_slice = [&](auto length) {
1592
0
        Vector<u32> run_code_points;
1593
0
        run_code_points.ensure_capacity(length);
1594
0
        for (size_t j = 0; j < length; ++j, ++run_code_points_start)
1595
0
            run_code_points.unchecked_append(*run_code_points_start);
1596
0
        return run_code_points;
1597
0
    };
1598
0
    Vector<DirectionalRun> runs;
1599
0
    size_t start = 0;
1600
0
    u8 level = embedding_levels[0];
1601
0
    for (size_t i = 1; i < embedding_levels.size(); ++i) {
1602
0
        if (embedding_levels[i] == level)
1603
0
            continue;
1604
0
        auto code_points_slice = next_code_points_slice(i - start);
1605
0
        runs.append({ move(code_points_slice), level });
1606
0
        start = i;
1607
0
        level = embedding_levels[i];
1608
0
    }
1609
0
    auto code_points_slice = next_code_points_slice(embedding_levels.size() - start);
1610
0
    runs.append({ move(code_points_slice), level });
1611
1612
    // reordering resolved levels
1613
    // FIXME: missing special cases for trailing whitespace characters
1614
0
    u8 minimum_level = 128;
1615
0
    u8 maximum_level = 0;
1616
0
    for (auto& run : runs) {
1617
0
        minimum_level = min(minimum_level, run.embedding_level());
1618
0
        maximum_level = max(minimum_level, run.embedding_level());
1619
0
    }
1620
0
    if ((minimum_level % 2) == 0)
1621
0
        minimum_level++;
1622
0
    auto runs_count = runs.size() - 1;
1623
0
    while (maximum_level <= minimum_level) {
1624
0
        size_t run_index = 0;
1625
0
        while (run_index < runs_count) {
1626
0
            while (run_index < runs_count && runs[run_index].embedding_level() < maximum_level)
1627
0
                run_index++;
1628
0
            auto reverse_start = run_index;
1629
0
            while (run_index <= runs_count && runs[run_index].embedding_level() >= maximum_level)
1630
0
                run_index++;
1631
0
            auto reverse_end = run_index - 1;
1632
0
            while (reverse_start < reverse_end) {
1633
0
                swap(runs[reverse_start], runs[reverse_end]);
1634
0
                reverse_start++;
1635
0
                reverse_end--;
1636
0
            }
1637
0
        }
1638
0
        maximum_level--;
1639
0
    }
1640
1641
    // mirroring RTL mirror characters
1642
0
    for (auto& run : runs) {
1643
0
        if (run.direction() == TextDirection::LTR)
1644
0
            continue;
1645
0
        for (auto& code_point : run.code_points()) {
1646
0
            code_point = get_mirror_char(code_point);
1647
0
        }
1648
0
    }
1649
1650
0
    return runs;
1651
0
}
1652
1653
bool Painter::text_contains_bidirectional_text(Utf8View const& text, TextDirection initial_direction)
1654
0
{
1655
0
    for (u32 code_point : text) {
1656
0
        auto char_class = get_char_bidi_class(code_point);
1657
0
        if (char_class == BidirectionalClass::NEUTRAL)
1658
0
            continue;
1659
0
        if (bidi_class_to_direction(char_class) != initial_direction)
1660
0
            return true;
1661
0
    }
1662
0
    return false;
1663
0
}
1664
1665
template<typename DrawGlyphFunction>
1666
void Painter::do_draw_text(FloatRect const& rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping, DrawGlyphFunction draw_glyph)
1667
0
{
1668
0
    if (draw_text_get_length(text) == 0)
1669
0
        return;
1670
1671
0
    TextLayout layout(font, text, rect);
1672
1673
0
    auto line_height = font.preferred_line_height();
1674
1675
0
    auto lines = layout.lines(elision, wrapping);
1676
0
    auto bounding_rect = layout.bounding_rect(wrapping);
1677
1678
0
    bounding_rect.align_within(rect, alignment);
1679
1680
0
    for (size_t i = 0; i < lines.size(); ++i) {
1681
0
        auto line = Utf8View { lines[i] };
1682
1683
0
        FloatRect line_rect { bounding_rect.x(), bounding_rect.y() + i * line_height, bounding_rect.width(), line_height };
1684
1685
0
        TextDirection line_direction = get_text_direction(line);
1686
0
        if (text_contains_bidirectional_text(line, line_direction)) { // Slow Path: The line contains mixed BiDi classes
1687
0
            auto directional_runs = split_text_into_directional_runs(line, line_direction);
1688
0
            auto current_dx = line_direction == TextDirection::LTR ? 0 : line_rect.width();
1689
0
            for (auto& directional_run : directional_runs) {
1690
0
                auto run_width = font.width(directional_run.text());
1691
0
                if (line_direction == TextDirection::RTL)
1692
0
                    current_dx -= run_width;
1693
0
                auto run_rect = line_rect.translated(current_dx, 0);
1694
0
                run_rect.set_width(run_width);
1695
1696
                // NOTE: DirectionalRun returns Utf32View which isn't
1697
                // compatible with draw_text_line.
1698
0
                StringBuilder builder;
1699
0
                builder.append(directional_run.text());
1700
0
                auto line_text = Utf8View { builder.string_view() };
1701
1702
0
                draw_text_line(run_rect, line_text, font, alignment, directional_run.direction(), draw_glyph);
1703
0
                if (line_direction == TextDirection::LTR)
1704
0
                    current_dx += run_width;
1705
0
            }
1706
0
        } else {
1707
0
            draw_text_line(line_rect, line, font, alignment, line_direction, draw_glyph);
1708
0
        }
1709
0
    }
1710
0
}
Unexecuted instantiation: Painter.cpp:void Gfx::Painter::do_draw_text<Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping, Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::Painter::do_draw_text<Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping, Gfx::Painter::draw_text(Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::Color, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::Painter::do_draw_text<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::Painter::do_draw_text<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::StringView, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
Unexecuted instantiation: Painter.cpp:void Gfx::Painter::do_draw_text<Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0>(Gfx::Rect<float> const&, AK::Utf8View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping, Gfx::Painter::draw_text(AK::Function<void (Gfx::Rect<float> const&, AK::Utf8CodePointIterator&)>, Gfx::Rect<float> const&, AK::Utf32View const&, Gfx::Font const&, Gfx::TextAlignment, Gfx::TextElision, Gfx::TextWrapping)::$_0)
1711
1712
void Painter::draw_text(FloatRect const& rect, StringView text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1713
0
{
1714
0
    draw_text(rect, text, font(), alignment, color, elision, wrapping);
1715
0
}
1716
1717
void Painter::draw_text(FloatRect const& rect, Utf32View const& text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1718
0
{
1719
0
    draw_text(rect, text, font(), alignment, color, elision, wrapping);
1720
0
}
1721
1722
void Painter::draw_text(FloatRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1723
0
{
1724
0
    Utf8View text { raw_text };
1725
0
    do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
1726
0
        draw_glyph_or_emoji(r.location(), it, font, color);
1727
0
    });
1728
0
}
1729
1730
void Painter::draw_text(FloatRect const& rect, Utf32View const& raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1731
0
{
1732
    // FIXME: UTF-32 should eventually be completely removed, but for the time
1733
    // being some places might depend on it, so we do some internal conversion.
1734
0
    StringBuilder builder;
1735
0
    builder.append(raw_text);
1736
0
    auto text = Utf8View { builder.string_view() };
1737
0
    do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
1738
0
        draw_glyph_or_emoji(r.location(), it, font, color);
1739
0
    });
1740
0
}
1741
1742
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, FloatRect const& rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1743
0
{
1744
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
1745
1746
0
    do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
1747
0
        draw_one_glyph(r, it);
1748
0
    });
1749
0
}
1750
1751
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, FloatRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1752
0
{
1753
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
1754
1755
0
    Utf8View text { raw_text };
1756
0
    do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
1757
0
        draw_one_glyph(r, it);
1758
0
    });
1759
0
}
1760
1761
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, FloatRect const& rect, Utf32View const& raw_text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1762
0
{
1763
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
1764
1765
    // FIXME: UTF-32 should eventually be completely removed, but for the time
1766
    // being some places might depend on it, so we do some internal conversion.
1767
0
    StringBuilder builder;
1768
0
    builder.append(raw_text);
1769
0
    auto text = Utf8View { builder.string_view() };
1770
0
    do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
1771
0
        draw_one_glyph(r, it);
1772
0
    });
1773
0
}
1774
1775
void Painter::draw_text(IntRect const& rect, StringView text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1776
0
{
1777
0
    draw_text(rect.to_type<float>(), text, font(), alignment, color, elision, wrapping);
1778
0
}
1779
1780
void Painter::draw_text(IntRect const& rect, Utf32View const& text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1781
0
{
1782
0
    draw_text(rect.to_type<float>(), text, font(), alignment, color, elision, wrapping);
1783
0
}
1784
1785
void Painter::draw_text(IntRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1786
0
{
1787
0
    draw_text(rect.to_type<float>(), raw_text, font, alignment, color, elision, wrapping);
1788
0
}
1789
1790
void Painter::draw_text(IntRect const& rect, Utf32View const& raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
1791
0
{
1792
0
    return draw_text(rect.to_type<float>(), raw_text, font, alignment, color, elision, wrapping);
1793
0
}
1794
1795
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, IntRect const& rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1796
0
{
1797
0
    return draw_text(move(draw_one_glyph), rect.to_type<float>(), text, font, alignment, elision, wrapping);
1798
0
}
1799
1800
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, IntRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1801
0
{
1802
0
    return draw_text(move(draw_one_glyph), rect.to_type<float>(), raw_text, font, alignment, elision, wrapping);
1803
0
}
1804
1805
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, IntRect const& rect, Utf32View const& raw_text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
1806
0
{
1807
0
    return draw_text(move(draw_one_glyph), rect.to_type<float>(), raw_text, font, alignment, elision, wrapping);
1808
0
}
1809
1810
void Painter::set_pixel(IntPoint p, Color color, bool blend)
1811
0
{
1812
0
    auto point = p;
1813
0
    point.translate_by(state().translation);
1814
    // Use the scale only to avoid clipping pixels set in drawing functions that handle
1815
    // scaling and call set_pixel() -- do not scale the pixel.
1816
0
    if (!clip_rect().contains(point / scale()))
1817
0
        return;
1818
0
    set_physical_pixel(point, color, blend);
1819
0
}
1820
1821
void Painter::set_physical_pixel(IntPoint physical_point, Color color, bool blend)
1822
0
{
1823
    // This function should only be called after translation, clipping, etc has been handled elsewhere
1824
    // if not use set_pixel().
1825
0
    auto& dst = target().scanline(physical_point.y())[physical_point.x()];
1826
0
    if (!blend || color.alpha() == 255)
1827
0
        dst = color.value();
1828
0
    else if (color.alpha())
1829
0
        dst = color_for_format(target().format(), dst).blend(color).value();
1830
0
}
1831
1832
Optional<Color> Painter::get_pixel(IntPoint p)
1833
0
{
1834
0
    auto point = p;
1835
0
    point.translate_by(state().translation);
1836
0
    if (!clip_rect().contains(point / scale()))
1837
0
        return {};
1838
0
    return target().get_pixel(point);
1839
0
}
1840
1841
ErrorOr<NonnullRefPtr<Bitmap>> Painter::get_region_bitmap(IntRect const& region, BitmapFormat format, Optional<IntRect&> actual_region)
1842
0
{
1843
0
    VERIFY(scale() == 1);
1844
0
    auto bitmap_region = region.translated(state().translation).intersected(target().rect());
1845
0
    if (actual_region.has_value())
1846
0
        actual_region.value() = bitmap_region.translated(-state().translation);
1847
0
    return target().cropped(bitmap_region, format);
1848
0
}
1849
1850
ALWAYS_INLINE void Painter::set_physical_pixel_with_draw_op(u32& pixel, Color color)
1851
0
{
1852
    // This always sets a single physical pixel, independent of scale().
1853
    // This should only be called by routines that already handle scale.
1854
1855
0
    switch (draw_op()) {
1856
0
    case DrawOp::Copy:
1857
0
        pixel = color.value();
1858
0
        break;
1859
0
    case DrawOp::Xor:
1860
0
        pixel = color.xored(Color::from_argb(pixel)).value();
1861
0
        break;
1862
0
    case DrawOp::Invert:
1863
0
        pixel = Color::from_argb(pixel).inverted().value();
1864
0
        break;
1865
0
    }
1866
0
}
1867
1868
ALWAYS_INLINE void Painter::fill_physical_scanline_with_draw_op(int y, int x, int width, Color color)
1869
0
{
1870
    // This always draws a single physical scanline, independent of scale().
1871
    // This should only be called by routines that already handle scale.
1872
0
    auto dst_format = target().format();
1873
0
    switch (draw_op()) {
1874
0
    case DrawOp::Copy:
1875
0
        fast_u32_fill(target().scanline(y) + x, color.value(), width);
1876
0
        break;
1877
0
    case DrawOp::Xor: {
1878
0
        auto* pixel = target().scanline(y) + x;
1879
0
        auto* end = pixel + width;
1880
0
        while (pixel < end) {
1881
0
            *pixel = color_for_format(dst_format, *pixel).xored(color).value();
1882
0
            pixel++;
1883
0
        }
1884
0
        break;
1885
0
    }
1886
0
    case DrawOp::Invert: {
1887
0
        auto* pixel = target().scanline(y) + x;
1888
0
        auto* end = pixel + width;
1889
0
        while (pixel < end) {
1890
0
            *pixel = color_for_format(dst_format, *pixel).inverted().value();
1891
0
            pixel++;
1892
0
        }
1893
0
        break;
1894
0
    }
1895
0
    }
1896
0
}
1897
1898
void Painter::draw_physical_pixel(IntPoint physical_position, Color color, int thickness)
1899
0
{
1900
    // This always draws a single physical pixel, independent of scale().
1901
    // This should only be called by routines that already handle scale
1902
    // (including scaling thickness).
1903
0
    VERIFY(draw_op() == DrawOp::Copy);
1904
1905
0
    if (thickness <= 0)
1906
0
        return;
1907
1908
0
    if (thickness == 1) { // Implies scale() == 1.
1909
0
        auto& pixel = target().scanline(physical_position.y())[physical_position.x()];
1910
0
        return set_physical_pixel_with_draw_op(pixel, color_for_format(target().format(), pixel).blend(color));
1911
0
    }
1912
1913
0
    IntRect rect { physical_position, { thickness, thickness } };
1914
0
    rect.intersect(clip_rect() * scale());
1915
0
    if (rect.is_empty())
1916
0
        return;
1917
0
    fill_physical_rect(rect, color);
1918
0
}
1919
1920
void Painter::draw_line(IntPoint a_p1, IntPoint a_p2, Color color, int thickness, LineStyle style, Color alternate_color)
1921
0
{
1922
0
    if (clip_rect().is_empty())
1923
0
        return;
1924
1925
0
    if (thickness <= 0)
1926
0
        return;
1927
1928
0
    if (color.alpha() == 0)
1929
0
        return;
1930
1931
0
    auto clip_rect = this->clip_rect() * scale();
1932
1933
0
    auto const p1 = thickness > 1 ? a_p1.translated(-(thickness / 2), -(thickness / 2)) : a_p1;
1934
0
    auto const p2 = thickness > 1 ? a_p2.translated(-(thickness / 2), -(thickness / 2)) : a_p2;
1935
1936
0
    auto point1 = to_physical(p1);
1937
0
    auto point2 = to_physical(p2);
1938
0
    thickness *= scale();
1939
1940
0
    auto alternate_color_is_transparent = alternate_color == Color::Transparent;
1941
1942
    // Special case: vertical line.
1943
0
    if (point1.x() == point2.x()) {
1944
0
        int const x = point1.x();
1945
0
        if (x < clip_rect.left() || x >= clip_rect.right())
1946
0
            return;
1947
0
        if (point1.y() > point2.y())
1948
0
            swap(point1, point2);
1949
0
        if (point1.y() >= clip_rect.bottom())
1950
0
            return;
1951
0
        if (point2.y() < clip_rect.top())
1952
0
            return;
1953
0
        int min_y = max(point1.y(), clip_rect.top());
1954
0
        int max_y = min(point2.y(), clip_rect.bottom() - 1);
1955
0
        if (style == LineStyle::Dotted) {
1956
0
            for (int y = min_y; y <= max_y; y += thickness * 2)
1957
0
                draw_physical_pixel({ x, y }, color, thickness);
1958
0
        } else if (style == LineStyle::Dashed) {
1959
0
            for (int y = min_y; y <= max_y; y += thickness * 6) {
1960
0
                draw_physical_pixel({ x, y }, color, thickness);
1961
0
                draw_physical_pixel({ x, min(y + thickness, max_y) }, color, thickness);
1962
0
                draw_physical_pixel({ x, min(y + thickness * 2, max_y) }, color, thickness);
1963
0
                if (!alternate_color_is_transparent) {
1964
0
                    draw_physical_pixel({ x, min(y + thickness * 3, max_y) }, alternate_color, thickness);
1965
0
                    draw_physical_pixel({ x, min(y + thickness * 4, max_y) }, alternate_color, thickness);
1966
0
                    draw_physical_pixel({ x, min(y + thickness * 5, max_y) }, alternate_color, thickness);
1967
0
                }
1968
0
            }
1969
0
        } else {
1970
0
            fill_physical_rect({ x, min_y, thickness, max_y - min_y + thickness }, color);
1971
0
        }
1972
0
        return;
1973
0
    }
1974
1975
    // Special case: horizontal line.
1976
0
    if (point1.y() == point2.y()) {
1977
0
        int const y = point1.y();
1978
0
        if (y < clip_rect.top() || y >= clip_rect.bottom())
1979
0
            return;
1980
0
        if (point1.x() > point2.x())
1981
0
            swap(point1, point2);
1982
0
        if (point1.x() >= clip_rect.right())
1983
0
            return;
1984
0
        if (point2.x() < clip_rect.left())
1985
0
            return;
1986
0
        int min_x = max(point1.x(), clip_rect.left());
1987
0
        int max_x = min(point2.x(), clip_rect.right() - 1);
1988
0
        if (style == LineStyle::Dotted) {
1989
0
            for (int x = min_x; x <= max_x; x += thickness * 2)
1990
0
                draw_physical_pixel({ x, y }, color, thickness);
1991
0
        } else if (style == LineStyle::Dashed) {
1992
0
            for (int x = min_x; x <= max_x; x += thickness * 6) {
1993
0
                draw_physical_pixel({ x, y }, color, thickness);
1994
0
                draw_physical_pixel({ min(x + thickness, max_x), y }, color, thickness);
1995
0
                draw_physical_pixel({ min(x + thickness * 2, max_x), y }, color, thickness);
1996
0
                if (!alternate_color_is_transparent) {
1997
0
                    draw_physical_pixel({ min(x + thickness * 3, max_x), y }, alternate_color, thickness);
1998
0
                    draw_physical_pixel({ min(x + thickness * 4, max_x), y }, alternate_color, thickness);
1999
0
                    draw_physical_pixel({ min(x + thickness * 5, max_x), y }, alternate_color, thickness);
2000
0
                }
2001
0
            }
2002
0
        } else {
2003
0
            fill_physical_rect({ min_x, y, max_x - min_x + thickness, thickness }, color);
2004
0
        }
2005
0
        return;
2006
0
    }
2007
2008
0
    int const adx = abs(point2.x() - point1.x());
2009
0
    int const ady = abs(point2.y() - point1.y());
2010
2011
0
    if (adx > ady) {
2012
0
        if (point1.x() > point2.x())
2013
0
            swap(point1, point2);
2014
0
    } else {
2015
0
        if (point1.y() > point2.y())
2016
0
            swap(point1, point2);
2017
0
    }
2018
2019
0
    int const dx = point2.x() - point1.x();
2020
0
    int const dy = point2.y() - point1.y();
2021
0
    int error = 0;
2022
2023
0
    size_t number_of_pixels_drawn = 0;
2024
2025
0
    auto draw_pixel_in_line = [&](int x, int y) {
2026
0
        bool should_draw_line = true;
2027
0
        if (style == LineStyle::Dotted && number_of_pixels_drawn % 2 == 1)
2028
0
            should_draw_line = false;
2029
0
        else if (style == LineStyle::Dashed && number_of_pixels_drawn % 6 >= 3)
2030
0
            should_draw_line = false;
2031
2032
0
        if (should_draw_line)
2033
0
            draw_physical_pixel({ x, y }, color, thickness);
2034
0
        else if (!alternate_color_is_transparent)
2035
0
            draw_physical_pixel({ x, y }, alternate_color, thickness);
2036
2037
0
        number_of_pixels_drawn++;
2038
0
    };
2039
2040
0
    if (dx > dy) {
2041
0
        int const y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
2042
0
        int const delta_error = 2 * abs(dy);
2043
0
        int y = point1.y();
2044
0
        for (int x = point1.x(); x <= point2.x(); ++x) {
2045
0
            if (clip_rect.contains(x, y))
2046
0
                draw_pixel_in_line(x, y);
2047
0
            error += delta_error;
2048
0
            if (error >= dx) {
2049
0
                y += y_step;
2050
0
                error -= 2 * dx;
2051
0
            }
2052
0
        }
2053
0
    } else {
2054
0
        int const x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
2055
0
        int const delta_error = 2 * abs(dx);
2056
0
        int x = point1.x();
2057
0
        for (int y = point1.y(); y <= point2.y(); ++y) {
2058
0
            if (clip_rect.contains(x, y))
2059
0
                draw_pixel_in_line(x, y);
2060
0
            error += delta_error;
2061
0
            if (error >= dy) {
2062
0
                x += x_step;
2063
0
                error -= 2 * dy;
2064
0
            }
2065
0
        }
2066
0
    }
2067
0
}
2068
2069
void Painter::draw_triangle_wave(IntPoint a_p1, IntPoint a_p2, Color color, int amplitude, int thickness)
2070
0
{
2071
    // FIXME: Support more than horizontal waves
2072
0
    VERIFY(a_p1.y() == a_p2.y());
2073
2074
0
    auto const p1 = thickness > 1 ? a_p1.translated(-(thickness / 2), -(thickness / 2)) : a_p1;
2075
0
    auto const p2 = thickness > 1 ? a_p2.translated(-(thickness / 2), -(thickness / 2)) : a_p2;
2076
2077
0
    auto point1 = to_physical(p1);
2078
0
    auto point2 = to_physical(p2);
2079
2080
0
    auto y = point1.y();
2081
2082
0
    for (int x = 0; x <= point2.x() - point1.x(); ++x) {
2083
0
        auto y_offset = abs(x % (2 * amplitude) - amplitude) - amplitude;
2084
0
        draw_physical_pixel({ point1.x() + x, y + y_offset }, color, thickness);
2085
0
    }
2086
0
}
2087
2088
static bool can_approximate_bezier_curve(FloatPoint p1, FloatPoint p2, FloatPoint control)
2089
239M
{
2090
    // TODO: Somehow calculate the required number of splits based on the curve (and its size).
2091
239M
    constexpr float tolerance = 0.5f;
2092
2093
239M
    auto p1x = 3 * control.x() - 2 * p1.x() - p2.x();
2094
239M
    auto p1y = 3 * control.y() - 2 * p1.y() - p2.y();
2095
239M
    auto p2x = 3 * control.x() - 2 * p2.x() - p1.x();
2096
239M
    auto p2y = 3 * control.y() - 2 * p2.y() - p1.y();
2097
2098
239M
    p1x = p1x * p1x;
2099
239M
    p1y = p1y * p1y;
2100
239M
    p2x = p2x * p2x;
2101
239M
    p2y = p2y * p2y;
2102
2103
239M
    auto error = max(p1x, p2x) + max(p1y, p2y);
2104
239M
    VERIFY(isfinite(error));
2105
2106
239M
    return error <= tolerance;
2107
239M
}
2108
2109
// static
2110
void Painter::for_each_line_segment_on_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>& callback)
2111
13.8k
{
2112
13.8k
    struct SegmentDescriptor {
2113
13.8k
        FloatPoint control_point;
2114
13.8k
        FloatPoint p1;
2115
13.8k
        FloatPoint p2;
2116
13.8k
    };
2117
2118
119M
    static constexpr auto split_quadratic_bezier_curve = [](FloatPoint original_control, FloatPoint p1, FloatPoint p2, auto& segments) {
2119
119M
        auto po1_midpoint = original_control + p1;
2120
119M
        po1_midpoint /= 2;
2121
2122
119M
        auto po2_midpoint = original_control + p2;
2123
119M
        po2_midpoint /= 2;
2124
2125
119M
        auto new_segment = po1_midpoint + po2_midpoint;
2126
119M
        new_segment /= 2;
2127
2128
119M
        segments.append({ po2_midpoint, new_segment, p2 });
2129
119M
        segments.append({ po1_midpoint, p1, new_segment });
2130
119M
    };
2131
2132
13.8k
    Vector<SegmentDescriptor> segments;
2133
13.8k
    segments.append({ control_point, p1, p2 });
2134
239M
    while (!segments.is_empty()) {
2135
239M
        auto segment = segments.take_last();
2136
2137
239M
        if (can_approximate_bezier_curve(segment.p1, segment.p2, segment.control_point))
2138
119M
            callback(segment.p1, segment.p2);
2139
119M
        else
2140
119M
            split_quadratic_bezier_curve(segment.control_point, segment.p1, segment.p2, segments);
2141
239M
    }
2142
13.8k
}
2143
2144
void Painter::for_each_line_segment_on_bezier_curve(FloatPoint control_point, FloatPoint p1, FloatPoint p2, Function<void(FloatPoint, FloatPoint)>&& callback)
2145
13.8k
{
2146
13.8k
    for_each_line_segment_on_bezier_curve(control_point, p1, p2, callback);
2147
13.8k
}
2148
2149
void Painter::draw_quadratic_bezier_curve(IntPoint control_point, IntPoint p1, IntPoint p2, Color color, int thickness, LineStyle style)
2150
0
{
2151
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
2152
2153
0
    if (thickness <= 0)
2154
0
        return;
2155
2156
0
    for_each_line_segment_on_bezier_curve(FloatPoint(control_point), FloatPoint(p1), FloatPoint(p2), [&](FloatPoint fp1, FloatPoint fp2) {
2157
0
        draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
2158
0
    });
2159
0
}
2160
2161
void Painter::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)
2162
2.21M
{
2163
2.21M
    for_each_line_segment_on_cubic_bezier_curve(control_point_0, control_point_1, p1, p2, callback);
2164
2.21M
}
2165
2166
static bool can_approximate_cubic_bezier_curve(FloatPoint p1, FloatPoint p2, FloatPoint control_0, FloatPoint control_1)
2167
10.7M
{
2168
    // TODO: Somehow calculate the required number of splits based on the curve (and its size).
2169
10.7M
    constexpr float tolerance = 0.5f;
2170
2171
10.7M
    auto ax = 3 * control_0.x() - 2 * p1.x() - p2.x();
2172
10.7M
    auto ay = 3 * control_0.y() - 2 * p1.y() - p2.y();
2173
10.7M
    auto bx = 3 * control_1.x() - p1.x() - 2 * p2.x();
2174
10.7M
    auto by = 3 * control_1.y() - p1.y() - 2 * p2.y();
2175
2176
10.7M
    ax *= ax;
2177
10.7M
    ay *= ay;
2178
10.7M
    bx *= bx;
2179
10.7M
    by *= by;
2180
2181
10.7M
    auto error = max(ax, bx) + max(ay, by);
2182
10.7M
    VERIFY(isfinite(error));
2183
2184
10.7M
    return error <= tolerance;
2185
10.7M
}
2186
2187
// static
2188
void Painter::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)
2189
2.21M
{
2190
2.21M
    struct ControlPair {
2191
2.21M
        FloatPoint control_point_0;
2192
2.21M
        FloatPoint control_point_1;
2193
2.21M
    };
2194
2.21M
    struct SegmentDescriptor {
2195
2.21M
        ControlPair control_points;
2196
2.21M
        FloatPoint p1;
2197
2.21M
        FloatPoint p2;
2198
2.21M
    };
2199
2200
4.24M
    static constexpr auto split_cubic_bezier_curve = [](ControlPair const& original_controls, FloatPoint p1, FloatPoint p2, auto& segments) {
2201
4.24M
        Array level_1_midpoints {
2202
4.24M
            (p1 + original_controls.control_point_0) / 2,
2203
4.24M
            (original_controls.control_point_0 + original_controls.control_point_1) / 2,
2204
4.24M
            (original_controls.control_point_1 + p2) / 2,
2205
4.24M
        };
2206
4.24M
        Array level_2_midpoints {
2207
4.24M
            (level_1_midpoints[0] + level_1_midpoints[1]) / 2,
2208
4.24M
            (level_1_midpoints[1] + level_1_midpoints[2]) / 2,
2209
4.24M
        };
2210
4.24M
        auto level_3_midpoint = (level_2_midpoints[0] + level_2_midpoints[1]) / 2;
2211
2212
4.24M
        segments.append({ { level_2_midpoints[1], level_1_midpoints[2] }, level_3_midpoint, p2 });
2213
4.24M
        segments.append({ { level_1_midpoints[0], level_2_midpoints[0] }, p1, level_3_midpoint });
2214
4.24M
    };
2215
2216
2.21M
    Vector<SegmentDescriptor> segments;
2217
2.21M
    segments.append({ { control_point_0, control_point_1 }, p1, p2 });
2218
12.9M
    while (!segments.is_empty()) {
2219
10.7M
        auto segment = segments.take_last();
2220
2221
10.7M
        if (can_approximate_cubic_bezier_curve(segment.p1, segment.p2, segment.control_points.control_point_0, segment.control_points.control_point_1))
2222
6.46M
            callback(segment.p1, segment.p2);
2223
4.24M
        else
2224
4.24M
            split_cubic_bezier_curve(segment.control_points, segment.p1, segment.p2, segments);
2225
10.7M
    }
2226
2.21M
}
2227
2228
void Painter::draw_cubic_bezier_curve(IntPoint control_point_0, IntPoint control_point_1, IntPoint p1, IntPoint p2, Color color, int thickness, LineStyle style)
2229
0
{
2230
0
    for_each_line_segment_on_cubic_bezier_curve(FloatPoint(control_point_0), FloatPoint(control_point_1), FloatPoint(p1), FloatPoint(p2), [&](FloatPoint fp1, FloatPoint fp2) {
2231
0
        draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
2232
0
    });
2233
0
}
2234
2235
// static
2236
void Painter::for_each_line_segment_on_elliptical_arc(FloatPoint p1, FloatPoint p2, FloatPoint center, FloatSize radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint, FloatPoint)>& callback)
2237
0
{
2238
0
    if (radii.width() <= 0 || radii.height() <= 0)
2239
0
        return;
2240
2241
0
    auto start = p1;
2242
0
    auto end = p2;
2243
2244
0
    bool start_swapped = false;
2245
0
    if (theta_delta < 0) {
2246
0
        swap(start, end);
2247
0
        theta_1 = theta_1 + theta_delta;
2248
0
        theta_delta = fabsf(theta_delta);
2249
0
        start_swapped = true;
2250
0
    }
2251
2252
0
    auto relative_start = start - center;
2253
2254
0
    auto a = radii.width();
2255
0
    auto b = radii.height();
2256
2257
    // The segments are at most 1 long
2258
0
    auto largest_radius = max(a, b);
2259
0
    float theta_step = AK::atan2(1.f, (float)largest_radius);
2260
2261
0
    FloatPoint current_point = relative_start;
2262
0
    FloatPoint next_point = { 0, 0 };
2263
2264
0
    float sin_x_axis, cos_x_axis;
2265
0
    AK::sincos(x_axis_rotation, sin_x_axis, cos_x_axis);
2266
0
    auto rotate_point = [sin_x_axis, cos_x_axis](FloatPoint& p) {
2267
0
        auto original_x = p.x();
2268
0
        auto original_y = p.y();
2269
2270
0
        p.set_x(original_x * cos_x_axis - original_y * sin_x_axis);
2271
0
        p.set_y(original_x * sin_x_axis + original_y * cos_x_axis);
2272
0
    };
2273
2274
0
    auto emit_point = [&](auto p0, auto p1) {
2275
        // NOTE: If we swap the start/end we must swap the emitted points, so correct winding orders can be calculated.
2276
0
        if (start_swapped)
2277
0
            swap(p0, p1);
2278
0
        callback(p0, p1);
2279
0
    };
2280
2281
0
    for (float theta = theta_1; theta <= theta_1 + theta_delta; theta += theta_step) {
2282
0
        float s, c;
2283
0
        AK::sincos(theta, s, c);
2284
0
        next_point.set_x(a * c);
2285
0
        next_point.set_y(b * s);
2286
0
        rotate_point(next_point);
2287
2288
0
        emit_point(current_point + center, next_point + center);
2289
2290
0
        current_point = next_point;
2291
0
    }
2292
2293
0
    emit_point(current_point + center, end);
2294
0
}
2295
2296
// static
2297
void Painter::for_each_line_segment_on_elliptical_arc(FloatPoint p1, FloatPoint p2, FloatPoint center, FloatSize radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(FloatPoint, FloatPoint)>&& callback)
2298
0
{
2299
0
    for_each_line_segment_on_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, callback);
2300
0
}
2301
2302
void Painter::draw_elliptical_arc(IntPoint p1, IntPoint p2, IntPoint center, FloatSize radii, float x_axis_rotation, float theta_1, float theta_delta, Color color, int thickness, LineStyle style)
2303
0
{
2304
0
    VERIFY(scale() == 1); // FIXME: Add scaling support.
2305
2306
0
    if (thickness <= 0)
2307
0
        return;
2308
2309
0
    for_each_line_segment_on_elliptical_arc(FloatPoint(p1), FloatPoint(p2), FloatPoint(center), radii, x_axis_rotation, theta_1, theta_delta, [&](FloatPoint fp1, FloatPoint fp2) {
2310
0
        draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
2311
0
    });
2312
0
}
2313
2314
void Painter::add_clip_rect(IntRect const& rect)
2315
0
{
2316
0
    state().clip_rect.intersect(rect.translated(translation()));
2317
0
    state().clip_rect.intersect(target().rect()); // FIXME: This shouldn't be necessary?
2318
0
}
2319
2320
void Painter::clear_clip_rect()
2321
0
{
2322
0
    state().clip_rect = m_clip_origin;
2323
0
}
2324
2325
PainterStateSaver::PainterStateSaver(Painter& painter)
2326
0
    : m_painter(painter)
2327
0
{
2328
0
    m_painter.save();
2329
0
}
2330
2331
PainterStateSaver::~PainterStateSaver()
2332
0
{
2333
0
    m_painter.restore();
2334
0
}
2335
2336
void Painter::stroke_path(Path const& path, Color color, int thickness)
2337
0
{
2338
0
    if (thickness <= 0)
2339
0
        return;
2340
0
    fill_path(path.stroke_to_fill({ .thickness = static_cast<float>(thickness) }), color);
2341
0
}
2342
2343
void Painter::blit_disabled(IntPoint location, Gfx::Bitmap const& bitmap, IntRect const& rect, Palette const& palette)
2344
0
{
2345
0
    auto bright_color = palette.threed_highlight();
2346
0
    auto dark_color = palette.threed_shadow1();
2347
0
    blit_filtered(location.translated(1, 1), bitmap, rect, [&](auto) {
2348
0
        return bright_color;
2349
0
    });
2350
0
    blit_filtered(location, bitmap, rect, [&](Color src) {
2351
0
        int gray = src.to_grayscale().red();
2352
0
        if (gray > 160)
2353
0
            return bright_color;
2354
0
        return dark_color;
2355
0
    });
2356
0
}
2357
2358
void Painter::blit_tiled(IntRect const& dst_rect, Gfx::Bitmap const& bitmap, IntRect const& rect)
2359
0
{
2360
0
    auto tile_width = rect.width();
2361
0
    auto tile_height = rect.height();
2362
0
    auto dst_right = dst_rect.right() - 1;
2363
0
    auto dst_bottom = dst_rect.bottom() - 1;
2364
0
    for (int tile_y = dst_rect.top(); tile_y < dst_bottom; tile_y += tile_height) {
2365
0
        for (int tile_x = dst_rect.left(); tile_x < dst_right; tile_x += tile_width) {
2366
0
            IntRect tile_src_rect = rect;
2367
0
            auto tile_x_overflow = tile_x + tile_width - dst_right;
2368
0
            if (tile_x_overflow > 0)
2369
0
                tile_src_rect.set_width(tile_width - tile_x_overflow);
2370
0
            auto tile_y_overflow = tile_y + tile_height - dst_bottom;
2371
0
            if (tile_y_overflow > 0)
2372
0
                tile_src_rect.set_height(tile_height - tile_y_overflow);
2373
0
            blit(IntPoint(tile_x, tile_y), bitmap, tile_src_rect);
2374
0
        }
2375
0
    }
2376
0
}
2377
2378
ByteString parse_ampersand_string(StringView raw_text, Optional<size_t>* underline_offset)
2379
0
{
2380
0
    if (raw_text.is_empty())
2381
0
        return ByteString::empty();
2382
2383
0
    StringBuilder builder;
2384
2385
0
    for (size_t i = 0; i < raw_text.length(); ++i) {
2386
0
        if (raw_text[i] == '&') {
2387
0
            if (i != (raw_text.length() - 1) && raw_text[i + 1] == '&') {
2388
0
                builder.append(raw_text[i]);
2389
0
                ++i;
2390
0
            } else if (underline_offset && !(*underline_offset).has_value()) {
2391
0
                *underline_offset = i;
2392
0
            }
2393
0
            continue;
2394
0
        }
2395
0
        builder.append(raw_text[i]);
2396
0
    }
2397
0
    return builder.to_byte_string();
2398
0
}
2399
2400
void Gfx::Painter::draw_ui_text(Gfx::IntRect const& rect, StringView text, Gfx::Font const& font, Gfx::TextAlignment text_alignment, Gfx::Color color)
2401
0
{
2402
0
    Optional<size_t> underline_offset;
2403
0
    auto name_to_draw = parse_ampersand_string(text, &underline_offset);
2404
2405
0
    Gfx::IntRect text_rect { 0, 0, font.width_rounded_up(name_to_draw), font.pixel_size_rounded_up() };
2406
0
    text_rect.align_within(rect, text_alignment);
2407
2408
0
    draw_text(text_rect, name_to_draw, font, text_alignment, color);
2409
2410
0
    if (underline_offset.has_value()) {
2411
0
        Utf8View utf8_view { name_to_draw };
2412
0
        float width = 0;
2413
0
        for (auto it = utf8_view.begin(); it != utf8_view.end(); ++it) {
2414
0
            if (utf8_view.byte_offset_of(it) >= underline_offset.value()) {
2415
0
                int y = text_rect.bottom();
2416
0
                int x1 = text_rect.left() + width;
2417
0
                int x2 = x1 + font.glyph_or_emoji_width(it);
2418
0
                draw_line({ x1, y }, { x2, y }, color);
2419
0
                break;
2420
0
            }
2421
0
            width += font.glyph_or_emoji_width(it) + font.glyph_spacing();
2422
0
        }
2423
0
    }
2424
0
}
2425
2426
void Painter::draw_text_run(IntPoint baseline_start, Utf8View const& string, Font const& font, Color color)
2427
0
{
2428
0
    draw_text_run(baseline_start.to_type<float>(), string, font, color);
2429
0
}
2430
2431
void Painter::draw_text_run(FloatPoint baseline_start, Utf8View const& string, Font const& font, Color color)
2432
0
{
2433
0
    for_each_glyph_position(baseline_start, string, font, [&](DrawGlyphOrEmoji glyph_or_emoji) {
2434
0
        if (glyph_or_emoji.has<DrawGlyph>()) {
2435
0
            auto& glyph = glyph_or_emoji.get<DrawGlyph>();
2436
0
            draw_glyph(glyph.position, glyph.code_point, font, color);
2437
0
        } else {
2438
0
            auto& emoji = glyph_or_emoji.get<DrawEmoji>();
2439
0
            draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, font);
2440
0
        }
2441
0
    });
2442
0
}
2443
2444
void Painter::draw_scaled_bitmap_with_transform(IntRect const& dst_rect, Bitmap const& bitmap, FloatRect const& src_rect, AffineTransform const& transform, float opacity, ScalingMode scaling_mode)
2445
0
{
2446
0
    if (transform.is_identity_or_translation_or_scale(Gfx::AffineTransform::AllowNegativeScaling::No)) {
2447
0
        draw_scaled_bitmap(transform.map(dst_rect.to_type<float>()).to_rounded<int>(), bitmap, src_rect, opacity, scaling_mode);
2448
0
    } else {
2449
        // The painter has an affine transform, we have to draw through it!
2450
2451
        // FIXME: This is kinda inefficient.
2452
        // What we currently do, roughly:
2453
        // - Map the destination rect through the context's transform.
2454
        // - Compute the bounding rect of the destination quad.
2455
        // - For each point in the clipped bounding rect, reverse-map it to a point in the source image.
2456
        //   - Sample the source image at the computed point.
2457
        //   - Set or blend (depending on alpha values) one pixel in the canvas.
2458
        //   - Loop.
2459
2460
        // FIXME: Painter should have an affine transform as part of its state and handle all of this instead.
2461
2462
0
        if (opacity == 0.0f)
2463
0
            return;
2464
2465
0
        auto inverse_transform = transform.inverse();
2466
0
        if (!inverse_transform.has_value())
2467
0
            return;
2468
2469
0
        auto destination_quad = transform.map_to_quad(dst_rect.to_type<float>());
2470
0
        auto destination_bounding_rect = destination_quad.bounding_rect().to_rounded<int>();
2471
0
        auto source_rect = enclosing_int_rect(src_rect).intersected(bitmap.rect());
2472
2473
0
        Gfx::AffineTransform source_transform;
2474
0
        source_transform.translate(src_rect.x(), src_rect.y());
2475
0
        source_transform.scale(src_rect.width() / dst_rect.width(), src_rect.height() / dst_rect.height());
2476
0
        source_transform.translate(-dst_rect.x(), -dst_rect.y());
2477
2478
0
        auto translated_dest_rect = destination_bounding_rect.translated(translation());
2479
0
        auto clipped_bounding_rect = translated_dest_rect.intersected(clip_rect());
2480
0
        if (clipped_bounding_rect.is_empty())
2481
0
            return;
2482
2483
0
        auto sample_transform = source_transform.multiply(*inverse_transform);
2484
0
        auto start_offset = destination_bounding_rect.location() + (clipped_bounding_rect.location() - translated_dest_rect.location());
2485
0
        for (int y = 0; y < clipped_bounding_rect.height(); ++y) {
2486
0
            for (int x = 0; x < clipped_bounding_rect.width(); ++x) {
2487
0
                auto point = Gfx::IntPoint { x, y };
2488
0
                auto sample_point = point + start_offset;
2489
2490
                // AffineTransform::map(IntPoint) rounds internally, which is wrong here. So explicitly call the FloatPoint version, and then truncate the result.
2491
0
                auto source_point = Gfx::IntPoint { sample_transform.map(Gfx::FloatPoint { sample_point }) };
2492
2493
0
                if (!source_rect.contains(source_point))
2494
0
                    continue;
2495
0
                auto source_color = bitmap.get_pixel(source_point);
2496
0
                if (source_color.alpha() == 0)
2497
0
                    continue;
2498
0
                if (opacity != 1.0f)
2499
0
                    source_color = source_color.with_opacity(opacity);
2500
0
                set_physical_pixel(point + clipped_bounding_rect.location(), source_color, true);
2501
0
            }
2502
0
        }
2503
0
    }
2504
0
}
2505
2506
}