From bd6f250c12fe291d75430be80ccb9b968d5ec979 Mon Sep 17 00:00:00 2001 From: Subhraman Sarkar Date: Tue, 11 Mar 2025 01:34:49 +0530 Subject: [PATCH] canvas: draw circle using cairo (#10007) the circle is now antialiased using cairo. --- .../celes/widgets/toggle_button_radio.cfg | 29 ++++---- images/buttons/modern/dot.png | Bin 898 -> 0 bytes images/buttons/modern/radiobox-pressed.png | Bin 4044 -> 0 bytes images/buttons/modern/radiobox.png | Bin 4363 -> 0 bytes src/draw.cpp | 64 ++++++++++++++++++ src/draw.hpp | 8 ++- src/font/cairo.hpp | 4 +- src/gui/core/canvas.cpp | 17 ++--- 8 files changed, 95 insertions(+), 27 deletions(-) delete mode 100644 images/buttons/modern/dot.png delete mode 100644 images/buttons/modern/radiobox-pressed.png delete mode 100644 images/buttons/modern/radiobox.png diff --git a/data/gui/themes/celes/widgets/toggle_button_radio.cfg b/data/gui/themes/celes/widgets/toggle_button_radio.cfg index 5b102e155c4..ae65d6c5ffd 100644 --- a/data/gui/themes/celes/widgets/toggle_button_radio.cfg +++ b/data/gui/themes/celes/widgets/toggle_button_radio.cfg @@ -18,20 +18,23 @@ #define _GUI_RADIO_BG COLOR [circle] x = 10 - y = 10 - radius = 10 + y = 13 + radius = 11 border_thickness = 2 border_color = {COLOR} fill_color = "28, 45, 64, 255" [/circle] #enddef -#define _GUI_RADIO_DOT IPF - [image] - x = 6 - y = 6 - name = "buttons/modern/dot.png{IPF}" - [/image] +#define _GUI_RADIO_DOT COLOR + [circle] + x = 10 + y = 13 + radius = 5 + border_thickness = 0 + border_color = "0, 0, 0, 0" + fill_color = {COLOR} + [/circle] #enddef #define _GUI_RESOLUTION RESOLUTION WIDTH HEIGHT EXTRA_WIDTH FONT_SIZE @@ -96,7 +99,7 @@ [draw] {_GUI_RADIO_BG ({GUI__COLOR_TOGGLE_ENABLED})} - {_GUI_RADIO_DOT ()} + {_GUI_RADIO_DOT ("255, 225, 104, 255")} {_GUI_TEXT ({EXTRA_WIDTH}) ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__TITLE}) } [/draw] @@ -108,7 +111,7 @@ [draw] {_GUI_RADIO_BG ({GUI__COLOR_TOGGLE_DISABLED})} - {_GUI_RADIO_DOT "~GS()"} + {_GUI_RADIO_DOT ("179, 179, 179, 255")} {_GUI_TEXT ({EXTRA_WIDTH}) ({FONT_SIZE}) ({GUI__FONT_COLOR_DISABLED__TITLE}) } [/draw] @@ -120,7 +123,7 @@ [draw] {_GUI_RADIO_BG ({GUI__FONT_COLOR_ENABLED__BRIGHT})} - {_GUI_RADIO_DOT ()} + {_GUI_RADIO_DOT ("255, 225, 104, 255")} {_GUI_TEXT ({EXTRA_WIDTH}) ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__TITLE}) } [/draw] @@ -136,7 +139,7 @@ id = "radio" description = "Radio button." - {_GUI_RESOLUTION () 32 24 25 ({GUI_FONT_SIZE_SMALL}) } + {_GUI_RESOLUTION () 36 26 25 ({GUI_FONT_SIZE_SMALL}) } [/toggle_button_definition] @@ -145,7 +148,7 @@ id = "radio_no_label" description = "Radio button." - {_GUI_RESOLUTION () 32 24 25 ({GUI_FONT_SIZE_SMALL}) } + {_GUI_RESOLUTION () 36 26 25 ({GUI_FONT_SIZE_SMALL}) } [/toggle_button_definition] diff --git a/images/buttons/modern/dot.png b/images/buttons/modern/dot.png deleted file mode 100644 index 55e5655d46368f1fae18138bc489bdefbbc17385..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 898 zcmZvbPiWIn9LF;c!_^LV@K6V0Kv@*mrmfD=*mi8|bY*{(wZ%@v`I5Z0p=lD6SKIC2 zV24VF9t1r(M7(*>%X$zuY&+N#I;|H`5!nWcpw2lFbnZ}JvQiZcyySh~`{na}zx;m5 zo*F#SXlgbQ1kvd4^$p=?gFd#`Ls|C?H2r3aF4s zrBXIQifbHDpiNezH@~=>31XX12!>I3pr7I7gbnaA3vKCyg5M?xcSl+QTpS{jg;7EB zP-`=JiWGPcb-Hc9KA?2Nn9w_>!ob*IkQDcGMx}5jY)ebgUjH# zzD!f35rX0#%AWvH$S^%=Arh2jk#sn1ZS53>sCe5Fp&ns2Q^L=6eP$JsWSm9Cy|GuOk@?!b2YM(R7Hc! zb2JoT0!m1u9X1DzaU|*HIfhdqK(eaKEqnl8FvOM;1#ck9##E315*Yw$9H<9azyd+j z3~}%n4@983Y5gQP*uWT~%5l^Gn&_UY5RD=2k7cKb{;|~aU_>BAJyhDt!w5)8sKqq;`X8(_06=L|+47TF_`eL{a#_4b&^l7cISkzhrO1K|XTzGY^Wd%1)-tX%S zT5fQ;eJFPTbw6b7U#ZxT-}>_`#A4tef=xD^0HhpUy*KIs%(~gmfZDM zmgmJ6GkJ@N8FAQqM%UR;0bjT8EiN>@pDz{OvBhkt&ivqv>ab@Urk6weZHse-@0d6{ zdG66hsgT?J**-gYaZhETkmEm5nM0{@5K=atoy@>DUWdJ}boc0I*ZS3!wWW&r&P1zu tYO3E{9;txv^oJeY<%u8Wj`2#AAabK{jT#9zX7^EL&pFB diff --git a/images/buttons/modern/radiobox-pressed.png b/images/buttons/modern/radiobox-pressed.png deleted file mode 100644 index af45091bf4dbcfddeb1fe04174b24373912e6ff5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4044 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 #include #include +#include static lg::log_domain log_draw("draw"); #define DBG_D LOG_STREAM(debug, log_draw) @@ -308,6 +311,67 @@ void draw::disc(int cx, int cy, int r, uint8_t octants) } } +/********************/ +/* Cairo primitives */ +/********************/ + +void draw::cairo_circle(int cx, int cy, int r, const color_t& c, int thickness) +{ + if (r <= 0) { + return; + } + + int size = 2*r; + surface sdl_surf(size, size); + auto cairo_surface = cairo::create_surface( + reinterpret_cast(sdl_surf->pixels), ::point(sdl_surf->w, sdl_surf->h)); + auto cairo_context = cairo::create_context(cairo_surface); + cairo_t* ctx = cairo_context.get(); + + cairo_set_antialias(ctx, CAIRO_ANTIALIAS_BEST); + + cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0); + cairo_paint(ctx); + + cairo_set_line_width(ctx, thickness); + cairo_set_source_rgba(ctx, + c.r / 255.0, + c.g / 255.0, + c.b / 255.0, + c.a / 255.0 + ); + cairo_arc(ctx, r, r, r-thickness, 0, 2*boost::math::constants::pi()); + cairo_stroke(ctx); + + draw::blit(texture(sdl_surf), ::rect(cx-r, cy-r, size, size)); +} + +void draw::cairo_disc(int cx, int cy, int r, const color_t& c) +{ + if (r <= 0) { + return; + } + + int size = 2*r; + surface sdl_surf(size, size); + auto cairo_surface = cairo::create_surface( + reinterpret_cast(sdl_surf->pixels), ::point(sdl_surf->w, sdl_surf->h)); + auto cairo_context = cairo::create_context(cairo_surface); + cairo_t* ctx = cairo_context.get(); + + cairo_set_antialias(ctx, CAIRO_ANTIALIAS_BEST); + + cairo_set_source_rgba(ctx, + c.r / 255.0, + c.g / 255.0, + c.b / 255.0, + c.a / 255.0 + ); + cairo_arc(ctx, r, r, r, 0, 2*2*boost::math::constants::pi()); + cairo_fill(ctx); + + draw::blit(texture(sdl_surf), ::rect(cx-r, cy-r, size, size)); +} /*******************/ /* texture drawing */ diff --git a/src/draw.hpp b/src/draw.hpp index 934079eb18e..f5af2399d97 100644 --- a/src/draw.hpp +++ b/src/draw.hpp @@ -32,10 +32,10 @@ #include "sdl/rect.hpp" #include "sdl/texture.hpp" +#include #include #include -#include struct color_t; @@ -210,6 +210,12 @@ void circle(int x, int y, int r, uint8_t octants = 0xff); void disc(int x, int y, int r, const color_t& c, uint8_t octants = 0xff); void disc(int x, int y, int r, uint8_t octants = 0xff); +/** Draw outline of circle using Cairo */ +void cairo_circle(int cx, int cy, int r, const color_t& c, int thickness); + +/** Draw filled circle using Cairo */ +void cairo_disc(int cx, int cy, int r, const color_t& c); + /*******************/ /* texture drawing */ diff --git a/src/font/cairo.hpp b/src/font/cairo.hpp index e8078333e31..39e2dba1e83 100644 --- a/src/font/cairo.hpp +++ b/src/font/cairo.hpp @@ -27,7 +27,7 @@ using context_ptr = std::unique_ptr; /** Color format for cairo surfaces. Should be equivalent to the format used by SDL. */ constexpr cairo_format_t format = CAIRO_FORMAT_ARGB32; -surface_ptr create_surface(uint8_t* buffer, const point& size) +inline surface_ptr create_surface(uint8_t* buffer, const point& size) { const auto& [width, height] = size; const int stride = cairo_format_stride_for_width(format, width); @@ -38,7 +38,7 @@ surface_ptr create_surface(uint8_t* buffer, const point& size) }; } -context_ptr create_context(const surface_ptr& surf) +inline context_ptr create_context(const surface_ptr& surf) { return { cairo_create(surf.get()), diff --git a/src/gui/core/canvas.cpp b/src/gui/core/canvas.cpp index 4a5cf81c3d4..3877ff76592 100644 --- a/src/gui/core/canvas.cpp +++ b/src/gui/core/canvas.cpp @@ -36,13 +36,12 @@ #include "picture.hpp" #include "sdl/point.hpp" #include "sdl/rect.hpp" +#include "sdl/surface.hpp" #include "sdl/texture.hpp" #include "sdl/utils.hpp" // blur_surface #include "video.hpp" // read_pixels_low_res, only used for blurring #include "wml_exception.hpp" -#include - namespace gui2 { @@ -219,22 +218,18 @@ void circle_shape::draw(wfl::map_formula_callable& variables) * silly unless there has been a resize. So to optimize we should use an * extra flag or do the calculation in a separate routine. */ - const int x = x_(variables); const int y = y_(variables); const unsigned radius = radius_(variables); - - DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius << "."; - const color_t fill_color = fill_color_(variables); - if(!fill_color.null() && radius) { - draw::disc(x, y, radius, fill_color); + if (!fill_color.null()) { + draw::cairo_disc(x, y, radius, fill_color); } const color_t border_color = border_color_(variables); - for(unsigned int i = 0; i < border_thickness_; i++) { - draw::circle(x, y, radius - i, border_color); - } + draw::cairo_circle(x, y, radius, border_color, border_thickness_); + + DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius << "."; } /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/