feat(tool): introduce blurring capability

The blur algorithm is largely inspired from Kristian Høgsberg & Chris
Wilson in [this file](https://www.cairographics.org/cookbook/blur.c/)

Closes #17
This commit is contained in:
Jeremy Attali 2020-05-30 23:18:58 -04:00
parent 2eb4ebbc28
commit fae0aeacab
12 changed files with 434 additions and 17 deletions

24
.vscode/launch.json vendored
View File

@ -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",

View File

@ -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)
<hr>

View File

@ -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);

View File

@ -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

View File

@ -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;
};

View File

@ -22,11 +22,26 @@
<property name="can_focus">False</property>
<property name="icon_name">zoom-in</property>
</object>
<object class="GtkImage" id="zoom-in2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-in</property>
</object>
<object class="GtkImage" id="zoom-out">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-out</property>
</object>
<object class="GtkImage" id="zoom-out1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-out</property>
</object>
<object class="GtkImage" id="zoom-out2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-out</property>
</object>
<object class="GtkApplicationWindow" id="paint-window">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -242,6 +257,18 @@
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">D</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -336,6 +363,22 @@
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="blur">
<property name="label" translatable="yes">💧</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="draw_indicator">False</property>
<property name="group">brush</property>
<signal name="clicked" handler="blur_clicked_handler" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<style>
<class name="drawing"/>
</style>
@ -581,6 +624,7 @@
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">10</property>
<property name="spacing">2</property>
<property name="homogeneous">True</property>
<child>
@ -646,6 +690,76 @@
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">10</property>
<property name="spacing">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Blur Radius</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="blur-minus-button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="image">zoom-out2</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="blur_radius_decrease_handler" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="blur-radius-button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="blur_radius_reset_handler" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="blur-plus-button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="image">zoom-in2</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="blur_radius_increase_handler" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
@ -688,9 +802,4 @@
</object>
</child>
</object>
<object class="GtkImage" id="zoom-out1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">zoom-out</property>
</object>
</interface>

View File

@ -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,

View File

@ -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;

View File

@ -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:

View File

@ -16,6 +16,142 @@
#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);
if (cairo_surface_status(tmp)) {
return;
}
src = cairo_image_surface_get_data(surface);
src_stride = cairo_image_surface_get_stride(surface);
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 +347,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 +384,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;

View File

@ -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

BIN
test/images/small-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B