pidgin: 5596fda5: Change pidgin_convert_buddy_icon() to be...

markdoliner at pidgin.im markdoliner at pidgin.im
Sat Mar 27 17:30:58 EDT 2010


-----------------------------------------------------------------
Revision: 5596fda5b3d5323e89b01f2a273b71eb74156b46
Ancestor: 8eed0a9af24ccfb18ff90b20d0d4fce2c3a4e4d4
Author: markdoliner at pidgin.im
Date: 2010-03-27T21:29:18
Branch: im.pidgin.pidgin
URL: http://d.pidgin.im/viewmtn/revision/info/5596fda5b3d5323e89b01f2a273b71eb74156b46

Modified files:
        ChangeLog pidgin/gtkutils.c

ChangeLog: 

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

-------------- next part --------------
============================================================
--- ChangeLog	c867be2c5b5c28e3d4af08344a369aa692c7c6a0
+++ ChangeLog	776f726fe0c9e1371b11b21d16379552ed990e41
@@ -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)
============================================================
--- pidgin/gtkutils.c	5a2a8714105dcb078f7e3f7af1eb8a37f2847512
+++ pidgin/gtkutils.c	65d75e763324176eb33aa9c39fd863cca0db94d5
@@ -2332,7 +2332,9 @@ GtkWidget *pidgin_buddy_icon_chooser_new
 	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 @@ pidgin_convert_buddy_icon(PurplePlugin *
 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
 {
 	PurplePluginProtocolInfo *prpl_info;
+	PurpleBuddyIconSpec *spec;
+	int orig_width, orig_height, new_width, new_height;
+	GdkPixbufFormat *format;
+	char **pixbuf_formats;
 	char **prpl_formats;
-	int width, height;
-	char **pixbuf_formats = NULL;
-	GdkPixbufFormat *format;
-	GdkPixbuf *pixbuf;
+	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;
 
+		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 contents;
+		}
+
+		/* The image was too big.  Fall-through and try scaling it down. */
+		g_free(contents);
+	} else {
 		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);
-			g_strfreev(prpl_formats);
-			return NULL;
-		}
+	/* 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));
 
-		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;
+	new_width = orig_width;
+	new_height = orig_height;
 
-			purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height);
+	/* 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);
 
-			scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height,
-					GDK_INTERP_HYPER);
-			g_object_unref(G_OBJECT(pixbuf));
-			pixbuf = scale;
-		}
+		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)


More information about the Commits mailing list