fix(blur): use rendered surface after commit

- Fix blur logic
- Reused rendered surface to optimize future render on comitted blur
- Include gaussian kernel function

Closes #20
Closes #22
This commit is contained in:
Jeremy Attali 2020-06-06 23:56:16 -04:00
parent 416b0adad9
commit 46fb08dce1
10 changed files with 226 additions and 119 deletions

13
include/algebra.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <glib.h>
struct gaussian_kernel {
gdouble *kernel;
gint size;
gdouble sigma;
gdouble sum;
};
struct gaussian_kernel *gaussian_kernel(gint width, gdouble sigma);
void gaussian_kernel_free(gpointer data);

View File

@ -79,9 +79,10 @@ struct swappy_paint_brush {
}; };
struct swappy_paint_blur { struct swappy_paint_blur {
double bluriness; double blur_level;
struct swappy_point from; struct swappy_point from;
struct swappy_point to; struct swappy_point to;
cairo_surface_t *surface;
}; };
struct swappy_paint { struct swappy_paint {

View File

@ -5,3 +5,4 @@
void string_remove_at(char *str, size_t pos); void string_remove_at(char *str, size_t pos);
gchar *string_insert_chars_at(gchar *str, gchar *chars, size_t pos); gchar *string_insert_chars_at(gchar *str, gchar *chars, size_t pos);
void pixel_data_print(guint32 pixel);

View File

@ -51,6 +51,7 @@ executable(
swappy_resources, swappy_resources,
files([ files([
'src/main.c', 'src/main.c',
'src/algebra.c',
'src/application.c', 'src/application.c',
'src/buffer.c', 'src/buffer.c',
'src/box.c', 'src/box.c',

37
src/algebra.c Normal file
View File

@ -0,0 +1,37 @@
#include "algebra.h"
#include <glib.h>
#include <math.h>
struct gaussian_kernel *gaussian_kernel(int width, double sigma) {
double sum = 0;
gint size = width * width + 1;
double *kernel = g_new(double, size);
struct gaussian_kernel *gaussian = g_new(struct gaussian_kernel, 1);
for (gint y = 0; y < width; y++) {
for (gint x = 0; x < width; x++) {
double j = y - width;
double i = x - width;
double cell = ((1.0 / (2.0 * G_PI * sigma)) *
exp((-(i * i + j * j)) / (2.0 * sigma * sigma))) *
0xff;
kernel[y * width + x] = cell;
sum += cell;
}
}
gaussian->kernel = kernel;
gaussian->size = size;
gaussian->sigma = sigma;
gaussian->sum = sum;
return gaussian;
}
void gaussian_kernel_free(gpointer data) {
struct gaussian_kernel *gaussian = (struct gaussian_kernel *)data;
if (gaussian != NULL) {
g_free(gaussian->kernel);
g_free(gaussian);
}
}

View File

@ -406,11 +406,18 @@ gboolean draw_area_configure_handler(GtkWidget *widget,
g_debug("received configure_event handler"); g_debug("received configure_event handler");
cairo_surface_destroy(state->cairo_surface); cairo_surface_destroy(state->cairo_surface);
state->cairo_surface = gdk_window_create_similar_surface( cairo_surface_t *surface = gdk_window_create_similar_surface(
gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR_ALPHA,
gtk_widget_get_allocated_width(widget), gtk_widget_get_allocated_width(widget),
gtk_widget_get_allocated_height(widget)); gtk_widget_get_allocated_height(widget));
g_info("size of cairo_surface: %ux%u with type: %d",
cairo_image_surface_get_width(surface),
cairo_image_surface_get_height(surface),
cairo_image_surface_get_format(surface));
state->cairo_surface = surface;
render_state(state); render_state(state);
return TRUE; return TRUE;

View File

@ -23,6 +23,9 @@ void paint_free(gpointer data) {
switch (paint->type) { switch (paint->type) {
case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BLUR:
if (paint->content.blur.surface) {
cairo_surface_destroy(paint->content.blur.surface);
}
break; break;
case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_BRUSH:
g_list_free_full(paint->content.brush.points, g_free); g_list_free_full(paint->content.brush.points, g_free);
@ -70,7 +73,7 @@ void paint_add_temporary(struct swappy_state *state, double x, double y,
if (type == SWAPPY_PAINT_MODE_TEXT) { if (type == SWAPPY_PAINT_MODE_TEXT) {
paint_commit_temporary(state); paint_commit_temporary(state);
} else { } else {
g_free(state->temp_paint); paint_free(state->temp_paint);
state->temp_paint = NULL; state->temp_paint = NULL;
} }
} }
@ -79,9 +82,10 @@ void paint_add_temporary(struct swappy_state *state, double x, double y,
case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BLUR:
paint->can_draw = false; paint->can_draw = false;
paint->content.blur.bluriness = state->settings.blur_level; paint->content.blur.blur_level = state->settings.blur_level;
paint->content.blur.from.x = x; paint->content.blur.from.x = x;
paint->content.blur.from.y = y; paint->content.blur.from.y = y;
paint->content.blur.surface = NULL;
break; break;
case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_BRUSH:
paint->can_draw = true; paint->can_draw = true;
@ -147,6 +151,14 @@ void paint_update_temporary_shape(struct swappy_state *state, double x,
return; return;
} }
int32_t width = state->window->width;
int32_t height = state->window->height;
// Bounding x and y to the window dimensions to avoid side effects in
// rendering.
x = fmin(fmax(x, 0), width);
y = fmin(fmax(y, 0), height);
switch (paint->type) { switch (paint->type) {
case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BLUR:
paint->can_draw = true; paint->can_draw = true;

View File

@ -1,157 +1,152 @@
#include <gdk/gdk.h> #include <glib.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <math.h> #include <math.h>
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#include "algebra.h"
#include "swappy.h" #include "swappy.h"
#include "util.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
#define RENDER_PANGO_FONT SWAPPY_TEXT_FONT_DEFAULT SWAPPY_TEXT_SIZE_DEFAULT
#define pango_layout_t PangoLayout #define pango_layout_t PangoLayout
#define pango_font_description_t PangoFontDescription #define pango_font_description_t PangoFontDescription
#define pango_rectangle_t PangoRectangle #define pango_rectangle_t PangoRectangle
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
static struct swappy_point swappy_point_scaled(struct swappy_point point,
gint scale) {
struct swappy_point ret = {
.x = point.x * scale,
.y = point.y * scale,
};
return ret;
}
/* /*
* This code was largely taken from Kristian Høgsberg and Chris Wilson from: * This code was largely taken from Kristian Høgsberg and Chris Wilson from:
* https://www.cairographics.org/cookbook/blur.c/ * https://www.cairographics.org/cookbook/blur.c/
*/ */
static void blur_paint(cairo_t *cr, struct swappy_paint_blur *blur, static cairo_surface_t *blur_surface(cairo_surface_t *surface, double x,
gint scaling_factor) { double y, double width, double height,
cairo_surface_t *tmp; gint blur_level) {
int width, height; cairo_surface_t *dest_surface, *tmp_surface;
cairo_t *cr;
int src_width, src_height;
int src_stride, dst_stride; int src_stride, dst_stride;
int x, y, z, w; guint u, v, w, z;
uint8_t *src, *dst; uint8_t *src, *dst, *tmp;
uint32_t *s, *d, a, p; uint32_t *s, *d, p;
int i, j, k; int i, j, k;
uint8_t kernel[17]; const int size = (int)blur_level * 2 + 1;
const int size = ARRAY_LENGTH(kernel);
const int half = size / 2; const int half = size / 2;
double bluriness = blur->bluriness; const double offset_y = 10.0;
struct swappy_point from = swappy_point_scaled(blur->from, scaling_factor); guint sum;
struct swappy_point to = swappy_point_scaled(blur->to, scaling_factor);
cairo_surface_t *surface = cairo_get_target(cr); if (cairo_surface_status(surface)) {
return NULL;
}
if (cairo_surface_status(surface)) return; cairo_format_t src_format = cairo_image_surface_get_format(surface);
switch (src_format) {
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: case CAIRO_FORMAT_A1:
default:
/* Don't even think about it! */
return;
case CAIRO_FORMAT_A8: 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_RGB24:
default:
g_warning("source surface format: %d is not supported", src_format);
return NULL;
case CAIRO_FORMAT_ARGB32: case CAIRO_FORMAT_ARGB32:
break; 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 = cairo_image_surface_get_data(surface);
src_stride = cairo_image_surface_get_stride(surface); src_stride = cairo_image_surface_get_stride(surface);
src_width = cairo_image_surface_get_width(surface);
src_height = cairo_image_surface_get_height(surface);
g_debug("sizeof(src): %lu", sizeof(src)); g_assert(src_height >= height);
g_debug("width*height*stride: %d", width * height * src_stride); g_assert(src_width >= width);
dst = cairo_image_surface_get_data(tmp); dest_surface = cairo_image_surface_create(src_format, src_width, src_height);
dst_stride = cairo_image_surface_get_stride(tmp); tmp_surface = cairo_image_surface_create(src_format, src_width, src_height);
a = 0; if (cairo_surface_status(dest_surface) || cairo_surface_status(tmp_surface)) {
for (i = 0; i < size; i++) { return NULL;
double f = i - half;
a += kernel[i] = exp(-f * f / bluriness) * 80;
} }
int start_x = fmax(fmin(from.x, to.x), 0); cr = cairo_create(tmp_surface);
int start_y = fmax(fmin(from.y, to.y), 0); cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
int max_x = fmin(fmax(from.x, to.x), width); cr = cairo_create(dest_surface);
int max_y = fmin(fmax(from.y, to.y), height); cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
for (i = 0; i < height; i++) { dst = cairo_image_surface_get_data(dest_surface);
s = (uint32_t *)(src + i * src_stride); tmp = cairo_image_surface_get_data(tmp_surface);
d = (uint32_t *)(dst + i * dst_stride); dst_stride = cairo_image_surface_get_stride(dest_surface);
for (j = 0; j < width; j++) {
d[j] = s[j]; struct gaussian_kernel *gaussian = gaussian_kernel(4, 3.1);
}
} int start_x = CLAMP(x, 0, src_width);
int start_y = CLAMP(y - offset_y, 0, src_height);
int end_x = CLAMP(x + width, 0, src_width);
int end_y = CLAMP(y + height + offset_y, 0, src_height);
sum = (guint)gaussian->sum;
/* Horizontally blur from surface -> tmp */ /* Horizontally blur from surface -> tmp */
for (i = start_y; i < max_y; i++) { for (i = start_y; i < end_y; i++) {
s = (uint32_t *)(src + i * src_stride); s = (uint32_t *)(src + i * src_stride);
d = (uint32_t *)(dst + i * dst_stride); d = (uint32_t *)(tmp + i * dst_stride);
for (j = start_x; j < max_x; j++) { for (j = start_x; j < end_x; j++) {
x = y = z = w = 0; u = v = w = z = 0;
for (k = 0; k < size; k++) { for (k = 0; k < gaussian->size; k++) {
if (j - half + k < 0 || j - half + k >= width) continue; gdouble multiplier = gaussian->kernel[k];
if (j - half + k < 0 || j - half + k >= src_width) {
continue;
}
p = s[j - half + k]; p = s[j - half + k];
x += ((p >> 24) & 0xff) * kernel[k]; u += ((p >> 24) & 0xff) * multiplier;
y += ((p >> 16) & 0xff) * kernel[k]; v += ((p >> 16) & 0xff) * multiplier;
z += ((p >> 8) & 0xff) * kernel[k]; w += ((p >> 8) & 0xff) * multiplier;
w += ((p >> 0) & 0xff) * kernel[k]; z += ((p >> 0) & 0xff) * multiplier;
} }
d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
d[j] = (u / sum << 24) | (v / sum << 16) | (w / sum << 8) | z / sum;
} }
} }
/* Then vertically blur from tmp -> surface */ /* Then vertically blur from tmp -> surface */
for (i = start_y; i < max_y; i++) { for (i = start_y; i < end_y; i++) {
s = (uint32_t *)(dst + i * dst_stride); d = (uint32_t *)(dst + i * dst_stride);
d = (uint32_t *)(src + i * src_stride); for (j = start_x; j < end_x; j++) {
for (j = start_x; j < max_x; j++) { u = v = w = z = 0;
x = y = z = w = 0; for (k = 0; k < gaussian->size; k++) {
for (k = 0; k < size; k++) { gdouble multiplier = gaussian->kernel[k];
if (i - half + k < 0 || i - half + k >= height) {
if (i - half + k < 0 || i - half + k >= src_height) {
continue; continue;
} }
s = (uint32_t *)(dst + (i - half + k) * dst_stride); s = (uint32_t *)(tmp + (i - half + k) * dst_stride);
p = s[j]; p = s[j];
x += ((p >> 24) & 0xff) * kernel[k]; u += ((p >> 24) & 0xff) * multiplier;
y += ((p >> 16) & 0xff) * kernel[k]; v += ((p >> 16) & 0xff) * multiplier;
z += ((p >> 8) & 0xff) * kernel[k]; w += ((p >> 8) & 0xff) * multiplier;
w += ((p >> 0) & 0xff) * kernel[k]; z += ((p >> 0) & 0xff) * multiplier;
} }
d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
d[j] = (u / sum << 24) | (v / sum << 16) | (w / sum << 8) | z / sum;
} }
} }
cairo_surface_destroy(tmp); // Mark destination surface as dirty since it was altered with custom data.
cairo_surface_mark_dirty(surface); cairo_surface_mark_dirty(dest_surface);
cairo_surface_t *final =
cairo_image_surface_create(src_format, (int)width, (int)height);
cr = cairo_create(final);
cairo_set_source_surface(cr, dest_surface, -x, -y);
cairo_paint(cr);
cairo_destroy(cr);
cairo_surface_destroy(dest_surface);
cairo_surface_destroy(tmp_surface);
gaussian_kernel_free(gaussian);
return final;
} }
static void convert_pango_rectangle_to_swappy_box(pango_rectangle_t rectangle, static void convert_pango_rectangle_to_swappy_box(pango_rectangle_t rectangle,
@ -227,7 +222,7 @@ static void render_shape_arrow(cairo_t *cr, struct swappy_paint_shape shape) {
double r = 20; double r = 20;
double scaling_factor = shape.w / 4; double scaling_factor = shape.w / 4;
double alpha = M_PI / 6; double alpha = G_PI / 6;
double ta = 5 * alpha; double ta = 5 * alpha;
double tb = 7 * alpha; double tb = 7 * alpha;
double xa = r * cos(ta); double xa = r * cos(ta);
@ -284,7 +279,7 @@ static void render_shape_ellipse(cairo_t *cr, struct swappy_paint_shape shape) {
cairo_get_matrix(cr, &save_matrix); cairo_get_matrix(cr, &save_matrix);
cairo_translate(cr, xc, yc); cairo_translate(cr, xc, yc);
cairo_scale(cr, x / n, y / n); cairo_scale(cr, x / n, y / n);
cairo_arc(cr, 0, 0, r, 0, 2 * M_PI); cairo_arc(cr, 0, 0, r, 0, 2 * G_PI);
cairo_set_matrix(cr, &save_matrix); cairo_set_matrix(cr, &save_matrix);
cairo_stroke(cr); cairo_stroke(cr);
cairo_close_path(cr); cairo_close_path(cr);
@ -344,14 +339,37 @@ static void render_buffers(cairo_t *cr, struct swappy_state *state) {
cairo_restore(cr); cairo_restore(cr);
} }
static void render_background(cairo_t *cr) { static void render_background(cairo_t *cr, struct swappy_state *state) {
cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr); cairo_paint(cr);
} }
static void render_blur(cairo_t *cr, struct swappy_paint_blur blur, static void render_blur(cairo_t *cr, struct swappy_paint *paint,
bool is_committed, gint scaling_factor) { gint scaling_factor) {
if (!is_committed) { struct swappy_paint_blur blur = paint->content.blur;
cairo_surface_t *target = cairo_get_target(cr);
double x = MIN(blur.from.x, blur.to.x);
double y = MIN(blur.from.y, blur.to.y);
double w = ABS(blur.from.x - blur.to.x);
double h = ABS(blur.from.y - blur.to.y);
cairo_save(cr);
if (!paint->is_committed) {
cairo_surface_t *blurred =
blur_surface(target, x, y, w, h, blur.blur_level);
if (blurred && cairo_surface_status(blurred) == CAIRO_STATUS_SUCCESS) {
cairo_set_source_surface(cr, blurred, x, y);
cairo_paint(cr);
if (blur.surface) {
cairo_surface_destroy(blur.surface);
}
paint->content.blur.surface = blurred;
}
// Blur not committed yet, draw bounding rectangle // Blur not committed yet, draw bounding rectangle
struct swappy_paint_shape rect = { struct swappy_paint_shape rect = {
.r = 0, .r = 0,
@ -364,8 +382,16 @@ static void render_blur(cairo_t *cr, struct swappy_paint_blur blur,
.type = SWAPPY_PAINT_MODE_RECTANGLE, .type = SWAPPY_PAINT_MODE_RECTANGLE,
}; };
render_shape_rectangle(cr, rect); render_shape_rectangle(cr, rect);
} else {
cairo_surface_t *surface = blur.surface;
if (surface && cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
cairo_set_source_surface(cr, surface, x, y);
cairo_paint(cr);
}
} }
blur_paint(cr, &blur, scaling_factor);
cairo_restore(cr);
} }
static void render_brush(cairo_t *cr, struct swappy_paint_brush brush) { static void render_brush(cairo_t *cr, struct swappy_paint_brush brush) {
@ -395,7 +421,7 @@ static void render_paint(cairo_t *cr, struct swappy_paint *paint,
} }
switch (paint->type) { switch (paint->type) {
case SWAPPY_PAINT_MODE_BLUR: case SWAPPY_PAINT_MODE_BLUR:
render_blur(cr, paint->content.blur, paint->is_committed, scaling_factor); render_blur(cr, paint, scaling_factor);
break; break;
case SWAPPY_PAINT_MODE_BRUSH: case SWAPPY_PAINT_MODE_BRUSH:
render_brush(cr, paint->content.brush); render_brush(cr, paint->content.brush);
@ -426,14 +452,15 @@ static void render_paints(cairo_t *cr, struct swappy_state *state) {
} }
void render_state(struct swappy_state *state) { void render_state(struct swappy_state *state) {
cairo_t *cr = cairo_create(state->cairo_surface); cairo_surface_t *surface = state->cairo_surface;
cairo_t *cr = cairo_create(surface);
render_background(cr); render_background(cr, state);
render_buffers(cr, state); render_buffers(cr, state);
render_paints(cr, state); render_paints(cr, state);
cairo_destroy(cr);
// Drawing is finished, notify the GtkDrawingArea it needs to be redrawn. // Drawing is finished, notify the GtkDrawingArea it needs to be redrawn.
gtk_widget_queue_draw(state->ui->area); gtk_widget_queue_draw(state->ui->area);
cairo_destroy(cr);
} }

View File

@ -1,4 +1,3 @@
#include "util.h" #include "util.h"
#include <glib.h> #include <glib.h>
@ -37,3 +36,12 @@ gchar *string_insert_chars_at(gchar *str, gchar *chars, size_t pos) {
return new_str; return new_str;
} }
void pixel_data_print(guint32 pixel) {
const guint32 r = pixel >> 24 & 0xff;
const guint32 g = pixel >> 16 & 0xff;
const guint32 b = pixel >> 8 & 0xff;
const guint32 a = pixel >> 0 & 0xff;
g_debug("rgba(%u, %d, %u, %u)", r, g, b, a);
}

BIN
test/images/passwords.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB