From 4b2921d1788703caec974df31947cb807a0164ed Mon Sep 17 00:00:00 2001 From: Jeremy Attali Date: Sat, 30 May 2020 23:18:58 -0400 Subject: [PATCH] feat(tool): introduce blurring capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blur algorithm is largely inspired from Kristian Høgsberg & Chris Wilson in [this file](https://www.cairographics.org/cookbook/blur.c/) Closes #17 --- .vscode/launch.json | 24 ++++++ README.md | 1 + include/application.h | 7 ++ include/config.h | 1 + include/swappy.h | 14 ++++ res/swappy.ui | 119 +++++++++++++++++++++++++++-- src/application.c | 69 +++++++++++++++++ src/config.c | 22 +++++- src/main.c | 2 +- src/paint.c | 41 +++++++--- src/render.c | 152 +++++++++++++++++++++++++++++++++++++ swappy.1.scd | 3 + test/images/small-blue.png | Bin 0 -> 89 bytes 13 files changed, 437 insertions(+), 18 deletions(-) create mode 100644 test/images/small-blue.png diff --git a/.vscode/launch.json b/.vscode/launch.json index 436d9bf..a1178c3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,30 @@ } ] }, + { + "name": "swappy - file (small blue)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/swappy", + "args": ["-f", "test/images/small-blue.png"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "G_MESSAGES_DEBUG", + "value": "all" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, { "name": "swappy - file (large)", "type": "cppdbg", diff --git a/README.md b/README.md index bc2373e..ebf757f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ The following lines can be used as swappy's default: - `r`: Switch to Rectangle - `o`: Switch to Ellipse - `a`: Switch to Arrow +- `d`: Switch to Blur (`d` stands for droplet)
diff --git a/include/application.h b/include/application.h index 5d33a66..3a162c5 100644 --- a/include/application.h +++ b/include/application.h @@ -27,11 +27,18 @@ void draw_area_button_release_handler(GtkWidget *widget, GdkEventButton *event, void draw_area_motion_notify_handler(GtkWidget *widget, GdkEventMotion *event, struct swappy_state *state); +void blur_radius_decrease_handler(GtkWidget *widget, + struct swappy_state *state); +void blur_radius_increase_handler(GtkWidget *widget, + struct swappy_state *state); +void blur_radius_reset_handler(GtkWidget *widget, struct swappy_state *state); + void brush_clicked_handler(GtkWidget *widget, struct swappy_state *state); void text_clicked_handler(GtkWidget *widget, struct swappy_state *state); void rectangle_clicked_handler(GtkWidget *widget, struct swappy_state *state); void ellipse_clicked_handler(GtkWidget *widget, struct swappy_state *state); void arrow_clicked_handler(GtkWidget *widget, struct swappy_state *state); +void blur_clicked_handler(GtkWidget *widget, struct swappy_state *state); void copy_clicked_handler(GtkWidget *widget, struct swappy_state *state); void save_clicked_handler(GtkWidget *widget, struct swappy_state *state); diff --git a/include/config.h b/include/config.h index ad0f2ee..841443d 100644 --- a/include/config.h +++ b/include/config.h @@ -1,5 +1,6 @@ #include "swappy.h" +#define CONFIG_BLUR_RADIUS_DEFAULT 15 #define CONFIG_LINE_SIZE_DEFAULT 5 #define CONFIG_TEXT_FONT_DEFAULT "sans-serif" #define CONFIG_TEXT_SIZE_DEFAULT 20 diff --git a/include/swappy.h b/include/swappy.h index 05bd2fb..bbb9864 100644 --- a/include/swappy.h +++ b/include/swappy.h @@ -19,6 +19,9 @@ #define SWAPPY_LINE_SIZE_MIN 1 #define SWAPPY_LINE_SIZE_MAX 50 +#define SWAPPY_BLUR_RADIUS_MIN 1 +#define SWAPPY_BLUR_RADIUS_MAX 50 + #define SWAPPY_TEXT_SIZE_MIN 10 #define SWAPPY_TEXT_SIZE_MAX 50 @@ -28,6 +31,7 @@ enum swappy_paint_type { SWAPPY_PAINT_MODE_RECTANGLE, /* Rectangle shapes */ SWAPPY_PAINT_MODE_ELLIPSE, /* Ellipse shapes */ SWAPPY_PAINT_MODE_ARROW, /* Arrow shapes */ + SWAPPY_PAINT_MODE_BLUR, /* Blur mode */ }; enum swappy_text_mode { @@ -74,6 +78,11 @@ struct swappy_paint_brush { GList *points; }; +struct swappy_paint_blur { + double radius; + GList *points; +}; + struct swappy_paint { enum swappy_paint_type type; bool can_draw; @@ -81,6 +90,7 @@ struct swappy_paint { struct swappy_paint_brush brush; struct swappy_paint_shape shape; struct swappy_paint_text text; + struct swappy_paint_blur blur; } content; }; @@ -98,6 +108,7 @@ struct swappy_state_settings { double a; double w; double t; + guint32 blur_radius; }; struct swappy_state_ui { @@ -115,6 +126,7 @@ struct swappy_state_ui { GtkRadioButton *rectangle; GtkRadioButton *ellipse; GtkRadioButton *arrow; + GtkRadioButton *blur; GtkRadioButton *red; GtkRadioButton *green; @@ -122,6 +134,7 @@ struct swappy_state_ui { GtkRadioButton *custom; GtkColorButton *color; + GtkButton *blur_radius; GtkButton *line_size; GtkButton *text_size; }; @@ -173,6 +186,7 @@ struct swappy_config { char *save_dir; guint32 line_size; guint32 text_size; + guint32 blur_radius; char *text_font; }; diff --git a/res/swappy.ui b/res/swappy.ui index 65f61aa..3f4580d 100644 --- a/res/swappy.ui +++ b/res/swappy.ui @@ -22,11 +22,26 @@ False zoom-in + + True + False + zoom-in + True False zoom-out + + True + False + zoom-out + + + True + False + zoom-out + True False @@ -242,6 +257,18 @@ 4 + + + True + False + D + + + False + True + 5 + + False @@ -336,6 +363,22 @@ 4 + + + 💧 + True + False + False + False + brush + + + + False + True + 5 + + @@ -581,6 +624,7 @@ True False + 10 2 True @@ -646,6 +690,76 @@ 4 + + + True + False + 10 + 2 + True + + + True + False + Blur Radius + + + False + True + 0 + + + + + True + False + True + zoom-out2 + True + + + + False + False + 1 + + + + + True + False + True + True + + + + False + True + 2 + + + + + True + False + True + zoom-in2 + True + + + + False + False + 3 + + + + + False + True + 5 + + False @@ -688,9 +802,4 @@ - - True - False - zoom-out - diff --git a/src/application.c b/src/application.c index 3a9ac95..c8c058a 100644 --- a/src/application.c +++ b/src/application.c @@ -23,6 +23,13 @@ static void update_ui_undo_redo(struct swappy_state *state) { gtk_widget_set_sensitive(redo, redo_sensitive); } +static void update_ui_blur_radius_widget(struct swappy_state *state) { + GtkButton *button = GTK_BUTTON(state->ui->blur_radius); + char label[255]; + snprintf(label, 255, "%u", state->settings.blur_radius); + gtk_button_set_label(button, label); +} + static void update_ui_stroke_size_widget(struct swappy_state *state) { GtkButton *button = GTK_BUTTON(state->ui->line_size); char label[255]; @@ -112,6 +119,37 @@ static void switch_mode_to_arrow(struct swappy_state *state) { state->mode = SWAPPY_PAINT_MODE_ARROW; } +static void switch_mode_to_blur(struct swappy_state *state) { + state->mode = SWAPPY_PAINT_MODE_BLUR; +} + +static void action_blur_radius_decrease(struct swappy_state *state) { + guint step = state->settings.blur_radius <= 10 ? 1 : 5; + + state->settings.blur_radius -= step; + + if (state->settings.blur_radius < SWAPPY_BLUR_RADIUS_MIN) { + state->settings.blur_radius = SWAPPY_BLUR_RADIUS_MIN; + } + + update_ui_blur_radius_widget(state); +} +static void action_blur_radius_increase(struct swappy_state *state) { + guint step = state->settings.blur_radius >= 10 ? 5 : 1; + state->settings.blur_radius += step; + + if (state->settings.blur_radius > SWAPPY_BLUR_RADIUS_MAX) { + state->settings.blur_radius = SWAPPY_BLUR_RADIUS_MAX; + } + + update_ui_blur_radius_widget(state); +} +static void action_blur_radius_reset(struct swappy_state *state) { + state->settings.blur_radius = state->config->blur_radius; + + update_ui_blur_radius_widget(state); +} + static void action_stroke_size_decrease(struct swappy_state *state) { guint step = state->settings.w <= 10 ? 1 : 5; @@ -210,6 +248,10 @@ void arrow_clicked_handler(GtkWidget *widget, struct swappy_state *state) { switch_mode_to_arrow(state); } +void blur_clicked_handler(GtkWidget *widget, struct swappy_state *state) { + switch_mode_to_blur(state); +} + void application_finish(struct swappy_state *state) { paint_free_all(state); buffer_free_all(state); @@ -297,6 +339,10 @@ void window_keypress_handler(GtkWidget *widget, GdkEventKey *event, switch_mode_to_arrow(state); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->arrow), true); break; + case GDK_KEY_d: + switch_mode_to_blur(state); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->blur), true); + break; case GDK_KEY_k: action_clear(state); break; @@ -374,6 +420,7 @@ void draw_area_button_press_handler(GtkWidget *widget, GdkEventButton *event, struct swappy_state *state) { if (event->button == 1) { switch (state->mode) { + case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_RECTANGLE: case SWAPPY_PAINT_MODE_ELLIPSE: @@ -400,6 +447,7 @@ void draw_area_motion_notify_handler(GtkWidget *widget, GdkEventMotion *event, gboolean is_button1_pressed = event->state & GDK_BUTTON1_MASK; switch (state->mode) { + case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_RECTANGLE: case SWAPPY_PAINT_MODE_ELLIPSE: @@ -427,6 +475,7 @@ void draw_area_button_release_handler(GtkWidget *widget, GdkEventButton *event, } switch (state->mode) { + case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_RECTANGLE: case SWAPPY_PAINT_MODE_ELLIPSE: @@ -447,6 +496,19 @@ void draw_area_button_release_handler(GtkWidget *widget, GdkEventButton *event, } } +void blur_radius_decrease_handler(GtkWidget *widget, + struct swappy_state *state) { + action_blur_radius_decrease(state); +} + +void blur_radius_increase_handler(GtkWidget *widget, + struct swappy_state *state) { + action_blur_radius_increase(state); +} +void blur_radius_reset_handler(GtkWidget *widget, struct swappy_state *state) { + action_blur_radius_reset(state); +} + void color_red_clicked_handler(GtkWidget *widget, struct swappy_state *state) { action_update_color_state(state, 1, 0, 0, 1, false); } @@ -578,6 +640,8 @@ static bool load_layout(struct swappy_state *state) { GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "ellipse")); GtkRadioButton *arrow = GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "arrow")); + GtkRadioButton *blur = + GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "blur")); state->ui->red = GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "color-red-button")); @@ -590,6 +654,8 @@ static bool load_layout(struct swappy_state *state) { state->ui->color = GTK_COLOR_BUTTON(gtk_builder_get_object(builder, "custom-color-button")); + state->ui->blur_radius = + GTK_BUTTON(gtk_builder_get_object(builder, "blur-radius-button")); state->ui->line_size = GTK_BUTTON(gtk_builder_get_object(builder, "stroke-size-button")); state->ui->text_size = @@ -600,6 +666,7 @@ static bool load_layout(struct swappy_state *state) { state->ui->rectangle = rectangle; state->ui->ellipse = ellipse; state->ui->arrow = arrow; + state->ui->blur = blur; state->ui->area = area; state->ui->window = window; @@ -627,6 +694,7 @@ static bool init_gtk_window(struct swappy_state *state) { return false; } + update_ui_blur_radius_widget(state); update_ui_stroke_size_widget(state); update_ui_text_size_widget(state); update_ui_undo_redo(state); @@ -653,6 +721,7 @@ static void init_settings(struct swappy_state *state) { state->settings.a = 1; state->settings.w = state->config->line_size; state->settings.t = state->config->text_size; + state->settings.blur_radius = state->config->blur_radius; } static gint command_line_handler(GtkApplication *app, diff --git a/src/config.c b/src/config.c index 130b053..6f038cb 100644 --- a/src/config.c +++ b/src/config.c @@ -13,6 +13,7 @@ static void print_config(struct swappy_config *config) { g_info("printing config:"); g_info("config_dir: %s", config->config_file); g_info("save_dir: %s", config->save_dir); + g_info("blur_radius: %d", config->blur_radius); g_info("line_size: %d", config->line_size); g_info("text_font: %s", config->text_font); g_info("text_size: %d", config->text_size); @@ -68,9 +69,8 @@ static void load_config_from_file(struct swappy_config *config, const gchar *group = "Default"; gchar *save_dir = NULL; gchar *save_dir_expanded = NULL; - guint64 line_size; + guint64 line_size, text_size, blur_radius; gchar *text_font = NULL; - guint64 text_size; GError *error = NULL; if (file == NULL) { @@ -140,6 +140,23 @@ static void load_config_from_file(struct swappy_config *config, error = NULL; } + blur_radius = g_key_file_get_uint64(gkf, group, "blur_radius", &error); + + if (error == NULL) { + if (blur_radius >= SWAPPY_BLUR_RADIUS_MIN && + blur_radius <= SWAPPY_BLUR_RADIUS_MAX) { + config->blur_radius = blur_radius; + } else { + g_warning( + "blur_radius is not a valid value: %ld - see man page for details", + blur_radius); + } + } else { + g_info("blur_radius is missing in %s (%s)", file, error->message); + g_error_free(error); + error = NULL; + } + text_font = g_key_file_get_string(gkf, group, "text_font", &error); if (error == NULL) { @@ -160,6 +177,7 @@ static void load_default_config(struct swappy_config *config) { } config->save_dir = get_default_save_dir(); + config->blur_radius = CONFIG_BLUR_RADIUS_DEFAULT; config->line_size = CONFIG_LINE_SIZE_DEFAULT; config->text_font = g_strdup(CONFIG_TEXT_FONT_DEFAULT); config->text_size = CONFIG_TEXT_SIZE_DEFAULT; diff --git a/src/main.c b/src/main.c index bcdfe38..4b72ed9 100644 --- a/src/main.c +++ b/src/main.c @@ -9,7 +9,7 @@ int main(int argc, char *argv[]) { state.argc = argc; state.argv = argv; - state.mode = SWAPPY_PAINT_MODE_BRUSH; + state.mode = SWAPPY_PAINT_MODE_BLUR; if (!application_init(&state)) { g_critical("failed to initialize gtk application"); diff --git a/src/paint.c b/src/paint.c index ab8332a..2b78895 100644 --- a/src/paint.c +++ b/src/paint.c @@ -22,6 +22,9 @@ void paint_free(gpointer data) { } switch (paint->type) { + case SWAPPY_PAINT_MODE_BLUR: + g_list_free_full(paint->content.blur.points, g_free); + break; case SWAPPY_PAINT_MODE_BRUSH: g_list_free_full(paint->content.brush.points, g_free); break; @@ -52,7 +55,7 @@ void paint_free_all(struct swappy_state *state) { void paint_add_temporary(struct swappy_state *state, double x, double y, enum swappy_paint_type type) { struct swappy_paint *paint = g_new(struct swappy_paint, 1); - struct swappy_point *brush; + struct swappy_point *point; double r = state->settings.r; double g = state->settings.g; @@ -73,6 +76,16 @@ void paint_add_temporary(struct swappy_state *state, double x, double y, } switch (type) { + case SWAPPY_PAINT_MODE_BLUR: + paint->can_draw = true; + + paint->content.blur.radius = state->settings.blur_radius; + point = g_new(struct swappy_point, 1); + point->x = x; + point->y = y; + + paint->content.blur.points = g_list_prepend(NULL, point); + break; case SWAPPY_PAINT_MODE_BRUSH: paint->can_draw = true; @@ -82,11 +95,11 @@ void paint_add_temporary(struct swappy_state *state, double x, double y, paint->content.brush.a = a; paint->content.brush.w = w; - brush = g_new(struct swappy_point, 1); - brush->x = x; - brush->y = y; + point = g_new(struct swappy_point, 1); + point->x = x; + point->y = y; - paint->content.brush.points = g_list_prepend(NULL, brush); + paint->content.brush.points = g_list_prepend(NULL, point); break; case SWAPPY_PAINT_MODE_RECTANGLE: case SWAPPY_PAINT_MODE_ELLIPSE: @@ -130,7 +143,7 @@ void paint_add_temporary(struct swappy_state *state, double x, double y, void paint_update_temporary_shape(struct swappy_state *state, double x, double y) { struct swappy_paint *paint = state->temp_paint; - struct swappy_point *brush; + struct swappy_point *point; GList *points; if (!paint) { @@ -138,13 +151,21 @@ void paint_update_temporary_shape(struct swappy_state *state, double x, } switch (paint->type) { + case SWAPPY_PAINT_MODE_BLUR: + points = paint->content.blur.points; + point = g_new(struct swappy_point, 1); + point->x = x; + point->y = y; + + paint->content.blur.points = g_list_prepend(points, point); + break; case SWAPPY_PAINT_MODE_BRUSH: points = paint->content.brush.points; - brush = g_new(struct swappy_point, 1); - brush->x = x; - brush->y = y; + point = g_new(struct swappy_point, 1); + point->x = x; + point->y = y; - paint->content.brush.points = g_list_prepend(points, brush); + paint->content.brush.points = g_list_prepend(points, point); break; case SWAPPY_PAINT_MODE_RECTANGLE: case SWAPPY_PAINT_MODE_ELLIPSE: diff --git a/src/render.c b/src/render.c index 1c8a4c0..62a3842 100644 --- a/src/render.c +++ b/src/render.c @@ -16,6 +16,144 @@ #define pango_font_description_t PangoFontDescription #define pango_rectangle_t PangoRectangle +#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0]) + +static gboolean is_point_within_circle(struct swappy_point *point, + struct swappy_point *center, + guint32 radius) { + return pow(point->x - center->x, 2) + pow(point->y - center->y, 2) < + pow(radius, 2); +} + +/* + * This code was largely taken from Kristian Høgsberg and Chris Wilson from: + * https://www.cairographics.org/cookbook/blur.c/ + */ +static void blur_image_surface_at_point(cairo_t *cr, int radius, + struct swappy_point *point) { + cairo_surface_t *tmp; + int width, height; + int src_stride, dst_stride; + int x, y, z, w; + uint8_t *src, *dst; + uint32_t *s, *d, a, p; + int i, j, k; + uint8_t kernel[17]; + const int size = ARRAY_LENGTH(kernel); + const int half = size / 2; + const int radius_extra = radius * 1.5; + + cairo_surface_t *surface = cairo_get_target(cr); + + if (cairo_surface_status(surface)) return; + + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + + switch (cairo_image_surface_get_format(surface)) { + case CAIRO_FORMAT_A1: + default: + /* Don't even think about it! */ + return; + + case CAIRO_FORMAT_A8: + /* Handle a8 surfaces by effectively unrolling the loops by a + * factor of 4 - this is safe since we know that stride has to be a + * multiple of uint32_t. */ + width /= 4; + break; + + case CAIRO_FORMAT_RGB24: + case CAIRO_FORMAT_ARGB32: + break; + } + + tmp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + // tmp = + if (cairo_surface_status(tmp)) { + return; + } + + src = cairo_image_surface_get_data(surface); + src_stride = cairo_image_surface_get_stride(surface); + + // dst = g_memdup(src, sizeof(src)); + dst = cairo_image_surface_get_data(tmp); + dst_stride = cairo_image_surface_get_stride(tmp); + + a = 0; + for (i = 0; i < size; i++) { + double f = i - half; + a += kernel[i] = exp(-f * f / 30.0) * 160; + } + + int start_x = fmax(point->x - radius_extra, 0); + int start_y = fmax(point->y - radius_extra, 0); + + int max_x = fmin(point->x + radius_extra, width); + int max_y = fmin(point->y + radius_extra, height); + + for (i = start_y; i < max_y; i++) { + s = (uint32_t *)(src + i * src_stride); + d = (uint32_t *)(dst + i * dst_stride); + for (j = start_x; j < max_x; j++) { + d[j] = s[j]; + } + } + + /* Horizontally blur from surface -> tmp */ + for (i = start_y; i < max_y; i++) { + s = (uint32_t *)(src + i * src_stride); + d = (uint32_t *)(dst + i * dst_stride); + for (j = start_x; j < max_x; j++) { + x = y = z = w = 0; + for (k = 0; k < size; k++) { + if (j - half + k < 0 || j - half + k >= width) continue; + + p = s[j - half + k]; + + x += ((p >> 24) & 0xff) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += ((p >> 0) & 0xff) * kernel[k]; + } + struct swappy_point pixel = {.x = j, .y = i}; + if (is_point_within_circle(&pixel, point, radius)) { + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + } + + /* Then vertically blur from tmp -> surface */ + for (i = start_y; i < max_y; i++) { + s = (uint32_t *)(dst + i * dst_stride); + d = (uint32_t *)(src + i * src_stride); + for (j = start_x; j < max_x; j++) { + x = y = z = w = 0; + for (k = 0; k < size; k++) { + if (i - half + k < 0 || i - half + k >= height) { + continue; + } + + s = (uint32_t *)(dst + (i - half + k) * dst_stride); + p = s[j]; + + x += ((p >> 24) & 0xff) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += ((p >> 0) & 0xff) * kernel[k]; + } + struct swappy_point pixel = {.x = j, .y = i}; + if (is_point_within_circle(&pixel, point, radius)) { + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + } + + cairo_surface_destroy(tmp); + cairo_surface_mark_dirty(surface); +} + static void convert_pango_rectangle_to_swappy_box(pango_rectangle_t rectangle, struct swappy_box *box) { if (!box) { @@ -211,6 +349,17 @@ static void render_background(cairo_t *cr) { cairo_paint(cr); } +static void render_blur(cairo_t *cr, struct swappy_paint_blur blur) { + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_set_line_width(cr, 1); + + for (GList *elem = blur.points; elem; elem = elem->next) { + struct swappy_point *point = elem->data; + + blur_image_surface_at_point(cr, blur.radius, point); + } +} + static void render_brush(cairo_t *cr, struct swappy_paint_brush brush) { cairo_set_source_rgba(cr, brush.r, brush.g, brush.b, brush.a); cairo_set_line_width(cr, brush.w); @@ -237,6 +386,9 @@ static void render_paint(cairo_t *cr, struct swappy_paint *paint) { } switch (paint->type) { + case SWAPPY_PAINT_MODE_BLUR: + render_blur(cr, paint->content.blur); + break; case SWAPPY_PAINT_MODE_BRUSH: render_brush(cr, paint->content.brush); break; diff --git a/swappy.1.scd b/swappy.1.scd index 4e83492..d92da06 100644 --- a/swappy.1.scd +++ b/swappy.1.scd @@ -62,12 +62,14 @@ The following lines can be used as swappy's default: ``` [Default] save_dir=$HOME/Desktop + blur_radius=15 line_size=5 text_size=20 text_font=sans-serif ``` - *save_dir* is where swappshots will be saved, can contain env variables and must exist in your filesystem +- *blur_raidus* is the default blur radius (must be between 1 and 50) - *line_size* is the default line size (must be between 1 and 50) - *text_size* is the default text size (must be between 10 and 50) - *text_font* is the font used to render text, its format is pango friendly @@ -85,6 +87,7 @@ The following lines can be used as swappy's default: - *r*: Switch to Rectangle - *o*: Switch to Ellipse - *a*: Switch to Arrow +- *d*: Switch to Blur (d stands for droplet) - *R*: Use Red Color - *G*: Use Green Color diff --git a/test/images/small-blue.png b/test/images/small-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..10faee4c631520e7d9d1d37be822abab2d71ff24 GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`3Z5>GAr}70CAUBQvS(&(D7?bZ mcF`a~f|of#PKxa%14G$Wy}%W>bxr`)GI+ZBxvX