2020-01-18 11:38:21 +03:00
/*
2022-07-09 16:12:10 +03:00
* Copyright ( c ) 2018 - 2022 , Andreas Kling < kling @ serenityos . org >
2021-04-24 16:20:51 +03:00
* Copyright ( c ) 2021 , Idan Horowitz < idan . horowitz @ serenityos . org >
2021-09-13 23:48:22 +03:00
* Copyright ( c ) 2021 , Mustafa Quraish < mustafa @ serenityos . org >
2021-09-20 21:48:56 +03:00
* Copyright ( c ) 2021 , Sam Atkins < atkinssj @ serenityos . org >
2022-01-20 21:55:14 +03:00
* Copyright ( c ) 2022 , Tobias Christiansen < tobyase @ serenityos . org >
2022-02-23 00:55:01 +03:00
* Copyright ( c ) 2022 , Linus Groh < linusg @ serenityos . org >
2022-03-23 11:02:14 +03:00
* Copyright ( c ) 2022 , Jelle Raaijmakers < jelle @ gmta . nl >
2020-01-18 11:38:21 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 11:38:21 +03:00
*/
2018-10-10 17:49:36 +03:00
# include "Painter.h"
2020-03-08 14:05:14 +03:00
# include "Bitmap.h"
2022-04-09 10:28:38 +03:00
# include "Font/Emoji.h"
# include "Font/Font.h"
2020-11-16 18:35:41 +03:00
# include "Gamma.h"
2018-10-10 17:49:36 +03:00
# include <AK/Assertions.h>
2021-01-16 17:51:56 +03:00
# include <AK/Debug.h>
2020-03-30 20:39:37 +03:00
# include <AK/Function.h>
2021-07-17 19:29:28 +03:00
# include <AK/Math.h>
2020-03-08 14:05:14 +03:00
# include <AK/Memory.h>
2021-04-15 02:25:07 +03:00
# include <AK/Queue.h>
2020-05-06 10:25:12 +03:00
# include <AK/QuickSort.h>
2018-12-21 04:18:16 +03:00
# include <AK/StdLibExtras.h>
2019-04-04 16:19:04 +03:00
# include <AK/StringBuilder.h>
2020-05-17 21:32:23 +03:00
# include <AK/Utf32View.h>
2019-09-06 20:23:36 +03:00
# include <AK/Utf8View.h>
2020-02-06 14:04:00 +03:00
# include <LibGfx/CharacterBitmap.h>
2020-10-27 23:17:50 +03:00
# include <LibGfx/Palette.h>
2020-04-16 22:03:17 +03:00
# include <LibGfx/Path.h>
2022-09-24 14:00:53 +03:00
# include <LibGfx/Quad.h>
2021-04-24 16:20:51 +03:00
# include <LibGfx/TextDirection.h>
2021-07-26 00:20:11 +03:00
# include <LibGfx/TextLayout.h>
2023-02-21 15:50:41 +03:00
# include <LibUnicode/CharacterTypes.h>
2023-02-23 16:53:35 +03:00
# include <LibUnicode/Emoji.h>
2019-06-05 19:22:11 +03:00
# include <stdio.h>
2019-02-28 20:57:36 +03:00
2022-10-04 22:04:13 +03:00
# if defined(AK_COMPILER_GCC)
2020-03-08 14:05:14 +03:00
# pragma GCC optimize("O3")
2020-01-31 02:59:03 +03:00
# endif
2020-02-06 13:56:38 +03:00
namespace Gfx {
2022-07-09 16:12:10 +03:00
static bool should_paint_as_space ( u32 code_point )
{
return is_ascii_space ( code_point ) | | code_point = = 0xa0 ;
}
2023-05-14 19:22:05 +03:00
ALWAYS_INLINE static Color color_for_format ( BitmapFormat format , ARGB32 value )
{
switch ( format ) {
case BitmapFormat : : BGRA8888 :
return Color : : from_argb ( value ) ;
case BitmapFormat : : BGRx8888 :
return Color : : from_rgb ( value ) ;
// FIXME: Handle other formats
default :
VERIFY_NOT_REACHED ( ) ;
}
2023-07-08 05:48:11 +03:00
}
2023-05-14 19:22:05 +03:00
2020-02-15 01:02:47 +03:00
template < BitmapFormat format = BitmapFormat : : Invalid >
2021-09-17 16:06:28 +03:00
ALWAYS_INLINE Color get_pixel ( Gfx : : Bitmap const & bitmap , int x , int y )
2019-05-11 04:53:28 +03:00
{
2020-02-15 01:02:47 +03:00
if constexpr ( format = = BitmapFormat : : Indexed8 )
2020-09-06 02:10:33 +03:00
return bitmap . palette_color ( bitmap . scanline_u8 ( y ) [ x ] ) ;
2020-06-17 22:37:16 +03:00
if constexpr ( format = = BitmapFormat : : Indexed4 )
2020-09-06 02:10:33 +03:00
return bitmap . palette_color ( bitmap . scanline_u8 ( y ) [ x ] ) ;
2020-06-17 22:37:16 +03:00
if constexpr ( format = = BitmapFormat : : Indexed2 )
2020-09-06 02:10:33 +03:00
return bitmap . palette_color ( bitmap . scanline_u8 ( y ) [ x ] ) ;
2020-06-17 22:37:16 +03:00
if constexpr ( format = = BitmapFormat : : Indexed1 )
2020-09-06 02:10:33 +03:00
return bitmap . palette_color ( bitmap . scanline_u8 ( y ) [ x ] ) ;
2021-03-16 13:48:42 +03:00
if constexpr ( format = = BitmapFormat : : BGRx8888 )
2019-05-11 04:53:28 +03:00
return Color : : from_rgb ( bitmap . scanline ( y ) [ x ] ) ;
2021-03-16 13:48:42 +03:00
if constexpr ( format = = BitmapFormat : : BGRA8888 )
2022-03-05 00:28:59 +03:00
return Color : : from_argb ( bitmap . scanline ( y ) [ x ] ) ;
2019-05-11 04:53:28 +03:00
return bitmap . get_pixel ( x , y ) ;
}
2021-01-19 20:10:47 +03:00
Painter : : Painter ( Gfx : : Bitmap & bitmap )
2019-02-25 18:04:08 +03:00
: m_target ( bitmap )
2019-01-12 05:42:50 +03:00
{
2021-01-19 20:10:47 +03:00
int scale = bitmap . scale ( ) ;
2021-03-16 13:48:42 +03:00
VERIFY ( bitmap . format ( ) = = Gfx : : BitmapFormat : : BGRx8888 | | bitmap . format ( ) = = Gfx : : BitmapFormat : : BGRA8888 ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( bitmap . physical_width ( ) % scale = = 0 ) ;
VERIFY ( bitmap . physical_height ( ) % scale = = 0 ) ;
2019-03-09 18:48:02 +03:00
m_state_stack . append ( State ( ) ) ;
2021-11-28 18:09:38 +03:00
state ( ) . font = nullptr ;
2021-05-03 17:37:05 +03:00
state ( ) . clip_rect = { { 0 , 0 } , bitmap . size ( ) } ;
state ( ) . scale = scale ;
2019-03-09 18:48:02 +03:00
m_clip_origin = state ( ) . clip_rect ;
2019-01-12 05:42:50 +03:00
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect_with_draw_op ( IntRect const & a_rect , Color color )
2019-02-01 07:23:05 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
auto rect = a_rect . translated ( translation ( ) ) . intersected ( clip_rect ( ) ) ;
2019-02-01 07:23:05 +03:00
if ( rect . is_empty ( ) )
return ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2019-02-01 07:23:05 +03:00
for ( int i = rect . height ( ) - 1 ; i > = 0 ; - - i ) {
for ( int j = 0 ; j < rect . width ( ) ; + + j )
2021-01-13 04:22:15 +03:00
set_physical_pixel_with_draw_op ( dst [ j ] , color ) ;
2019-02-01 07:23:05 +03:00
dst + = dst_skip ;
}
}
2021-09-17 16:06:28 +03:00
void Painter : : clear_rect ( IntRect const & a_rect , Color color )
2019-11-25 13:34:55 +03:00
{
2021-05-03 17:37:05 +03:00
auto rect = a_rect . translated ( translation ( ) ) . intersected ( clip_rect ( ) ) ;
2019-11-25 13:34:55 +03:00
if ( rect . is_empty ( ) )
return ;
2021-05-03 17:37:05 +03:00
VERIFY ( m_target - > rect ( ) . contains ( rect ) ) ;
rect * = scale ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2019-11-25 13:34:55 +03:00
for ( int i = rect . height ( ) - 1 ; i > = 0 ; - - i ) {
fast_u32_fill ( dst , color . value ( ) , rect . width ( ) ) ;
dst + = dst_skip ;
}
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_physical_rect ( IntRect const & physical_rect , Color color )
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
{
2021-01-20 17:59:22 +03:00
// Callers must do clipping.
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( physical_rect . top ( ) ) + physical_rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2021-05-03 17:37:05 +03:00
2023-05-14 19:22:05 +03:00
auto dst_format = target ( ) - > format ( ) ;
2021-05-03 17:37:05 +03:00
for ( int i = physical_rect . height ( ) - 1 ; i > = 0 ; - - i ) {
for ( int j = 0 ; j < physical_rect . width ( ) ; + + j )
2023-05-14 19:22:05 +03:00
dst [ j ] = color_for_format ( dst_format , dst [ j ] ) . blend ( color ) . value ( ) ;
2021-05-03 17:37:05 +03:00
dst + = dst_skip ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
}
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect ( IntRect const & a_rect , Color color )
2018-10-10 21:06:58 +03:00
{
2019-11-16 21:26:17 +03:00
if ( color . alpha ( ) = = 0 )
return ;
2019-03-09 18:48:02 +03:00
if ( draw_op ( ) ! = DrawOp : : Copy ) {
2019-02-01 07:23:05 +03:00
fill_rect_with_draw_op ( a_rect , color ) ;
return ;
}
2019-11-25 13:34:55 +03:00
if ( color . alpha ( ) = = 0xff ) {
clear_rect ( a_rect , color ) ;
return ;
}
2021-05-03 17:37:05 +03:00
auto rect = a_rect . translated ( translation ( ) ) . intersected ( clip_rect ( ) ) ;
2021-01-20 17:59:22 +03:00
if ( rect . is_empty ( ) )
return ;
2021-05-03 17:37:05 +03:00
VERIFY ( m_target - > rect ( ) . contains ( rect ) ) ;
2021-01-20 17:59:22 +03:00
2021-05-03 17:37:05 +03:00
fill_physical_rect ( rect * scale ( ) , color ) ;
2018-10-10 17:49:36 +03:00
}
2018-10-10 21:06:58 +03:00
2023-01-16 01:18:46 +03:00
void Painter : : fill_rect ( IntRect const & rect , PaintStyle const & paint_style )
{
auto a_rect = rect . translated ( translation ( ) ) ;
auto clipped_rect = a_rect . intersected ( clip_rect ( ) ) ;
if ( clipped_rect . is_empty ( ) )
return ;
a_rect * = scale ( ) ;
clipped_rect * = scale ( ) ;
auto start_offset = clipped_rect . location ( ) - a_rect . location ( ) ;
paint_style . paint ( a_rect , [ & ] ( PaintStyle : : SamplerFunction sample ) {
for ( int y = 0 ; y < clipped_rect . height ( ) ; + + y ) {
for ( int x = 0 ; x < clipped_rect . width ( ) ; + + x ) {
IntPoint point ( x , y ) ;
set_physical_pixel ( point + clipped_rect . location ( ) , sample ( point + start_offset ) , true ) ;
}
}
} ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect_with_dither_pattern ( IntRect const & a_rect , Color color_a , Color color_b )
2020-05-10 02:00:21 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
auto rect = a_rect . translated ( translation ( ) ) . intersected ( clip_rect ( ) ) ;
2020-05-10 02:00:21 +03:00
if ( rect . is_empty ( ) )
return ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2020-05-10 02:00:21 +03:00
for ( int i = 0 ; i < rect . height ( ) ; + + i ) {
for ( int j = 0 ; j < rect . width ( ) ; + + j ) {
2021-07-15 19:59:49 +03:00
bool checkboard_use_a = ( ( rect . left ( ) + i ) & 1 ) ^ ( ( rect . top ( ) + j ) & 1 ) ;
2020-09-01 20:10:09 +03:00
if ( checkboard_use_a & & ! color_a . alpha ( ) )
continue ;
if ( ! checkboard_use_a & & ! color_b . alpha ( ) )
continue ;
2020-05-11 01:14:31 +03:00
dst [ j ] = checkboard_use_a ? color_a . value ( ) : color_b . value ( ) ;
2020-05-10 02:00:21 +03:00
}
dst + = dst_skip ;
}
}
2022-12-07 00:35:32 +03:00
void Painter : : fill_rect_with_checkerboard ( IntRect const & a_rect , IntSize cell_size , Color color_dark , Color color_light )
2020-04-05 14:01:13 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
2023-02-03 20:11:59 +03:00
auto translated_rect = a_rect . translated ( translation ( ) ) ;
auto rect = translated_rect . intersected ( clip_rect ( ) ) ;
2020-04-05 14:01:13 +03:00
if ( rect . is_empty ( ) )
return ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2020-04-05 14:01:13 +03:00
2023-02-03 20:11:59 +03:00
int first_cell_column = ( rect . x ( ) - translated_rect . x ( ) ) / cell_size . width ( ) ;
int prologue_length = min ( rect . width ( ) , cell_size . width ( ) - ( ( rect . x ( ) - translated_rect . x ( ) ) % cell_size . width ( ) ) ) ;
2021-07-07 11:43:03 +03:00
int number_of_aligned_strips = ( rect . width ( ) - prologue_length ) / cell_size . width ( ) ;
2021-07-06 12:50:27 +03:00
2020-04-05 14:01:13 +03:00
for ( int i = 0 ; i < rect . height ( ) ; + + i ) {
2023-02-03 20:11:59 +03:00
int y = rect . y ( ) - translated_rect . y ( ) + i ;
2021-05-15 12:18:45 +03:00
int cell_row = y / cell_size . height ( ) ;
2021-07-06 12:50:27 +03:00
bool odd_row = cell_row & 1 ;
// Prologue: Paint the unaligned part up to the first intersection.
int j = 0 ;
int cell_column = first_cell_column ;
2021-07-07 11:43:03 +03:00
{
bool odd_cell = cell_column & 1 ;
auto color = ( odd_row ^ odd_cell ) ? color_light . value ( ) : color_dark . value ( ) ;
fast_u32_fill ( & dst [ j ] , color , prologue_length ) ;
j + = prologue_length ;
2021-07-06 12:50:27 +03:00
}
// Aligned run: Paint the maximum number of aligned cell strips.
for ( int strip = 0 ; strip < number_of_aligned_strips ; + + strip ) {
+ + cell_column ;
bool odd_cell = cell_column & 1 ;
auto color = ( odd_row ^ odd_cell ) ? color_light . value ( ) : color_dark . value ( ) ;
fast_u32_fill ( & dst [ j ] , color , cell_size . width ( ) ) ;
j + = cell_size . width ( ) ;
2020-04-05 14:01:13 +03:00
}
2021-07-06 12:50:27 +03:00
// Epilogue: Paint the unaligned part until the end of the rect.
2021-07-07 11:43:03 +03:00
if ( j ! = rect . width ( ) ) {
+ + cell_column ;
bool odd_cell = cell_column & 1 ;
auto color = ( odd_row ^ odd_cell ) ? color_light . value ( ) : color_dark . value ( ) ;
int epilogue_length = rect . width ( ) - j ;
fast_u32_fill ( & dst [ j ] , color , epilogue_length ) ;
j + = epilogue_length ;
2021-07-06 12:50:27 +03:00
}
2020-04-05 14:01:13 +03:00
dst + = dst_skip ;
}
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect_with_gradient ( Orientation orientation , IntRect const & a_rect , Color gradient_start , Color gradient_end )
2019-01-25 07:01:27 +03:00
{
2021-02-13 00:25:08 +03:00
if ( gradient_start = = gradient_end ) {
fill_rect ( a_rect , gradient_start ) ;
return ;
}
2023-01-10 04:07:06 +03:00
return fill_rect_with_linear_gradient ( a_rect , Array { ColorStop { gradient_start , 0 } , ColorStop { gradient_end , 1 } } , orientation = = Orientation : : Horizontal ? 90.0f : 0.0f ) ;
2019-01-25 07:01:27 +03:00
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect_with_gradient ( IntRect const & a_rect , Color gradient_start , Color gradient_end )
2020-03-30 17:59:45 +03:00
{
return fill_rect_with_gradient ( Orientation : : Horizontal , a_rect , gradient_start , gradient_end ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rect_with_rounded_corners ( IntRect const & a_rect , Color color , int radius )
2021-06-04 14:38:30 +03:00
{
return fill_rect_with_rounded_corners ( a_rect , color , radius , radius , radius , radius ) ;
}
2021-09-17 16:06:28 +03:00
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 )
2021-05-16 00:33:21 +03:00
{
// Fasttrack for rects without any border radii
if ( ! top_left_radius & & ! top_right_radius & & ! bottom_right_radius & & ! bottom_left_radius )
return fill_rect ( a_rect , color ) ;
// Fully transparent, dont care.
if ( color . alpha ( ) = = 0 )
return ;
// FIXME: Allow for elliptically rounded corners
IntRect top_left_corner = {
a_rect . x ( ) ,
a_rect . y ( ) ,
top_left_radius ,
top_left_radius
} ;
IntRect top_right_corner = {
a_rect . x ( ) + a_rect . width ( ) - top_right_radius ,
a_rect . y ( ) ,
top_right_radius ,
top_right_radius
} ;
IntRect bottom_right_corner = {
a_rect . x ( ) + a_rect . width ( ) - bottom_right_radius ,
a_rect . y ( ) + a_rect . height ( ) - bottom_right_radius ,
bottom_right_radius ,
bottom_right_radius
} ;
IntRect bottom_left_corner = {
a_rect . x ( ) ,
a_rect . y ( ) + a_rect . height ( ) - bottom_left_radius ,
bottom_left_radius ,
bottom_left_radius
} ;
IntRect top_rect = {
a_rect . x ( ) + top_left_radius ,
a_rect . y ( ) ,
a_rect . width ( ) - top_left_radius - top_right_radius , top_left_radius
} ;
IntRect right_rect = {
a_rect . x ( ) + a_rect . width ( ) - top_right_radius ,
a_rect . y ( ) + top_right_radius ,
top_right_radius ,
a_rect . height ( ) - top_right_radius - bottom_right_radius
} ;
IntRect bottom_rect = {
a_rect . x ( ) + bottom_left_radius ,
a_rect . y ( ) + a_rect . height ( ) - bottom_right_radius ,
a_rect . width ( ) - bottom_left_radius - bottom_right_radius ,
bottom_right_radius
} ;
IntRect left_rect = {
a_rect . x ( ) ,
a_rect . y ( ) + top_left_radius ,
bottom_left_radius ,
a_rect . height ( ) - top_left_radius - bottom_left_radius
} ;
IntRect inner = {
left_rect . x ( ) + left_rect . width ( ) ,
left_rect . y ( ) ,
a_rect . width ( ) - left_rect . width ( ) - right_rect . width ( ) ,
a_rect . height ( ) - top_rect . height ( ) - bottom_rect . height ( )
} ;
fill_rect ( top_rect , color ) ;
fill_rect ( right_rect , color ) ;
fill_rect ( bottom_rect , color ) ;
fill_rect ( left_rect , color ) ;
fill_rect ( inner , color ) ;
if ( top_left_radius )
fill_rounded_corner ( top_left_corner , top_left_radius , color , CornerOrientation : : TopLeft ) ;
if ( top_right_radius )
fill_rounded_corner ( top_right_corner , top_right_radius , color , CornerOrientation : : TopRight ) ;
if ( bottom_left_radius )
fill_rounded_corner ( bottom_left_corner , bottom_left_radius , color , CornerOrientation : : BottomLeft ) ;
if ( bottom_right_radius )
fill_rounded_corner ( bottom_right_corner , bottom_right_radius , color , CornerOrientation : : BottomRight ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : fill_rounded_corner ( IntRect const & a_rect , int radius , Color color , CornerOrientation orientation )
2021-05-16 00:33:21 +03:00
{
// Care about clipping
auto translated_a_rect = a_rect . translated ( translation ( ) ) ;
auto rect = translated_a_rect . intersected ( clip_rect ( ) ) ;
if ( rect . is_empty ( ) )
return ;
VERIFY ( m_target - > rect ( ) . contains ( rect ) ) ;
// We got cut on the top!
// FIXME: Also account for clipping on the x-axis
int clip_offset = 0 ;
if ( translated_a_rect . y ( ) < rect . y ( ) )
clip_offset = rect . y ( ) - translated_a_rect . y ( ) ;
2021-06-23 00:12:36 +03:00
radius * = scale ( ) ;
rect * = scale ( ) ;
clip_offset * = scale ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2021-05-16 00:33:21 +03:00
IntPoint circle_center ;
switch ( orientation ) {
case CornerOrientation : : TopLeft :
circle_center = { radius , radius + 1 } ;
break ;
case CornerOrientation : : TopRight :
circle_center = { - 1 , radius + 1 } ;
break ;
case CornerOrientation : : BottomRight :
circle_center = { - 1 , 0 } ;
break ;
case CornerOrientation : : BottomLeft :
circle_center = { radius , 0 } ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
int radius2 = radius * radius ;
auto is_in_circle = [ & ] ( int x , int y ) {
int distance2 = ( circle_center . x ( ) - x ) * ( circle_center . x ( ) - x ) + ( circle_center . y ( ) - y ) * ( circle_center . y ( ) - y ) ;
// To reflect the grid and be compatible with the draw_circle_arc_intersecting algorithm
// add 1/2 to the radius
return distance2 < = ( radius2 + radius + 0.25 ) ;
} ;
2023-05-14 19:22:05 +03:00
auto dst_format = target ( ) - > format ( ) ;
2021-05-16 00:33:21 +03:00
for ( int i = rect . height ( ) - 1 ; i > = 0 ; - - i ) {
for ( int j = 0 ; j < rect . width ( ) ; + + j )
if ( is_in_circle ( j , rect . height ( ) - i + clip_offset ) )
2023-05-14 19:22:05 +03:00
dst [ j ] = color_for_format ( dst_format , dst [ j ] ) . blend ( color ) . value ( ) ;
2021-05-16 00:33:21 +03:00
dst + = dst_skip ;
}
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_circle_arc_intersecting ( IntRect const & a_rect , IntPoint center , int radius , Color color , int thickness )
2021-05-16 17:14:33 +03:00
{
2022-03-11 02:27:57 +03:00
if ( thickness < = 0 | | radius < = 0 )
2021-05-16 17:14:33 +03:00
return ;
// Care about clipping
auto translated_a_rect = a_rect . translated ( translation ( ) ) ;
auto rect = translated_a_rect . intersected ( clip_rect ( ) ) ;
if ( rect . is_empty ( ) )
return ;
VERIFY ( m_target - > rect ( ) . contains ( rect ) ) ;
// We got cut on the top!
// FIXME: Also account for clipping on the x-axis
int clip_offset = 0 ;
if ( translated_a_rect . y ( ) < rect . y ( ) )
clip_offset = rect . y ( ) - translated_a_rect . y ( ) ;
if ( thickness > radius )
thickness = radius ;
int radius2 = radius * radius ;
auto is_on_arc = [ & ] ( int x , int y ) {
int distance2 = ( center . x ( ) - x ) * ( center . x ( ) - x ) + ( center . y ( ) - y ) * ( center . y ( ) - y ) ;
// Is within a circle of radius 1/2 around (x,y), so basically within the current pixel.
// Technically this is angle-dependent and should be between 1/2 and sqrt(2)/2, but this works.
return distance2 < = ( radius2 + radius + 0.25 ) & & distance2 > = ( radius2 - radius + 0.25 ) ;
} ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( rect . top ( ) ) + rect . left ( ) ;
2023-05-14 19:22:05 +03:00
auto dst_format = target ( ) - > format ( ) ;
2022-03-05 00:05:20 +03:00
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2021-05-16 17:14:33 +03:00
for ( int i = rect . height ( ) - 1 ; i > = 0 ; - - i ) {
for ( int j = 0 ; j < rect . width ( ) ; + + j )
if ( is_on_arc ( j , rect . height ( ) - i + clip_offset ) )
2023-05-14 19:22:05 +03:00
dst [ j ] = color_for_format ( dst_format , dst [ j ] ) . blend ( color ) . value ( ) ;
2021-05-16 17:14:33 +03:00
dst + = dst_skip ;
}
return draw_circle_arc_intersecting ( a_rect , center , radius - 1 , color , thickness - 1 ) ;
}
2023-01-01 17:55:57 +03:00
// The callback will only be called for a quarter of the ellipse, the user is intended to deduce other points.
// As the coordinate space is relative to the center of the rectangle, it's simply (x, y), (x, -y), (-x, y) and (-x, -y).
static void on_each_ellipse_point ( IntRect const & rect , Function < void ( IntPoint ) > & & callback )
2020-05-24 21:03:48 +03:00
{
2023-01-01 17:55:57 +03:00
// Note: This is an implementation of the Midpoint Ellipse Algorithm.
2022-11-08 19:07:14 +03:00
double const a = rect . width ( ) / 2 ;
double const a_square = a * a ;
double const b = rect . height ( ) / 2 ;
double const b_square = b * b ;
int x = 0 ;
auto y = static_cast < int > ( b ) ;
double dx = 2 * b_square * x ;
double dy = 2 * a_square * y ;
// For region 1:
auto decision_parameter = b_square - a_square * b + .25 * a_square ;
while ( dx < dy ) {
2023-01-01 17:55:57 +03:00
callback ( { x , y } ) ;
2022-11-08 19:07:14 +03:00
if ( decision_parameter > = 0 ) {
y - - ;
dy - = 2 * a_square ;
decision_parameter - = dy ;
}
x + + ;
dx + = 2 * b_square ;
decision_parameter + = dx + b_square ;
}
// For region 2:
decision_parameter = b_square * ( ( x + 0.5 ) * ( x + 0.5 ) ) + a_square * ( ( y - 1 ) * ( y - 1 ) ) - a_square * b_square ;
while ( y > = 0 ) {
2023-01-01 17:55:57 +03:00
callback ( { x , y } ) ;
2022-11-08 19:07:14 +03:00
if ( decision_parameter < = 0 ) {
x + + ;
dx + = 2 * b_square ;
decision_parameter + = dx ;
}
y - - ;
dy - = 2 * a_square ;
decision_parameter + = a_square - dy ;
2019-12-27 01:19:45 +03:00
}
}
2023-01-01 17:55:57 +03:00
void Painter : : fill_ellipse ( IntRect const & a_rect , Color color )
{
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
auto rect = a_rect . translated ( translation ( ) ) . intersected ( clip_rect ( ) ) ;
if ( rect . is_empty ( ) )
return ;
VERIFY ( m_target - > rect ( ) . contains ( rect ) ) ;
2023-01-01 18:42:46 +03:00
auto const center = a_rect . center ( ) ;
on_each_ellipse_point ( rect , [ this , & color , center ] ( IntPoint position ) {
IntPoint const directions [ 4 ] = { { position . x ( ) , position . y ( ) } , { - position . x ( ) , position . y ( ) } , { position . x ( ) , - position . y ( ) } , { - position . x ( ) , - position . y ( ) } } ;
draw_line ( center + directions [ 0 ] , center + directions [ 1 ] , color ) ;
draw_line ( center + directions [ 2 ] , center + directions [ 3 ] , color ) ;
} ) ;
2023-01-01 17:55:57 +03:00
}
void Painter : : draw_ellipse_intersecting ( IntRect const & rect , Color color , int thickness )
{
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
if ( thickness < = 0 )
return ;
auto const center = rect . center ( ) ;
on_each_ellipse_point ( rect , [ this , & color , thickness , center ] ( IntPoint position ) {
IntPoint const directions [ 4 ] = { { position . x ( ) , position . y ( ) } , { position . x ( ) , - position . y ( ) } , { - position . x ( ) , position . y ( ) } , { - position . x ( ) , - position . y ( ) } } ;
for ( auto const delta : directions ) {
auto const point = center + delta ;
draw_line ( point , point , color , thickness ) ;
}
} ) ;
}
2021-05-03 17:37:05 +03:00
template < typename RectType , typename Callback >
2021-09-17 16:06:28 +03:00
static void for_each_pixel_around_rect_clockwise ( RectType const & rect , Callback callback )
2020-10-26 22:43:59 +03:00
{
if ( rect . is_empty ( ) )
return ;
2023-05-22 01:41:18 +03:00
for ( auto x = rect . left ( ) ; x < rect . right ( ) ; + + x )
2020-10-26 22:43:59 +03:00
callback ( x , rect . top ( ) ) ;
2023-05-22 01:41:18 +03:00
for ( auto y = rect . top ( ) + 1 ; y < rect . bottom ( ) ; + + y )
callback ( rect . right ( ) - 1 , y ) ;
for ( auto x = rect . right ( ) - 2 ; x > = rect . left ( ) ; - - x )
callback ( x , rect . bottom ( ) - 1 ) ;
for ( auto y = rect . bottom ( ) - 2 ; y > rect . top ( ) ; - - y )
2020-10-26 22:43:59 +03:00
callback ( rect . left ( ) , y ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : draw_focus_rect ( IntRect const & rect , Color color )
2020-10-26 22:43:59 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
2020-10-26 22:43:59 +03:00
if ( rect . is_empty ( ) )
return ;
bool state = false ;
for_each_pixel_around_rect_clockwise ( rect , [ & ] ( auto x , auto y ) {
if ( state )
set_pixel ( x , y , color ) ;
state = ! state ;
} ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : draw_rect ( IntRect const & a_rect , Color color , bool rough )
2018-10-12 00:14:51 +03:00
{
2021-05-03 17:37:05 +03:00
IntRect rect = a_rect . translated ( translation ( ) ) ;
auto clipped_rect = rect . intersected ( clip_rect ( ) ) ;
if ( clipped_rect . is_empty ( ) )
return ;
int min_y = clipped_rect . top ( ) ;
2023-05-22 01:41:18 +03:00
int max_y = clipped_rect . bottom ( ) - 1 ;
2021-05-03 17:37:05 +03:00
int scale = this - > scale ( ) ;
2021-01-13 04:22:15 +03:00
2023-05-22 01:41:18 +03:00
if ( rect . top ( ) > = clipped_rect . top ( ) & & rect . top ( ) < clipped_rect . bottom ( ) ) {
2021-05-03 17:37:05 +03:00
int start_x = rough ? max ( rect . x ( ) + 1 , clipped_rect . x ( ) ) : clipped_rect . x ( ) ;
int width = rough ? min ( rect . width ( ) - 2 , clipped_rect . width ( ) ) : clipped_rect . width ( ) ;
for ( int i = 0 ; i < scale ; + + i )
fill_physical_scanline_with_draw_op ( rect . top ( ) * scale + i , start_x * scale , width * scale , color ) ;
+ + min_y ;
}
2023-05-22 01:41:18 +03:00
if ( rect . bottom ( ) > clipped_rect . top ( ) & & rect . bottom ( ) < = clipped_rect . bottom ( ) ) {
2021-05-03 17:37:05 +03:00
int start_x = rough ? max ( rect . x ( ) + 1 , clipped_rect . x ( ) ) : clipped_rect . x ( ) ;
int width = rough ? min ( rect . width ( ) - 2 , clipped_rect . width ( ) ) : clipped_rect . width ( ) ;
for ( int i = 0 ; i < scale ; + + i )
fill_physical_scanline_with_draw_op ( max_y * scale + i , start_x * scale , width * scale , color ) ;
- - max_y ;
}
bool draw_left_side = rect . left ( ) > = clipped_rect . left ( ) ;
bool draw_right_side = rect . right ( ) = = clipped_rect . right ( ) ;
if ( draw_left_side & & draw_right_side ) {
// Specialized loop when drawing both sides.
for ( int y = min_y * scale ; y < = max_y * scale ; + + y ) {
auto * bits = m_target - > scanline ( y ) ;
for ( int i = 0 ; i < scale ; + + i )
set_physical_pixel_with_draw_op ( bits [ rect . left ( ) * scale + i ] , color ) ;
for ( int i = 0 ; i < scale ; + + i )
2023-05-22 01:41:18 +03:00
set_physical_pixel_with_draw_op ( bits [ ( rect . right ( ) - 1 ) * scale + i ] , color ) ;
2021-05-03 17:37:05 +03:00
}
} else {
for ( int y = min_y * scale ; y < = max_y * scale ; + + y ) {
auto * bits = m_target - > scanline ( y ) ;
if ( draw_left_side )
for ( int i = 0 ; i < scale ; + + i )
set_physical_pixel_with_draw_op ( bits [ rect . left ( ) * scale + i ] , color ) ;
if ( draw_right_side )
for ( int i = 0 ; i < scale ; + + i )
2023-05-22 01:41:18 +03:00
set_physical_pixel_with_draw_op ( bits [ ( rect . right ( ) - 1 ) * scale + i ] , color ) ;
2021-05-03 17:37:05 +03:00
}
}
2018-10-12 00:14:51 +03:00
}
2021-09-17 16:06:28 +03:00
void Painter : : draw_rect_with_thickness ( IntRect const & rect , Color color , int thickness )
2021-09-03 15:24:38 +03:00
{
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2021-09-03 15:24:38 +03:00
IntPoint p1 = rect . location ( ) ;
IntPoint p2 = { rect . location ( ) . x ( ) + rect . width ( ) , rect . location ( ) . y ( ) } ;
IntPoint p3 = { rect . location ( ) . x ( ) + rect . width ( ) , rect . location ( ) . y ( ) + rect . height ( ) } ;
IntPoint p4 = { rect . location ( ) . x ( ) , rect . location ( ) . y ( ) + rect . height ( ) } ;
draw_line ( p1 , p2 , color , thickness ) ;
draw_line ( p2 , p3 , color , thickness ) ;
draw_line ( p3 , p4 , color , thickness ) ;
draw_line ( p4 , p1 , color , thickness ) ;
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_bitmap ( IntPoint p , CharacterBitmap const & bitmap , Color color )
2018-10-12 13:29:58 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2020-06-10 11:57:59 +03:00
auto rect = IntRect ( p , bitmap . size ( ) ) . translated ( translation ( ) ) ;
2019-04-16 14:58:02 +03:00
auto clipped_rect = rect . intersected ( clip_rect ( ) ) ;
2019-01-25 08:37:56 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-09-17 16:06:28 +03:00
int const first_row = clipped_rect . top ( ) - rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - rect . left ( ) ;
int const last_column = clipped_rect . right ( ) - rect . left ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( clipped_rect . y ( ) ) + clipped_rect . x ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2021-09-17 16:06:28 +03:00
char const * bitmap_row = & bitmap . bits ( ) [ first_row * bitmap . width ( ) + first_column ] ;
size_t const bitmap_skip = bitmap . width ( ) ;
2019-01-25 04:39:45 +03:00
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
for ( int j = 0 ; j < ( last_column - first_column ) ; + + j ) {
2019-01-25 04:39:45 +03:00
char fc = bitmap_row [ j ] ;
2018-10-12 13:29:58 +03:00
if ( fc = = ' # ' )
2019-01-25 04:39:45 +03:00
dst [ j ] = color . value ( ) ;
2018-10-12 13:29:58 +03:00
}
2019-01-25 04:39:45 +03:00
bitmap_row + = bitmap_skip ;
dst + = dst_skip ;
2018-10-12 13:29:58 +03:00
}
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_bitmap ( IntPoint p , GlyphBitmap const & bitmap , Color color )
2019-02-05 08:43:33 +03:00
{
2021-01-20 17:59:22 +03:00
auto dst_rect = IntRect ( p , bitmap . size ( ) ) . translated ( translation ( ) ) ;
2019-04-16 14:58:02 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) ) ;
2019-02-05 08:43:33 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-09-17 16:06:28 +03:00
int const first_row = clipped_rect . top ( ) - dst_rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - dst_rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - dst_rect . left ( ) ;
int const last_column = clipped_rect . right ( ) - dst_rect . left ( ) ;
2021-01-20 17:59:22 +03:00
int scale = this - > scale ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( clipped_rect . y ( ) * scale ) + clipped_rect . x ( ) * scale ;
2023-05-14 19:22:05 +03:00
auto dst_format = target ( ) - > format ( ) ;
2022-03-05 00:05:20 +03:00
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2019-02-05 08:43:33 +03:00
2021-01-20 17:59:22 +03:00
if ( scale = = 1 ) {
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
for ( int j = 0 ; j < ( last_column - first_column ) ; + + j ) {
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
if ( bitmap . bit_at ( j + first_column , row ) )
2023-05-14 19:22:05 +03:00
dst [ j ] = color_for_format ( dst_format , dst [ j ] ) . blend ( color ) . value ( ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
}
dst + = dst_skip ;
}
} else {
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
for ( int j = 0 ; j < ( last_column - first_column ) ; + + j ) {
2021-01-20 17:59:22 +03:00
if ( bitmap . bit_at ( ( j + first_column ) , row ) ) {
for ( int iy = 0 ; iy < scale ; + + iy )
2022-02-12 12:40:43 +03:00
for ( int ix = 0 ; ix < scale ; + + ix ) {
auto pixel_index = j * scale + ix + iy * dst_skip ;
2023-05-14 19:22:05 +03:00
dst [ pixel_index ] = color_for_format ( dst_format , dst [ pixel_index ] ) . blend ( color ) . value ( ) ;
2022-02-12 12:40:43 +03:00
}
2021-01-20 17:59:22 +03:00
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
}
2021-01-20 17:59:22 +03:00
dst + = dst_skip * scale ;
2019-02-05 08:43:33 +03:00
}
}
}
2023-02-05 22:02:54 +03:00
void Painter : : draw_triangle ( IntPoint offset , ReadonlySpan < IntPoint > control_points , Color color )
2022-06-13 21:03:09 +03:00
{
VERIFY ( control_points . size ( ) = = 3 ) ;
draw_triangle ( control_points [ 0 ] + offset , control_points [ 1 ] + offset , control_points [ 2 ] + offset , color ) ;
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_triangle ( IntPoint a , IntPoint b , IntPoint c , Color color )
2020-04-18 01:13:22 +03:00
{
2021-09-05 13:25:02 +03:00
IntPoint p0 ( to_physical ( a ) ) ;
IntPoint p1 ( to_physical ( b ) ) ;
IntPoint p2 ( to_physical ( c ) ) ;
2020-04-18 01:13:22 +03:00
2021-03-06 01:29:58 +03:00
// sort points from top to bottom
2020-04-18 01:13:22 +03:00
if ( p0 . y ( ) > p1 . y ( ) )
swap ( p0 , p1 ) ;
if ( p0 . y ( ) > p2 . y ( ) )
swap ( p0 , p2 ) ;
if ( p1 . y ( ) > p2 . y ( ) )
swap ( p1 , p2 ) ;
2021-03-06 01:29:58 +03:00
// return if top and bottom points are on same line
if ( p0 . y ( ) = = p2 . y ( ) )
return ;
2022-06-15 04:55:34 +03:00
// return if all points are on the same line vertically
if ( p0 . x ( ) = = p1 . x ( ) & & p1 . x ( ) = = p2 . x ( ) )
return ;
2021-03-06 01:29:58 +03:00
// return if top is below clip rect or bottom is above clip rect
2020-04-18 01:13:22 +03:00
auto clip = clip_rect ( ) ;
2023-05-22 01:41:18 +03:00
if ( p0 . y ( ) > = clip . bottom ( ) - 1 )
2020-04-18 01:13:22 +03:00
return ;
if ( p2 . y ( ) < clip . top ( ) )
return ;
2022-06-15 04:55:34 +03:00
class BoundaryLine {
private :
IntPoint m_base { } ;
IntPoint m_path { } ;
public :
BoundaryLine ( IntPoint a , IntPoint b )
{
VERIFY ( a . y ( ) < = b . y ( ) ) ;
m_base = a ;
m_path = b - a ;
}
2020-04-18 01:13:22 +03:00
2022-06-15 04:55:34 +03:00
int top_y ( ) const { return m_base . y ( ) ; }
2020-04-18 01:13:22 +03:00
2022-06-15 04:55:34 +03:00
int bottom_y ( ) const { return m_base . y ( ) + m_path . y ( ) ; }
bool is_vertical ( ) const { return m_path . x ( ) = = 0 ; }
bool is_horizontal ( ) const { return m_path . y ( ) = = 0 ; }
bool in_y_range ( int y ) const { return y > = top_y ( ) & & y < = bottom_y ( ) ; }
Optional < int > intersection_on_x ( int y ) const
{
if ( ! in_y_range ( y ) )
return { } ;
if ( is_horizontal ( ) )
return { } ;
if ( is_vertical ( ) )
return m_base . x ( ) ;
2020-04-18 01:13:22 +03:00
2022-06-15 04:55:34 +03:00
int y_diff = y - top_y ( ) ;
int x_d = m_path . x ( ) * y_diff , y_d = m_path . y ( ) ;
return ( x_d / y_d ) + m_base . x ( ) ;
2021-03-06 01:29:58 +03:00
}
2022-06-15 04:55:34 +03:00
} ;
BoundaryLine l0 ( p0 , p1 ) , l1 ( p0 , p2 ) , l2 ( p1 , p2 ) ;
int rgba = color . value ( ) ;
2021-03-06 01:29:58 +03:00
2023-05-22 01:41:18 +03:00
for ( int y = max ( p0 . y ( ) , clip . top ( ) ) ; y < min ( p2 . y ( ) + 1 , clip . bottom ( ) ) ; y + + ) {
2022-06-15 04:55:34 +03:00
Optional < int >
x0 = l0 . intersection_on_x ( y ) ,
x1 = l1 . intersection_on_x ( y ) ,
x2 = l2 . intersection_on_x ( y ) ;
int result_a = 0 , result_b = 0 ;
if ( x0 . has_value ( ) ) {
result_a = x0 . value ( ) ;
if ( x1 . has_value ( ) & & ( ( ! x2 . has_value ( ) ) | | ( result_a ! = x1 . value ( ) ) ) ) {
result_b = x1 . value ( ) ;
} else {
result_b = x2 . value ( ) ;
2021-03-06 01:29:58 +03:00
}
2022-06-15 04:55:34 +03:00
} else if ( x1 . has_value ( ) ) {
result_a = x1 . value ( ) ;
result_b = x2 . value ( ) ;
2020-04-18 01:13:22 +03:00
}
2022-06-15 04:55:34 +03:00
if ( result_a > result_b )
swap ( result_a , result_b ) ;
2020-04-18 01:13:22 +03:00
2022-06-15 04:55:34 +03:00
int left_bound = result_a , right_bound = result_b ;
2020-04-18 01:13:22 +03:00
2022-06-15 04:55:34 +03:00
ARGB32 * scanline = m_target - > scanline ( y ) ;
2023-05-22 01:41:18 +03:00
for ( int x = max ( left_bound , clip . left ( ) ) ; x < = min ( right_bound , clip . right ( ) - 1 ) ; x + + )
2020-04-18 01:13:22 +03:00
scanline [ x ] = rgba ;
}
}
2021-03-21 11:40:14 +03:00
struct BlitState {
enum AlphaState {
NoAlpha = 0 ,
SrcAlpha = 1 ,
DstAlpha = 2 ,
BothAlpha = SrcAlpha | DstAlpha
} ;
2022-03-05 00:05:20 +03:00
ARGB32 const * src ;
ARGB32 * dst ;
2021-03-21 11:40:14 +03:00
size_t src_pitch ;
size_t dst_pitch ;
int row_count ;
int column_count ;
float opacity ;
2022-03-06 02:14:34 +03:00
BitmapFormat src_format ;
2021-03-21 11:40:14 +03:00
} ;
2022-03-06 02:14:34 +03:00
// FIXME: This is a hack to support blit_with_opacity() with RGBA8888 source.
// Ideally we'd have a more generic solution that allows any source format.
static void swap_red_and_blue_channels ( Color & color )
{
u32 rgba = color . value ( ) ;
u32 bgra = ( rgba & 0xff00ff00 )
| ( ( rgba & 0x000000ff ) < < 16 )
| ( ( rgba & 0x00ff0000 ) > > 16 ) ;
color = Color : : from_argb ( bgra ) ;
}
// FIXME: This function is very unoptimized.
2021-03-21 11:40:14 +03:00
template < BlitState : : AlphaState has_alpha >
static void do_blit_with_opacity ( BlitState & state )
{
for ( int row = 0 ; row < state . row_count ; + + row ) {
for ( int x = 0 ; x < state . column_count ; + + x ) {
2022-03-05 00:28:59 +03:00
Color dest_color = ( has_alpha & BlitState : : DstAlpha ) ? Color : : from_argb ( state . dst [ x ] ) : Color : : from_rgb ( state . dst [ x ] ) ;
2021-03-21 11:40:14 +03:00
if constexpr ( has_alpha & BlitState : : SrcAlpha ) {
2022-03-05 00:28:59 +03:00
Color src_color_with_alpha = Color : : from_argb ( state . src [ x ] ) ;
2022-03-06 02:14:34 +03:00
if ( state . src_format = = BitmapFormat : : RGBA8888 )
swap_red_and_blue_channels ( src_color_with_alpha ) ;
2021-03-21 11:40:14 +03:00
float pixel_opacity = src_color_with_alpha . alpha ( ) / 255.0 ;
src_color_with_alpha . set_alpha ( 255 * ( state . opacity * pixel_opacity ) ) ;
state . dst [ x ] = dest_color . blend ( src_color_with_alpha ) . value ( ) ;
} else {
Color src_color_with_alpha = Color : : from_rgb ( state . src [ x ] ) ;
2022-03-06 02:14:34 +03:00
if ( state . src_format = = BitmapFormat : : RGBA8888 )
swap_red_and_blue_channels ( src_color_with_alpha ) ;
2021-03-21 11:40:14 +03:00
src_color_with_alpha . set_alpha ( state . opacity * 255 ) ;
state . dst [ x ] = dest_color . blend ( src_color_with_alpha ) . value ( ) ;
}
}
state . dst + = state . dst_pitch ;
state . src + = state . src_pitch ;
}
}
2022-12-06 23:27:44 +03:00
void Painter : : blit_with_opacity ( IntPoint position , Gfx : : Bitmap const & source , IntRect const & a_src_rect , float opacity , bool apply_alpha )
2019-02-19 03:42:53 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) > = source . scale ( ) & & " painter doesn't support downsampling scale factors " ) ;
2019-02-19 03:42:53 +03:00
2021-02-13 21:10:27 +03:00
if ( opacity > = 1.0f & & ! ( source . has_alpha_channel ( ) & & apply_alpha ) )
2021-01-25 05:41:38 +03:00
return blit ( position , source , a_src_rect ) ;
2019-02-19 03:42:53 +03:00
2021-01-25 05:41:38 +03:00
IntRect safe_src_rect = IntRect : : intersection ( a_src_rect , source . rect ( ) ) ;
if ( scale ( ) ! = source . scale ( ) )
return draw_scaled_bitmap ( { position , safe_src_rect . size ( ) } , source , safe_src_rect , opacity ) ;
auto dst_rect = IntRect ( position , safe_src_rect . size ( ) ) . translated ( translation ( ) ) ;
2021-01-25 20:25:02 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) ) ;
2019-02-19 03:42:53 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-01-25 05:41:38 +03:00
int scale = this - > scale ( ) ;
auto src_rect = a_src_rect * scale ;
clipped_rect * = scale ;
dst_rect * = scale ;
2021-09-17 16:06:28 +03:00
int const first_row = clipped_rect . top ( ) - dst_rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - dst_rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - dst_rect . left ( ) ;
int const last_column = clipped_rect . right ( ) - dst_rect . left ( ) ;
2019-02-19 03:42:53 +03:00
2021-03-21 11:40:14 +03:00
BlitState blit_state {
. src = source . scanline ( src_rect . top ( ) + first_row ) + src_rect . left ( ) + first_column ,
. dst = m_target - > scanline ( clipped_rect . y ( ) ) + clipped_rect . x ( ) ,
2022-03-05 00:05:20 +03:00
. src_pitch = source . pitch ( ) / sizeof ( ARGB32 ) ,
. dst_pitch = m_target - > pitch ( ) / sizeof ( ARGB32 ) ,
2023-05-22 01:41:18 +03:00
. row_count = last_row - first_row ,
. column_count = last_column - first_column ,
2022-03-06 02:14:34 +03:00
. opacity = opacity ,
. src_format = source . format ( ) ,
2021-02-13 21:10:27 +03:00
} ;
2021-03-21 11:40:14 +03:00
if ( source . has_alpha_channel ( ) & & apply_alpha ) {
if ( m_target - > has_alpha_channel ( ) )
do_blit_with_opacity < BlitState : : BothAlpha > ( blit_state ) ;
else
do_blit_with_opacity < BlitState : : SrcAlpha > ( blit_state ) ;
} else {
if ( m_target - > has_alpha_channel ( ) )
do_blit_with_opacity < BlitState : : DstAlpha > ( blit_state ) ;
else
do_blit_with_opacity < BlitState : : NoAlpha > ( blit_state ) ;
}
2019-02-19 03:42:53 +03:00
}
2023-07-29 21:33:53 +03:00
void Painter : : blit_filtered ( IntPoint position , Gfx : : Bitmap const & source , IntRect const & src_rect , Function < Color ( Color ) > const & filter , bool apply_alpha )
2019-04-10 17:14:44 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( ( source . scale ( ) = = 1 | | source . scale ( ) = = scale ( ) ) & & " blit_filtered only supports integer upsampling " ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2020-06-10 11:57:59 +03:00
IntRect safe_src_rect = src_rect . intersected ( source . rect ( ) ) ;
auto dst_rect = IntRect ( position , safe_src_rect . size ( ) ) . translated ( translation ( ) ) ;
2019-04-16 14:58:02 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) ) ;
2019-04-10 17:14:44 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-01-25 22:54:38 +03:00
int scale = this - > scale ( ) ;
clipped_rect * = scale ;
dst_rect * = scale ;
safe_src_rect * = source . scale ( ) ;
2021-09-17 16:06:28 +03:00
int const first_row = clipped_rect . top ( ) - dst_rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - dst_rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - dst_rect . left ( ) ;
int const last_column = clipped_rect . right ( ) - dst_rect . left ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( clipped_rect . y ( ) ) + clipped_rect . x ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2023-05-14 19:22:05 +03:00
auto dst_format = target ( ) - > format ( ) ;
auto src_format = source . format ( ) ;
2019-04-10 17:14:44 +03:00
2021-01-25 22:54:38 +03:00
int s = scale / source . scale ( ) ;
if ( s = = 1 ) {
2022-03-05 00:05:20 +03:00
ARGB32 const * src = source . scanline ( safe_src_rect . top ( ) + first_row ) + safe_src_rect . left ( ) + first_column ;
size_t const src_skip = source . pitch ( ) / sizeof ( ARGB32 ) ;
2021-01-25 22:54:38 +03:00
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
for ( int x = 0 ; x < ( last_column - first_column ) ; + + x ) {
2023-06-01 11:48:12 +03:00
auto source_color = color_for_format ( src_format , src [ x ] ) ;
if ( source_color . alpha ( ) = = 0 )
2021-01-25 22:54:38 +03:00
continue ;
2023-06-01 11:48:12 +03:00
auto filtered_color = filter ( source_color ) ;
2023-07-29 21:33:53 +03:00
if ( ! apply_alpha | | filtered_color . alpha ( ) = = 0xff )
2023-06-01 11:48:12 +03:00
dst [ x ] = filtered_color . value ( ) ;
2021-01-25 22:54:38 +03:00
else
2023-06-01 11:48:12 +03:00
dst [ x ] = color_for_format ( dst_format , dst [ x ] ) . blend ( filtered_color ) . value ( ) ;
2021-01-25 22:54:38 +03:00
}
dst + = dst_skip ;
src + = src_skip ;
}
} else {
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
2022-03-05 00:05:20 +03:00
ARGB32 const * src = source . scanline ( safe_src_rect . top ( ) + row / s ) + safe_src_rect . left ( ) + first_column / s ;
2023-05-22 01:41:18 +03:00
for ( int x = 0 ; x < ( last_column - first_column ) ; + + x ) {
2023-06-01 11:48:12 +03:00
auto source_color = color_for_format ( src_format , src [ x / s ] ) ;
if ( source_color . alpha ( ) = = 0 )
2021-01-25 22:54:38 +03:00
continue ;
2023-06-01 11:48:12 +03:00
auto filtered_color = filter ( source_color ) ;
2023-07-29 21:33:53 +03:00
if ( ! apply_alpha | | filtered_color . alpha ( ) = = 0xff )
2023-06-01 11:48:12 +03:00
dst [ x ] = filtered_color . value ( ) ;
2021-01-25 22:54:38 +03:00
else
2023-06-01 11:48:12 +03:00
dst [ x ] = color_for_format ( dst_format , dst [ x ] ) . blend ( filtered_color ) . value ( ) ;
2021-01-25 22:54:38 +03:00
}
dst + = dst_skip ;
2019-04-10 17:14:44 +03:00
}
}
}
2022-12-06 23:27:44 +03:00
void Painter : : blit_brightened ( IntPoint position , Gfx : : Bitmap const & source , IntRect const & src_rect )
2020-03-30 20:39:37 +03:00
{
return blit_filtered ( position , source , src_rect , [ ] ( Color src ) {
return src . lightened ( ) ;
} ) ;
}
2022-12-06 23:27:44 +03:00
void Painter : : blit_dimmed ( IntPoint position , Gfx : : Bitmap const & source , IntRect const & src_rect )
2020-03-30 20:39:37 +03:00
{
return blit_filtered ( position , source , src_rect , [ ] ( Color src ) {
return src . to_grayscale ( ) . lightened ( ) ;
} ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : draw_tiled_bitmap ( IntRect const & a_dst_rect , Gfx : : Bitmap const & source )
2019-05-26 20:14:03 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( ( source . scale ( ) = = 1 | | source . scale ( ) = = scale ( ) ) & & " draw_tiled_bitmap only supports integer upsampling " ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2019-10-19 12:05:21 +03:00
auto dst_rect = a_dst_rect . translated ( translation ( ) ) ;
2019-05-26 20:14:03 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) ) ;
if ( clipped_rect . is_empty ( ) )
return ;
2021-01-22 21:33:57 +03:00
int scale = this - > scale ( ) ;
clipped_rect * = scale ;
dst_rect * = scale ;
2023-05-22 01:41:18 +03:00
int const first_row = clipped_rect . top ( ) - dst_rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - dst_rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - dst_rect . left ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( clipped_rect . y ( ) ) + clipped_rect . x ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2019-05-26 20:14:03 +03:00
2021-03-16 13:48:42 +03:00
if ( source . format ( ) = = BitmapFormat : : BGRx8888 | | source . format ( ) = = BitmapFormat : : BGRA8888 ) {
2021-01-22 21:33:57 +03:00
int s = scale / source . scale ( ) ;
if ( s = = 1 ) {
2021-01-23 02:53:09 +03:00
int x_start = first_column + a_dst_rect . left ( ) * scale ;
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
2022-03-05 00:05:20 +03:00
ARGB32 const * sl = source . scanline ( ( row + a_dst_rect . top ( ) * scale ) % source . physical_height ( ) ) ;
2023-05-22 01:41:18 +03:00
for ( int x = x_start ; x < clipped_rect . width ( ) + x_start ; + + x )
2021-01-22 21:33:57 +03:00
dst [ x - x_start ] = sl [ x % source . physical_width ( ) ] ;
dst + = dst_skip ;
}
} else {
2021-01-23 02:53:09 +03:00
int x_start = first_column + a_dst_rect . left ( ) * scale ;
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
2022-03-05 00:05:20 +03:00
ARGB32 const * sl = source . scanline ( ( ( row + a_dst_rect . top ( ) * scale ) / s ) % source . physical_height ( ) ) ;
2023-05-22 01:41:18 +03:00
for ( int x = x_start ; x < clipped_rect . width ( ) + x_start ; + + x )
2021-01-23 02:53:09 +03:00
dst [ x - x_start ] = sl [ ( x / s ) % source . physical_width ( ) ] ;
2021-01-22 21:33:57 +03:00
dst + = dst_skip ;
2019-05-27 05:36:16 +03:00
}
2019-05-26 20:14:03 +03:00
}
2019-05-27 05:36:16 +03:00
return ;
2019-05-26 20:14:03 +03:00
}
2019-05-27 05:36:16 +03:00
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2019-05-26 20:14:03 +03:00
}
2019-04-10 17:14:44 +03:00
2022-12-06 23:27:44 +03:00
void Painter : : blit_offset ( IntPoint a_position , Gfx : : Bitmap const & source , IntRect const & a_src_rect , IntPoint offset )
2019-05-27 06:10:23 +03:00
{
2021-01-22 19:12:00 +03:00
auto src_rect = IntRect { a_src_rect . location ( ) - offset , a_src_rect . size ( ) } ;
auto position = a_position ;
if ( src_rect . x ( ) < 0 ) {
position . set_x ( position . x ( ) - src_rect . x ( ) ) ;
src_rect . set_x ( 0 ) ;
2019-05-27 06:10:23 +03:00
}
2021-01-22 19:12:00 +03:00
if ( src_rect . y ( ) < 0 ) {
position . set_y ( position . y ( ) - src_rect . y ( ) ) ;
src_rect . set_y ( 0 ) ;
}
blit ( position , source , src_rect ) ;
2019-05-27 06:10:23 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : blit ( IntPoint position , Gfx : : Bitmap const & source , IntRect const & a_src_rect , float opacity , bool apply_alpha )
2019-02-10 09:46:29 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) > = source . scale ( ) & & " painter doesn't support downsampling scale factors " ) ;
2021-01-19 20:10:47 +03:00
2021-02-13 21:10:27 +03:00
if ( opacity < 1.0f | | ( source . has_alpha_channel ( ) & & apply_alpha ) )
return blit_with_opacity ( position , source , a_src_rect , opacity , apply_alpha ) ;
2021-01-23 02:56:01 +03:00
auto safe_src_rect = a_src_rect . intersected ( source . rect ( ) ) ;
2021-01-19 20:10:47 +03:00
if ( scale ( ) ! = source . scale ( ) )
2021-01-23 02:56:01 +03:00
return draw_scaled_bitmap ( { position , safe_src_rect . size ( ) } , source , safe_src_rect , opacity ) ;
2021-01-19 20:10:47 +03:00
// If we get here, the Painter might have a scale factor, but the source bitmap has the same scale factor.
// We need to transform from logical to physical coordinates, but we can just copy pixels without resampling.
2021-01-20 17:59:22 +03:00
auto dst_rect = IntRect ( position , safe_src_rect . size ( ) ) . translated ( translation ( ) ) ;
2019-04-16 14:58:02 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) ) ;
2019-02-10 09:46:29 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-01-20 17:59:22 +03:00
// All computations below are in physical coordinates.
int scale = this - > scale ( ) ;
auto src_rect = a_src_rect * scale ;
clipped_rect * = scale ;
dst_rect * = scale ;
2021-09-17 16:06:28 +03:00
int const first_row = clipped_rect . top ( ) - dst_rect . top ( ) ;
int const last_row = clipped_rect . bottom ( ) - dst_rect . top ( ) ;
int const first_column = clipped_rect . left ( ) - dst_rect . left ( ) ;
2022-03-05 00:05:20 +03:00
ARGB32 * dst = m_target - > scanline ( clipped_rect . y ( ) ) + clipped_rect . x ( ) ;
size_t const dst_skip = m_target - > pitch ( ) / sizeof ( ARGB32 ) ;
2019-02-10 09:46:29 +03:00
2021-03-16 13:48:42 +03:00
if ( source . format ( ) = = BitmapFormat : : BGRx8888 | | source . format ( ) = = BitmapFormat : : BGRA8888 ) {
2022-03-05 00:05:20 +03:00
ARGB32 const * src = source . scanline ( src_rect . top ( ) + first_row ) + src_rect . left ( ) + first_column ;
size_t const src_skip = source . pitch ( ) / sizeof ( ARGB32 ) ;
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
2022-09-14 15:29:27 +03:00
memcpy ( dst , src , sizeof ( ARGB32 ) * clipped_rect . width ( ) ) ;
2019-05-06 20:32:56 +03:00
dst + = dst_skip ;
src + = src_skip ;
}
return ;
}
2021-03-16 14:09:15 +03:00
if ( source . format ( ) = = BitmapFormat : : RGBA8888 ) {
2021-09-17 16:06:28 +03:00
u32 const * src = source . scanline ( src_rect . top ( ) + first_row ) + src_rect . left ( ) + first_column ;
size_t const src_skip = source . pitch ( ) / sizeof ( u32 ) ;
2023-05-22 01:41:18 +03:00
for ( int row = first_row ; row < last_row ; + + row ) {
2021-03-16 14:09:15 +03:00
for ( int i = 0 ; i < clipped_rect . width ( ) ; + + i ) {
u32 rgba = src [ i ] ;
u32 bgra = ( rgba & 0xff00ff00 )
| ( ( rgba & 0x000000ff ) < < 16 )
| ( ( rgba & 0x00ff0000 ) > > 16 ) ;
dst [ i ] = bgra ;
}
dst + = dst_skip ;
src + = src_skip ;
}
return ;
}
2020-06-17 22:37:16 +03:00
if ( Bitmap : : is_indexed ( source . format ( ) ) ) {
2021-09-17 16:06:28 +03:00
u8 const * src = source . scanline_u8 ( src_rect . top ( ) + first_row ) + src_rect . left ( ) + first_column ;
size_t const src_skip = source . pitch ( ) ;
2019-05-06 20:32:56 +03:00
for ( int row = first_row ; row < = last_row ; + + row ) {
for ( int i = 0 ; i < clipped_rect . width ( ) ; + + i )
dst [ i ] = source . palette_color ( src [ i ] ) . value ( ) ;
dst + = dst_skip ;
src + = src_skip ;
}
return ;
2019-02-10 09:46:29 +03:00
}
2019-05-06 20:32:56 +03:00
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2019-02-10 09:46:29 +03:00
}
2019-05-11 04:53:28 +03:00
template < bool has_alpha_channel , typename GetPixel >
2021-09-17 16:06:28 +03:00
ALWAYS_INLINE static void do_draw_integer_scaled_bitmap ( Gfx : : Bitmap & target , IntRect const & dst_rect , IntRect const & src_rect , Gfx : : Bitmap const & source , int hfactor , int vfactor , GetPixel get_pixel , float opacity )
2019-05-11 04:53:28 +03:00
{
2020-08-05 15:40:11 +03:00
bool has_opacity = opacity ! = 1.0f ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
for ( int y = 0 ; y < src_rect . height ( ) ; + + y ) {
2019-05-11 17:53:53 +03:00
int dst_y = dst_rect . y ( ) + y * vfactor ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
for ( int x = 0 ; x < src_rect . width ( ) ; + + x ) {
auto src_pixel = get_pixel ( source , x + src_rect . left ( ) , y + src_rect . top ( ) ) ;
2020-08-05 15:40:11 +03:00
if ( has_opacity )
src_pixel . set_alpha ( src_pixel . alpha ( ) * opacity ) ;
2023-05-21 22:54:05 +03:00
for ( int yo = 0 ; yo < vfactor ; + + yo ) {
2019-05-11 17:53:53 +03:00
auto * scanline = ( Color * ) target . scanline ( dst_y + yo ) ;
int dst_x = dst_rect . x ( ) + x * hfactor ;
2023-05-21 22:54:05 +03:00
for ( int xo = 0 ; xo < hfactor ; + + xo ) {
2019-05-11 06:34:20 +03:00
if constexpr ( has_alpha_channel )
2019-05-11 17:53:53 +03:00
scanline [ dst_x + xo ] = scanline [ dst_x + xo ] . blend ( src_pixel ) ;
2019-05-11 06:34:20 +03:00
else
2019-05-11 17:53:53 +03:00
scanline [ dst_x + xo ] = src_pixel ;
2019-05-11 06:34:20 +03:00
}
}
}
}
}
2023-05-19 01:26:34 +03:00
template < bool has_alpha_channel , typename GetPixel >
ALWAYS_INLINE static void do_draw_box_sampled_scaled_bitmap ( Gfx : : Bitmap & target , IntRect const & dst_rect , IntRect const & clipped_rect , Gfx : : Bitmap const & source , FloatRect const & src_rect , GetPixel get_pixel , float opacity )
{
float source_pixel_width = src_rect . width ( ) / dst_rect . width ( ) ;
float source_pixel_height = src_rect . height ( ) / dst_rect . height ( ) ;
float source_pixel_area = source_pixel_width * source_pixel_height ;
FloatRect const pixel_box = { 0.f , 0.f , 1.f , 1.f } ;
2023-05-22 01:41:18 +03:00
for ( int y = clipped_rect . top ( ) ; y < clipped_rect . bottom ( ) ; + + y ) {
2023-05-19 01:26:34 +03:00
auto * scanline = reinterpret_cast < Color * > ( target . scanline ( y ) ) ;
2023-05-22 01:41:18 +03:00
for ( int x = clipped_rect . left ( ) ; x < clipped_rect . right ( ) ; + + x ) {
2023-05-19 01:26:34 +03:00
// Project the destination pixel in the source image
FloatRect const source_box = {
src_rect . left ( ) + ( x - dst_rect . x ( ) ) * source_pixel_width ,
src_rect . top ( ) + ( y - dst_rect . y ( ) ) * source_pixel_height ,
source_pixel_width ,
source_pixel_height ,
} ;
IntRect enclosing_source_box = enclosing_int_rect ( source_box ) . intersected ( source . rect ( ) ) ;
// Sum the contribution of all source pixels inside the projected pixel
float red_accumulator = 0.f ;
float green_accumulator = 0.f ;
float blue_accumulator = 0.f ;
float total_area = 0.f ;
2023-05-22 01:41:18 +03:00
for ( int sy = enclosing_source_box . y ( ) ; sy < enclosing_source_box . bottom ( ) ; + + sy ) {
for ( int sx = enclosing_source_box . x ( ) ; sx < enclosing_source_box . right ( ) ; + + sx ) {
2023-05-22 01:58:42 +03:00
float area = source_box . intersected ( pixel_box . translated ( sx , sy ) ) . size ( ) . area ( ) ;
2023-05-19 01:26:34 +03:00
auto pixel = get_pixel ( source , sx , sy ) ;
area * = pixel . alpha ( ) / 255.f ;
red_accumulator + = pixel . red ( ) * area ;
green_accumulator + = pixel . green ( ) * area ;
blue_accumulator + = pixel . blue ( ) * area ;
total_area + = area ;
}
}
Color src_pixel = {
round_to < u8 > ( min ( red_accumulator / total_area , 255.f ) ) ,
round_to < u8 > ( min ( green_accumulator / total_area , 255.f ) ) ,
round_to < u8 > ( min ( blue_accumulator / total_area , 255.f ) ) ,
round_to < u8 > ( min ( total_area * 255.f / source_pixel_area * opacity , 255.f ) ) ,
} ;
if constexpr ( has_alpha_channel )
scanline [ x ] = scanline [ x ] . blend ( src_pixel ) ;
else
scanline [ x ] = src_pixel ;
}
}
}
2022-06-05 19:00:33 +03:00
template < bool has_alpha_channel , Painter : : ScalingMode scaling_mode , typename GetPixel >
2021-09-17 16:06:28 +03:00
ALWAYS_INLINE static void do_draw_scaled_bitmap ( Gfx : : Bitmap & target , IntRect const & dst_rect , IntRect const & clipped_rect , Gfx : : Bitmap const & source , FloatRect const & src_rect , GetPixel get_pixel , float opacity )
2019-05-11 06:34:20 +03:00
{
2022-03-23 11:05:37 +03:00
auto int_src_rect = enclosing_int_rect ( src_rect ) ;
auto clipped_src_rect = int_src_rect . intersected ( source . rect ( ) ) ;
2022-03-22 00:03:15 +03:00
if ( clipped_src_rect . is_empty ( ) )
return ;
2022-06-03 18:16:38 +03:00
if constexpr ( scaling_mode = = Painter : : ScalingMode : : NearestNeighbor | | scaling_mode = = Painter : : ScalingMode : : SmoothPixels ) {
2021-09-20 21:48:56 +03:00
if ( dst_rect = = clipped_rect & & int_src_rect = = src_rect & & ! ( dst_rect . width ( ) % int_src_rect . width ( ) ) & & ! ( dst_rect . height ( ) % int_src_rect . height ( ) ) ) {
int hfactor = dst_rect . width ( ) / int_src_rect . width ( ) ;
int vfactor = dst_rect . height ( ) / int_src_rect . height ( ) ;
if ( hfactor = = 2 & & vfactor = = 2 )
return do_draw_integer_scaled_bitmap < has_alpha_channel > ( target , dst_rect , int_src_rect , source , 2 , 2 , get_pixel , opacity ) ;
if ( hfactor = = 3 & & vfactor = = 3 )
return do_draw_integer_scaled_bitmap < has_alpha_channel > ( target , dst_rect , int_src_rect , source , 3 , 3 , get_pixel , opacity ) ;
if ( hfactor = = 4 & & vfactor = = 4 )
return do_draw_integer_scaled_bitmap < has_alpha_channel > ( target , dst_rect , int_src_rect , source , 4 , 4 , get_pixel , opacity ) ;
return do_draw_integer_scaled_bitmap < has_alpha_channel > ( target , dst_rect , int_src_rect , source , hfactor , vfactor , get_pixel , opacity ) ;
}
2019-05-11 06:34:20 +03:00
}
2023-05-19 01:26:34 +03:00
if constexpr ( scaling_mode = = Painter : : ScalingMode : : BoxSampling )
return do_draw_box_sampled_scaled_bitmap < has_alpha_channel > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
2023-05-19 01:26:09 +03:00
bool has_opacity = opacity ! = 1.f ;
i64 shift = 1ll < < 32 ;
i64 fractional_mask = shift - 1 ;
2022-09-06 15:06:10 +03:00
i64 bilinear_offset_x = ( 1ll < < 31 ) * ( src_rect . width ( ) / dst_rect . width ( ) - 1 ) ;
i64 bilinear_offset_y = ( 1ll < < 31 ) * ( src_rect . height ( ) / dst_rect . height ( ) - 1 ) ;
2023-05-19 01:26:09 +03:00
i64 hscale = src_rect . width ( ) * shift / dst_rect . width ( ) ;
i64 vscale = src_rect . height ( ) * shift / dst_rect . height ( ) ;
2021-09-10 20:18:49 +03:00
i64 src_left = src_rect . left ( ) * shift ;
i64 src_top = src_rect . top ( ) * shift ;
2020-07-23 21:32:39 +03:00
2023-05-22 01:41:18 +03:00
for ( int y = clipped_rect . top ( ) ; y < clipped_rect . bottom ( ) ; + + y ) {
2023-05-19 01:26:09 +03:00
auto * scanline = reinterpret_cast < Color * > ( target . scanline ( y ) ) ;
auto desired_y = ( y - dst_rect . y ( ) ) * vscale + src_top ;
2022-03-23 11:02:14 +03:00
2023-05-22 01:41:18 +03:00
for ( int x = clipped_rect . left ( ) ; x < clipped_rect . right ( ) ; + + x ) {
2023-05-19 01:26:09 +03:00
auto desired_x = ( x - dst_rect . x ( ) ) * hscale + src_left ;
2021-09-20 21:48:56 +03:00
Color src_pixel ;
2022-06-05 19:00:33 +03:00
if constexpr ( scaling_mode = = Painter : : ScalingMode : : BilinearBlend ) {
2022-09-06 15:06:10 +03:00
auto shifted_x = desired_x + bilinear_offset_x ;
auto shifted_y = desired_y + bilinear_offset_y ;
2021-09-20 21:48:56 +03:00
2023-05-22 01:41:18 +03:00
auto scaled_x0 = clamp ( shifted_x > > 32 , clipped_src_rect . left ( ) , clipped_src_rect . right ( ) - 1 ) ;
auto scaled_x1 = clamp ( ( shifted_x > > 32 ) + 1 , clipped_src_rect . left ( ) , clipped_src_rect . right ( ) - 1 ) ;
auto scaled_y0 = clamp ( shifted_y > > 32 , clipped_src_rect . top ( ) , clipped_src_rect . bottom ( ) - 1 ) ;
auto scaled_y1 = clamp ( ( shifted_y > > 32 ) + 1 , clipped_src_rect . top ( ) , clipped_src_rect . bottom ( ) - 1 ) ;
2022-09-06 15:06:10 +03:00
float x_ratio = ( shifted_x & fractional_mask ) / static_cast < float > ( shift ) ;
float y_ratio = ( shifted_y & fractional_mask ) / static_cast < float > ( shift ) ;
2021-09-20 21:48:56 +03:00
2022-03-21 02:08:52 +03:00
auto top_left = get_pixel ( source , scaled_x0 , scaled_y0 ) ;
auto top_right = get_pixel ( source , scaled_x1 , scaled_y0 ) ;
auto bottom_left = get_pixel ( source , scaled_x0 , scaled_y1 ) ;
auto bottom_right = get_pixel ( source , scaled_x1 , scaled_y1 ) ;
2023-02-17 01:22:21 +03:00
auto top = top_left . mixed_with ( top_right , x_ratio ) ;
auto bottom = bottom_left . mixed_with ( bottom_right , x_ratio ) ;
2022-03-21 02:08:52 +03:00
2023-02-17 01:22:21 +03:00
src_pixel = top . mixed_with ( bottom , y_ratio ) ;
2022-06-03 18:16:38 +03:00
} else if constexpr ( scaling_mode = = Painter : : ScalingMode : : SmoothPixels ) {
2023-05-22 01:41:18 +03:00
auto scaled_x1 = clamp ( desired_x > > 32 , clipped_src_rect . left ( ) , clipped_src_rect . right ( ) - 1 ) ;
auto scaled_x0 = clamp ( scaled_x1 - 1 , clipped_src_rect . left ( ) , clipped_src_rect . right ( ) - 1 ) ;
auto scaled_y1 = clamp ( desired_y > > 32 , clipped_src_rect . top ( ) , clipped_src_rect . bottom ( ) - 1 ) ;
auto scaled_y0 = clamp ( scaled_y1 - 1 , clipped_src_rect . top ( ) , clipped_src_rect . bottom ( ) - 1 ) ;
2022-06-03 18:16:38 +03:00
float x_ratio = ( desired_x & fractional_mask ) / ( float ) shift ;
float y_ratio = ( desired_y & fractional_mask ) / ( float ) shift ;
2023-05-19 01:26:09 +03:00
float scaled_x_ratio = clamp ( x_ratio * dst_rect . width ( ) / ( float ) src_rect . width ( ) , 0.f , 1.f ) ;
float scaled_y_ratio = clamp ( y_ratio * dst_rect . height ( ) / ( float ) src_rect . height ( ) , 0.f , 1.f ) ;
2022-06-03 18:16:38 +03:00
auto top_left = get_pixel ( source , scaled_x0 , scaled_y0 ) ;
auto top_right = get_pixel ( source , scaled_x1 , scaled_y0 ) ;
auto bottom_left = get_pixel ( source , scaled_x0 , scaled_y1 ) ;
auto bottom_right = get_pixel ( source , scaled_x1 , scaled_y1 ) ;
2023-02-17 01:22:21 +03:00
auto top = top_left . mixed_with ( top_right , scaled_x_ratio ) ;
auto bottom = bottom_left . mixed_with ( bottom_right , scaled_x_ratio ) ;
2022-06-03 18:16:38 +03:00
2023-02-17 01:22:21 +03:00
src_pixel = top . mixed_with ( bottom , scaled_y_ratio ) ;
2021-09-20 21:48:56 +03:00
} else {
2023-05-22 01:41:18 +03:00
auto scaled_x = clamp ( desired_x > > 32 , clipped_src_rect . left ( ) , clipped_src_rect . right ( ) - 1 ) ;
auto scaled_y = clamp ( desired_y > > 32 , clipped_src_rect . top ( ) , clipped_src_rect . bottom ( ) - 1 ) ;
2021-09-20 21:48:56 +03:00
src_pixel = get_pixel ( source , scaled_x , scaled_y ) ;
}
2020-08-05 15:40:11 +03:00
if ( has_opacity )
src_pixel . set_alpha ( src_pixel . alpha ( ) * opacity ) ;
2023-05-19 01:26:09 +03:00
if constexpr ( has_alpha_channel )
2019-05-11 04:53:28 +03:00
scanline [ x ] = scanline [ x ] . blend ( src_pixel ) ;
2023-05-19 01:26:09 +03:00
else
2019-05-11 04:53:28 +03:00
scanline [ x ] = src_pixel ;
}
}
}
2021-09-20 21:48:56 +03:00
template < bool has_alpha_channel , typename GetPixel >
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 , Painter : : ScalingMode scaling_mode )
{
switch ( scaling_mode ) {
case Painter : : ScalingMode : : NearestNeighbor :
2022-06-05 19:00:33 +03:00
do_draw_scaled_bitmap < has_alpha_channel , Painter : : ScalingMode : : NearestNeighbor > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
2021-09-20 21:48:56 +03:00
break ;
2022-06-03 18:16:38 +03:00
case Painter : : ScalingMode : : SmoothPixels :
do_draw_scaled_bitmap < has_alpha_channel , Painter : : ScalingMode : : SmoothPixels > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
break ;
2021-09-20 21:48:56 +03:00
case Painter : : ScalingMode : : BilinearBlend :
2022-06-05 19:00:33 +03:00
do_draw_scaled_bitmap < has_alpha_channel , Painter : : ScalingMode : : BilinearBlend > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
2023-05-19 01:26:34 +03:00
break ;
case Painter : : ScalingMode : : BoxSampling :
do_draw_scaled_bitmap < has_alpha_channel , Painter : : ScalingMode : : BoxSampling > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
2021-09-20 21:48:56 +03:00
break ;
2022-10-10 22:54:06 +03:00
case Painter : : ScalingMode : : None :
do_draw_scaled_bitmap < has_alpha_channel , Painter : : ScalingMode : : None > ( target , dst_rect , clipped_rect , source , src_rect , get_pixel , opacity ) ;
break ;
2021-09-20 21:48:56 +03:00
}
}
void Painter : : draw_scaled_bitmap ( IntRect const & a_dst_rect , Gfx : : Bitmap const & source , IntRect const & a_src_rect , float opacity , ScalingMode scaling_mode )
2019-03-22 06:20:10 +03:00
{
2021-09-20 21:48:56 +03:00
draw_scaled_bitmap ( a_dst_rect , source , FloatRect { a_src_rect } , opacity , scaling_mode ) ;
LibGfx: Add a draw_scaled_bitmap() variant that takes a FloatRect as src_rect
Consider
draw_scaled_bitmap({0, 0, 10, 10}, source, {0, 0, 5, 5}).
Imagine wanting to split that up into two calls, like e.g. the
compositor when redrawing the background with damage rects. You really
want to be able to say
draw_scaled_bitmap({0, 0, 5, 10}, source, {0, 0, 2.5, 5})
but up to now you couldn't. Now you can.
This makes painting very low-res images (such as tile.png) in mode
"stretch" work much better.
2021-01-22 23:13:47 +03:00
}
2021-09-20 21:48:56 +03:00
void Painter : : draw_scaled_bitmap ( IntRect const & a_dst_rect , Gfx : : Bitmap const & source , FloatRect const & a_src_rect , float opacity , ScalingMode scaling_mode )
LibGfx: Add a draw_scaled_bitmap() variant that takes a FloatRect as src_rect
Consider
draw_scaled_bitmap({0, 0, 10, 10}, source, {0, 0, 5, 5}).
Imagine wanting to split that up into two calls, like e.g. the
compositor when redrawing the background with damage rects. You really
want to be able to say
draw_scaled_bitmap({0, 0, 5, 10}, source, {0, 0, 2.5, 5})
but up to now you couldn't. Now you can.
This makes painting very low-res images (such as tile.png) in mode
"stretch" work much better.
2021-01-22 23:13:47 +03:00
{
IntRect int_src_rect = enclosing_int_rect ( a_src_rect ) ;
if ( scale ( ) = = source . scale ( ) & & a_src_rect = = int_src_rect & & a_dst_rect . size ( ) = = int_src_rect . size ( ) )
return blit ( a_dst_rect . location ( ) , source , int_src_rect , opacity ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2022-10-10 22:54:06 +03:00
if ( scaling_mode = = ScalingMode : : None ) {
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 ( ) } ;
return blit ( a_dst_rect . location ( ) , source , clipped_draw_rect , opacity ) ;
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
auto dst_rect = to_physical ( a_dst_rect ) ;
2021-01-19 20:10:47 +03:00
auto src_rect = a_src_rect * source . scale ( ) ;
2021-01-20 17:59:22 +03:00
auto clipped_rect = dst_rect . intersected ( clip_rect ( ) * scale ( ) ) ;
2019-03-22 06:20:10 +03:00
if ( clipped_rect . is_empty ( ) )
return ;
2021-01-25 20:25:56 +03:00
if ( source . has_alpha_channel ( ) | | opacity ! = 1.0f ) {
2019-05-11 04:53:28 +03:00
switch ( source . format ( ) ) {
2021-03-16 13:48:42 +03:00
case BitmapFormat : : BGRx8888 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : BGRx8888 > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2021-03-16 13:48:42 +03:00
case BitmapFormat : : BGRA8888 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : BGRA8888 > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2020-02-15 01:02:47 +03:00
case BitmapFormat : : Indexed8 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Indexed8 > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2020-06-17 22:37:16 +03:00
case BitmapFormat : : Indexed4 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Indexed4 > , opacity , scaling_mode ) ;
2020-06-17 22:37:16 +03:00
break ;
case BitmapFormat : : Indexed2 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Indexed2 > , opacity , scaling_mode ) ;
2020-06-17 22:37:16 +03:00
break ;
case BitmapFormat : : Indexed1 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Indexed1 > , opacity , scaling_mode ) ;
2020-06-17 22:37:16 +03:00
break ;
2019-06-05 19:22:11 +03:00
default :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < true > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Invalid > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2019-05-11 04:53:28 +03:00
}
} else {
switch ( source . format ( ) ) {
2021-03-16 13:48:42 +03:00
case BitmapFormat : : BGRx8888 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < false > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : BGRx8888 > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2020-02-15 01:02:47 +03:00
case BitmapFormat : : Indexed8 :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < false > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Indexed8 > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
default :
2022-06-15 23:05:58 +03:00
do_draw_scaled_bitmap < false > ( * m_target , dst_rect , clipped_rect , source , src_rect , Gfx : : get_pixel < BitmapFormat : : Invalid > , opacity , scaling_mode ) ;
2019-06-05 19:22:11 +03:00
break ;
2019-03-22 06:20:10 +03:00
}
}
}
2023-01-01 21:42:00 +03:00
FLATTEN void Painter : : draw_glyph ( FloatPoint point , u32 code_point , Color color )
2019-01-15 06:44:47 +03:00
{
2020-08-05 23:31:20 +03:00
draw_glyph ( point , code_point , font ( ) , color ) ;
2019-01-15 06:44:47 +03:00
}
2023-01-01 21:42:00 +03:00
FLATTEN void Painter : : draw_glyph ( FloatPoint point , u32 code_point , Font const & font , Color color )
2019-03-09 23:24:12 +03:00
{
2023-01-04 22:10:00 +03:00
auto top_left = point + FloatPoint ( font . glyph_left_bearing ( code_point ) , 0 ) ;
auto glyph_position = Gfx : : GlyphRasterPosition : : get_nearest_fit_for ( top_left ) ;
auto glyph = font . glyph ( code_point , glyph_position . subpixel_offset ) ;
2021-01-03 19:31:18 +03:00
2021-01-02 20:22:22 +03:00
if ( glyph . is_glyph_bitmap ( ) ) {
2023-01-01 21:42:00 +03:00
draw_bitmap ( top_left . to_type < int > ( ) , glyph . glyph_bitmap ( ) , color ) ;
2023-03-04 22:33:02 +03:00
} else if ( glyph . is_color_bitmap ( ) ) {
float scaled_width = glyph . advance ( ) ;
float ratio = static_cast < float > ( glyph . bitmap ( ) - > height ( ) ) / static_cast < float > ( glyph . bitmap ( ) - > width ( ) ) ;
float scaled_height = scaled_width * ratio ;
FloatRect rect ( point . x ( ) , point . y ( ) , scaled_width , scaled_height ) ;
draw_scaled_bitmap ( rect . to_rounded < int > ( ) , * glyph . bitmap ( ) , glyph . bitmap ( ) - > rect ( ) , 1.0f , ScalingMode : : BilinearBlend ) ;
2023-06-01 10:04:39 +03:00
} else if ( color . alpha ( ) ! = 255 ) {
blit_filtered ( glyph_position . blit_position , * glyph . bitmap ( ) , glyph . bitmap ( ) - > rect ( ) , [ color ] ( Color pixel ) - > Color {
return pixel . multiply ( color ) ;
} ) ;
2021-01-02 20:22:22 +03:00
} else {
2023-01-04 22:10:00 +03:00
blit_filtered ( glyph_position . blit_position , * glyph . bitmap ( ) , glyph . bitmap ( ) - > rect ( ) , [ color ] ( Color pixel ) - > Color {
2023-05-31 23:01:43 +03:00
return color . with_alpha ( pixel . alpha ( ) ) ;
2021-01-02 20:22:22 +03:00
} ) ;
}
2019-03-09 23:24:12 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_emoji ( IntPoint point , Gfx : : Bitmap const & emoji , Font const & font )
2019-09-04 23:47:03 +03:00
{
2022-01-21 20:48:47 +03:00
IntRect dst_rect {
point . x ( ) ,
point . y ( ) ,
2023-03-03 21:38:05 +03:00
font . pixel_size_rounded_up ( ) * emoji . width ( ) / emoji . height ( ) ,
font . pixel_size_rounded_up ( ) ,
2022-01-21 20:48:47 +03:00
} ;
draw_scaled_bitmap ( dst_rect , emoji , emoji . rect ( ) ) ;
2019-09-04 23:47:03 +03:00
}
2023-01-01 21:42:00 +03:00
void Painter : : draw_glyph_or_emoji ( FloatPoint point , u32 code_point , Font const & font , Color color )
2019-09-04 23:47:03 +03:00
{
2022-02-23 00:55:01 +03:00
StringBuilder builder ;
builder . append_code_point ( code_point ) ;
auto it = Utf8View { builder . string_view ( ) } . begin ( ) ;
return draw_glyph_or_emoji ( point , it , font , color ) ;
}
2023-01-01 21:42:00 +03:00
void Painter : : draw_glyph_or_emoji ( FloatPoint point , Utf8CodePointIterator & it , Font const & font , Color color )
2022-02-23 00:55:01 +03:00
{
u32 code_point = * it ;
2022-02-23 01:22:10 +03:00
auto next_code_point = it . peek ( 1 ) ;
2023-02-21 15:50:41 +03:00
ScopeGuard consume_variation_selector = [ & , initial_it = it ] {
2023-02-23 16:53:35 +03:00
static auto const variation_selector = Unicode : : property_from_string ( " Variation_Selector " sv ) ;
if ( ! variation_selector . has_value ( ) )
return ;
2022-02-23 01:22:10 +03:00
// If we advanced the iterator to consume an emoji sequence, don't look for another variation selector.
if ( initial_it ! = it )
return ;
2023-02-21 15:50:41 +03:00
2022-02-23 01:22:10 +03:00
// Otherwise, discard one code point if it's a variation selector.
2023-02-21 15:50:41 +03:00
if ( next_code_point . has_value ( ) & & Unicode : : code_point_has_property ( * next_code_point , * variation_selector ) )
2022-02-23 01:22:10 +03:00
+ + it ;
} ;
2023-03-04 22:54:25 +03:00
// NOTE: We don't check for emoji
2022-02-23 01:22:10 +03:00
auto font_contains_glyph = font . contains_glyph ( code_point ) ;
2023-03-04 22:54:25 +03:00
auto check_for_emoji = ! font . has_color_bitmaps ( ) & & Unicode : : could_be_start_of_emoji_sequence ( it , font_contains_glyph ? Unicode : : SequenceType : : EmojiPresentation : Unicode : : SequenceType : : Any ) ;
2022-02-23 01:22:10 +03:00
// If the font contains the glyph, and we know it's not the start of an emoji, draw a text glyph.
if ( font_contains_glyph & & ! check_for_emoji ) {
2021-01-03 21:24:59 +03:00
draw_glyph ( point , code_point , font , color ) ;
2019-09-04 23:47:03 +03:00
return ;
}
2022-02-23 01:22:10 +03:00
// If we didn't find a text glyph, or have an emoji variation selector or regional indicator, try to draw an emoji glyph.
2022-02-23 00:55:01 +03:00
if ( auto const * emoji = Emoji : : emoji_for_code_point_iterator ( it ) ) {
2023-01-01 21:42:00 +03:00
draw_emoji ( point . to_type < int > ( ) , * emoji , font ) ;
2019-09-04 23:47:03 +03:00
return ;
}
2022-02-23 01:22:10 +03:00
// If that failed, but we have a text glyph fallback, draw that.
if ( font_contains_glyph ) {
draw_glyph ( point , code_point , font , color ) ;
return ;
}
// No suitable glyph found, draw a replacement character.
2022-02-23 00:55:01 +03:00
dbgln_if ( EMOJI_DEBUG , " Failed to find a glyph or emoji for code_point {} " , code_point ) ;
draw_glyph ( point , 0xFFFD , font , color ) ;
2019-09-04 23:47:03 +03:00
}
2023-01-01 21:42:00 +03:00
void Painter : : draw_glyph ( IntPoint point , u32 code_point , Color color )
{
draw_glyph ( point . to_type < float > ( ) , code_point , font ( ) , color ) ;
}
void Painter : : draw_glyph ( IntPoint point , u32 code_point , Font const & font , Color color )
{
draw_glyph ( point . to_type < float > ( ) , code_point , font , color ) ;
}
void Painter : : draw_glyph_or_emoji ( IntPoint point , u32 code_point , Font const & font , Color color )
{
draw_glyph_or_emoji ( point . to_type < float > ( ) , code_point , font , color ) ;
}
void Painter : : draw_glyph_or_emoji ( IntPoint point , Utf8CodePointIterator & it , Font const & font , Color color )
{
draw_glyph_or_emoji ( point . to_type < float > ( ) , it , font , color ) ;
}
2021-07-26 00:20:11 +03:00
template < typename DrawGlyphFunction >
2023-01-01 21:42:00 +03:00
void draw_text_line ( FloatRect const & a_rect , Utf8View const & text , Font const & font , TextAlignment alignment , TextDirection direction , DrawGlyphFunction draw_glyph )
2018-10-10 21:06:58 +03:00
{
2019-07-12 20:43:29 +03:00
auto rect = a_rect ;
2019-04-04 16:19:04 +03:00
2019-09-06 20:23:36 +03:00
switch ( alignment ) {
case TextAlignment : : TopLeft :
case TextAlignment : : CenterLeft :
2021-05-21 03:03:02 +03:00
case TextAlignment : : BottomLeft :
2019-09-06 20:23:36 +03:00
break ;
case TextAlignment : : TopRight :
case TextAlignment : : CenterRight :
2020-08-21 17:52:39 +03:00
case TextAlignment : : BottomRight :
2023-05-22 01:41:18 +03:00
rect . set_x ( rect . right ( ) - 1 - font . width ( text ) ) ;
2019-09-06 20:23:36 +03:00
break ;
2022-02-27 03:33:48 +03:00
case TextAlignment : : TopCenter :
case TextAlignment : : BottomCenter :
2019-09-06 20:23:36 +03:00
case TextAlignment : : Center : {
2019-07-12 20:43:29 +03:00
auto shrunken_rect = rect ;
2021-07-26 00:20:11 +03:00
shrunken_rect . set_width ( font . width ( text ) ) ;
2019-07-12 20:43:29 +03:00
shrunken_rect . center_within ( rect ) ;
rect = shrunken_rect ;
2019-09-06 20:23:36 +03:00
break ;
}
default :
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2018-10-11 02:48:09 +03:00
}
2018-10-10 21:06:58 +03:00
2019-07-12 20:43:29 +03:00
auto point = rect . location ( ) ;
2023-01-01 21:42:00 +03:00
auto space_width = font . glyph_width ( ' ' ) + font . glyph_spacing ( ) ;
2019-07-12 20:43:29 +03:00
2021-04-24 16:20:51 +03:00
if ( direction = = TextDirection : : RTL ) {
2021-04-12 21:47:09 +03:00
point . translate_by ( rect . width ( ) , 0 ) ; // Start drawing from the end
space_width = - space_width ; // Draw spaces backwards
2021-04-24 16:20:51 +03:00
}
2022-03-24 18:20:43 +03:00
u32 last_code_point { 0 } ;
2022-02-23 00:55:01 +03:00
for ( auto it = text . begin ( ) ; it ! = text . end ( ) ; + + it ) {
auto code_point = * it ;
2022-07-09 16:12:10 +03:00
if ( should_paint_as_space ( code_point ) ) {
2021-04-12 21:47:09 +03:00
point . translate_by ( space_width , 0 ) ;
2022-03-24 18:20:43 +03:00
last_code_point = code_point ;
2018-10-12 13:29:58 +03:00
continue ;
2019-03-06 13:03:10 +03:00
}
2022-03-24 18:20:43 +03:00
2023-01-01 21:42:00 +03:00
auto kerning = font . glyphs_horizontal_kerning ( last_code_point , code_point ) ;
if ( kerning ! = 0.0f )
2022-03-24 18:20:43 +03:00
point . translate_by ( direction = = TextDirection : : LTR ? kerning : - kerning , 0 ) ;
2023-02-20 21:15:06 +03:00
auto it_copy = it ; // The callback function will advance the iterator, so create a copy for this lookup.
FloatSize glyph_size ( font . glyph_or_emoji_width ( it_copy ) + font . glyph_spacing ( ) , font . pixel_size ( ) ) ;
2021-04-24 16:20:51 +03:00
if ( direction = = TextDirection : : RTL )
2021-04-12 21:47:09 +03:00
point . translate_by ( - glyph_size . width ( ) , 0 ) ; // If we are drawing right to left, we have to move backwards before drawing the glyph
2022-02-23 00:55:01 +03:00
draw_glyph ( { point , glyph_size } , it ) ;
2021-04-24 16:20:51 +03:00
if ( direction = = TextDirection : : LTR )
2021-04-12 21:47:09 +03:00
point . translate_by ( glyph_size . width ( ) , 0 ) ;
2022-02-23 00:55:01 +03:00
// The callback function might have exhausted the iterator.
if ( it = = text . end ( ) )
break ;
2022-03-24 18:20:43 +03:00
last_code_point = code_point ;
2018-10-10 21:06:58 +03:00
}
}
2021-09-17 16:06:28 +03:00
static inline size_t draw_text_get_length ( Utf8View const & text )
2019-07-12 20:43:29 +03:00
{
2020-10-20 18:51:04 +03:00
return text . byte_length ( ) ;
2019-07-12 20:43:29 +03:00
}
2021-07-26 00:20:11 +03:00
Vector < DirectionalRun > Painter : : split_text_into_directional_runs ( Utf8View const & text , TextDirection initial_direction )
2021-04-24 16:20:51 +03:00
{
// FIXME: This is a *very* simplified version of the UNICODE BIDIRECTIONAL ALGORITHM (https://www.unicode.org/reports/tr9/), that can render most bidirectional text
// 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.
// FIXME: Support HTML "dir" attribute (how?)
u8 paragraph_embedding_level = initial_direction = = TextDirection : : LTR ? 0 : 1 ;
Vector < u8 > embedding_levels ;
embedding_levels . ensure_capacity ( text . length ( ) ) ;
for ( size_t i = 0 ; i < text . length ( ) ; i + + )
embedding_levels . unchecked_append ( paragraph_embedding_level ) ;
// FIXME: Support Explicit Directional Formatting Characters
Vector < BidirectionalClass > character_classes ;
character_classes . ensure_capacity ( text . length ( ) ) ;
for ( u32 code_point : text )
character_classes . unchecked_append ( get_char_bidi_class ( code_point ) ) ;
// resolving weak types
BidirectionalClass paragraph_class = initial_direction = = TextDirection : : LTR ? BidirectionalClass : : STRONG_LTR : BidirectionalClass : : STRONG_RTL ;
for ( size_t i = 0 ; i < character_classes . size ( ) ; i + + ) {
if ( character_classes [ i ] ! = BidirectionalClass : : WEAK_SEPARATORS )
continue ;
for ( ssize_t j = i - 1 ; j > = 0 ; j - - ) {
auto character_class = character_classes [ j ] ;
if ( character_class ! = BidirectionalClass : : STRONG_RTL & & character_class ! = BidirectionalClass : : STRONG_LTR )
continue ;
character_classes [ i ] = character_class ;
break ;
}
if ( character_classes [ i ] = = BidirectionalClass : : WEAK_SEPARATORS )
character_classes [ i ] = paragraph_class ;
}
// resolving neutral types
auto left_side = BidirectionalClass : : NEUTRAL ;
auto sequence_length = 0 ;
for ( size_t i = 0 ; i < character_classes . size ( ) ; i + + ) {
auto character_class = character_classes [ i ] ;
if ( left_side = = BidirectionalClass : : NEUTRAL ) {
if ( character_class ! = BidirectionalClass : : NEUTRAL )
left_side = character_class ;
else
character_classes [ i ] = paragraph_class ;
continue ;
}
if ( character_class ! = BidirectionalClass : : NEUTRAL ) {
BidirectionalClass sequence_class ;
if ( bidi_class_to_direction ( left_side ) = = bidi_class_to_direction ( character_class ) ) {
sequence_class = left_side = = BidirectionalClass : : STRONG_RTL ? BidirectionalClass : : STRONG_RTL : BidirectionalClass : : STRONG_LTR ;
} else {
sequence_class = paragraph_class ;
}
for ( auto j = 0 ; j < sequence_length ; j + + ) {
character_classes [ i - j - 1 ] = sequence_class ;
}
sequence_length = 0 ;
left_side = character_class ;
} else {
sequence_length + + ;
}
}
for ( auto i = 0 ; i < sequence_length ; i + + )
character_classes [ character_classes . size ( ) - i - 1 ] = paragraph_class ;
// resolving implicit levels
for ( size_t i = 0 ; i < character_classes . size ( ) ; i + + ) {
auto character_class = character_classes [ i ] ;
if ( ( embedding_levels [ i ] % 2 ) = = 0 ) {
if ( character_class = = BidirectionalClass : : STRONG_RTL )
embedding_levels [ i ] + = 1 ;
else if ( character_class = = BidirectionalClass : : WEAK_NUMBERS | | character_class = = BidirectionalClass : : WEAK_SEPARATORS )
embedding_levels [ i ] + = 2 ;
} else {
if ( character_class = = BidirectionalClass : : STRONG_LTR | | character_class = = BidirectionalClass : : WEAK_NUMBERS | | character_class = = BidirectionalClass : : WEAK_SEPARATORS )
embedding_levels [ i ] + = 1 ;
}
}
// splitting into runs
auto run_code_points_start = text . begin ( ) ;
auto next_code_points_slice = [ & ] ( auto length ) {
Vector < u32 > run_code_points ;
run_code_points . ensure_capacity ( length ) ;
for ( size_t j = 0 ; j < length ; + + j , + + run_code_points_start )
run_code_points . unchecked_append ( * run_code_points_start ) ;
return run_code_points ;
} ;
Vector < DirectionalRun > runs ;
size_t start = 0 ;
u8 level = embedding_levels [ 0 ] ;
for ( size_t i = 1 ; i < embedding_levels . size ( ) ; + + i ) {
if ( embedding_levels [ i ] = = level )
continue ;
auto code_points_slice = next_code_points_slice ( i - start ) ;
runs . append ( { move ( code_points_slice ) , level } ) ;
start = i ;
level = embedding_levels [ i ] ;
}
auto code_points_slice = next_code_points_slice ( embedding_levels . size ( ) - start ) ;
runs . append ( { move ( code_points_slice ) , level } ) ;
// reordering resolved levels
// FIXME: missing special cases for trailing whitespace characters
u8 minimum_level = 128 ;
u8 maximum_level = 0 ;
for ( auto & run : runs ) {
minimum_level = min ( minimum_level , run . embedding_level ( ) ) ;
maximum_level = max ( minimum_level , run . embedding_level ( ) ) ;
}
if ( ( minimum_level % 2 ) = = 0 )
minimum_level + + ;
auto runs_count = runs . size ( ) - 1 ;
while ( maximum_level < = minimum_level ) {
size_t run_index = 0 ;
while ( run_index < runs_count ) {
while ( run_index < runs_count & & runs [ run_index ] . embedding_level ( ) < maximum_level )
run_index + + ;
auto reverse_start = run_index ;
while ( run_index < = runs_count & & runs [ run_index ] . embedding_level ( ) > = maximum_level )
run_index + + ;
auto reverse_end = run_index - 1 ;
while ( reverse_start < reverse_end ) {
swap ( runs [ reverse_start ] , runs [ reverse_end ] ) ;
reverse_start + + ;
reverse_end - - ;
}
}
maximum_level - - ;
}
// mirroring RTL mirror characters
for ( auto & run : runs ) {
if ( run . direction ( ) = = TextDirection : : LTR )
continue ;
for ( auto & code_point : run . code_points ( ) ) {
code_point = get_mirror_char ( code_point ) ;
}
}
return runs ;
}
2021-07-26 00:20:11 +03:00
bool Painter : : text_contains_bidirectional_text ( Utf8View const & text , TextDirection initial_direction )
2021-04-24 16:20:51 +03:00
{
for ( u32 code_point : text ) {
auto char_class = get_char_bidi_class ( code_point ) ;
if ( char_class = = BidirectionalClass : : NEUTRAL )
continue ;
if ( bidi_class_to_direction ( char_class ) ! = initial_direction )
return true ;
}
return false ;
}
2021-07-26 00:20:11 +03:00
template < typename DrawGlyphFunction >
2023-01-01 21:42:00 +03:00
void Painter : : do_draw_text ( FloatRect const & rect , Utf8View const & text , Font const & font , TextAlignment alignment , TextElision elision , TextWrapping wrapping , DrawGlyphFunction draw_glyph )
2019-07-12 20:43:29 +03:00
{
2021-04-24 16:20:51 +03:00
if ( draw_text_get_length ( text ) = = 0 )
return ;
2023-01-05 22:41:19 +03:00
TextLayout layout ( font , text , rect ) ;
2019-07-12 20:43:29 +03:00
2023-01-06 13:55:23 +03:00
auto line_height = font . preferred_line_height ( ) ;
2020-05-17 21:32:23 +03:00
2023-01-06 13:55:23 +03:00
auto lines = layout . lines ( elision , wrapping ) ;
auto bounding_rect = layout . bounding_rect ( wrapping ) ;
2020-05-17 21:32:23 +03:00
2023-01-06 11:30:07 +03:00
bounding_rect . align_within ( rect , alignment ) ;
2021-08-31 22:18:53 +03:00
2020-05-17 21:32:23 +03:00
for ( size_t i = 0 ; i < lines . size ( ) ; + + i ) {
2021-09-04 14:06:25 +03:00
auto line = Utf8View { lines [ i ] } ;
2021-04-24 16:20:51 +03:00
2023-01-01 21:42:00 +03:00
FloatRect line_rect { bounding_rect . x ( ) , bounding_rect . y ( ) + i * line_height , bounding_rect . width ( ) , line_height } ;
2021-04-24 16:20:51 +03:00
TextDirection line_direction = get_text_direction ( line ) ;
2021-09-04 14:06:25 +03:00
if ( text_contains_bidirectional_text ( line , line_direction ) ) { // Slow Path: The line contains mixed BiDi classes
auto directional_runs = split_text_into_directional_runs ( line , line_direction ) ;
2021-04-24 16:20:51 +03:00
auto current_dx = line_direction = = TextDirection : : LTR ? 0 : line_rect . width ( ) ;
for ( auto & directional_run : directional_runs ) {
auto run_width = font . width ( directional_run . text ( ) ) ;
if ( line_direction = = TextDirection : : RTL )
current_dx - = run_width ;
auto run_rect = line_rect . translated ( current_dx , 0 ) ;
run_rect . set_width ( run_width ) ;
2021-07-26 00:20:11 +03:00
// NOTE: DirectionalRun returns Utf32View which isn't
// compatible with draw_text_line.
StringBuilder builder ;
builder . append ( directional_run . text ( ) ) ;
2021-09-04 14:26:28 +03:00
auto line_text = Utf8View { builder . string_view ( ) } ;
2021-07-26 00:20:11 +03:00
2021-09-04 14:06:25 +03:00
draw_text_line ( run_rect , line_text , font , alignment , directional_run . direction ( ) , draw_glyph ) ;
2021-04-24 16:20:51 +03:00
if ( line_direction = = TextDirection : : LTR )
current_dx + = run_width ;
}
} else {
2021-09-04 14:06:25 +03:00
draw_text_line ( line_rect , line , font , alignment , line_direction , draw_glyph ) ;
2021-04-24 16:20:51 +03:00
}
2020-05-17 21:32:23 +03:00
}
}
2023-01-01 21:42:00 +03:00
void Painter : : draw_text ( FloatRect const & rect , StringView text , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
2020-05-17 21:32:23 +03:00
{
2021-07-26 00:20:11 +03:00
draw_text ( rect , text , font ( ) , alignment , color , elision , wrapping ) ;
2020-10-20 18:51:04 +03:00
}
2020-05-17 21:32:23 +03:00
2023-01-01 21:42:00 +03:00
void Painter : : draw_text ( FloatRect const & rect , Utf32View const & text , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
2020-10-20 18:51:04 +03:00
{
2021-07-26 00:20:11 +03:00
draw_text ( rect , text , font ( ) , alignment , color , elision , wrapping ) ;
2020-10-20 18:51:04 +03:00
}
2020-05-17 21:32:23 +03:00
2023-01-01 21:42:00 +03:00
void Painter : : draw_text ( FloatRect const & rect , StringView raw_text , Font const & font , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
2020-10-20 18:51:04 +03:00
{
Utf8View text { raw_text } ;
2023-01-01 21:42:00 +03:00
do_draw_text ( rect , text , font , alignment , elision , wrapping , [ & ] ( FloatRect const & r , Utf8CodePointIterator & it ) {
2022-02-23 00:55:01 +03:00
draw_glyph_or_emoji ( r . location ( ) , it , font , color ) ;
2020-10-20 18:51:04 +03:00
} ) ;
}
2020-05-17 21:32:23 +03:00
2023-01-01 21:42:00 +03:00
void Painter : : draw_text ( FloatRect const & rect , Utf32View const & raw_text , Font const & font , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
2020-10-20 18:51:04 +03:00
{
2021-07-26 00:20:11 +03:00
// FIXME: UTF-32 should eventually be completely removed, but for the time
// being some places might depend on it, so we do some internal conversion.
StringBuilder builder ;
builder . append ( raw_text ) ;
auto text = Utf8View { builder . string_view ( ) } ;
2023-01-01 21:42:00 +03:00
do_draw_text ( rect , text , font , alignment , elision , wrapping , [ & ] ( FloatRect const & r , Utf8CodePointIterator & it ) {
2022-02-23 00:55:01 +03:00
draw_glyph_or_emoji ( r . location ( ) , it , font , color ) ;
2020-10-20 18:51:04 +03:00
} ) ;
}
2019-07-12 20:43:29 +03:00
2023-01-01 21:42:00 +03:00
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 )
2020-10-20 18:51:04 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2023-01-01 21:42:00 +03:00
do_draw_text ( rect , text , font , alignment , elision , wrapping , [ & ] ( FloatRect const & r , Utf8CodePointIterator & it ) {
2022-02-23 00:55:01 +03:00
draw_one_glyph ( r , it ) ;
2020-10-20 18:51:04 +03:00
} ) ;
}
2019-07-12 20:43:29 +03:00
2023-01-01 21:42:00 +03:00
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 )
2020-10-20 18:51:04 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2021-07-26 00:20:11 +03:00
Utf8View text { raw_text } ;
2023-01-01 21:42:00 +03:00
do_draw_text ( rect , text , font , alignment , elision , wrapping , [ & ] ( FloatRect const & r , Utf8CodePointIterator & it ) {
2022-02-23 00:55:01 +03:00
draw_one_glyph ( r , it ) ;
2020-10-20 18:51:04 +03:00
} ) ;
}
2019-07-12 20:43:29 +03:00
2023-01-01 21:42:00 +03:00
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 )
2020-10-20 18:51:04 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2021-07-26 00:20:11 +03:00
// FIXME: UTF-32 should eventually be completely removed, but for the time
// being some places might depend on it, so we do some internal conversion.
StringBuilder builder ;
builder . append ( raw_text ) ;
auto text = Utf8View { builder . string_view ( ) } ;
2023-01-01 21:42:00 +03:00
do_draw_text ( rect , text , font , alignment , elision , wrapping , [ & ] ( FloatRect const & r , Utf8CodePointIterator & it ) {
2022-02-23 00:55:01 +03:00
draw_one_glyph ( r , it ) ;
2020-10-20 18:51:04 +03:00
} ) ;
2019-07-12 20:43:29 +03:00
}
2023-01-01 21:42:00 +03:00
void Painter : : draw_text ( IntRect const & rect , StringView text , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
{
draw_text ( rect . to_type < float > ( ) , text , font ( ) , alignment , color , elision , wrapping ) ;
}
void Painter : : draw_text ( IntRect const & rect , Utf32View const & text , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
{
draw_text ( rect . to_type < float > ( ) , text , font ( ) , alignment , color , elision , wrapping ) ;
}
void Painter : : draw_text ( IntRect const & rect , StringView raw_text , Font const & font , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
{
draw_text ( rect . to_type < float > ( ) , raw_text , font , alignment , color , elision , wrapping ) ;
}
void Painter : : draw_text ( IntRect const & rect , Utf32View const & raw_text , Font const & font , TextAlignment alignment , Color color , TextElision elision , TextWrapping wrapping )
{
return draw_text ( rect . to_type < float > ( ) , raw_text , font , alignment , color , elision , wrapping ) ;
}
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 )
{
return draw_text ( move ( draw_one_glyph ) , rect . to_type < float > ( ) , text , font , alignment , elision , wrapping ) ;
}
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 )
{
return draw_text ( move ( draw_one_glyph ) , rect . to_type < float > ( ) , raw_text , font , alignment , elision , wrapping ) ;
}
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 )
{
return draw_text ( move ( draw_one_glyph ) , rect . to_type < float > ( ) , raw_text , font , alignment , elision , wrapping ) ;
}
2022-12-06 23:27:44 +03:00
void Painter : : set_pixel ( IntPoint p , Color color , bool blend )
2018-10-12 15:58:16 +03:00
{
2021-05-03 17:37:05 +03:00
auto point = p ;
point . translate_by ( state ( ) . translation ) ;
2022-03-19 03:29:22 +03:00
// Use the scale only to avoid clipping pixels set in drawing functions that handle
// scaling and call set_pixel() -- do not scale the pixel.
if ( ! clip_rect ( ) . contains ( point / scale ( ) ) )
2018-10-12 23:50:28 +03:00
return ;
2022-12-24 17:18:53 +03:00
set_physical_pixel ( point , color , blend ) ;
}
void Painter : : set_physical_pixel ( IntPoint physical_point , Color color , bool blend )
{
// This function should only be called after translation, clipping, etc has been handled elsewhere
// if not use set_pixel().
auto & dst = m_target - > scanline ( physical_point . y ( ) ) [ physical_point . x ( ) ] ;
2022-12-17 22:55:44 +03:00
if ( ! blend | | color . alpha ( ) = = 255 )
2022-03-10 05:21:02 +03:00
dst = color . value ( ) ;
2022-11-29 01:26:46 +03:00
else if ( color . alpha ( ) )
2023-05-14 19:22:05 +03:00
dst = color_for_format ( target ( ) - > format ( ) , dst ) . blend ( color ) . value ( ) ;
2018-10-12 15:58:16 +03:00
}
2022-12-06 23:27:44 +03:00
Optional < Color > Painter : : get_pixel ( IntPoint p )
2022-06-15 23:05:58 +03:00
{
auto point = p ;
point . translate_by ( state ( ) . translation ) ;
if ( ! clip_rect ( ) . contains ( point / scale ( ) ) )
return { } ;
2023-05-14 19:22:05 +03:00
return m_target - > get_pixel ( point ) ;
2022-06-15 23:05:58 +03:00
}
2022-09-15 10:31:29 +03:00
ErrorOr < NonnullRefPtr < Bitmap > > Painter : : get_region_bitmap ( IntRect const & region , BitmapFormat format , Optional < IntRect & > actual_region )
{
VERIFY ( scale ( ) = = 1 ) ;
auto bitmap_region = region . translated ( state ( ) . translation ) . intersected ( m_target - > rect ( ) ) ;
if ( actual_region . has_value ( ) )
actual_region . value ( ) = bitmap_region . translated ( - state ( ) . translation ) ;
return m_target - > cropped ( bitmap_region , format ) ;
}
2022-12-06 22:43:46 +03:00
ALWAYS_INLINE void Painter : : set_physical_pixel_with_draw_op ( u32 & pixel , Color color )
2019-01-11 05:52:09 +03:00
{
2021-01-13 04:22:15 +03:00
// This always sets a single physical pixel, independent of scale().
// This should only be called by routines that already handle scale.
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2020-09-08 07:25:30 +03:00
switch ( draw_op ( ) ) {
case DrawOp : : Copy :
2019-01-11 05:52:09 +03:00
pixel = color . value ( ) ;
2020-09-08 07:25:30 +03:00
break ;
case DrawOp : : Xor :
2022-03-05 00:28:59 +03:00
pixel = color . xored ( Color : : from_argb ( pixel ) ) . value ( ) ;
2020-09-08 07:25:30 +03:00
break ;
case DrawOp : : Invert :
2022-03-05 00:28:59 +03:00
pixel = Color : : from_argb ( pixel ) . inverted ( ) . value ( ) ;
2020-09-08 07:25:30 +03:00
break ;
}
}
2022-12-06 22:43:46 +03:00
ALWAYS_INLINE void Painter : : fill_physical_scanline_with_draw_op ( int y , int x , int width , Color color )
2020-09-08 07:25:30 +03:00
{
2021-01-13 04:22:15 +03:00
// This always draws a single physical scanline, independent of scale().
// This should only be called by routines that already handle scale.
2023-05-14 19:22:05 +03:00
auto dst_format = m_target - > format ( ) ;
2020-09-08 07:25:30 +03:00
switch ( draw_op ( ) ) {
case DrawOp : : Copy :
fast_u32_fill ( m_target - > scanline ( y ) + x , color . value ( ) , width ) ;
break ;
case DrawOp : : Xor : {
auto * pixel = m_target - > scanline ( y ) + x ;
auto * end = pixel + width ;
while ( pixel < end ) {
2023-05-14 19:22:05 +03:00
* pixel = color_for_format ( dst_format , * pixel ) . xored ( color ) . value ( ) ;
2020-09-08 07:25:30 +03:00
pixel + + ;
}
break ;
}
case DrawOp : : Invert : {
auto * pixel = m_target - > scanline ( y ) + x ;
auto * end = pixel + width ;
while ( pixel < end ) {
2023-05-14 19:22:05 +03:00
* pixel = color_for_format ( dst_format , * pixel ) . inverted ( ) . value ( ) ;
2020-09-08 07:25:30 +03:00
pixel + + ;
}
break ;
}
}
2019-01-11 05:52:09 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_physical_pixel ( IntPoint physical_position , Color color , int thickness )
2019-06-23 11:00:02 +03:00
{
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
// This always draws a single physical pixel, independent of scale().
// This should only be called by routines that already handle scale
// (including scaling thickness).
2021-02-23 22:42:32 +03:00
VERIFY ( draw_op ( ) = = DrawOp : : Copy ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2021-05-03 17:37:05 +03:00
if ( thickness = = 1 ) { // Implies scale() == 1.
2021-01-19 20:10:47 +03:00
auto & pixel = m_target - > scanline ( physical_position . y ( ) ) [ physical_position . x ( ) ] ;
2023-05-14 19:22:05 +03:00
return set_physical_pixel_with_draw_op ( pixel , color_for_format ( m_target - > format ( ) , pixel ) . blend ( color ) ) ;
2021-01-12 23:00:04 +03:00
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
2021-05-03 17:37:05 +03:00
IntRect rect { physical_position , { thickness , thickness } } ;
rect . intersect ( clip_rect ( ) * scale ( ) ) ;
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
fill_physical_rect ( rect , color ) ;
2019-06-23 11:00:02 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_line ( IntPoint a_p1 , IntPoint a_p2 , Color color , int thickness , LineStyle style , Color alternate_color )
2018-10-12 15:58:16 +03:00
{
2023-06-26 09:05:12 +03:00
if ( clip_rect ( ) . is_empty ( ) )
return ;
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2020-06-13 21:38:36 +03:00
if ( color . alpha ( ) = = 0 )
return ;
2021-05-03 17:37:05 +03:00
auto clip_rect = this - > clip_rect ( ) * scale ( ) ;
2019-05-07 18:01:55 +03:00
2021-07-09 19:18:46 +03:00
auto const p1 = thickness > 1 ? a_p1 . translated ( - ( thickness / 2 ) , - ( thickness / 2 ) ) : a_p1 ;
auto const p2 = thickness > 1 ? a_p2 . translated ( - ( thickness / 2 ) , - ( thickness / 2 ) ) : a_p2 ;
2021-05-03 17:36:57 +03:00
auto point1 = to_physical ( p1 ) ;
auto point2 = to_physical ( p2 ) ;
2021-05-03 17:37:05 +03:00
thickness * = scale ( ) ;
2021-05-03 17:36:57 +03:00
2021-08-05 17:34:03 +03:00
auto alternate_color_is_transparent = alternate_color = = Color : : Transparent ;
2018-10-12 15:58:16 +03:00
// Special case: vertical line.
2021-05-03 17:36:57 +03:00
if ( point1 . x ( ) = = point2 . x ( ) ) {
2021-09-17 16:06:28 +03:00
int const x = point1 . x ( ) ;
2023-05-22 01:41:18 +03:00
if ( x < clip_rect . left ( ) | | x > = clip_rect . right ( ) )
2018-10-12 23:50:28 +03:00
return ;
2021-05-03 17:36:57 +03:00
if ( point1 . y ( ) > point2 . y ( ) )
swap ( point1 , point2 ) ;
2023-05-22 01:41:18 +03:00
if ( point1 . y ( ) > = clip_rect . bottom ( ) )
2019-02-03 05:37:55 +03:00
return ;
2021-05-03 17:36:57 +03:00
if ( point2 . y ( ) < clip_rect . top ( ) )
2019-02-03 05:37:55 +03:00
return ;
2021-05-03 17:36:57 +03:00
int min_y = max ( point1 . y ( ) , clip_rect . top ( ) ) ;
2023-05-22 01:41:18 +03:00
int max_y = min ( point2 . y ( ) , clip_rect . bottom ( ) - 1 ) ;
2020-05-10 13:58:33 +03:00
if ( style = = LineStyle : : Dotted ) {
2021-05-03 17:37:05 +03:00
for ( int y = min_y ; y < = max_y ; y + = thickness * 2 )
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2020-05-10 18:17:26 +03:00
} else if ( style = = LineStyle : : Dashed ) {
2021-05-03 17:37:05 +03:00
for ( int y = min_y ; y < = max_y ; y + = thickness * 6 ) {
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2021-05-03 17:37:05 +03:00
draw_physical_pixel ( { x , min ( y + thickness , max_y ) } , color , thickness ) ;
draw_physical_pixel ( { x , min ( y + thickness * 2 , max_y ) } , color , thickness ) ;
2021-08-05 17:34:03 +03:00
if ( ! alternate_color_is_transparent ) {
draw_physical_pixel ( { x , min ( y + thickness * 3 , max_y ) } , alternate_color , thickness ) ;
draw_physical_pixel ( { x , min ( y + thickness * 4 , max_y ) } , alternate_color , thickness ) ;
draw_physical_pixel ( { x , min ( y + thickness * 5 , max_y ) } , alternate_color , thickness ) ;
}
2020-05-10 18:17:26 +03:00
}
2019-11-27 22:52:11 +03:00
} else {
2021-05-03 17:37:05 +03:00
for ( int y = min_y ; y < = max_y ; y + = thickness )
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2021-10-02 19:45:07 +03:00
draw_physical_pixel ( { x , max_y } , color , thickness ) ;
2019-11-27 22:52:11 +03:00
}
2018-10-12 15:58:16 +03:00
return ;
}
// Special case: horizontal line.
2021-05-03 17:36:57 +03:00
if ( point1 . y ( ) = = point2 . y ( ) ) {
2021-09-17 16:06:28 +03:00
int const y = point1 . y ( ) ;
2023-05-22 01:41:18 +03:00
if ( y < clip_rect . top ( ) | | y > = clip_rect . bottom ( ) )
2018-10-12 23:50:28 +03:00
return ;
2021-05-03 17:36:57 +03:00
if ( point1 . x ( ) > point2 . x ( ) )
swap ( point1 , point2 ) ;
2023-05-22 01:41:18 +03:00
if ( point1 . x ( ) > = clip_rect . right ( ) )
2019-02-03 05:37:55 +03:00
return ;
2021-05-03 17:36:57 +03:00
if ( point2 . x ( ) < clip_rect . left ( ) )
2019-02-03 05:37:55 +03:00
return ;
2021-05-03 17:36:57 +03:00
int min_x = max ( point1 . x ( ) , clip_rect . left ( ) ) ;
2023-05-22 01:41:18 +03:00
int max_x = min ( point2 . x ( ) , clip_rect . right ( ) - 1 ) ;
2020-05-10 13:58:33 +03:00
if ( style = = LineStyle : : Dotted ) {
2021-05-03 17:37:05 +03:00
for ( int x = min_x ; x < = max_x ; x + = thickness * 2 )
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2020-05-10 18:17:26 +03:00
} else if ( style = = LineStyle : : Dashed ) {
2021-05-03 17:37:05 +03:00
for ( int x = min_x ; x < = max_x ; x + = thickness * 6 ) {
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2021-05-03 17:37:05 +03:00
draw_physical_pixel ( { min ( x + thickness , max_x ) , y } , color , thickness ) ;
draw_physical_pixel ( { min ( x + thickness * 2 , max_x ) , y } , color , thickness ) ;
2021-08-05 17:34:03 +03:00
if ( ! alternate_color_is_transparent ) {
draw_physical_pixel ( { min ( x + thickness * 3 , max_x ) , y } , alternate_color , thickness ) ;
draw_physical_pixel ( { min ( x + thickness * 4 , max_x ) , y } , alternate_color , thickness ) ;
draw_physical_pixel ( { min ( x + thickness * 5 , max_x ) , y } , alternate_color , thickness ) ;
}
2020-05-10 18:17:26 +03:00
}
2019-11-27 22:52:11 +03:00
} else {
2021-05-03 17:37:05 +03:00
for ( int x = min_x ; x < = max_x ; x + = thickness )
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
draw_physical_pixel ( { x , y } , color , thickness ) ;
2021-10-02 19:45:07 +03:00
draw_physical_pixel ( { max_x , y } , color , thickness ) ;
2019-11-27 22:52:11 +03:00
}
2018-10-12 15:58:16 +03:00
return ;
}
2021-09-17 16:06:28 +03:00
int const adx = abs ( point2 . x ( ) - point1 . x ( ) ) ;
int const ady = abs ( point2 . y ( ) - point1 . y ( ) ) ;
2019-05-07 18:01:55 +03:00
if ( adx > ady ) {
2021-05-03 17:36:57 +03:00
if ( point1 . x ( ) > point2 . x ( ) )
swap ( point1 , point2 ) ;
2019-05-07 18:01:55 +03:00
} else {
2021-05-03 17:36:57 +03:00
if ( point1 . y ( ) > point2 . y ( ) )
swap ( point1 , point2 ) ;
2019-05-07 18:01:55 +03:00
}
2018-10-12 23:50:28 +03:00
2021-09-17 16:06:28 +03:00
int const dx = point2 . x ( ) - point1 . x ( ) ;
int const dy = point2 . y ( ) - point1 . y ( ) ;
2021-04-03 17:11:35 +03:00
int error = 0 ;
2019-05-07 18:01:55 +03:00
2023-01-12 14:56:23 +03:00
size_t number_of_pixels_drawn = 0 ;
auto draw_pixel_in_line = [ & ] ( int x , int y ) {
bool should_draw_line = true ;
if ( style = = LineStyle : : Dotted & & number_of_pixels_drawn % 2 = = 1 )
should_draw_line = false ;
else if ( style = = LineStyle : : Dashed & & number_of_pixels_drawn % 6 > = 3 )
should_draw_line = false ;
if ( should_draw_line )
draw_physical_pixel ( { x , y } , color , thickness ) ;
else if ( ! alternate_color_is_transparent )
draw_physical_pixel ( { x , y } , alternate_color , thickness ) ;
number_of_pixels_drawn + + ;
} ;
2019-05-07 18:01:55 +03:00
if ( dx > dy ) {
2021-09-17 16:06:28 +03:00
int const y_step = dy = = 0 ? 0 : ( dy > 0 ? 1 : - 1 ) ;
int const delta_error = 2 * abs ( dy ) ;
2021-05-03 17:36:57 +03:00
int y = point1 . y ( ) ;
for ( int x = point1 . x ( ) ; x < = point2 . x ( ) ; + + x ) {
2019-05-07 18:01:55 +03:00
if ( clip_rect . contains ( x , y ) )
2023-01-12 14:56:23 +03:00
draw_pixel_in_line ( x , y ) ;
2019-05-07 18:01:55 +03:00
error + = delta_error ;
2021-04-03 17:11:35 +03:00
if ( error > = dx ) {
y + = y_step ;
error - = 2 * dx ;
2019-05-07 18:01:55 +03:00
}
}
} else {
2021-09-17 16:06:28 +03:00
int const x_step = dx = = 0 ? 0 : ( dx > 0 ? 1 : - 1 ) ;
int const delta_error = 2 * abs ( dx ) ;
2021-05-03 17:36:57 +03:00
int x = point1 . x ( ) ;
for ( int y = point1 . y ( ) ; y < = point2 . y ( ) ; + + y ) {
2019-05-07 18:01:55 +03:00
if ( clip_rect . contains ( x , y ) )
2023-01-12 14:56:23 +03:00
draw_pixel_in_line ( x , y ) ;
2019-05-07 18:01:55 +03:00
error + = delta_error ;
2021-04-03 17:11:35 +03:00
if ( error > = dy ) {
x + = x_step ;
error - = 2 * dy ;
2019-05-07 18:01:55 +03:00
}
2018-10-12 15:58:16 +03:00
}
}
}
2018-10-13 21:59:25 +03:00
2022-12-06 23:27:44 +03:00
void Painter : : draw_triangle_wave ( IntPoint a_p1 , IntPoint a_p2 , Color color , int amplitude , int thickness )
2022-01-20 21:55:14 +03:00
{
// FIXME: Support more than horizontal waves
VERIFY ( a_p1 . y ( ) = = a_p2 . y ( ) ) ;
auto const p1 = thickness > 1 ? a_p1 . translated ( - ( thickness / 2 ) , - ( thickness / 2 ) ) : a_p1 ;
auto const p2 = thickness > 1 ? a_p2 . translated ( - ( thickness / 2 ) , - ( thickness / 2 ) ) : a_p2 ;
auto point1 = to_physical ( p1 ) ;
auto point2 = to_physical ( p2 ) ;
auto y = point1 . y ( ) ;
for ( int x = 0 ; x < = point2 . x ( ) - point1 . x ( ) ; + + x ) {
auto y_offset = abs ( x % ( 2 * amplitude ) - amplitude ) - amplitude ;
draw_physical_pixel ( { point1 . x ( ) + x , y + y_offset } , color , thickness ) ;
}
}
2022-12-06 23:57:07 +03:00
static bool can_approximate_bezier_curve ( FloatPoint p1 , FloatPoint p2 , FloatPoint control )
2020-10-21 01:03:07 +03:00
{
2023-04-19 00:50:40 +03:00
constexpr float tolerance = 0.015f ;
2020-10-21 01:03:07 +03:00
auto p1x = 3 * control . x ( ) - 2 * p1 . x ( ) - p2 . x ( ) ;
auto p1y = 3 * control . y ( ) - 2 * p1 . y ( ) - p2 . y ( ) ;
auto p2x = 3 * control . x ( ) - 2 * p2 . x ( ) - p1 . x ( ) ;
auto p2y = 3 * control . y ( ) - 2 * p2 . y ( ) - p1 . y ( ) ;
p1x = p1x * p1x ;
p1y = p1y * p1y ;
p2x = p2x * p2x ;
p2y = p2y * p2y ;
2023-05-12 00:20:38 +03:00
auto error = max ( p1x , p2x ) + max ( p1y , p2y ) ;
VERIFY ( isfinite ( error ) ) ;
return error < = tolerance ;
2020-10-21 01:03:07 +03:00
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
// static
2022-12-06 23:57:07 +03:00
void Painter : : for_each_line_segment_on_bezier_curve ( FloatPoint control_point , FloatPoint p1 , FloatPoint p2 , Function < void ( FloatPoint , FloatPoint ) > & callback )
2020-05-05 04:15:17 +03:00
{
2021-04-15 02:25:07 +03:00
struct SegmentDescriptor {
FloatPoint control_point ;
FloatPoint p1 ;
FloatPoint p2 ;
} ;
2020-05-05 04:15:17 +03:00
2022-12-06 23:57:07 +03:00
static constexpr auto split_quadratic_bezier_curve = [ ] ( FloatPoint original_control , FloatPoint p1 , FloatPoint p2 , auto & segments ) {
2021-04-15 02:25:07 +03:00
auto po1_midpoint = original_control + p1 ;
po1_midpoint / = 2 ;
2020-05-06 10:25:12 +03:00
2021-04-15 02:25:07 +03:00
auto po2_midpoint = original_control + p2 ;
po2_midpoint / = 2 ;
2020-07-22 09:46:15 +03:00
2021-04-15 02:25:07 +03:00
auto new_segment = po1_midpoint + po2_midpoint ;
new_segment / = 2 ;
2020-07-22 09:46:15 +03:00
2021-04-15 02:25:07 +03:00
segments . enqueue ( { po1_midpoint , p1 , new_segment } ) ;
segments . enqueue ( { po2_midpoint , new_segment , p2 } ) ;
} ;
Queue < SegmentDescriptor > segments ;
segments . enqueue ( { control_point , p1 , p2 } ) ;
while ( ! segments . is_empty ( ) ) {
auto segment = segments . dequeue ( ) ;
2020-07-22 09:46:15 +03:00
2021-04-15 02:25:07 +03:00
if ( can_approximate_bezier_curve ( segment . p1 , segment . p2 , segment . control_point ) )
callback ( segment . p1 , segment . p2 ) ;
else
split_quadratic_bezier_curve ( segment . control_point , segment . p1 , segment . p2 , segments ) ;
}
}
2020-07-22 09:46:15 +03:00
2022-12-06 23:57:07 +03:00
void Painter : : for_each_line_segment_on_bezier_curve ( FloatPoint control_point , FloatPoint p1 , FloatPoint p2 , Function < void ( FloatPoint , FloatPoint ) > & & callback )
2021-04-15 02:25:07 +03:00
{
for_each_line_segment_on_bezier_curve ( control_point , p1 , p2 , callback ) ;
2020-07-22 09:46:15 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_quadratic_bezier_curve ( IntPoint control_point , IntPoint p1 , IntPoint p2 , Color color , int thickness , LineStyle style )
2020-05-06 10:25:12 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2022-12-06 23:57:07 +03:00
for_each_line_segment_on_bezier_curve ( FloatPoint ( control_point ) , FloatPoint ( p1 ) , FloatPoint ( p2 ) , [ & ] ( FloatPoint fp1 , FloatPoint fp2 ) {
2021-05-03 17:37:05 +03:00
draw_line ( IntPoint ( fp1 . x ( ) , fp1 . y ( ) ) , IntPoint ( fp2 . x ( ) , fp2 . y ( ) ) , color , thickness , style ) ;
2020-07-22 09:46:15 +03:00
} ) ;
}
2022-12-06 23:57:07 +03:00
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 )
2021-09-17 17:12:30 +03:00
{
for_each_line_segment_on_cubic_bezier_curve ( control_point_0 , control_point_1 , p1 , p2 , callback ) ;
}
2022-12-06 23:57:07 +03:00
static bool can_approximate_cubic_bezier_curve ( FloatPoint p1 , FloatPoint p2 , FloatPoint control_0 , FloatPoint control_1 )
2021-09-17 17:12:30 +03:00
{
2023-04-19 00:50:40 +03:00
constexpr float tolerance = 0.015f ;
2021-09-17 17:12:30 +03:00
auto ax = 3 * control_0 . x ( ) - 2 * p1 . x ( ) - p2 . x ( ) ;
auto ay = 3 * control_0 . y ( ) - 2 * p1 . y ( ) - p2 . y ( ) ;
auto bx = 3 * control_1 . x ( ) - p1 . x ( ) - 2 * p2 . x ( ) ;
auto by = 3 * control_1 . y ( ) - p1 . y ( ) - 2 * p2 . y ( ) ;
ax * = ax ;
ay * = ay ;
bx * = bx ;
by * = by ;
2023-05-12 00:20:38 +03:00
auto error = max ( ax , bx ) + max ( ay , by ) ;
VERIFY ( isfinite ( error ) ) ;
return error < = tolerance ;
2021-09-17 17:12:30 +03:00
}
// static
2022-12-06 23:57:07 +03:00
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 )
2021-09-17 17:12:30 +03:00
{
struct ControlPair {
FloatPoint control_point_0 ;
FloatPoint control_point_1 ;
} ;
struct SegmentDescriptor {
ControlPair control_points ;
FloatPoint p1 ;
FloatPoint p2 ;
} ;
2022-12-06 23:57:07 +03:00
static constexpr auto split_cubic_bezier_curve = [ ] ( ControlPair const & original_controls , FloatPoint p1 , FloatPoint p2 , auto & segments ) {
2021-09-17 17:12:30 +03:00
Array level_1_midpoints {
( p1 + original_controls . control_point_0 ) / 2 ,
( original_controls . control_point_0 + original_controls . control_point_1 ) / 2 ,
( original_controls . control_point_1 + p2 ) / 2 ,
} ;
Array level_2_midpoints {
( level_1_midpoints [ 0 ] + level_1_midpoints [ 1 ] ) / 2 ,
( level_1_midpoints [ 1 ] + level_1_midpoints [ 2 ] ) / 2 ,
} ;
auto level_3_midpoint = ( level_2_midpoints [ 0 ] + level_2_midpoints [ 1 ] ) / 2 ;
segments . enqueue ( { { level_1_midpoints [ 0 ] , level_2_midpoints [ 0 ] } , p1 , level_3_midpoint } ) ;
segments . enqueue ( { { level_2_midpoints [ 1 ] , level_1_midpoints [ 2 ] } , level_3_midpoint , p2 } ) ;
} ;
Queue < SegmentDescriptor > segments ;
segments . enqueue ( { { control_point_0 , control_point_1 } , p1 , p2 } ) ;
while ( ! segments . is_empty ( ) ) {
auto segment = segments . dequeue ( ) ;
if ( can_approximate_cubic_bezier_curve ( segment . p1 , segment . p2 , segment . control_points . control_point_0 , segment . control_points . control_point_1 ) )
callback ( segment . p1 , segment . p2 ) ;
else
split_cubic_bezier_curve ( segment . control_points , segment . p1 , segment . p2 , segments ) ;
}
}
2022-12-06 23:27:44 +03:00
void Painter : : draw_cubic_bezier_curve ( IntPoint control_point_0 , IntPoint control_point_1 , IntPoint p1 , IntPoint p2 , Color color , int thickness , Painter : : LineStyle style )
2021-09-17 17:12:30 +03:00
{
2022-12-06 23:57:07 +03:00
for_each_line_segment_on_cubic_bezier_curve ( FloatPoint ( control_point_0 ) , FloatPoint ( control_point_1 ) , FloatPoint ( p1 ) , FloatPoint ( p2 ) , [ & ] ( FloatPoint fp1 , FloatPoint fp2 ) {
2021-09-17 17:12:30 +03:00
draw_line ( IntPoint ( fp1 . x ( ) , fp1 . y ( ) ) , IntPoint ( fp2 . x ( ) , fp2 . y ( ) ) , color , thickness , style ) ;
} ) ;
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
// static
2023-02-10 12:52:14 +03:00
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 )
2020-07-22 09:46:15 +03:00
{
2023-02-10 12:52:14 +03:00
if ( radii . width ( ) < = 0 | | radii . height ( ) < = 0 )
2021-05-22 22:33:07 +03:00
return ;
2021-04-15 02:25:07 +03:00
2021-05-22 22:33:07 +03:00
auto start = p1 ;
auto end = p2 ;
2021-04-15 02:25:07 +03:00
2023-06-01 01:49:16 +03:00
bool start_swapped = false ;
2021-05-22 22:33:07 +03:00
if ( theta_delta < 0 ) {
swap ( start , end ) ;
theta_1 = theta_1 + theta_delta ;
2021-07-05 19:56:06 +03:00
theta_delta = fabsf ( theta_delta ) ;
2023-06-01 01:49:16 +03:00
start_swapped = true ;
2021-05-22 22:33:07 +03:00
}
auto relative_start = start - center ;
2023-02-10 12:52:14 +03:00
auto a = radii . width ( ) ;
auto b = radii . height ( ) ;
2021-04-15 02:25:07 +03:00
2021-05-22 22:33:07 +03:00
// The segments are at most 1 long
auto largest_radius = max ( a , b ) ;
2022-06-13 18:14:42 +03:00
float theta_step = AK : : atan2 ( 1.f , ( float ) largest_radius ) ;
2021-04-15 02:25:07 +03:00
2021-05-22 22:33:07 +03:00
FloatPoint current_point = relative_start ;
FloatPoint next_point = { 0 , 0 } ;
2021-04-15 02:25:07 +03:00
2022-06-13 18:14:42 +03:00
float sin_x_axis , cos_x_axis ;
AK : : sincos ( x_axis_rotation , sin_x_axis , cos_x_axis ) ;
2021-05-22 22:33:07 +03:00
auto rotate_point = [ sin_x_axis , cos_x_axis ] ( FloatPoint & p ) {
auto original_x = p . x ( ) ;
auto original_y = p . y ( ) ;
p . set_x ( original_x * cos_x_axis - original_y * sin_x_axis ) ;
p . set_y ( original_x * sin_x_axis + original_y * cos_x_axis ) ;
2021-04-15 02:25:07 +03:00
} ;
2023-06-01 01:49:16 +03:00
auto emit_point = [ & ] ( auto p0 , auto p1 ) {
// NOTE: If we swap the start/end we must swap the emitted points, so correct winding orders can be calculated.
if ( start_swapped )
swap ( p0 , p1 ) ;
callback ( p0 , p1 ) ;
} ;
2022-06-13 18:14:42 +03:00
for ( float theta = theta_1 ; theta < = theta_1 + theta_delta ; theta + = theta_step ) {
float s , c ;
AK : : sincos ( theta , s , c ) ;
next_point . set_x ( a * c ) ;
next_point . set_y ( b * s ) ;
2021-05-22 22:33:07 +03:00
rotate_point ( next_point ) ;
2023-06-01 01:49:16 +03:00
emit_point ( current_point + center , next_point + center ) ;
2021-05-22 22:33:07 +03:00
current_point = next_point ;
2020-07-22 09:46:15 +03:00
}
2021-05-22 22:33:07 +03:00
2023-06-01 01:49:16 +03:00
emit_point ( current_point + center , end ) ;
2020-07-22 09:46:15 +03:00
}
LibGfx: Make it possible to apply an (integer) scale to a Painter
This adds a scale factor to Painter, which will be used for HighDPI
support. It's also a step towards general affine transforms on Painters.
All of Painter's public API takes logical coordinates, while some
internals deal with physical coordinates now. If scale == 1, logical
and physical coordinates are the same. For scale == 2, a 200x100 bitmap
would be covered by a logical {0, 0, 100, 50} rect, while its physical
size would be {0, 0, 200, 100}.
Most of Painter's functions just assert that scale() == 1 is for now,
but most functions called by WindowServer are updated to handle
arbitrary (integer) scale.
Also add a new Demo "LibGfxScaleDemo" that covers the converted
functions and that can be used to iteratively add scaling support
to more functions.
To make Painter's interface deal with logical coordinates only,
make translation() and clip_rect() non-public.
2021-01-12 23:53:07 +03:00
// static
2023-02-10 12:52:14 +03:00
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 )
2020-07-22 09:46:15 +03:00
{
for_each_line_segment_on_elliptical_arc ( p1 , p2 , center , radii , x_axis_rotation , theta_1 , theta_delta , callback ) ;
}
2023-02-10 12:52:14 +03:00
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 )
2020-07-22 09:46:15 +03:00
{
2021-05-03 17:37:05 +03:00
VERIFY ( scale ( ) = = 1 ) ; // FIXME: Add scaling support.
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2022-12-06 23:57:07 +03:00
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 ) {
2021-05-03 17:37:05 +03:00
draw_line ( IntPoint ( fp1 . x ( ) , fp1 . y ( ) ) , IntPoint ( fp2 . x ( ) , fp2 . y ( ) ) , color , thickness , style ) ;
2020-05-06 10:25:12 +03:00
} ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : add_clip_rect ( IntRect const & rect )
2019-01-25 01:40:12 +03:00
{
2021-05-03 17:37:05 +03:00
state ( ) . clip_rect . intersect ( rect . translated ( translation ( ) ) ) ;
state ( ) . clip_rect . intersect ( m_target - > rect ( ) ) ; // FIXME: This shouldn't be necessary?
2019-01-25 01:40:12 +03:00
}
void Painter : : clear_clip_rect ( )
{
2019-03-09 18:48:02 +03:00
state ( ) . clip_rect = m_clip_origin ;
2019-01-25 01:40:12 +03:00
}
2019-04-16 02:01:03 +03:00
PainterStateSaver : : PainterStateSaver ( Painter & painter )
: m_painter ( painter )
{
m_painter . save ( ) ;
}
PainterStateSaver : : ~ PainterStateSaver ( )
{
m_painter . restore ( ) ;
}
2020-02-06 13:56:38 +03:00
2021-09-17 16:06:28 +03:00
void Painter : : stroke_path ( Path const & path , Color color , int thickness )
2020-04-16 22:03:17 +03:00
{
2021-09-16 18:51:45 +03:00
if ( thickness < = 0 )
return ;
2023-07-16 18:27:30 +03:00
fill_path ( path . stroke_to_fill ( thickness ) , color ) ;
2020-04-16 22:03:17 +03:00
}
2022-12-06 23:27:44 +03:00
void Painter : : blit_disabled ( IntPoint location , Gfx : : Bitmap const & bitmap , IntRect const & rect , Palette const & palette )
2020-10-27 23:17:50 +03:00
{
auto bright_color = palette . threed_highlight ( ) ;
auto dark_color = palette . threed_shadow1 ( ) ;
blit_filtered ( location . translated ( 1 , 1 ) , bitmap , rect , [ & ] ( auto ) {
return bright_color ;
} ) ;
blit_filtered ( location , bitmap , rect , [ & ] ( Color src ) {
int gray = src . to_grayscale ( ) . red ( ) ;
if ( gray > 160 )
return bright_color ;
return dark_color ;
} ) ;
}
2021-09-17 16:06:28 +03:00
void Painter : : blit_tiled ( IntRect const & dst_rect , Gfx : : Bitmap const & bitmap , IntRect const & rect )
2021-03-06 03:42:50 +03:00
{
auto tile_width = rect . width ( ) ;
auto tile_height = rect . height ( ) ;
2023-05-22 01:41:18 +03:00
auto dst_right = dst_rect . right ( ) - 1 ;
auto dst_bottom = dst_rect . bottom ( ) - 1 ;
2021-03-06 03:42:50 +03:00
for ( int tile_y = dst_rect . top ( ) ; tile_y < dst_bottom ; tile_y + = tile_height ) {
for ( int tile_x = dst_rect . left ( ) ; tile_x < dst_right ; tile_x + = tile_width ) {
IntRect tile_src_rect = rect ;
auto tile_x_overflow = tile_x + tile_width - dst_right ;
2023-05-22 01:41:18 +03:00
if ( tile_x_overflow > 0 )
2021-03-06 03:42:50 +03:00
tile_src_rect . set_width ( tile_width - tile_x_overflow ) ;
auto tile_y_overflow = tile_y + tile_height - dst_bottom ;
2023-05-22 01:41:18 +03:00
if ( tile_y_overflow > 0 )
2021-03-06 03:42:50 +03:00
tile_src_rect . set_height ( tile_height - tile_y_overflow ) ;
blit ( IntPoint ( tile_x , tile_y ) , bitmap , tile_src_rect ) ;
}
}
}
2022-12-04 21:02:33 +03:00
DeprecatedString parse_ampersand_string ( StringView raw_text , Optional < size_t > * underline_offset )
2021-04-05 23:40:07 +03:00
{
2021-04-11 02:09:44 +03:00
if ( raw_text . is_empty ( ) )
2022-12-04 21:02:33 +03:00
return DeprecatedString : : empty ( ) ;
2021-04-05 23:40:07 +03:00
2021-04-11 02:09:44 +03:00
StringBuilder builder ;
2021-04-05 23:40:07 +03:00
2021-04-11 02:09:44 +03:00
for ( size_t i = 0 ; i < raw_text . length ( ) ; + + i ) {
if ( raw_text [ i ] = = ' & ' ) {
2021-07-14 18:10:05 +03:00
if ( i ! = ( raw_text . length ( ) - 1 ) & & raw_text [ i + 1 ] = = ' & ' ) {
2021-04-11 02:09:44 +03:00
builder . append ( raw_text [ i ] ) ;
2021-07-14 18:10:05 +03:00
+ + i ;
} else if ( underline_offset & & ! ( * underline_offset ) . has_value ( ) ) {
2021-04-11 02:09:44 +03:00
* underline_offset = i ;
2021-07-14 18:10:05 +03:00
}
2021-04-11 02:09:44 +03:00
continue ;
2021-04-05 23:40:07 +03:00
}
2021-04-11 02:09:44 +03:00
builder . append ( raw_text [ i ] ) ;
}
2022-12-06 04:12:49 +03:00
return builder . to_deprecated_string ( ) ;
2021-04-11 02:09:44 +03:00
}
2021-04-05 23:40:07 +03:00
2021-11-11 02:55:02 +03:00
void Gfx : : Painter : : draw_ui_text ( Gfx : : IntRect const & rect , StringView text , Gfx : : Font const & font , Gfx : : TextAlignment text_alignment , Gfx : : Color color )
2021-04-11 02:09:44 +03:00
{
2021-04-05 23:40:07 +03:00
Optional < size_t > underline_offset ;
2021-04-11 02:09:44 +03:00
auto name_to_draw = parse_ampersand_string ( text , & underline_offset ) ;
2021-04-05 23:40:07 +03:00
2023-04-14 15:52:47 +03:00
Gfx : : IntRect text_rect { 0 , 0 , font . width_rounded_up ( name_to_draw ) , font . pixel_size_rounded_up ( ) } ;
2021-04-09 12:15:41 +03:00
text_rect . align_within ( rect , text_alignment ) ;
2021-04-05 23:40:07 +03:00
2021-04-09 12:15:41 +03:00
draw_text ( text_rect , name_to_draw , font , text_alignment , color ) ;
2021-04-05 23:40:07 +03:00
if ( underline_offset . has_value ( ) ) {
Utf8View utf8_view { name_to_draw } ;
2023-01-03 16:55:48 +03:00
float width = 0 ;
2021-04-05 23:40:07 +03:00
for ( auto it = utf8_view . begin ( ) ; it ! = utf8_view . end ( ) ; + + it ) {
if ( utf8_view . byte_offset_of ( it ) > = underline_offset . value ( ) ) {
2023-05-22 01:41:18 +03:00
int y = text_rect . bottom ( ) ;
2021-04-05 23:40:07 +03:00
int x1 = text_rect . left ( ) + width ;
2023-02-20 21:15:06 +03:00
int x2 = x1 + font . glyph_or_emoji_width ( it ) ;
2021-04-11 02:11:36 +03:00
draw_line ( { x1 , y } , { x2 , y } , color ) ;
2021-04-05 23:40:07 +03:00
break ;
}
2023-02-20 21:15:06 +03:00
width + = font . glyph_or_emoji_width ( it ) + font . glyph_spacing ( ) ;
2021-04-05 23:40:07 +03:00
}
}
}
2022-03-28 14:40:48 +03:00
2022-12-07 23:50:34 +03:00
void Painter : : draw_text_run ( IntPoint baseline_start , Utf8View const & string , Font const & font , Color color )
{
draw_text_run ( baseline_start . to_type < float > ( ) , string , font , color ) ;
}
2022-12-06 23:57:07 +03:00
void Painter : : draw_text_run ( FloatPoint baseline_start , Utf8View const & string , Font const & font , Color color )
2022-03-28 14:40:48 +03:00
{
2023-03-02 15:53:30 +03:00
float space_width = font . glyph_width ( ' ' ) + font . glyph_spacing ( ) ;
2022-03-28 14:40:48 +03:00
2022-04-01 13:41:38 +03:00
u32 last_code_point = 0 ;
2022-04-11 20:09:22 +03:00
2023-03-02 15:53:30 +03:00
auto point = baseline_start ;
point . translate_by ( 0 , - font . pixel_metrics ( ) . ascent ) ;
2022-04-11 20:09:22 +03:00
for ( auto code_point_iterator = string . begin ( ) ; code_point_iterator ! = string . end ( ) ; + + code_point_iterator ) {
auto code_point = * code_point_iterator ;
2022-07-09 16:12:10 +03:00
if ( should_paint_as_space ( code_point ) ) {
2023-03-02 15:53:30 +03:00
point . translate_by ( space_width , 0 ) ;
2022-04-01 13:41:38 +03:00
last_code_point = code_point ;
2022-03-28 14:40:48 +03:00
continue ;
}
2022-04-11 20:09:22 +03:00
2023-03-02 15:53:30 +03:00
auto kerning = font . glyphs_horizontal_kerning ( last_code_point , code_point ) ;
if ( kerning ! = 0.0f )
point . translate_by ( kerning , 0 ) ;
auto it = code_point_iterator ; // The callback function will advance the iterator, so create a copy for this lookup.
auto glyph_width = font . glyph_or_emoji_width ( it ) + font . glyph_spacing ( ) ;
draw_glyph_or_emoji ( point , code_point_iterator , font , color ) ;
point . translate_by ( glyph_width , 0 ) ;
2022-04-01 13:41:38 +03:00
last_code_point = code_point ;
2022-03-28 14:40:48 +03:00
}
}
2022-12-07 23:50:34 +03:00
void Painter : : draw_scaled_bitmap_with_transform ( IntRect const & dst_rect , Bitmap const & bitmap , FloatRect const & src_rect , AffineTransform const & transform , float opacity , Painter : : ScalingMode scaling_mode )
2022-09-24 14:00:53 +03:00
{
if ( transform . is_identity_or_translation ( ) ) {
translate ( transform . e ( ) , transform . f ( ) ) ;
draw_scaled_bitmap ( dst_rect , bitmap , src_rect , opacity , scaling_mode ) ;
translate ( - transform . e ( ) , - transform . f ( ) ) ;
} else {
// The painter has an affine transform, we have to draw through it!
2023-04-11 00:02:16 +03:00
// FIXME: This is kinda inefficient.
2022-09-24 14:00:53 +03:00
// What we currently do, roughly:
// - Map the destination rect through the context's transform.
// - Compute the bounding rect of the destination quad.
2023-04-11 00:02:16 +03:00
// - For each point in the clipped bounding rect, reverse-map it to a point in the source image.
2022-09-24 14:00:53 +03:00
// - Sample the source image at the computed point.
// - Set or blend (depending on alpha values) one pixel in the canvas.
// - Loop.
// FIXME: Painter should have an affine transform as part of its state and handle all of this instead.
2023-06-18 20:24:45 +03:00
if ( opacity = = 0.0f )
return ;
2022-09-24 14:00:53 +03:00
auto inverse_transform = transform . inverse ( ) ;
if ( ! inverse_transform . has_value ( ) )
return ;
auto destination_quad = transform . map_to_quad ( dst_rect . to_type < float > ( ) ) ;
auto destination_bounding_rect = destination_quad . bounding_rect ( ) . to_rounded < int > ( ) ;
2023-04-11 00:02:16 +03:00
auto source_rect = enclosing_int_rect ( src_rect ) . intersected ( bitmap . rect ( ) ) ;
2022-09-24 14:00:53 +03:00
Gfx : : AffineTransform source_transform ;
source_transform . translate ( src_rect . x ( ) , src_rect . y ( ) ) ;
source_transform . scale ( src_rect . width ( ) / dst_rect . width ( ) , src_rect . height ( ) / dst_rect . height ( ) ) ;
source_transform . translate ( - dst_rect . x ( ) , - dst_rect . y ( ) ) ;
2023-04-11 00:02:16 +03:00
auto translated_dest_rect = destination_bounding_rect . translated ( translation ( ) ) ;
auto clipped_bounding_rect = translated_dest_rect . intersected ( clip_rect ( ) ) ;
if ( clipped_bounding_rect . is_empty ( ) )
return ;
auto sample_transform = source_transform . multiply ( * inverse_transform ) ;
auto start_offset = destination_bounding_rect . location ( ) + ( clipped_bounding_rect . location ( ) - translated_dest_rect . location ( ) ) ;
for ( int y = 0 ; y < clipped_bounding_rect . height ( ) ; + + y ) {
for ( int x = 0 ; x < clipped_bounding_rect . width ( ) ; + + x ) {
auto point = Gfx : : IntPoint { x , y } ;
auto sample_point = point + start_offset ;
auto source_point = sample_transform . map ( sample_point ) . to_rounded < int > ( ) ;
if ( ! source_rect . contains ( source_point ) )
2022-09-24 14:00:53 +03:00
continue ;
auto source_color = bitmap . get_pixel ( source_point ) ;
if ( source_color . alpha ( ) = = 0 )
continue ;
2023-06-18 20:24:45 +03:00
if ( opacity ! = 1.0f )
source_color = source_color . with_opacity ( opacity ) ;
2023-04-11 00:02:16 +03:00
set_physical_pixel ( point + clipped_bounding_rect . location ( ) , source_color , true ) ;
2022-09-24 14:00:53 +03:00
}
}
}
}
2023-03-21 22:20:00 +03:00
void Painter : : draw_signed_distance_field ( IntRect const & dst_rect , Color color , Gfx : : GrayscaleBitmap const & sdf , float smoothing )
{
auto target_rect = dst_rect . translated ( translation ( ) ) ;
auto clipped_rect = target_rect . intersected ( clip_rect ( ) ) ;
if ( clipped_rect . is_empty ( ) )
return ;
target_rect * = scale ( ) ;
clipped_rect * = scale ( ) ;
auto start_offset = clipped_rect . location ( ) - target_rect . location ( ) ;
auto x_ratio = static_cast < float > ( sdf . width ( ) - 1 ) / ( dst_rect . width ( ) - 1 ) ;
auto y_ratio = static_cast < float > ( sdf . height ( ) - 1 ) / ( dst_rect . height ( ) - 1 ) ;
auto smooth_step = [ ] ( auto edge0 , auto edge1 , auto x ) {
x = clamp ( ( x - edge0 ) / ( edge1 - edge0 ) , 0.0f , 1.0f ) ;
return x * x * ( 3 - 2 * x ) ;
} ;
2023-03-24 01:48:12 +03:00
auto pixel_at = [ & ] ( unsigned x , unsigned y ) - > u8 {
// Returning 255 means this pixel is outside the shape.
if ( x > = sdf . width ( ) | | y > = sdf . height ( ) )
return 255 ;
return sdf . pixel_at ( x , y ) ;
} ;
2023-03-21 22:20:00 +03:00
for ( int i = 0 ; i < clipped_rect . height ( ) ; + + i ) {
for ( int j = 0 ; j < clipped_rect . width ( ) ; + + j ) {
auto point = IntPoint { j , i } ;
auto sample_point = point + start_offset ;
auto target_x = static_cast < int > ( x_ratio * sample_point . x ( ) ) ;
auto target_y = static_cast < int > ( y_ratio * sample_point . y ( ) ) ;
auto target_fraction_x = ( x_ratio * sample_point . x ( ) ) - target_x ;
auto target_fraction_y = ( y_ratio * sample_point . y ( ) ) - target_y ;
2023-03-24 01:48:12 +03:00
auto a = pixel_at ( target_x , target_y ) ;
auto b = pixel_at ( target_x + 1 , target_y ) ;
auto c = pixel_at ( target_x , target_y + 1 ) ;
auto d = pixel_at ( target_x + 1 , target_y + 1 ) ;
2023-03-21 22:20:00 +03:00
float distance = ( a * ( 1 - target_fraction_x ) * ( 1 - target_fraction_y )
+ b * target_fraction_x * ( 1 - target_fraction_y )
+ c * ( 1 - target_fraction_x ) * target_fraction_y
+ d * target_fraction_x * target_fraction_y )
/ 255.0f ;
u8 alpha = ( 1 - clamp ( smooth_step ( 0.5f - smoothing , 0.5f + smoothing , distance ) , 0.0f , 1.0f ) ) * 255 ;
set_physical_pixel ( point + clipped_rect . location ( ) , color . with_alpha ( alpha ) , true ) ;
}
}
}
2020-02-06 13:56:38 +03:00
}