mirror of
https://github.com/jtheoof/swappy.git
synced 2024-10-26 15:44:47 +03:00
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:
parent
2eb4ebbc28
commit
fae0aeacab
24
.vscode/launch.json
vendored
24
.vscode/launch.json
vendored
@ -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",
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
119
res/swappy.ui
119
res/swappy.ui
@ -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>
|
||||
|
@ -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,
|
||||
|
22
src/config.c
22
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;
|
||||
|
41
src/paint.c
41
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:
|
||||
|
150
src/render.c
150
src/render.c
@ -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;
|
||||
|
@ -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
BIN
test/images/small-blue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 B |
Loading…
Reference in New Issue
Block a user