Skip to content

Commit c294bac

Browse files
committed
perf: draw crop selection as a live vector overlay instead of re-rasterizing the whole image per drag frame
1 parent 0ed2d0b commit c294bac

6 files changed

Lines changed: 177 additions & 132 deletions

File tree

‎src/meme-canvas.c‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,12 @@ void on_drag_update (GtkGestureDrag *gesture, double offset_x, double offset_y,
163163
self->selected_layer->pixbuf = NULL;
164164
}
165165
}
166-
render_meme(self);
166+
167+
if (self->drag_type == DRAG_TYPE_CROP_MOVE || self->drag_type == DRAG_TYPE_CROP_RESIZE) {
168+
gtk_widget_queue_draw(GTK_WIDGET(self->crop_overlay_area));
169+
} else {
170+
render_meme(self);
171+
}
167172
}
168173

169174
void on_drag_end (GtkGestureDrag *g, double x, double y, MemeWindow *self) {

‎src/meme-renderer.c‎

Lines changed: 118 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,123 @@ GdkPixbuf *meme_render_composite(GdkPixbuf *bg, GList *layers,
310310
return comp;
311311
}
312312

313+
void meme_draw_crop_chrome (cairo_t *cr, double w, double h,
314+
double abs_x, double abs_y, double abs_w, double abs_h) {
315+
double scale_ref = (w < h ? w : h) / 800.0;
316+
double corner_len = CLAMP(18.0 * scale_ref, 12.0, 26.0);
317+
double edge_len = CLAMP(14.0 * scale_ref, 10.0, 20.0);
318+
double handle_thick = 3.0;
319+
int i;
320+
321+
cairo_set_source_rgba(cr, 0, 0, 0, 0.55);
322+
if (abs_y > 0) {
323+
cairo_rectangle(cr, 0, 0, w, abs_y);
324+
cairo_fill(cr);
325+
}
326+
if (abs_y + abs_h < h) {
327+
cairo_rectangle(cr, 0, abs_y + abs_h, w, h - (abs_y + abs_h));
328+
cairo_fill(cr);
329+
}
330+
if (abs_x > 0) {
331+
cairo_rectangle(cr, 0, abs_y, abs_x, abs_h);
332+
cairo_fill(cr);
333+
}
334+
if (abs_x + abs_w < w) {
335+
cairo_rectangle(cr, abs_x + abs_w, abs_y, w - (abs_x + abs_w), abs_h);
336+
cairo_fill(cr);
337+
}
338+
339+
cairo_set_line_width(cr, 1.0);
340+
cairo_set_source_rgba(cr, 1, 1, 1, 0.35);
341+
for (i = 1; i <= 2; i++) {
342+
double gx = abs_x + abs_w * i / 3.0;
343+
double gy = abs_y + abs_h * i / 3.0;
344+
cairo_move_to(cr, gx, abs_y);
345+
cairo_line_to(cr, gx, abs_y + abs_h);
346+
cairo_move_to(cr, abs_x, gy);
347+
cairo_line_to(cr, abs_x + abs_w, gy);
348+
}
349+
cairo_stroke(cr);
350+
351+
352+
cairo_set_line_width(cr, 3.0);
353+
cairo_set_source_rgba(cr, 0, 0, 0, 0.45);
354+
cairo_rectangle(cr, abs_x, abs_y, abs_w, abs_h);
355+
cairo_stroke(cr);
356+
357+
cairo_set_line_width(cr, 1.5);
358+
cairo_set_source_rgba(cr, 1, 1, 1, 0.95);
359+
cairo_rectangle(cr, abs_x, abs_y, abs_w, abs_h);
360+
cairo_stroke(cr);
361+
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
362+
363+
{
364+
struct { double x, y, dx, dy; } corners[4] = {
365+
{ abs_x, abs_y, 1, 1 },
366+
{ abs_x + abs_w, abs_y, -1, 1 },
367+
{ abs_x, abs_y + abs_h, 1, -1 },
368+
{ abs_x + abs_w, abs_y + abs_h, -1, -1 },
369+
};
370+
int c;
371+
for (c = 0; c < 4; c++) {
372+
double x = corners[c].x, y = corners[c].y;
373+
double dx = corners[c].dx, dy = corners[c].dy;
374+
375+
cairo_set_line_width(cr, handle_thick + 2.0);
376+
cairo_set_source_rgba(cr, 0, 0, 0, 0.4);
377+
cairo_move_to(cr, x + dx * corner_len, y);
378+
cairo_line_to(cr, x, y);
379+
cairo_line_to(cr, x, y + dy * corner_len);
380+
cairo_stroke(cr);
381+
382+
cairo_set_line_width(cr, handle_thick);
383+
cairo_set_source_rgba(cr, 1, 1, 1, 1);
384+
cairo_move_to(cr, x + dx * corner_len, y);
385+
cairo_line_to(cr, x, y);
386+
cairo_line_to(cr, x, y + dy * corner_len);
387+
cairo_stroke(cr);
388+
}
389+
}
390+
391+
{
392+
struct { double x, y, horiz; } edges[4] = {
393+
{ abs_x + abs_w / 2.0, abs_y, 1 },
394+
{ abs_x + abs_w / 2.0, abs_y + abs_h, 1 },
395+
{ abs_x, abs_y + abs_h / 2.0, 0 },
396+
{ abs_x + abs_w, abs_y + abs_h / 2.0, 0 },
397+
};
398+
int e;
399+
for (e = 0; e < 4; e++) {
400+
double x = edges[e].x, y = edges[e].y;
401+
double half = edge_len / 2.0;
402+
403+
cairo_set_line_width(cr, handle_thick + 2.0);
404+
cairo_set_source_rgba(cr, 0, 0, 0, 0.4);
405+
if (edges[e].horiz) {
406+
cairo_move_to(cr, x - half, y);
407+
cairo_line_to(cr, x + half, y);
408+
} else {
409+
cairo_move_to(cr, x, y - half);
410+
cairo_line_to(cr, x, y + half);
411+
}
412+
cairo_stroke(cr);
413+
414+
cairo_set_line_width(cr, handle_thick);
415+
cairo_set_source_rgba(cr, 1, 1, 1, 1);
416+
if (edges[e].horiz) {
417+
cairo_move_to(cr, x - half, y);
418+
cairo_line_to(cr, x + half, y);
419+
} else {
420+
cairo_move_to(cr, x, y - half);
421+
cairo_line_to(cr, x, y + half);
422+
}
423+
cairo_stroke(cr);
424+
}
425+
}
426+
427+
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
428+
}
429+
313430
GdkTexture *meme_render_editor_overlay(GdkPixbuf *composite, GList *layers, ImageLayer *selected, gboolean crop_active, double cx, double cy, double cw, double ch) {
314431
GdkPixbuf *res;
315432
GdkTexture *tex;
@@ -330,123 +447,7 @@ GdkTexture *meme_render_editor_overlay(GdkPixbuf *composite, GList *layers, Imag
330447
cairo_paint(cr);
331448

332449
if (crop_active) {
333-
double abs_x = cx * w;
334-
double abs_y = cy * h;
335-
double abs_w = cw * w;
336-
double abs_h = ch * h;
337-
double scale_ref = (w < h ? w : h) / 800.0;
338-
double corner_len = CLAMP(18.0 * scale_ref, 12.0, 26.0);
339-
double edge_len = CLAMP(14.0 * scale_ref, 10.0, 20.0);
340-
double handle_thick = 3.0;
341-
int i;
342-
343-
cairo_set_source_rgba(cr, 0, 0, 0, 0.55);
344-
if (abs_y > 0) {
345-
cairo_rectangle(cr, 0, 0, w, abs_y);
346-
cairo_fill(cr);
347-
}
348-
if (abs_y + abs_h < h) {
349-
cairo_rectangle(cr, 0, abs_y + abs_h, w, h - (abs_y + abs_h));
350-
cairo_fill(cr);
351-
}
352-
if (abs_x > 0) {
353-
cairo_rectangle(cr, 0, abs_y, abs_x, abs_h);
354-
cairo_fill(cr);
355-
}
356-
if (abs_x + abs_w < w) {
357-
cairo_rectangle(cr, abs_x + abs_w, abs_y, w - (abs_x + abs_w), abs_h);
358-
cairo_fill(cr);
359-
}
360-
361-
cairo_set_line_width(cr, 1.0);
362-
cairo_set_source_rgba(cr, 1, 1, 1, 0.35);
363-
for (i = 1; i <= 2; i++) {
364-
double gx = abs_x + abs_w * i / 3.0;
365-
double gy = abs_y + abs_h * i / 3.0;
366-
cairo_move_to(cr, gx, abs_y);
367-
cairo_line_to(cr, gx, abs_y + abs_h);
368-
cairo_move_to(cr, abs_x, gy);
369-
cairo_line_to(cr, abs_x + abs_w, gy);
370-
}
371-
cairo_stroke(cr);
372-
373-
374-
cairo_set_line_width(cr, 3.0);
375-
cairo_set_source_rgba(cr, 0, 0, 0, 0.45);
376-
cairo_rectangle(cr, abs_x, abs_y, abs_w, abs_h);
377-
cairo_stroke(cr);
378-
379-
cairo_set_line_width(cr, 1.5);
380-
cairo_set_source_rgba(cr, 1, 1, 1, 0.95);
381-
cairo_rectangle(cr, abs_x, abs_y, abs_w, abs_h);
382-
cairo_stroke(cr);
383-
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
384-
385-
{
386-
struct { double x, y, dx, dy; } corners[4] = {
387-
{ abs_x, abs_y, 1, 1 },
388-
{ abs_x + abs_w, abs_y, -1, 1 },
389-
{ abs_x, abs_y + abs_h, 1, -1 },
390-
{ abs_x + abs_w, abs_y + abs_h, -1, -1 },
391-
};
392-
int c;
393-
for (c = 0; c < 4; c++) {
394-
double x = corners[c].x, y = corners[c].y;
395-
double dx = corners[c].dx, dy = corners[c].dy;
396-
397-
cairo_set_line_width(cr, handle_thick + 2.0);
398-
cairo_set_source_rgba(cr, 0, 0, 0, 0.4);
399-
cairo_move_to(cr, x + dx * corner_len, y);
400-
cairo_line_to(cr, x, y);
401-
cairo_line_to(cr, x, y + dy * corner_len);
402-
cairo_stroke(cr);
403-
404-
cairo_set_line_width(cr, handle_thick);
405-
cairo_set_source_rgba(cr, 1, 1, 1, 1);
406-
cairo_move_to(cr, x + dx * corner_len, y);
407-
cairo_line_to(cr, x, y);
408-
cairo_line_to(cr, x, y + dy * corner_len);
409-
cairo_stroke(cr);
410-
}
411-
}
412-
413-
{
414-
struct { double x, y, horiz; } edges[4] = {
415-
{ abs_x + abs_w / 2.0, abs_y, 1 },
416-
{ abs_x + abs_w / 2.0, abs_y + abs_h, 1 },
417-
{ abs_x, abs_y + abs_h / 2.0, 0 },
418-
{ abs_x + abs_w, abs_y + abs_h / 2.0, 0 },
419-
};
420-
int e;
421-
for (e = 0; e < 4; e++) {
422-
double x = edges[e].x, y = edges[e].y;
423-
double half = edge_len / 2.0;
424-
425-
cairo_set_line_width(cr, handle_thick + 2.0);
426-
cairo_set_source_rgba(cr, 0, 0, 0, 0.4);
427-
if (edges[e].horiz) {
428-
cairo_move_to(cr, x - half, y);
429-
cairo_line_to(cr, x + half, y);
430-
} else {
431-
cairo_move_to(cr, x, y - half);
432-
cairo_line_to(cr, x, y + half);
433-
}
434-
cairo_stroke(cr);
435-
436-
cairo_set_line_width(cr, handle_thick);
437-
cairo_set_source_rgba(cr, 1, 1, 1, 1);
438-
if (edges[e].horiz) {
439-
cairo_move_to(cr, x - half, y);
440-
cairo_line_to(cr, x + half, y);
441-
} else {
442-
cairo_move_to(cr, x, y - half);
443-
cairo_line_to(cr, x, y + half);
444-
}
445-
cairo_stroke(cr);
446-
}
447-
}
448-
449-
cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
450+
meme_draw_crop_chrome(cr, w, h, cx * w, cy * h, cw * w, ch * h);
450451
} else if (selected) {
451452
double sx = selected->x * w;
452453
double sy = selected->y * h;

‎src/meme-renderer.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ GdkTexture *meme_render_editor_overlay (GdkPixbuf *composite,
1515
ImageLayer *selected_layer,
1616
gboolean crop_active,
1717
double cx, double cy, double cw, double ch);
18+
19+
20+
void meme_draw_crop_chrome (cairo_t *cr, double w, double h,
21+
double abs_x, double abs_y, double abs_w, double abs_h);

‎src/meme-window-private.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct _MemeWindow {
1313
AdwToastOverlay *copy_clip_feedback;
1414
GtkStack *content_stack;
1515
GtkPicture *meme_preview;
16+
GtkDrawingArea *crop_overlay_area;
1617
GtkImage *add_text_button;
1718
AdwActionRow *font_choose_row;
1819
GtkFontDialogButton *font_choose_btn;

‎src/meme-window.blp‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,20 @@ template $MemeWindow: Adw.ApplicationWindow {
541541
margin-top: 10;
542542
margin-bottom: 10;
543543

544-
Picture meme_preview {
545-
content-fit: contain;
546-
can-shrink: true;
547-
hexpand: true;
548-
vexpand: false;
544+
Overlay {
545+
Picture meme_preview {
546+
content-fit: contain;
547+
can-shrink: true;
548+
hexpand: true;
549+
vexpand: false;
550+
}
551+
552+
[overlay]
553+
DrawingArea crop_overlay_area {
554+
can-target: false;
555+
hexpand: true;
556+
vexpand: false;
557+
}
549558
}
550559
};
551560
}

‎src/meme-window.c‎

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,38 @@ G_DEFINE_FINAL_TYPE (MemeWindow, meme_window, ADW_TYPE_APPLICATION_WINDOW)
3333

3434
static void populate_template_gallery (MemeWindow *self);
3535

36+
static void draw_crop_overlay (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
37+
MemeWindow *self = MEME_WINDOW (user_data);
38+
double img_w, img_h, scale, off_x, off_y;
39+
40+
if (!self->template_image || !gtk_toggle_button_get_active (self->crop_mode_button))
41+
return;
42+
43+
img_w = gdk_pixbuf_get_width (self->template_image);
44+
img_h = gdk_pixbuf_get_height (self->template_image);
45+
if (width <= 0 || height <= 0 || img_w <= 0 || img_h <= 0) return;
46+
47+
scale = MIN (width / img_w, height / img_h);
48+
off_x = (width - img_w * scale) / 2.0;
49+
off_y = (height - img_h * scale) / 2.0;
50+
51+
meme_draw_crop_chrome (cr, width, height,
52+
off_x + self->crop_x * img_w * scale,
53+
off_y + self->crop_y * img_h * scale,
54+
self->crop_w * img_w * scale,
55+
self->crop_h * img_h * scale);
56+
}
57+
3658
void render_meme (MemeWindow *self) {
37-
gboolean is_dragging, is_crop_drag, cinematic, deepfry, bw_button;
59+
gboolean is_dragging, is_crop_drag, crop_active, cinematic, deepfry, bw_button;
3860
GdkTexture *tex;
3961

4062
if (!self->template_image) return;
4163

4264
is_dragging = (self->drag_type != DRAG_TYPE_NONE);
4365
is_crop_drag = (self->drag_type == DRAG_TYPE_CROP_MOVE ||
4466
self->drag_type == DRAG_TYPE_CROP_RESIZE);
67+
crop_active = gtk_toggle_button_get_active(self->crop_mode_button);
4568
cinematic = gtk_toggle_button_get_active(self->cinematic_button);
4669
deepfry = gtk_toggle_button_get_active(self->deep_fry_button);
4770
bw_button = gtk_toggle_button_get_active(self->bw_button);
@@ -56,22 +79,20 @@ void render_meme (MemeWindow *self) {
5679
is_dragging);
5780
}
5881
gtk_widget_queue_draw(GTK_WIDGET(self->meme_preview));
59-
if (is_dragging && !is_crop_drag) {
60-
// FAST PATH: Skip the expensive blue overlay handles while dragging layers.
82+
83+
if (crop_active || (is_dragging && !is_crop_drag)) {
6184
tex = gdk_texture_new_for_pixbuf(self->final_meme);
6285
} else {
63-
// SLOW PATH: Draw the selection handles AND the crop overlay.
6486
tex = meme_render_editor_overlay(
65-
self->final_meme,
66-
self->layers,
67-
self->selected_layer,
68-
gtk_toggle_button_get_active(self->crop_mode_button),
69-
self->crop_x, self->crop_y, self->crop_w, self->crop_h
87+
self->final_meme, self->layers, self->selected_layer,
88+
FALSE, 0, 0, 0, 0
7089
);
7190
}
7291

7392
gtk_picture_set_paintable(self->meme_preview, GDK_PAINTABLE(tex));
7493
g_object_unref(tex);
94+
95+
gtk_widget_queue_draw(GTK_WIDGET(self->crop_overlay_area));
7596
}
7697

7798
static void on_color_changed (GObject *object, GParamSpec *pspec, MemeWindow *self) {
@@ -780,6 +801,7 @@ static void meme_window_class_init (MemeWindowClass *klass) {
780801
gtk_widget_class_bind_template_child (widget_class, MemeWindow, templates_group);
781802
gtk_widget_class_bind_template_child (widget_class, MemeWindow, transform_group);
782803
gtk_widget_class_bind_template_child (widget_class, MemeWindow, meme_preview);
804+
gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_overlay_area);
783805
gtk_widget_class_bind_template_child (widget_class, MemeWindow, content_stack);
784806
gtk_widget_class_bind_template_child (widget_class, MemeWindow, split_view);
785807
gtk_widget_class_bind_template_child (widget_class, MemeWindow, add_text_button);
@@ -916,6 +938,9 @@ static void meme_window_init (MemeWindow *self) {
916938
g_signal_connect_swapped (self->blend_mode_row, "notify::selected", G_CALLBACK (on_layer_control_changed), self);
917939
g_signal_connect_swapped (self->delete_layer_button, "clicked", G_CALLBACK (on_delete_layer_clicked), self);
918940

941+
gtk_drawing_area_set_draw_func (self->crop_overlay_area, draw_crop_overlay, self, NULL);
942+
gtk_widget_set_can_target (GTK_WIDGET (self->crop_overlay_area), FALSE);
943+
919944
// Handlers moved to meme-canvas.c
920945
self->drag_gesture = GTK_GESTURE_DRAG (gtk_gesture_drag_new ());
921946
gtk_widget_add_controller (GTK_WIDGET (self->meme_preview), GTK_EVENT_CONTROLLER (self->drag_gesture));

0 commit comments

Comments
 (0)