summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Doliner <markdoliner@pidgin.im>2010-03-27 21:29:18 +0000
committerMark Doliner <markdoliner@pidgin.im>2010-03-27 21:29:18 +0000
commitc168c3b9672ee76523a02858abc7dbeb16e89e01 (patch)
tree62058c926ca7ec06659b413a254344e1c6b8c240
parentdeaf871f7411a504153675320ef591d6d738b890 (diff)
downloadpidgin-c168c3b9672ee76523a02858abc7dbeb16e89e01.tar.gz
Change pidgin_convert_buddy_icon() to be more accommodating when attempting
to scale a large buddy icon. I spent entirely too much time on this, but I'm pretty happy with the result. We now try to set increasingly lower quality levels when trying to save as a jpeg until we have an image that is smaller than the max file size limit specified by the prpl. If the image is still too large when quality level is 70, we'll try scaling down the image dimensions to be 80% of the size we just tried. Fixes #11565
-rw-r--r--ChangeLog2
-rw-r--r--pidgin/gtkutils.c240
2 files changed, 140 insertions, 102 deletions
diff --git a/ChangeLog b/ChangeLog
index 708d508e8c..c467eef51b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,8 @@ version 2.7.0 (??/??/????):
Ctrl+v can be bound to 'Paste as Plain Text' by default.
* Plugins can now handle markup in buddy names by attaching to the signal
"drawing-buddy". (Daniele Ricci, Andrea Piccinelli)
+ * Be more accommodating when scaling down large images for use as
+ buddy icons.
Bonjour:
* Added support for IPv6. (Thanks to T_X for testing)
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
index 5c0945382d..82ec16648c 100644
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -2332,7 +2332,9 @@ GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(cons
return dialog->icon_filesel;
}
-
+/**
+ * @return True if any string from array a exists in array b.
+ */
static gboolean
str_array_match(char **a, char **b)
{
@@ -2351,137 +2353,171 @@ gpointer
pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
{
PurplePluginProtocolInfo *prpl_info;
- char **prpl_formats;
- int width, height;
- char **pixbuf_formats = NULL;
+ PurpleBuddyIconSpec *spec;
+ int orig_width, orig_height, new_width, new_height;
GdkPixbufFormat *format;
- GdkPixbuf *pixbuf;
+ char **pixbuf_formats;
+ char **prpl_formats;
+ GError *error = NULL;
gchar *contents;
gsize length;
+ GdkPixbuf *pixbuf, *original;
+ float scale_factor;
+ int i;
+ gchar *tmp;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
+ spec = &prpl_info->icon_spec;
+ g_return_val_if_fail(spec->format != NULL, NULL);
- g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL);
-
-
- format = gdk_pixbuf_get_file_info(path, &width, &height);
-
- if (format == NULL)
+ format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
+ if (format == NULL) {
+ purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
return NULL;
+ }
pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
- prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0);
- if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
- (!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
- (prpl_info->icon_spec.min_width <= width &&
- prpl_info->icon_spec.max_width >= width &&
- prpl_info->icon_spec.min_height <= height &&
- prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */
+ prpl_formats = g_strsplit(spec->format, ",", 0);
+
+ if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
+ (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
+ (spec->min_width <= orig_width && spec->max_width >= orig_width &&
+ spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
{
- g_strfreev(prpl_formats);
g_strfreev(pixbuf_formats);
- /* We don't need to scale the image. */
- contents = NULL;
- if (!g_file_get_contents(path, &contents, &length, NULL))
- {
- g_free(contents);
+ if (!g_file_get_contents(path, &contents, &length, &error)) {
+ purple_debug_warning("buddyicon", "Could not get file contents "
+ "of %s: %s\n", path, error->message);
+ g_strfreev(prpl_formats);
return NULL;
}
- }
- else
- {
- int i;
- GError *error = NULL;
- GdkPixbuf *scale;
- gboolean success = FALSE;
- g_strfreev(pixbuf_formats);
-
- pixbuf = gdk_pixbuf_new_from_file(path, &error);
- if (error) {
- purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
- g_error_free(error);
+ if (spec->max_filesize == 0 || length < spec->max_filesize) {
+ /* The supplied image fits the file size, dimensions and type
+ constraints. Great! Return it without making any changes. */
+ if (len)
+ *len = length;
g_strfreev(prpl_formats);
- return NULL;
+ return contents;
}
- if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) &&
- (width < prpl_info->icon_spec.min_width ||
- width > prpl_info->icon_spec.max_width ||
- height < prpl_info->icon_spec.min_height ||
- height > prpl_info->icon_spec.max_height))
- {
- int new_width = width;
- int new_height = height;
+ /* The image was too big. Fall-through and try scaling it down. */
+ g_free(contents);
+ } else {
+ g_strfreev(pixbuf_formats);
+ }
+
+ /* The original image wasn't compatible. Scale it or convert file type. */
+ pixbuf = gdk_pixbuf_new_from_file(path, &error);
+ if (error) {
+ purple_debug_warning("buddyicon", "Could not open icon '%s' for "
+ "conversion: %s\n", path, error->message);
+ g_error_free(error);
+ g_strfreev(prpl_formats);
+ return NULL;
+ }
+ original = g_object_ref(G_OBJECT(pixbuf));
- purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height);
+ new_width = orig_width;
+ new_height = orig_height;
- scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height,
- GDK_INTERP_HYPER);
- g_object_unref(G_OBJECT(pixbuf));
- pixbuf = scale;
- }
+ /* Make sure the image is the correct dimensions */
+ if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
+ (orig_width < spec->min_width || orig_width > spec->max_width ||
+ orig_height < spec->min_height || orig_height > spec->max_height))
+ {
+ purple_buddy_icon_get_scale_size(spec, &new_width, &new_height);
+
+ g_object_unref(G_OBJECT(pixbuf));
+ pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
+ }
+ scale_factor = 1;
+ do {
for (i = 0; prpl_formats[i]; i++) {
- purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
- /* The "compression" param wasn't supported until gdk-pixbuf 2.8.
- * Using it in previous versions causes the save to fail (and an assert message). */
- if (g_str_equal(prpl_formats[i], "png")) {
- if (gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
- prpl_formats[i], &error, "compression", "9", NULL))
+ int quality = 100;
+ do {
+ const char *key = NULL;
+ const char *value = NULL;
+ gchar tmp_buf[4];
+
+ purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
+
+ if (g_str_equal(prpl_formats[i], "png")) {
+ key = "compression";
+ value = "9";
+ } else if (g_str_equal(prpl_formats[i], "jpeg")) {
+ sprintf(tmp_buf, "%u", quality);
+ key = "quality";
+ value = tmp_buf;
+ }
+
+ if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
+ prpl_formats[i], &error, key, value, NULL))
{
- success = TRUE;
+ /* The NULL checking of error is necessary due to this bug:
+ * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
+ purple_debug_warning("buddyicon",
+ "Could not convert to %s: %s\n", prpl_formats[i],
+ (error && error->message) ? error->message : "Unknown error");
+ g_error_free(error);
+ error = NULL;
+
+ /* We couldn't convert to this image type. Try the next
+ image type. */
break;
}
- } else if (gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
- prpl_formats[i], &error, NULL))
- {
- success = TRUE;
- break;
- }
- /* The NULL checking of error is necessary due to this bug:
- * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
- purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i],
- (error && error->message) ? error->message : "Unknown error");
- g_error_free(error);
- error = NULL;
+ if (spec->max_filesize == 0 || length < spec->max_filesize) {
+ /* We were able to save the image as this image type and
+ have it be within the size constraints. Great! Return
+ the image. */
+ purple_debug_info("buddyicon", "Converted image from "
+ "%dx%d to %dx%d, format=%s, quality=%u, "
+ "filesize=%zu\n", orig_width, orig_height,
+ new_width, new_height, prpl_formats[i], quality,
+ length);
+ if (len)
+ *len = length;
+ g_strfreev(prpl_formats);
+ g_object_unref(G_OBJECT(pixbuf));
+ g_object_unref(G_OBJECT(original));
+ return contents;
+ }
+
+ g_free(contents);
+
+ if (!g_str_equal(prpl_formats[i], "jpeg")) {
+ /* File size was too big and we can't lower the quality,
+ so skip to the next image type. */
+ break;
+ }
+
+ /* File size was too big, but we're dealing with jpeg so try
+ lowering the quality. */
+ quality -= 5;
+ } while (quality >= 70);
}
- g_strfreev(prpl_formats);
+
+ /* We couldn't save the image in any format that was below the max
+ file size. Maybe we can reduce the image dimensions? */
+ scale_factor *= 0.8;
+ new_width = orig_width * scale_factor;
+ new_height = orig_height * scale_factor;
g_object_unref(G_OBJECT(pixbuf));
- if (!success) {
- purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
- return NULL;
- }
- }
+ pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
+ } while (new_width > 10 || new_height > 10);
+ g_strfreev(prpl_formats);
+ g_object_unref(G_OBJECT(pixbuf));
+ g_object_unref(G_OBJECT(original));
- /* Check the image size */
- /*
- * TODO: If the file is too big, it would be cool if we checked if
- * the prpl supported jpeg, and then we could convert to that
- * and use a lower quality setting.
- */
- if ((prpl_info->icon_spec.max_filesize != 0) &&
- (length > prpl_info->icon_spec.max_filesize))
- {
- gchar *tmp;
- tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
- path, plugin->info->name);
- purple_notify_error(NULL, _("Icon Error"),
- _("Could not set icon"), tmp);
- purple_debug_info("buddyicon",
- "'%s' was converted to an image which is %" G_GSIZE_FORMAT
- " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
- " bytes\n", path, length, plugin->info->name,
- prpl_info->icon_spec.max_filesize);
- g_free(tmp);
- return NULL;
- }
+ tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
+ path, plugin->info->name);
+ purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp);
+ g_free(tmp);
- if (len)
- *len = length;
- return contents;
+ return NULL;
}
void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)