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