From 90df6aec51309372260b02b3a9bd518d35da4f40 Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Wed, 6 Jan 2010 19:23:09 +0100 Subject: Implemented threaded preloading of images. The number of preloads is currently hardcoded in preload.c, an option to change this will be added shortly. --- preload.c | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ preload.h | 22 +++++ wallpapers.c | 40 +++++++++ wallpapers.h | 2 + window_main.c | 100 ++++++++++++--------- 5 files changed, 402 insertions(+), 43 deletions(-) create mode 100644 preload.c create mode 100644 preload.h diff --git a/preload.c b/preload.c new file mode 100644 index 0000000..281bdf6 --- /dev/null +++ b/preload.c @@ -0,0 +1,281 @@ +#include +#include +#include + +#include "preload.h" +#include "wallpapers.h" + +guint preload_max = 6; + +static gboolean preload_thread_exit = FALSE; +static GHashTable *hashtable = NULL; +static GMutex *preload_hashtable_mutex = NULL, *preload_thread_mutex = NULL; +static GThread *preload_thread = NULL; + +static gpointer preload_thread_func(gpointer); + +struct preload_thread_data_t { + GArray *array; + gint win_width, win_height; +}; + +static void preload_hash_table_free(gpointer data) { + struct preload_hash_table_value_t *pt; + + pt = data; + + g_object_unref(pt->pb); + + g_free(data); +} + +void preload_init() { + preload_hashtable_mutex = g_mutex_new(); + preload_thread_mutex = g_mutex_new(); + + hashtable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, preload_hash_table_free); +} + +void preload_free() { + if(preload_thread) { + preload_thread_exit = TRUE; + g_thread_join(preload_thread); + } + + g_mutex_free(preload_hashtable_mutex); + g_mutex_free(preload_thread_mutex); + + g_hash_table_destroy(hashtable); + preload_hashtable_mutex = NULL; +} + +#define preload_index(i) (((i) / 2) + 1) * (((i) % 2 * (-2) + 1)) + +static GArray *preload_prepare_data(GtkWidget *thumbview) { + GList *list; + GArray *array; + GtkTreePath *sel_path, *path; + GtkTreeModel *model; + GtkTreeIter iter; + GValue value = {0}; + gchar *filename; + guint list_len; + gint selected_index, *indices, index; + gboolean b; + + array = g_array_new(FALSE, FALSE, sizeof(gchar*)); + + list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(thumbview)); + + list_len = g_list_length(list); + + if(list_len != 1) { + g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); + g_list_free(list); + + return array; + } + + sel_path = g_list_nth_data(list, 0); + + indices = gtk_tree_path_get_indices(sel_path); + if(!indices) { + g_warning("preload_thread: Failed to get indices"); + + g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); + g_list_free(list); + + return array; + } + + selected_index = indices[0]; + + model = gtk_icon_view_get_model(GTK_ICON_VIEW(thumbview)); + + /* Make sure the selected pixbuf is also preloaded. */ + if(gtk_tree_model_get_iter(model, &iter, sel_path)) { + gtk_tree_model_get_value(model, &iter, 1, &value); + filename = g_value_dup_string(&value); + g_value_unset(&value); + + g_array_append_val(array, filename); + } + + for(gint i = 0; i < preload_max; i++) { + /* Make sure the selected pixbuf is also preloaded. */ + if(array->len == 0 && gtk_tree_model_get_iter(model, &iter, sel_path)) { + gtk_tree_model_get_value(model, &iter, 1, &value); + filename = g_value_dup_string(&value); + g_value_unset(&value); + + g_array_append_val(array, filename); + } + + index = selected_index + preload_index(i); + if(index < 0) { + continue; + } + + path = gtk_tree_path_new_from_indices(index, -1); + + if(gtk_tree_path_get_depth(path) != 1) { + /* Assume we're out of bounds. */ + gtk_tree_path_free(path); + continue; + } + + b = gtk_tree_model_get_iter(model, &iter, path); + + gtk_tree_path_free(path); + + if(b == FALSE) + continue; + + gtk_tree_model_get_value(model, &iter, 1, &value); + filename = g_value_dup_string(&value); + g_value_unset(&value); + + g_array_append_val(array, filename); + } + + g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); + g_list_free(list); + + return array; +} + +void preload_start_thread(GtkWidget *thumbview, gint win_width, gint win_height) { + struct preload_thread_data_t *data; + GError *error = NULL; + + if(preload_thread) { + preload_thread_exit = TRUE; + g_thread_join(preload_thread); + } + + data = g_malloc(sizeof(struct preload_thread_data_t)); + + data->array = preload_prepare_data(thumbview); + data->win_width = win_width; + data->win_height = win_height; + + preload_thread_exit = FALSE; + preload_thread = g_thread_create(preload_thread_func, data, TRUE, &error); + + if(!preload_thread) { + g_error("%s", error->message); + g_error_free(error); + } +} + +void preload_clear() { + g_hash_table_remove_all(hashtable); +} + +static void preload_add(const gchar *filename, gpointer data) { + g_mutex_lock(preload_hashtable_mutex); + + g_hash_table_replace(hashtable, g_strdup(filename), data); + + g_mutex_unlock(preload_hashtable_mutex); +} + +gpointer preload_get(const gchar *filename) { + gpointer data; + + g_mutex_lock(preload_hashtable_mutex); + + data = g_hash_table_lookup(hashtable, filename); + + g_mutex_unlock(preload_hashtable_mutex); + + return data; +} + +inline static void add_pixbuf(const gchar *filename, gpointer _data) { + struct preload_thread_data_t *data; + struct preload_hash_table_value_t *value; + GdkPixbuf *orig, *pb; + GError *error = NULL; + gint width, height; + gdouble ratio; + + /* Check if we already loaded this filename. */ + if(preload_get(filename)) + return; + + orig = gdk_pixbuf_new_from_file(filename, &error); + + if(!orig) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + data = _data; + + pb = resize_pixbuf(orig, data->win_width, data->win_height, &width, &height, &ratio); + + value = g_malloc(sizeof(struct preload_hash_table_value_t)); + + if(pb) { + value->pb = pb; + value->width = width; + value->height = height; + value->ratio = ratio; + + g_object_unref(orig); + } else { + value->pb = orig; + value->width = 0; + value->height = 0; + value->ratio = 0; + } + + preload_add(filename, value); +} + +static gpointer preload_thread_func(gpointer _data) { + struct preload_thread_data_t *data; + GArray *array; + GList *keys; + gchar *added_name; + const gchar *key_name; + gboolean found; + + data = _data; + array = data->array; + + g_mutex_lock(preload_hashtable_mutex); + keys = g_hash_table_get_keys(hashtable); + g_mutex_unlock(preload_hashtable_mutex); + + for(int i = 0; i < g_list_length(keys) && preload_thread_exit == FALSE; i++) { + found = FALSE; + key_name = g_list_nth_data(keys, i); + for(int j = 0; j < array->len && preload_thread_exit == FALSE; j++) { + added_name = g_array_index(array, gchar*, j); + if(g_strcmp0(key_name, added_name) == 0) { + found = TRUE; + break; + } + } + if(found == FALSE) { + g_mutex_lock(preload_hashtable_mutex); + g_hash_table_remove(hashtable, key_name); + g_mutex_unlock(preload_hashtable_mutex); + } + } + + for(int i = 0; i < array->len && preload_thread_exit == FALSE; i++) { + added_name = g_array_index(array, gchar*, i); + add_pixbuf(added_name, data); + } + + g_list_free(keys); + g_array_free(array, TRUE); + + g_free(data); + preload_thread = NULL; + return NULL; +} diff --git a/preload.h b/preload.h new file mode 100644 index 0000000..dbadbeb --- /dev/null +++ b/preload.h @@ -0,0 +1,22 @@ +#ifndef _PRELOAD_H_ +#define _PRELOAD_H_ + +#include +#include +#include + +struct preload_hash_table_value_t { + GdkPixbuf *pb; + gint width, height; + gdouble ratio; +}; + +void preload_init(); +void preload_free(); +void preload_start_thread(GtkWidget*, gint, gint); +void preload_clear(); +gpointer preload_get(const gchar*); + +extern guint preload_max; + +#endif diff --git a/wallpapers.c b/wallpapers.c index 56a72c6..fd893f3 100644 --- a/wallpapers.c +++ b/wallpapers.c @@ -118,3 +118,43 @@ void add_dir_recursive(const gchar *path, sqlite_uint64 parent, GtkStatusbar *st g_free(msg); gdk_threads_leave(); } + +GdkPixbuf *resize_pixbuf(GdkPixbuf *orig, gint win_width, gint win_height, gint *_width, gint *_height, gdouble *ratio) { + GdkPixbuf *pb; + gint img_width, img_height, width, height; + gdouble scalex, scaley, width_ratio, height_ratio; + + img_width = gdk_pixbuf_get_width(orig); + img_height = gdk_pixbuf_get_height(orig); + + /* do we need to resize? */ + if(img_width > win_width || img_height > win_height) { + /* resize by width */ + width_ratio = (gdouble)img_width / (gdouble)win_width; + height_ratio = (gdouble)img_height / (gdouble)win_height; + if(width_ratio > height_ratio) { + width = win_width; + height = (gint)(1.0 / width_ratio * img_height); + scalex = (gdouble)width / (gdouble)img_width; + scaley = (gdouble)height / (gdouble)img_height; + } else { /* resize by height */ + height = win_height; + width = (gint)(1.0 / height_ratio * img_width); + scalex = (gdouble)width / (gdouble)img_width; + scaley = (gdouble)height / (gdouble)img_height; + } + if(width == 0 || height == 0) return NULL; + pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); + gdk_pixbuf_scale(orig, pb, 0, 0, width, height, 0, 0, scalex, scaley, GDK_INTERP_BILINEAR); + } else { + width = img_width; + height = img_height; + pb = gdk_pixbuf_copy(orig); + } + + *ratio = (gdouble)width / (gdouble)img_width; + *_width = width; + *_height = height; + + return pb; +} diff --git a/wallpapers.h b/wallpapers.h index ce4430c..049cba0 100644 --- a/wallpapers.h +++ b/wallpapers.h @@ -2,9 +2,11 @@ #define _WALLPAPERS_H_ #include +#include #include "db.h" void add_dir_recursive(const gchar*, sqlite_uint64, GtkStatusbar*); +GdkPixbuf *resize_pixbuf(GdkPixbuf*, gint, gint, gint*, gint*, gdouble*); #endif diff --git a/window_main.c b/window_main.c index 984a4e0..a550943 100644 --- a/window_main.c +++ b/window_main.c @@ -9,6 +9,7 @@ #include "thumbnails.h" #include "walls_conf.h" #include "window_tag.h" +#include "preload.h" struct wallpaper_t *cur_wall = NULL; GdkPixbuf *orig_pixbuf = NULL; @@ -113,58 +114,41 @@ static void set_resize_msg(gdouble scale) { g_free(msg); } -static void resize_pixbuf() { - GdkPixbuf *pb; +static void set_image_pixbuf_resized(GdkPixbuf *pb, gint win_width, gint win_height, gint width, gint height, gdouble ratio) { + set_resize_msg(ratio); + + gtk_widget_set_size_request(GTK_WIDGET(image), width, height); + gtk_widget_set_uposition(GTK_WIDGET(image), (width < win_width ? win_width / 2 - width / 2 : 0), + (height < win_height ? win_height / 2 - height / 2 : 0)); + gtk_image_set_from_pixbuf(image, pb); + + gtk_layout_set_size(GTK_LAYOUT(layout), win_width, win_height); +} + +static void set_image_pixbuf() { GdkWindow *window; - gint win_width, win_height, img_width, img_height, width, height; - gdouble scalex, scaley, width_ratio, height_ratio; + GdkPixbuf *pb; + gint win_width, win_height, width, height; + gdouble ratio; - /* Skip if no pixbuf is loaded yet. */ - if(!orig_pixbuf) return; + if(!orig_pixbuf) + return; window = gtk_widget_get_window(layout); gdk_drawable_get_size(window, &win_width, &win_height); - gtk_layout_set_size(GTK_LAYOUT(layout), win_width, win_height); - img_width = gdk_pixbuf_get_width(orig_pixbuf); - img_height = gdk_pixbuf_get_height(orig_pixbuf); - - /* do we need to resize? */ - if(img_width > win_width || img_height > win_height) { - /* resize by width */ - width_ratio = (gdouble)img_width / (gdouble)win_width; - height_ratio = (gdouble)img_height / (gdouble)win_height; - if(width_ratio > height_ratio) { - width = win_width; - height = (gint)(1.0 / width_ratio * img_height); - scalex = (gdouble)width / (gdouble)img_width; - scaley = (gdouble)height / (gdouble)img_height; - } else { /* resize by height */ - height = win_height; - width = (gint)(1.0 / height_ratio * img_width); - scalex = (gdouble)width / (gdouble)img_width; - scaley = (gdouble)height / (gdouble)img_height; - } - if(width == 0 || height == 0) return; - pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); - gdk_pixbuf_scale(orig_pixbuf, pb, 0, 0, width, height, 0, 0, scalex, scaley, GDK_INTERP_BILINEAR); - } else { - width = img_width; - height = img_height; - pb = gdk_pixbuf_copy(orig_pixbuf); - } + pb = resize_pixbuf(orig_pixbuf, win_width, win_height, &width, &height, &ratio); + if(!pb) + return; - gtk_widget_set_size_request(GTK_WIDGET(image), width, height); - gtk_widget_set_uposition(GTK_WIDGET(image), (width < win_width ? win_width / 2 - width / 2 : 0), (height < win_height ? win_height / 2 - height / 2 : 0)); - gtk_image_set_from_pixbuf(image, pb); + set_image_pixbuf_resized(pb, win_width, win_height, width, height, ratio); g_object_unref(pb); - - set_resize_msg((gdouble)width / (gdouble)img_width); } void on_window_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data) { if(orig_pixbuf && (allocation->width != last_width || allocation->height != last_height)) { - resize_pixbuf(); + preload_clear(); + set_image_pixbuf(); last_width = allocation->width; last_height = allocation->height; } @@ -176,7 +160,8 @@ static gboolean hpane_resize_idle_func(gpointer data) { pos = gtk_paned_get_position(GTK_PANED(data)); if(pos != hpane_last_pos) { - resize_pixbuf(); + preload_clear(); + set_image_pixbuf(); hpane_last_pos = pos; return TRUE; /* Ensure that we'll keep checking until we're done resizing. */ } @@ -208,11 +193,23 @@ static void load_pixbuf(const gchar *filepath) { GError *error = NULL; sqlite_uint64 wallid; struct wallpaper_t *wall; + struct preload_hash_table_value_t *pt; gchar *base; + GdkWindow *window; + gint win_width, win_height; if(orig_pixbuf) g_object_unref(orig_pixbuf); - orig_pixbuf = gdk_pixbuf_new_from_file(filepath, &error); + + pt = preload_get(filepath); + if(pt) + orig_pixbuf = gdk_pixbuf_copy(pt->pb); + else + orig_pixbuf = NULL; + + if(!orig_pixbuf) + orig_pixbuf = gdk_pixbuf_new_from_file(filepath, &error); + if(!orig_pixbuf) { g_warning("%s", error->message); g_error_free(error); @@ -246,7 +243,13 @@ static void load_pixbuf(const gchar *filepath) { walls_set_window_title(); } - resize_pixbuf(); + if(pt) { + window = gtk_widget_get_window(layout); + gdk_drawable_get_size(window, &win_width, &win_height); + + set_image_pixbuf_resized(orig_pixbuf, win_width, win_height, pt->width, pt->height, pt->ratio); + } else + set_image_pixbuf(); } /** @@ -326,6 +329,8 @@ void on_thumbview_selection_changed(GtkIconView *iconview, gpointer user_data) { GtkTreePath *path; GtkTreeModel *model; GtkTreeIter iter; + GdkWindow *window; + gint win_width, win_height; list = gtk_icon_view_get_selected_items(iconview); if(list) { @@ -341,6 +346,11 @@ void on_thumbview_selection_changed(GtkIconView *iconview, gpointer user_data) { g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); g_list_free(list); + + window = gtk_widget_get_window(layout); + gdk_drawable_get_size(window, &win_width, &win_height); + + preload_start_thread(GTK_WIDGET(thumbview), win_width, win_height); } } @@ -690,10 +700,14 @@ int gui_main(int argc, char **argv) { gtk_widget_show_all(GTK_WIDGET(window)); + preload_init(); + gdk_threads_enter(); gtk_main(); gdk_threads_leave(); + preload_free(); + db_close(); conf_close(); -- cgit v1.2.3