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 {
double bluriness;
double blur_level;
struct swappy_point from;
struct swappy_point to;
cairo_surface_t *surface;
};
struct swappy_paint {

View File

@ -4,4 +4,5 @@
#include <glib.h>
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,
files([
'src/main.c',
'src/algebra.c',
'src/application.c',
'src/buffer.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");
cairo_surface_destroy(state->cairo_surface);
state->cairo_surface = gdk_window_create_similar_surface(
gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR,
cairo_surface_t *surface = gdk_window_create_similar_surface(
gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR_ALPHA,
gtk_widget_get_allocated_width(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);
return TRUE;

View File

@ -23,6 +23,9 @@ void paint_free(gpointer data) {
switch (paint->type) {
case SWAPPY_PAINT_MODE_BLUR:
if (paint->content.blur.surface) {
cairo_surface_destroy(paint->content.blur.surface);
}
break;
case SWAPPY_PAINT_MODE_BRUSH:
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) {
paint_commit_temporary(state);
} else {
g_free(state->temp_paint);
paint_free(state->temp_paint);
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:
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.y = y;
paint->content.blur.surface = NULL;
break;
case SWAPPY_PAINT_MODE_BRUSH:
paint->can_draw = true;
@ -147,6 +151,14 @@ void paint_update_temporary_shape(struct swappy_state *state, double x,
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) {
case SWAPPY_PAINT_MODE_BLUR:
paint->can_draw = true;

View File

@ -1,157 +1,152 @@
#include <gdk/gdk.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <math.h>
#include <pango/pangocairo.h>
#include "algebra.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_font_description_t PangoFontDescription
#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:
* https://www.cairographics.org/cookbook/blur.c/
*/
static void blur_paint(cairo_t *cr, struct swappy_paint_blur *blur,
gint scaling_factor) {
cairo_surface_t *tmp;
int width, height;
static cairo_surface_t *blur_surface(cairo_surface_t *surface, double x,
double y, double width, double height,
gint blur_level) {
cairo_surface_t *dest_surface, *tmp_surface;
cairo_t *cr;
int src_width, src_height;
int src_stride, dst_stride;
int x, y, z, w;
uint8_t *src, *dst;
uint32_t *s, *d, a, p;
guint u, v, w, z;
uint8_t *src, *dst, *tmp;
uint32_t *s, *d, p;
int i, j, k;
uint8_t kernel[17];
const int size = ARRAY_LENGTH(kernel);
const int size = (int)blur_level * 2 + 1;
const int half = size / 2;
double bluriness = blur->bluriness;
struct swappy_point from = swappy_point_scaled(blur->from, scaling_factor);
struct swappy_point to = swappy_point_scaled(blur->to, scaling_factor);
const double offset_y = 10.0;
guint sum;
cairo_surface_t *surface = cairo_get_target(cr);
if (cairo_surface_status(surface)) {
return NULL;
}
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)) {
cairo_format_t src_format = cairo_image_surface_get_format(surface);
switch (src_format) {
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:
default:
g_warning("source surface format: %d is not supported", src_format);
return NULL;
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);
src_width = cairo_image_surface_get_width(surface);
src_height = cairo_image_surface_get_height(surface);
g_debug("sizeof(src): %lu", sizeof(src));
g_debug("width*height*stride: %d", width * height * src_stride);
g_assert(src_height >= height);
g_assert(src_width >= width);
dst = cairo_image_surface_get_data(tmp);
dst_stride = cairo_image_surface_get_stride(tmp);
dest_surface = cairo_image_surface_create(src_format, src_width, src_height);
tmp_surface = cairo_image_surface_create(src_format, src_width, src_height);
a = 0;
for (i = 0; i < size; i++) {
double f = i - half;
a += kernel[i] = exp(-f * f / bluriness) * 80;
if (cairo_surface_status(dest_surface) || cairo_surface_status(tmp_surface)) {
return NULL;
}
int start_x = fmax(fmin(from.x, to.x), 0);
int start_y = fmax(fmin(from.y, to.y), 0);
cr = cairo_create(tmp_surface);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
int max_x = fmin(fmax(from.x, to.x), width);
int max_y = fmin(fmax(from.y, to.y), height);
cr = cairo_create(dest_surface);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
for (i = 0; i < height; i++) {
s = (uint32_t *)(src + i * src_stride);
d = (uint32_t *)(dst + i * dst_stride);
for (j = 0; j < width; j++) {
d[j] = s[j];
}
}
dst = cairo_image_surface_get_data(dest_surface);
tmp = cairo_image_surface_get_data(tmp_surface);
dst_stride = cairo_image_surface_get_stride(dest_surface);
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 */
for (i = start_y; i < max_y; i++) {
for (i = start_y; i < end_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;
d = (uint32_t *)(tmp + i * dst_stride);
for (j = start_x; j < end_x; j++) {
u = v = w = z = 0;
for (k = 0; k < gaussian->size; k++) {
gdouble multiplier = gaussian->kernel[k];
if (j - half + k < 0 || j - half + k >= src_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];
u += ((p >> 24) & 0xff) * multiplier;
v += ((p >> 16) & 0xff) * multiplier;
w += ((p >> 8) & 0xff) * multiplier;
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 */
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) {
for (i = start_y; i < end_y; i++) {
d = (uint32_t *)(dst + i * dst_stride);
for (j = start_x; j < end_x; j++) {
u = v = w = z = 0;
for (k = 0; k < gaussian->size; k++) {
gdouble multiplier = gaussian->kernel[k];
if (i - half + k < 0 || i - half + k >= src_height) {
continue;
}
s = (uint32_t *)(dst + (i - half + k) * dst_stride);
s = (uint32_t *)(tmp + (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];
u += ((p >> 24) & 0xff) * multiplier;
v += ((p >> 16) & 0xff) * multiplier;
w += ((p >> 8) & 0xff) * multiplier;
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);
cairo_surface_mark_dirty(surface);
// Mark destination surface as dirty since it was altered with custom data.
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,
@ -227,7 +222,7 @@ static void render_shape_arrow(cairo_t *cr, struct swappy_paint_shape shape) {
double r = 20;
double scaling_factor = shape.w / 4;
double alpha = M_PI / 6;
double alpha = G_PI / 6;
double ta = 5 * alpha;
double tb = 7 * alpha;
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_translate(cr, xc, yc);
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_stroke(cr);
cairo_close_path(cr);
@ -344,14 +339,37 @@ static void render_buffers(cairo_t *cr, struct swappy_state *state) {
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_paint(cr);
}
static void render_blur(cairo_t *cr, struct swappy_paint_blur blur,
bool is_committed, gint scaling_factor) {
if (!is_committed) {
static void render_blur(cairo_t *cr, struct swappy_paint *paint,
gint scaling_factor) {
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
struct swappy_paint_shape rect = {
.r = 0,
@ -364,8 +382,16 @@ static void render_blur(cairo_t *cr, struct swappy_paint_blur blur,
.type = SWAPPY_PAINT_MODE_RECTANGLE,
};
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) {
@ -395,7 +421,7 @@ 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, paint->is_committed, scaling_factor);
render_blur(cr, paint, scaling_factor);
break;
case SWAPPY_PAINT_MODE_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) {
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_paints(cr, state);
cairo_destroy(cr);
// Drawing is finished, notify the GtkDrawingArea it needs to be redrawn.
gtk_widget_queue_draw(state->ui->area);
cairo_destroy(cr);
}

View File

@ -1,4 +1,3 @@
#include "util.h"
#include <glib.h>
@ -36,4 +35,13 @@ gchar *string_insert_chars_at(gchar *str, gchar *chars, size_t pos) {
}
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