1 #include <glib.h>
2 #include <gtk/gtk.h>
3 #include <math.h>
4 #include <pango/pangocairo.h>
5
6 #include "algebra.h"
7 #include "swappy.h"
8 #include "util.h"
9
10 #define pango_layout_t PangoLayout
11 #define pango_font_description_t PangoFontDescription
12 #define pango_rectangle_t PangoRectangle
13
14 /*
15 * This code was largely taken from Kristian Høgsberg and Chris Wilson from:
16 * https://www.cairographics.org/cookbook/blur.c/
17 */
blur_surface(cairo_surface_t * surface,double x,double y,double width,double height)18 static cairo_surface_t *blur_surface(cairo_surface_t *surface, double x,
19 double y, double width, double height) {
20 cairo_surface_t *dest_surface, *tmp_surface, *final = NULL;
21 cairo_t *cr;
22 int src_width, src_height;
23 int src_stride, dst_stride;
24 guint u, v, w, z;
25 uint8_t *dst, *tmp;
26 uint32_t *s, *d, p;
27 int i, j, k;
28 const int radius = 4;
29 const double sigma = 3.1;
30 struct gaussian_kernel *gaussian = gaussian_kernel(radius, sigma);
31 const int size = gaussian->size;
32 const int half = (int)radius * 2;
33 gdouble scale_x, scale_y;
34 guint sum, pass, nb_passes;
35
36 sum = (guint)gaussian->sum;
37
38 if (cairo_surface_status(surface)) {
39 return NULL;
40 }
41
42 cairo_surface_get_device_scale(surface, &scale_x, &scale_y);
43
44 cairo_format_t src_format = cairo_image_surface_get_format(surface);
45 switch (src_format) {
46 case CAIRO_FORMAT_A1:
47 case CAIRO_FORMAT_A8:
48 default:
49 g_warning("source surface format: %d is not supported", src_format);
50 return NULL;
51 case CAIRO_FORMAT_RGB24:
52 case CAIRO_FORMAT_ARGB32:
53 break;
54 }
55
56 src_stride = cairo_image_surface_get_stride(surface);
57 src_width = cairo_image_surface_get_width(surface);
58 src_height = cairo_image_surface_get_height(surface);
59
60 g_assert(src_height >= height);
61 g_assert(src_width >= width);
62
63 dest_surface = cairo_image_surface_create(src_format, src_width, src_height);
64 tmp_surface = cairo_image_surface_create(src_format, src_width, src_height);
65
66 cairo_surface_set_device_scale(dest_surface, scale_x, scale_y);
67 cairo_surface_set_device_scale(tmp_surface, scale_x, scale_y);
68
69 if (cairo_surface_status(dest_surface) || cairo_surface_status(tmp_surface)) {
70 goto cleanup;
71 }
72
73 cr = cairo_create(tmp_surface);
74 cairo_set_source_surface(cr, surface, 0, 0);
75 cairo_paint(cr);
76 cairo_destroy(cr);
77
78 cr = cairo_create(dest_surface);
79 cairo_set_source_surface(cr, surface, 0, 0);
80 cairo_paint(cr);
81 cairo_destroy(cr);
82
83 dst = cairo_image_surface_get_data(dest_surface);
84 tmp = cairo_image_surface_get_data(tmp_surface);
85 dst_stride = cairo_image_surface_get_stride(dest_surface);
86
87 nb_passes = (guint)sqrt(scale_x * scale_y) + 1;
88
89 int start_x = CLAMP(x * scale_x, 0, src_width);
90 int start_y = CLAMP(y * scale_y, 0, src_height);
91
92 int end_x = CLAMP((x + width) * scale_x, 0, src_width);
93 int end_y = CLAMP((y + height) * scale_y, 0, src_height);
94
95 for (pass = 0; pass < nb_passes; pass++) {
96 /* Horizontally blur from dst -> tmp */
97 for (i = start_y; i < end_y; i++) {
98 s = (uint32_t *)(dst + i * src_stride);
99 d = (uint32_t *)(tmp + i * dst_stride);
100 for (j = start_x; j < end_x; j++) {
101 u = v = w = z = 0;
102 for (k = 0; k < size; k++) {
103 gdouble multiplier = gaussian->kernel[k];
104
105 if (j - half + k < 0 || j - half + k >= src_width) {
106 continue;
107 }
108
109 p = s[j - half + k];
110
111 u += ((p >> 24) & 0xff) * multiplier;
112 v += ((p >> 16) & 0xff) * multiplier;
113 w += ((p >> 8) & 0xff) * multiplier;
114 z += ((p >> 0) & 0xff) * multiplier;
115 }
116
117 d[j] = (u / sum << 24) | (v / sum << 16) | (w / sum << 8) | z / sum;
118 }
119 }
120
121 /* Then vertically blur from tmp -> dst */
122 for (i = start_y; i < end_y; i++) {
123 d = (uint32_t *)(dst + i * dst_stride);
124 for (j = start_x; j < end_x; j++) {
125 u = v = w = z = 0;
126 for (k = 0; k < size; k++) {
127 gdouble multiplier = gaussian->kernel[k];
128
129 if (i - half + k < 0 || i - half + k >= src_height) {
130 continue;
131 }
132
133 s = (uint32_t *)(tmp + (i - half + k) * dst_stride);
134 p = s[j];
135
136 u += ((p >> 24) & 0xff) * multiplier;
137 v += ((p >> 16) & 0xff) * multiplier;
138 w += ((p >> 8) & 0xff) * multiplier;
139 z += ((p >> 0) & 0xff) * multiplier;
140 }
141
142 d[j] = (u / sum << 24) | (v / sum << 16) | (w / sum << 8) | z / sum;
143 }
144 }
145 }
146
147 // Mark destination surface as dirty since it was altered with custom data.
148 cairo_surface_mark_dirty(dest_surface);
149
150 final = cairo_image_surface_create(src_format, (int)(width * scale_x),
151 (int)(height * scale_y));
152
153 if (cairo_surface_status(final)) {
154 goto cleanup;
155 }
156
157 cairo_surface_set_device_scale(final, scale_x, scale_y);
158 cr = cairo_create(final);
159 cairo_set_source_surface(cr, dest_surface, -x, -y);
160 cairo_paint(cr);
161 cairo_destroy(cr);
162
163 cleanup:
164 cairo_surface_destroy(dest_surface);
165 cairo_surface_destroy(tmp_surface);
166 gaussian_kernel_free(gaussian);
167 return final;
168 }
169
convert_pango_rectangle_to_swappy_box(pango_rectangle_t rectangle,struct swappy_box * box)170 static void convert_pango_rectangle_to_swappy_box(pango_rectangle_t rectangle,
171 struct swappy_box *box) {
172 if (!box) {
173 return;
174 }
175
176 box->x = pango_units_to_double(rectangle.x);
177 box->y = pango_units_to_double(rectangle.y);
178 box->width = pango_units_to_double(rectangle.width);
179 box->height = pango_units_to_double(rectangle.height);
180 }
181
render_text(cairo_t * cr,struct swappy_paint_text text)182 static void render_text(cairo_t *cr, struct swappy_paint_text text) {
183 char pango_font[255];
184 double x = fmin(text.from.x, text.to.x);
185 double y = fmin(text.from.y, text.to.y);
186 double w = fabs(text.from.x - text.to.x);
187 double h = fabs(text.from.y - text.to.y);
188
189 cairo_surface_t *surface =
190 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
191 cairo_t *crt = cairo_create(surface);
192
193 pango_layout_t *layout = pango_cairo_create_layout(crt);
194 pango_layout_set_text(layout, text.text, -1);
195 g_snprintf(pango_font, 255, "%s %d", text.font, (int)text.s);
196 pango_font_description_t *desc =
197 pango_font_description_from_string(pango_font);
198 pango_layout_set_width(layout, pango_units_from_double(w));
199 pango_layout_set_font_description(layout, desc);
200 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
201 pango_font_description_free(desc);
202
203 if (text.mode == SWAPPY_TEXT_MODE_EDIT) {
204 pango_rectangle_t strong_pos;
205 struct swappy_box cursor_box;
206 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 0.3);
207 cairo_set_line_width(cr, 5);
208 cairo_rectangle(cr, x, y, w, h);
209 cairo_stroke(cr);
210 glong bytes_til_cursor = string_get_nb_bytes_until(text.text, text.cursor);
211 pango_layout_get_cursor_pos(layout, bytes_til_cursor, &strong_pos, NULL);
212 convert_pango_rectangle_to_swappy_box(strong_pos, &cursor_box);
213 cairo_move_to(crt, cursor_box.x, cursor_box.y);
214 cairo_set_source_rgba(crt, 0.3, 0.3, 0.3, 1);
215 cairo_line_to(crt, cursor_box.x, cursor_box.y + cursor_box.height);
216 cairo_stroke(crt);
217 }
218
219 cairo_rectangle(crt, 0, 0, w, h);
220 cairo_set_source_rgba(crt, text.r, text.g, text.b, text.a);
221 cairo_move_to(crt, 0, 0);
222 pango_cairo_show_layout(crt, layout);
223
224 cairo_set_source_surface(cr, surface, x, y);
225 cairo_paint(cr);
226
227 cairo_destroy(crt);
228 cairo_surface_destroy(surface);
229 g_object_unref(layout);
230 }
231
render_shape_arrow(cairo_t * cr,struct swappy_paint_shape shape)232 static void render_shape_arrow(cairo_t *cr, struct swappy_paint_shape shape) {
233 cairo_set_source_rgba(cr, shape.r, shape.g, shape.b, shape.a);
234 cairo_set_line_width(cr, shape.w);
235
236 double ftx = shape.to.x - shape.from.x;
237 double fty = shape.to.y - shape.from.y;
238 double ftn = sqrt(ftx * ftx + fty * fty);
239
240 double r = 20;
241 double scaling_factor = shape.w / 4;
242
243 double alpha = G_PI / 6;
244 double ta = 5 * alpha;
245 double tb = 7 * alpha;
246 double xa = r * cos(ta);
247 double ya = r * sin(ta);
248 double xb = r * cos(tb);
249 double yb = r * sin(tb);
250 double xc = ftn - fabs(xa) * scaling_factor;
251
252 if (xc < DBL_EPSILON) {
253 xc = 0;
254 }
255
256 if (ftn < DBL_EPSILON) {
257 return;
258 }
259
260 double theta = copysign(1.0, fty) * acos(ftx / ftn);
261
262 // Draw line
263 cairo_save(cr);
264 cairo_translate(cr, shape.from.x, shape.from.y);
265 cairo_rotate(cr, theta);
266 cairo_move_to(cr, 0, 0);
267 cairo_line_to(cr, xc, 0);
268 cairo_stroke(cr);
269 cairo_restore(cr);
270
271 // Draw arrow
272 cairo_save(cr);
273 cairo_translate(cr, shape.to.x, shape.to.y);
274 cairo_rotate(cr, theta);
275 cairo_scale(cr, scaling_factor, scaling_factor);
276 cairo_move_to(cr, 0, 0);
277 cairo_line_to(cr, xa, ya);
278 cairo_line_to(cr, xb, yb);
279 cairo_line_to(cr, 0, 0);
280 cairo_fill(cr);
281 cairo_restore(cr);
282 }
283
render_shape_ellipse(cairo_t * cr,struct swappy_paint_shape shape)284 static void render_shape_ellipse(cairo_t *cr, struct swappy_paint_shape shape) {
285 double x = fabs(shape.from.x - shape.to.x);
286 double y = fabs(shape.from.y - shape.to.y);
287
288 double n = sqrt(x * x + y * y);
289
290 double xc, yc, r;
291
292 if (shape.should_center_at_from) {
293 xc = shape.from.x;
294 yc = shape.from.y;
295
296 r = n;
297 } else {
298 xc = shape.from.x + ((shape.to.x - shape.from.x) / 2);
299 yc = shape.from.y + ((shape.to.y - shape.from.y) / 2);
300
301 r = n / 2;
302 }
303
304 cairo_set_source_rgba(cr, shape.r, shape.g, shape.b, shape.a);
305 cairo_set_line_width(cr, shape.w);
306
307 cairo_matrix_t save_matrix;
308 cairo_get_matrix(cr, &save_matrix);
309 cairo_translate(cr, xc, yc);
310 cairo_scale(cr, x / n, y / n);
311 cairo_arc(cr, 0, 0, r, 0, 2 * G_PI);
312 cairo_set_matrix(cr, &save_matrix);
313 cairo_stroke(cr);
314 cairo_close_path(cr);
315 }
316
render_shape_rectangle(cairo_t * cr,struct swappy_paint_shape shape)317 static void render_shape_rectangle(cairo_t *cr,
318 struct swappy_paint_shape shape) {
319 double x, y, w, h;
320
321 if (shape.should_center_at_from) {
322 x = shape.from.x - fabs(shape.from.x - shape.to.x);
323 y = shape.from.y - fabs(shape.from.y - shape.to.y);
324 w = fabs(shape.from.x - shape.to.x) * 2;
325 h = fabs(shape.from.y - shape.to.y) * 2;
326 } else {
327 x = fmin(shape.from.x, shape.to.x);
328 y = fmin(shape.from.y, shape.to.y);
329 w = fabs(shape.from.x - shape.to.x);
330 h = fabs(shape.from.y - shape.to.y);
331 }
332
333 cairo_set_source_rgba(cr, shape.r, shape.g, shape.b, shape.a);
334 cairo_set_line_width(cr, shape.w);
335
336 cairo_rectangle(cr, x, y, w, h);
337 cairo_close_path(cr);
338
339 switch (shape.operation) {
340 case SWAPPY_PAINT_SHAPE_OPERATION_STROKE:
341 cairo_stroke(cr);
342 break;
343 case SWAPPY_PAINT_SHAPE_OPERATION_FILL:
344 cairo_fill(cr);
345 break;
346 default:
347 cairo_stroke(cr);
348 break;
349 }
350 }
351
render_shape(cairo_t * cr,struct swappy_paint_shape shape)352 static void render_shape(cairo_t *cr, struct swappy_paint_shape shape) {
353 cairo_save(cr);
354 switch (shape.type) {
355 case SWAPPY_PAINT_MODE_RECTANGLE:
356 render_shape_rectangle(cr, shape);
357 break;
358 case SWAPPY_PAINT_MODE_ELLIPSE:
359 render_shape_ellipse(cr, shape);
360 break;
361 case SWAPPY_PAINT_MODE_ARROW:
362 render_shape_arrow(cr, shape);
363 break;
364 default:
365 break;
366 }
367 cairo_restore(cr);
368 }
369
render_background(cairo_t * cr,struct swappy_state * state)370 static void render_background(cairo_t *cr, struct swappy_state *state) {
371 cairo_set_source_rgb(cr, 0, 0, 0);
372 cairo_paint(cr);
373 }
374
render_blur(cairo_t * cr,struct swappy_paint * paint)375 static void render_blur(cairo_t *cr, struct swappy_paint *paint) {
376 struct swappy_paint_blur blur = paint->content.blur;
377
378 cairo_surface_t *target = cairo_get_target(cr);
379
380 double x = MIN(blur.from.x, blur.to.x);
381 double y = MIN(blur.from.y, blur.to.y);
382 double w = ABS(blur.from.x - blur.to.x);
383 double h = ABS(blur.from.y - blur.to.y);
384
385 cairo_save(cr);
386
387 if (paint->is_committed) {
388 // Surface has already been blurred, reuse it in future passes
389 if (blur.surface) {
390 cairo_surface_t *surface = blur.surface;
391 if (surface && cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
392 cairo_set_source_surface(cr, surface, x, y);
393 cairo_paint(cr);
394 }
395 } else {
396 // Blur surface and reuse it in future passes
397 g_info(
398 "blurring surface on following image coordinates: %.2lf,%.2lf size: "
399 "%.2lfx%.2lf",
400 x, y, w, h);
401 cairo_surface_t *blurred = blur_surface(target, x, y, w, h);
402
403 if (blurred && cairo_surface_status(blurred) == CAIRO_STATUS_SUCCESS) {
404 cairo_set_source_surface(cr, blurred, x, y);
405 cairo_paint(cr);
406 paint->content.blur.surface = blurred;
407 }
408 }
409 } else {
410 // Blur not committed yet, draw bounding rectangle
411 struct swappy_paint_shape rect = {
412 .r = 0,
413 .g = 0.5,
414 .b = 1,
415 .a = 0.5,
416 .w = 5,
417 .from = blur.from,
418 .to = blur.to,
419 .type = SWAPPY_PAINT_MODE_RECTANGLE,
420 .operation = SWAPPY_PAINT_SHAPE_OPERATION_FILL,
421 };
422 render_shape_rectangle(cr, rect);
423 }
424
425 cairo_restore(cr);
426 }
427
render_brush(cairo_t * cr,struct swappy_paint_brush brush)428 static void render_brush(cairo_t *cr, struct swappy_paint_brush brush) {
429 cairo_set_source_rgba(cr, brush.r, brush.g, brush.b, brush.a);
430 cairo_set_line_width(cr, brush.w);
431 cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
432
433 guint l = g_list_length(brush.points);
434
435 if (l == 1) {
436 struct swappy_point *point = g_list_nth_data(brush.points, 0);
437 cairo_rectangle(cr, point->x, point->y, brush.w, brush.w);
438 cairo_fill(cr);
439 } else {
440 for (GList *elem = brush.points; elem; elem = elem->next) {
441 struct swappy_point *point = elem->data;
442 cairo_line_to(cr, point->x, point->y);
443 }
444 cairo_stroke(cr);
445 }
446 }
447
render_image(cairo_t * cr,struct swappy_state * state)448 static void render_image(cairo_t *cr, struct swappy_state *state) {
449 cairo_surface_t *surface = state->original_image_surface;
450
451 cairo_save(cr);
452
453 if (surface && !cairo_surface_status(surface)) {
454 cairo_set_source_surface(cr, surface, 0, 0);
455 cairo_paint(cr);
456 }
457
458 cairo_restore(cr);
459 }
460
render_paint(cairo_t * cr,struct swappy_paint * paint)461 static void render_paint(cairo_t *cr, struct swappy_paint *paint) {
462 if (!paint->can_draw) {
463 return;
464 }
465 switch (paint->type) {
466 case SWAPPY_PAINT_MODE_BLUR:
467 render_blur(cr, paint);
468 break;
469 case SWAPPY_PAINT_MODE_BRUSH:
470 render_brush(cr, paint->content.brush);
471 break;
472 case SWAPPY_PAINT_MODE_RECTANGLE:
473 case SWAPPY_PAINT_MODE_ELLIPSE:
474 case SWAPPY_PAINT_MODE_ARROW:
475 render_shape(cr, paint->content.shape);
476 break;
477 case SWAPPY_PAINT_MODE_TEXT:
478 render_text(cr, paint->content.text);
479 break;
480 default:
481 g_info("unable to render paint with type: %d", paint->type);
482 break;
483 }
484 }
485
render_paints(cairo_t * cr,struct swappy_state * state)486 static void render_paints(cairo_t *cr, struct swappy_state *state) {
487 for (GList *elem = g_list_last(state->paints); elem; elem = elem->prev) {
488 struct swappy_paint *paint = elem->data;
489 render_paint(cr, paint);
490 }
491
492 if (state->temp_paint) {
493 render_paint(cr, state->temp_paint);
494 }
495 }
496
render_state(struct swappy_state * state)497 void render_state(struct swappy_state *state) {
498 cairo_surface_t *surface = state->rendering_surface;
499 cairo_t *cr = cairo_create(surface);
500
501 render_background(cr, state);
502 render_image(cr, state);
503 render_paints(cr, state);
504
505 cairo_destroy(cr);
506
507 // Drawing is finished, notify the GtkDrawingArea it needs to be redrawn.
508 gtk_widget_queue_draw(state->ui->area);
509 }
510