Work around broken Cairo Quartz backend on macOS

Pre-render the padded pattern, costing us 2 megabytes of memory there.
This commit is contained in:
Přemysl Eric Janouch 2022-07-23 20:39:19 +02:00
parent 4e84d6a802
commit 4b4e24e71a
Signed by: p
GPG Key ID: A0420B94F92B9493
1 changed files with 62 additions and 38 deletions

View File

@ -24,12 +24,14 @@
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#endif // GDK_WINDOWING_X11 #endif // GDK_WINDOWING_X11
#ifdef GDK_WINDOWING_QUARTZ
#include <gdk/gdkquartz.h>
#endif // GDK_WINDOWING_QUARTZ
#include "fiv-browser.h" #include "fiv-browser.h"
#include "fiv-context-menu.h" #include "fiv-context-menu.h"
#include "fiv-io.h" #include "fiv-io.h"
#include "fiv-thumbnail.h" #include "fiv-thumbnail.h"
#include "fiv-view.h"
// --- Widget ------------------------------------------------------------------ // --- Widget ------------------------------------------------------------------
// _________________________________ // _________________________________
@ -85,7 +87,10 @@ struct _FivBrowser {
GList *thumbnailers_queue; ///< Queued up Entry pointers GList *thumbnailers_queue; ///< Queued up Entry pointers
GdkCursor *pointer; ///< Cached pointer cursor 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_x; ///< L/R .item margin + border
int item_border_y; ///< T/B .item margin + border int item_border_y; ///< T/B .item margin + border
}; };
@ -219,37 +224,30 @@ relayout(FivBrowser *self, int width)
static void static void
draw_outer_border(FivBrowser *self, cairo_t *cr, int width, int height) 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_matrix_t matrix;
cairo_pattern_set_extend(mask, CAIRO_EXTEND_PAD);
cairo_save(cr); cairo_save(cr);
cairo_translate(cr, -offset_x, -offset_y); cairo_translate(cr, -self->glow_w, -self->glow_h);
cairo_rectangle(cr, 0, 0, offset_x + width, offset_y + height); cairo_rectangle(cr, 0, 0, self->glow_w + width, self->glow_h + height);
cairo_clip(cr); cairo_clip(cr);
cairo_mask(cr, mask); cairo_mask(cr, self->glow_padded);
cairo_restore(cr); cairo_restore(cr);
cairo_save(cr); cairo_save(cr);
cairo_translate(cr, width + offset_x, height + offset_y); cairo_translate(cr, width + self->glow_w, height + self->glow_h);
cairo_rectangle(cr, 0, 0, -offset_x - width, -offset_y - height); cairo_rectangle(cr, 0, 0, -self->glow_w - width, -self->glow_h - height);
cairo_clip(cr); cairo_clip(cr);
cairo_scale(cr, -1, -1); cairo_scale(cr, -1, -1);
cairo_mask(cr, mask); cairo_mask(cr, self->glow_padded);
cairo_restore(cr); cairo_restore(cr);
cairo_pattern_set_extend(mask, CAIRO_EXTEND_NONE);
cairo_matrix_init_scale(&matrix, -1, 1); cairo_matrix_init_scale(&matrix, -1, 1);
cairo_matrix_translate(&matrix, -width - offset_x, offset_y); cairo_matrix_translate(&matrix, -width - self->glow_w, self->glow_h);
cairo_pattern_set_matrix(mask, &matrix); cairo_pattern_set_matrix(self->glow, &matrix);
cairo_mask(cr, mask); cairo_mask(cr, self->glow);
cairo_matrix_init_scale(&matrix, 1, -1); cairo_matrix_init_scale(&matrix, 1, -1);
cairo_matrix_translate(&matrix, offset_x, -height - offset_y); cairo_matrix_translate(&matrix, self->glow_w, -height - self->glow_h);
cairo_pattern_set_matrix(mask, &matrix); cairo_pattern_set_matrix(self->glow, &matrix);
cairo_mask(cr, mask); cairo_mask(cr, self->glow);
cairo_pattern_destroy(mask);
} }
static GdkRectangle static GdkRectangle
@ -811,7 +809,8 @@ fiv_browser_finalize(GObject *gobject)
g_hash_table_destroy(self->thumbnail_cache); 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); g_clear_object(&self->pointer);
replace_adjustment(self, &self->hadjustment, NULL); 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_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
gtk_style_context_restore(style); gtk_style_context_restore(style);
const int glow_w = (margin.left + margin.right) / 2; self->glow_w = (margin.left + margin.right) / 2;
const int glow_h = (margin.top + margin.bottom) / 2; self->glow_h = (margin.top + margin.bottom) / 2;
// Don't set different opposing sides, it will misrender, your problem. // Don't set different opposing sides, it will misrender, your problem.
// When the style of the class changes, this virtual method isn't invoked, // When the style of the class changes, this virtual method isn't invoked,
// so the update check is mildly pointless. // so the update check is mildly pointless.
int item_border_x = glow_w + (border.left + border.right) / 2; int item_border_x = self->glow_w + (border.left + border.right) / 2;
int item_border_y = glow_h + (border.top + border.bottom) / 2; int item_border_y = self->glow_h + (border.top + border.bottom) / 2;
if (item_border_x != self->item_border_x || if (item_border_x != self->item_border_x ||
item_border_y != self->item_border_y) { item_border_y != self->item_border_y) {
self->item_border_x = item_border_x; self->item_border_x = item_border_x;
@ -1559,22 +1558,21 @@ fiv_browser_style_updated(GtkWidget *widget)
gtk_widget_queue_resize(widget); gtk_widget_queue_resize(widget);
} }
if (self->glow_padded)
cairo_pattern_destroy(self->glow_padded);
if (self->glow) if (self->glow)
cairo_surface_destroy(self->glow); cairo_pattern_destroy(self->glow);
if (glow_w <= 0 || glow_h <= 0) {
self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
return;
}
self->glow = cairo_image_surface_create(CAIRO_FORMAT_A8, glow_w, glow_h); cairo_surface_t *corner = cairo_image_surface_create(
unsigned char *data = cairo_image_surface_get_data(self->glow); CAIRO_FORMAT_A8, MAX(0, self->glow_w), MAX(0, self->glow_h));
int stride = cairo_image_surface_get_stride(self->glow); 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. // Smooth out the curve, so that the edge of the glow isn't too jarring.
const double fade_factor = 1.5; const double fade_factor = 1.5;
const int x_max = glow_w - 1; const int x_max = self->glow_w - 1;
const int y_max = glow_h - 1; const int y_max = self->glow_h - 1;
const double x_scale = 1. / MAX(1, x_max); const double x_scale = 1. / MAX(1, x_max);
const double y_scale = 1. / MAX(1, y_max); const double y_scale = 1. / MAX(1, y_max);
for (int y = 0; y <= y_max; y++) 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); double v = MIN(sqrt(xn * xn + yn * yn), 1);
data[y * stride + x] = round(pow(1 - v, fade_factor) * 255); 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 static void
@ -1667,7 +1690,8 @@ fiv_browser_init(FivBrowser *self)
self->thumbnailers[i].self = self; self->thumbnailers[i].self = self;
set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL); 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(), g_signal_connect_swapped(gtk_settings_get_default(),
"notify::gtk-icon-theme-name", G_CALLBACK(reload_thumbnails), self); "notify::gtk-icon-theme-name", G_CALLBACK(reload_thumbnails), self);