From 4b4e24e71af4d7ae5b062954acf60b79e4273715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Sat, 23 Jul 2022 20:39:19 +0200 Subject: [PATCH] Work around broken Cairo Quartz backend on macOS Pre-render the padded pattern, costing us 2 megabytes of memory there. --- fiv-browser.c | 100 +++++++++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/fiv-browser.c b/fiv-browser.c index 89fb00c..25f12f4 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -24,12 +24,14 @@ #ifdef GDK_WINDOWING_X11 #include #endif // GDK_WINDOWING_X11 +#ifdef GDK_WINDOWING_QUARTZ +#include +#endif // GDK_WINDOWING_QUARTZ #include "fiv-browser.h" #include "fiv-context-menu.h" #include "fiv-io.h" #include "fiv-thumbnail.h" -#include "fiv-view.h" // --- Widget ------------------------------------------------------------------ // _________________________________ @@ -85,7 +87,10 @@ struct _FivBrowser { GList *thumbnailers_queue; ///< Queued up Entry pointers GdkCursor *pointer; ///< Cached pointer cursor - cairo_surface_t *glow; ///< CAIRO_FORMAT_A8 mask + cairo_pattern_t *glow; ///< CAIRO_FORMAT_A8 mask for corners + cairo_pattern_t *glow_padded; ///< CAIRO_FORMAT_A8 mask + int glow_w; ///< Glow corner width + int glow_h; ///< Glow corner height int item_border_x; ///< L/R .item margin + border int item_border_y; ///< T/B .item margin + border }; @@ -219,37 +224,30 @@ relayout(FivBrowser *self, int width) static void draw_outer_border(FivBrowser *self, cairo_t *cr, int width, int height) { - int offset_x = cairo_image_surface_get_width(self->glow); - int offset_y = cairo_image_surface_get_height(self->glow); - cairo_pattern_t *mask = cairo_pattern_create_for_surface(self->glow); cairo_matrix_t matrix; - cairo_pattern_set_extend(mask, CAIRO_EXTEND_PAD); cairo_save(cr); - cairo_translate(cr, -offset_x, -offset_y); - cairo_rectangle(cr, 0, 0, offset_x + width, offset_y + height); + cairo_translate(cr, -self->glow_w, -self->glow_h); + cairo_rectangle(cr, 0, 0, self->glow_w + width, self->glow_h + height); cairo_clip(cr); - cairo_mask(cr, mask); + cairo_mask(cr, self->glow_padded); cairo_restore(cr); cairo_save(cr); - cairo_translate(cr, width + offset_x, height + offset_y); - cairo_rectangle(cr, 0, 0, -offset_x - width, -offset_y - height); + cairo_translate(cr, width + self->glow_w, height + self->glow_h); + cairo_rectangle(cr, 0, 0, -self->glow_w - width, -self->glow_h - height); cairo_clip(cr); cairo_scale(cr, -1, -1); - cairo_mask(cr, mask); + cairo_mask(cr, self->glow_padded); cairo_restore(cr); - cairo_pattern_set_extend(mask, CAIRO_EXTEND_NONE); cairo_matrix_init_scale(&matrix, -1, 1); - cairo_matrix_translate(&matrix, -width - offset_x, offset_y); - cairo_pattern_set_matrix(mask, &matrix); - cairo_mask(cr, mask); + cairo_matrix_translate(&matrix, -width - self->glow_w, self->glow_h); + cairo_pattern_set_matrix(self->glow, &matrix); + cairo_mask(cr, self->glow); cairo_matrix_init_scale(&matrix, 1, -1); - cairo_matrix_translate(&matrix, offset_x, -height - offset_y); - cairo_pattern_set_matrix(mask, &matrix); - cairo_mask(cr, mask); - - cairo_pattern_destroy(mask); + cairo_matrix_translate(&matrix, self->glow_w, -height - self->glow_h); + cairo_pattern_set_matrix(self->glow, &matrix); + cairo_mask(cr, self->glow); } static GdkRectangle @@ -811,7 +809,8 @@ fiv_browser_finalize(GObject *gobject) g_hash_table_destroy(self->thumbnail_cache); - cairo_surface_destroy(self->glow); + cairo_pattern_destroy(self->glow_padded); + cairo_pattern_destroy(self->glow); g_clear_object(&self->pointer); replace_adjustment(self, &self->hadjustment, NULL); @@ -1544,14 +1543,14 @@ fiv_browser_style_updated(GtkWidget *widget) gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); gtk_style_context_restore(style); - const int glow_w = (margin.left + margin.right) / 2; - const int glow_h = (margin.top + margin.bottom) / 2; + self->glow_w = (margin.left + margin.right) / 2; + self->glow_h = (margin.top + margin.bottom) / 2; // Don't set different opposing sides, it will misrender, your problem. // When the style of the class changes, this virtual method isn't invoked, // so the update check is mildly pointless. - int item_border_x = glow_w + (border.left + border.right) / 2; - int item_border_y = glow_h + (border.top + border.bottom) / 2; + int item_border_x = self->glow_w + (border.left + border.right) / 2; + int item_border_y = self->glow_h + (border.top + border.bottom) / 2; if (item_border_x != self->item_border_x || item_border_y != self->item_border_y) { self->item_border_x = item_border_x; @@ -1559,22 +1558,21 @@ fiv_browser_style_updated(GtkWidget *widget) gtk_widget_queue_resize(widget); } + if (self->glow_padded) + cairo_pattern_destroy(self->glow_padded); if (self->glow) - cairo_surface_destroy(self->glow); - if (glow_w <= 0 || glow_h <= 0) { - self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0); - return; - } + cairo_pattern_destroy(self->glow); - self->glow = cairo_image_surface_create(CAIRO_FORMAT_A8, glow_w, glow_h); - unsigned char *data = cairo_image_surface_get_data(self->glow); - int stride = cairo_image_surface_get_stride(self->glow); + cairo_surface_t *corner = cairo_image_surface_create( + CAIRO_FORMAT_A8, MAX(0, self->glow_w), MAX(0, self->glow_h)); + unsigned char *data = cairo_image_surface_get_data(corner); + int stride = cairo_image_surface_get_stride(corner); // Smooth out the curve, so that the edge of the glow isn't too jarring. const double fade_factor = 1.5; - const int x_max = glow_w - 1; - const int y_max = glow_h - 1; + const int x_max = self->glow_w - 1; + const int y_max = self->glow_h - 1; const double x_scale = 1. / MAX(1, x_max); const double y_scale = 1. / MAX(1, y_max); for (int y = 0; y <= y_max; y++) @@ -1584,7 +1582,32 @@ fiv_browser_style_updated(GtkWidget *widget) double v = MIN(sqrt(xn * xn + yn * yn), 1); data[y * stride + x] = round(pow(1 - v, fade_factor) * 255); } - cairo_surface_mark_dirty(self->glow); + + cairo_surface_mark_dirty(corner); + self->glow = cairo_pattern_create_for_surface(corner); + self->glow_padded = cairo_pattern_create_for_surface(corner); + cairo_pattern_set_extend(self->glow_padded, CAIRO_EXTEND_PAD); + +#ifdef GDK_WINDOWING_QUARTZ + // Cairo's Quartz backend doesn't support CAIRO_EXTEND_PAD, work around it. + if (GDK_IS_QUARTZ_DISPLAY(gtk_widget_get_display(widget))) { + int max_size = fiv_thumbnail_sizes[FIV_THUMBNAIL_SIZE_MAX].size; + cairo_surface_t *padded = cairo_image_surface_create(CAIRO_FORMAT_A8, + cairo_image_surface_get_width(corner) + + max_size * FIV_THUMBNAIL_WIDE_COEFFICIENT, + cairo_image_surface_get_height(corner) + max_size); + cairo_t *cr = cairo_create(padded); + cairo_set_source(cr, self->glow_padded); + cairo_paint(cr); + cairo_destroy(cr); + + cairo_pattern_destroy(self->glow_padded); + self->glow_padded = cairo_pattern_create_for_surface(padded); + cairo_surface_destroy(padded); + } +#endif // GDK_WINDOWING_QUARTZ + + cairo_surface_destroy(corner); } static void @@ -1667,7 +1690,8 @@ fiv_browser_init(FivBrowser *self) self->thumbnailers[i].self = self; set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL); - self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0); + self->glow_padded = cairo_pattern_create_rgba(0, 0, 0, 0); + self->glow = cairo_pattern_create_rgba(0, 0, 0, 0); g_signal_connect_swapped(gtk_settings_get_default(), "notify::gtk-icon-theme-name", G_CALLBACK(reload_thumbnails), self);