+static gboolean
+avatar_chooser_maybe_convert_and_scale (EmpathyAvatarChooser *self,
+ GdkPixbuf *pixbuf,
+ GArray *avatar,
+ gchar *mime_type,
+ GArray **ret_avatar,
+ gchar **ret_mime_type)
+{
+ TpAvatarRequirements *req;
+ gboolean needs_conversion = FALSE;
+ guint width, height;
+ gchar *new_format_name = NULL;
+ gchar *new_mime_type = NULL;
+ gdouble min_factor, max_factor;
+ gdouble factor;
+ gchar *best_image_data = NULL;
+ gsize best_image_size = 0;
+ guint count = 0;
+
+ g_assert (ret_avatar != NULL);
+ g_assert (ret_mime_type != NULL);
+
+ req = get_requirements (self);
+ if (req == NULL)
+ {
+ DEBUG ("Avatar requirements not ready");
+ return FALSE;
+ }
+
+ /* Smaller is the factor, smaller will be the image.
+ * 0 is an empty image, 1 is the full size. */
+ min_factor = 0;
+ max_factor = 1;
+ factor = 1;
+
+ /* Check if we need to convert to another image format */
+ if (avatar_chooser_need_mime_type_conversion (mime_type,
+ req->supported_mime_types, &new_format_name, &new_mime_type))
+ {
+ DEBUG ("Format conversion needed, we'll use mime type '%s' "
+ "and format name '%s'. Current mime type is '%s'",
+ new_mime_type, new_format_name, mime_type);
+ needs_conversion = TRUE;
+ }
+
+ /* If there is no format we can use, report error to the user. */
+ if (new_mime_type == NULL || new_format_name == NULL)
+ {
+ avatar_chooser_error_show (self, _("Couldn't convert image"),
+ _("None of the accepted image formats are "
+ "supported on your system"));
+ return FALSE;
+ }
+
+ /* If width or height are too big, it needs converting. */
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ if ((req->maximum_width > 0 && width > req->maximum_width) ||
+ (req->maximum_height > 0 && height > req->maximum_height))
+ {
+ gdouble h_factor, v_factor;
+
+ h_factor = (gdouble) req->maximum_width / width;
+ v_factor = (gdouble) req->maximum_height / height;
+ factor = max_factor = MIN (h_factor, v_factor);
+
+ DEBUG ("Image dimensions (%dx%d) are too big. Max is %dx%d.",
+ width, height, req->maximum_width, req->maximum_height);
+
+ needs_conversion = TRUE;
+ }
+
+ /* If the data len is too big and no other conversion is needed,
+ * try with a lower factor. */
+ if (req->maximum_bytes > 0 && avatar->len > req->maximum_bytes &&
+ !needs_conversion)
+ {
+ DEBUG ("Image data (%u bytes) is too big "
+ "(max is %u bytes), conversion needed.",
+ avatar->len, req->maximum_bytes);
+
+ factor = 0.5;
+ needs_conversion = TRUE;
+ }
+
+ /* If no conversion is needed, return the avatar */
+ if (!needs_conversion)
+ {
+ *ret_avatar = g_array_ref (avatar);
+ *ret_mime_type = g_strdup (mime_type);
+ return TRUE;
+ }
+
+ do
+ {
+ GdkPixbuf *pixbuf_scaled = NULL;
+ gboolean saved;
+ gint new_width, new_height;
+ gchar *converted_image_data;
+ gsize converted_image_size;
+ GError *error = NULL;
+
+ if (factor != 1)
+ {
+ new_width = width * factor;
+ new_height = height * factor;
+ pixbuf_scaled = gdk_pixbuf_scale_simple (pixbuf,
+ new_width,
+ new_height,
+ GDK_INTERP_HYPER);
+ }
+ else
+ {
+ new_width = width;
+ new_height = height;
+ pixbuf_scaled = g_object_ref (pixbuf);
+ }
+
+ DEBUG ("Trying with factor %f (%dx%d) and format %s...", factor,
+ new_width, new_height, new_format_name);
+
+ saved = gdk_pixbuf_save_to_buffer (pixbuf_scaled,
+ &converted_image_data,
+ &converted_image_size,
+ new_format_name,
+ &error, NULL);
+ g_object_unref (pixbuf_scaled);
+
+ if (!saved)
+ {
+ g_free (new_format_name);
+ g_free (new_mime_type);
+ avatar_chooser_error_show (self,
+ _("Couldn't convert image"),
+ error ? error->message : NULL);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ DEBUG ("Produced an image data of %"G_GSIZE_FORMAT" bytes.",
+ converted_image_size);
+
+ /* If the new image satisfy the req, keep it as current best */
+ if (req->maximum_bytes == 0 || converted_image_size <= req->maximum_bytes)
+ {
+ g_free (best_image_data);
+
+ best_image_data = converted_image_data;
+ best_image_size = converted_image_size;
+
+ /* If this image is close enough to the optimal size,
+ * stop searching */
+ if (req->maximum_bytes == 0 ||
+ req->maximum_bytes - converted_image_size <= 1024)
+ break;
+ }
+ else
+ {
+ g_free (converted_image_data);
+ }
+
+ /* Make a binary search for the bigest factor that produce
+ * an image data size less than max_size */
+ if (converted_image_size > req->maximum_bytes)
+ max_factor = factor;
+ if (converted_image_size < req->maximum_bytes)
+ min_factor = factor;
+ factor = (min_factor + max_factor)/2;
+
+ if ((int) (width * factor) == new_width ||
+ (int) (height * factor) == new_height)
+ {
+ /* min_factor and max_factor are too close, so the new
+ * factor will produce the same image as previous
+ * iteration. No need to continue, we already found
+ * the optimal size. */
+ break;
+ }
+
+ /* Do 10 iterations in the worst case */
+ } while (++count < 10);
+
+ g_free (new_format_name);
+
+ /* FIXME: there is no way to create a GArray with zero copy? */
+ *ret_avatar = g_array_sized_new (FALSE, FALSE, sizeof (gchar),
+ best_image_size);
+ g_array_append_vals (*ret_avatar, best_image_data, best_image_size);
+ g_free (best_image_data);
+
+ *ret_mime_type = new_mime_type;
+
+ return TRUE;