comparison pidgin/gtkutils.c @ 30051:3b24193663bc

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
author Mark Doliner <mark@kingant.net>
date Sat, 27 Mar 2010 21:29:18 +0000
parents 08d4ec689d66
children 70b0f46f2966 0625cebc84d7
comparison
equal deleted inserted replaced
30050:c35fd54ec64b 30051:3b24193663bc
2330 #endif 2330 #endif
2331 2331
2332 return dialog->icon_filesel; 2332 return dialog->icon_filesel;
2333 } 2333 }
2334 2334
2335 2335 /**
2336 * @return True if any string from array a exists in array b.
2337 */
2336 static gboolean 2338 static gboolean
2337 str_array_match(char **a, char **b) 2339 str_array_match(char **a, char **b)
2338 { 2340 {
2339 int i, j; 2341 int i, j;
2340 2342
2349 2351
2350 gpointer 2352 gpointer
2351 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len) 2353 pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
2352 { 2354 {
2353 PurplePluginProtocolInfo *prpl_info; 2355 PurplePluginProtocolInfo *prpl_info;
2356 PurpleBuddyIconSpec *spec;
2357 int orig_width, orig_height, new_width, new_height;
2358 GdkPixbufFormat *format;
2359 char **pixbuf_formats;
2354 char **prpl_formats; 2360 char **prpl_formats;
2355 int width, height; 2361 GError *error = NULL;
2356 char **pixbuf_formats = NULL;
2357 GdkPixbufFormat *format;
2358 GdkPixbuf *pixbuf;
2359 gchar *contents; 2362 gchar *contents;
2360 gsize length; 2363 gsize length;
2364 GdkPixbuf *pixbuf, *original;
2365 float scale_factor;
2366 int i;
2367 gchar *tmp;
2361 2368
2362 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); 2369 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
2363 2370 spec = &prpl_info->icon_spec;
2364 g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL); 2371 g_return_val_if_fail(spec->format != NULL, NULL);
2365 2372
2366 2373 format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height);
2367 format = gdk_pixbuf_get_file_info(path, &width, &height); 2374 if (format == NULL) {
2368 2375 purple_debug_warning("buddyicon", "Could not get file info of %s\n", path);
2369 if (format == NULL)
2370 return NULL; 2376 return NULL;
2377 }
2371 2378
2372 pixbuf_formats = gdk_pixbuf_format_get_extensions(format); 2379 pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
2373 prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0); 2380 prpl_formats = g_strsplit(spec->format, ",", 0);
2374 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */ 2381
2375 (!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */ 2382 if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
2376 (prpl_info->icon_spec.min_width <= width && 2383 (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
2377 prpl_info->icon_spec.max_width >= width && 2384 (spec->min_width <= orig_width && spec->max_width >= orig_width &&
2378 prpl_info->icon_spec.min_height <= height && 2385 spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */
2379 prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */
2380 { 2386 {
2381 g_strfreev(prpl_formats);
2382 g_strfreev(pixbuf_formats); 2387 g_strfreev(pixbuf_formats);
2383 2388
2384 /* We don't need to scale the image. */ 2389 if (!g_file_get_contents(path, &contents, &length, &error)) {
2385 contents = NULL; 2390 purple_debug_warning("buddyicon", "Could not get file contents "
2386 if (!g_file_get_contents(path, &contents, &length, NULL)) 2391 "of %s: %s\n", path, error->message);
2387 {
2388 g_free(contents);
2389 return NULL;
2390 }
2391 }
2392 else
2393 {
2394 int i;
2395 GError *error = NULL;
2396 GdkPixbuf *scale;
2397 gboolean success = FALSE;
2398
2399 g_strfreev(pixbuf_formats);
2400
2401 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2402 if (error) {
2403 purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
2404 g_error_free(error);
2405 g_strfreev(prpl_formats); 2392 g_strfreev(prpl_formats);
2406 return NULL; 2393 return NULL;
2407 } 2394 }
2408 2395
2409 if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) && 2396 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2410 (width < prpl_info->icon_spec.min_width || 2397 /* The supplied image fits the file size, dimensions and type
2411 width > prpl_info->icon_spec.max_width || 2398 constraints. Great! Return it without making any changes. */
2412 height < prpl_info->icon_spec.min_height || 2399 if (len)
2413 height > prpl_info->icon_spec.max_height)) 2400 *len = length;
2414 { 2401 g_strfreev(prpl_formats);
2415 int new_width = width; 2402 return contents;
2416 int new_height = height; 2403 }
2417 2404
2418 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height); 2405 /* The image was too big. Fall-through and try scaling it down. */
2419 2406 g_free(contents);
2420 scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height, 2407 } else {
2421 GDK_INTERP_HYPER); 2408 g_strfreev(pixbuf_formats);
2422 g_object_unref(G_OBJECT(pixbuf)); 2409 }
2423 pixbuf = scale; 2410
2424 } 2411 /* The original image wasn't compatible. Scale it or convert file type. */
2425 2412 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2413 if (error) {
2414 purple_debug_warning("buddyicon", "Could not open icon '%s' for "
2415 "conversion: %s\n", path, error->message);
2416 g_error_free(error);
2417 g_strfreev(prpl_formats);
2418 return NULL;
2419 }
2420 original = g_object_ref(G_OBJECT(pixbuf));
2421
2422 new_width = orig_width;
2423 new_height = orig_height;
2424
2425 /* Make sure the image is the correct dimensions */
2426 if (spec->scale_rules & PURPLE_ICON_SCALE_SEND &&
2427 (orig_width < spec->min_width || orig_width > spec->max_width ||
2428 orig_height < spec->min_height || orig_height > spec->max_height))
2429 {
2430 purple_buddy_icon_get_scale_size(spec, &new_width, &new_height);
2431
2432 g_object_unref(G_OBJECT(pixbuf));
2433 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2434 }
2435
2436 scale_factor = 1;
2437 do {
2426 for (i = 0; prpl_formats[i]; i++) { 2438 for (i = 0; prpl_formats[i]; i++) {
2427 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]); 2439 int quality = 100;
2428 /* The "compression" param wasn't supported until gdk-pixbuf 2.8. 2440 do {
2429 * Using it in previous versions causes the save to fail (and an assert message). */ 2441 const char *key = NULL;
2430 if (g_str_equal(prpl_formats[i], "png")) { 2442 const char *value = NULL;
2431 if (gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length, 2443 gchar tmp_buf[4];
2432 prpl_formats[i], &error, "compression", "9", NULL)) 2444
2445 purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]);
2446
2447 if (g_str_equal(prpl_formats[i], "png")) {
2448 key = "compression";
2449 value = "9";
2450 } else if (g_str_equal(prpl_formats[i], "jpeg")) {
2451 sprintf(tmp_buf, "%u", quality);
2452 key = "quality";
2453 value = tmp_buf;
2454 }
2455
2456 if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length,
2457 prpl_formats[i], &error, key, value, NULL))
2433 { 2458 {
2434 success = TRUE; 2459 /* The NULL checking of error is necessary due to this bug:
2460 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
2461 purple_debug_warning("buddyicon",
2462 "Could not convert to %s: %s\n", prpl_formats[i],
2463 (error && error->message) ? error->message : "Unknown error");
2464 g_error_free(error);
2465 error = NULL;
2466
2467 /* We couldn't convert to this image type. Try the next
2468 image type. */
2435 break; 2469 break;
2436 } 2470 }
2437 } else if (gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length, 2471
2438 prpl_formats[i], &error, NULL)) 2472 if (spec->max_filesize == 0 || length < spec->max_filesize) {
2439 { 2473 /* We were able to save the image as this image type and
2440 success = TRUE; 2474 have it be within the size constraints. Great! Return
2441 break; 2475 the image. */
2442 } 2476 purple_debug_info("buddyicon", "Converted image from "
2443 2477 "%dx%d to %dx%d, format=%s, quality=%u, "
2444 /* The NULL checking of error is necessary due to this bug: 2478 "filesize=%zu\n", orig_width, orig_height,
2445 * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */ 2479 new_width, new_height, prpl_formats[i], quality,
2446 purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i], 2480 length);
2447 (error && error->message) ? error->message : "Unknown error"); 2481 if (len)
2448 g_error_free(error); 2482 *len = length;
2449 error = NULL; 2483 g_strfreev(prpl_formats);
2450 } 2484 g_object_unref(G_OBJECT(pixbuf));
2451 g_strfreev(prpl_formats); 2485 g_object_unref(G_OBJECT(original));
2486 return contents;
2487 }
2488
2489 g_free(contents);
2490
2491 if (!g_str_equal(prpl_formats[i], "jpeg")) {
2492 /* File size was too big and we can't lower the quality,
2493 so skip to the next image type. */
2494 break;
2495 }
2496
2497 /* File size was too big, but we're dealing with jpeg so try
2498 lowering the quality. */
2499 quality -= 5;
2500 } while (quality >= 70);
2501 }
2502
2503 /* We couldn't save the image in any format that was below the max
2504 file size. Maybe we can reduce the image dimensions? */
2505 scale_factor *= 0.8;
2506 new_width = orig_width * scale_factor;
2507 new_height = orig_height * scale_factor;
2452 g_object_unref(G_OBJECT(pixbuf)); 2508 g_object_unref(G_OBJECT(pixbuf));
2453 if (!success) { 2509 pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
2454 purple_debug_error("buddyicon", "Could not convert icon to usable format.\n"); 2510 } while (new_width > 10 || new_height > 10);
2455 return NULL; 2511 g_strfreev(prpl_formats);
2456 } 2512 g_object_unref(G_OBJECT(pixbuf));
2457 } 2513 g_object_unref(G_OBJECT(original));
2458 2514
2459 /* Check the image size */ 2515 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2460 /* 2516 path, plugin->info->name);
2461 * TODO: If the file is too big, it would be cool if we checked if 2517 purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp);
2462 * the prpl supported jpeg, and then we could convert to that 2518 g_free(tmp);
2463 * and use a lower quality setting. 2519
2464 */ 2520 return NULL;
2465 if ((prpl_info->icon_spec.max_filesize != 0) &&
2466 (length > prpl_info->icon_spec.max_filesize))
2467 {
2468 gchar *tmp;
2469 tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
2470 path, plugin->info->name);
2471 purple_notify_error(NULL, _("Icon Error"),
2472 _("Could not set icon"), tmp);
2473 purple_debug_info("buddyicon",
2474 "'%s' was converted to an image which is %" G_GSIZE_FORMAT
2475 " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
2476 " bytes\n", path, length, plugin->info->name,
2477 prpl_info->icon_spec.max_filesize);
2478 g_free(tmp);
2479 return NULL;
2480 }
2481
2482 if (len)
2483 *len = length;
2484 return contents;
2485 } 2521 }
2486 2522
2487 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename) 2523 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
2488 { 2524 {
2489 PurpleBuddy *buddy; 2525 PurpleBuddy *buddy;