Mercurial > pidgin.yaz
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; |