From bfbea630e193cfe70e045609180671a750885f28 Mon Sep 17 00:00:00 2001 From: 72374 <250991390+72374@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:45:00 +0100 Subject: [PATCH 1/3] chore: Move the definition of the `target_wh`-variable to the only part of the function where it is used. --- src/blob.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index bd426a35c0..892996c32a 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -388,14 +388,9 @@ impl<'a> BlobObject<'a> { _ => img, }; - // max_wh is the maximum image width and height, i.e. the resolution-limit. - // target_wh target-resolution for resizing the image. + // max_wh is the maximum image width and height, i.e. the resolution-limit, + // as set by the media-quality-setting. let exceeds_wh = img.width() > max_wh || img.height() > max_wh; - let mut target_wh = if exceeds_wh { - max_wh - } else { - max(img.width(), img.height()) - }; let exceeds_max_bytes = nr_bytes > max_bytes as u64; let jpeg_quality = 75; @@ -434,6 +429,15 @@ impl<'a> BlobObject<'a> { }); if do_scale { + // target_wh will be used as the target-resolution for resizing the image, + // so that the longest sides of the image match the target-resolution, + // without changing the aspect-ratio. + let mut target_wh = if exceeds_wh { + max_wh + } else { + max(img.width(), img.height()) + }; + loop { if mem::take(&mut add_white_bg) { self::add_white_bg(&mut img); From f12afdf6d0f3fb53d92009003c3ebb8bc33ea47a Mon Sep 17 00:00:00 2001 From: 72374 <250991390+72374@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:07:28 +0100 Subject: [PATCH 2/3] feat: Make quality of images sent in chats more consistent Currently, the resolution of a resized image that was sent in a chat, depends on the aspect-ratio. Assuming the `balanced`-quality-setting is used, a square image, that is larger than the limits for resolution and file-size, will be resized to 1280x1280 (1,638,400 pixels), an image with an aspect-ratio of 16:9, will be resized to 1280x720 (921,600 pixels), and if the aspect-ratio is 32:9, to 1280x360 (460,800 pixels). This change makes it so, that the number of pixels, in images with different aspect-ratios, will be similar. --- src/blob.rs | 18 +++++++++++++++--- src/blob/blob_tests.rs | 12 ++++++------ src/tests/pre_messages/additional_text.rs | 2 +- src/tests/pre_messages/receiving.rs | 6 +++--- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index 892996c32a..2e55b9e5c2 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -429,13 +429,25 @@ impl<'a> BlobObject<'a> { }); if do_scale { + let resolution_of_longest_side_of_image = max(img.width(), img.height()); + let square_root_of_number_of_pixels_in_image = + f64::from(img.width() * img.height()).sqrt(); + // target_wh will be used as the target-resolution for resizing the image, // so that the longest sides of the image match the target-resolution, // without changing the aspect-ratio. - let mut target_wh = if exceeds_wh { - max_wh + let mut target_wh = if !is_avatar && exceeds_wh { + // Limit resolution to the number of pixels that fit within max_wh * max_wh, + // so that the image-quality does not depend on the aspect-ratio. + (f64::from(resolution_of_longest_side_of_image) + * (f64::from(max_wh) / square_root_of_number_of_pixels_in_image)) + as u32 } else { - max(img.width(), img.height()) + max_wh + }; + + if target_wh > resolution_of_longest_side_of_image { + target_wh = resolution_of_longest_side_of_image }; loop { diff --git a/src/blob/blob_tests.rs b/src/blob/blob_tests.rs index ed5a2b0017..9ce88fdd1f 100644 --- a/src/blob/blob_tests.rs +++ b/src/blob/blob_tests.rs @@ -384,8 +384,8 @@ async fn test_recode_image_balanced_png() { extension: "png", original_width: 1920, original_height: 1080, - compressed_width: constants::WORSE_IMAGE_SIZE, - compressed_height: constants::WORSE_IMAGE_SIZE * 1080 / 1920, + compressed_width: 853, + compressed_height: 480, ..Default::default() } .test() @@ -475,8 +475,8 @@ async fn test_recode_image_rgba_png_to_jpeg() { extension: "png", original_width: 1920, original_height: 1080, - compressed_width: constants::WORSE_IMAGE_SIZE, - compressed_height: constants::WORSE_IMAGE_SIZE * 1080 / 1920, + compressed_width: 853, + compressed_height: 480, ..Default::default() } .test() @@ -495,8 +495,8 @@ async fn test_recode_image_huge_jpg() { has_exif: true, original_width: 1920, original_height: 1080, - compressed_width: constants::BALANCED_IMAGE_SIZE, - compressed_height: constants::BALANCED_IMAGE_SIZE * 1080 / 1920, + compressed_width: 1706, + compressed_height: 960, ..Default::default() } .test() diff --git a/src/tests/pre_messages/additional_text.rs b/src/tests/pre_messages/additional_text.rs index 0af33ec8c6..75e97e9c6d 100644 --- a/src/tests/pre_messages/additional_text.rs +++ b/src/tests/pre_messages/additional_text.rs @@ -34,7 +34,7 @@ async fn test_additional_text_on_different_viewtypes() -> Result<()> { let (pre_message, _, _) = send_large_image_message(alice, a_group_id).await?; let msg = bob.recv_msg(&pre_message).await; assert_eq!(msg.text, "test".to_owned()); - assert_eq!(msg.get_text(), "test [Image – 146.12 KiB]".to_owned()); + assert_eq!(msg.get_text(), "test [Image – 227.50 KiB]".to_owned()); Ok(()) } diff --git a/src/tests/pre_messages/receiving.rs b/src/tests/pre_messages/receiving.rs index 0657f523fd..fbbd1c47bd 100644 --- a/src/tests/pre_messages/receiving.rs +++ b/src/tests/pre_messages/receiving.rs @@ -323,9 +323,9 @@ async fn test_receive_pre_message_image() -> Result<()> { // test that metadata is correctly returned by methods assert_eq!(msg.get_post_message_viewtype(), Some(Viewtype::Image)); // recoded image dimensions - assert_eq!(msg.get_filebytes(bob).await?, Some(149632)); - assert_eq!(msg.get_height(), 1280); - assert_eq!(msg.get_width(), 720); + assert_eq!(msg.get_filebytes(bob).await?, Some(232957)); + assert_eq!(msg.get_height(), 1706); + assert_eq!(msg.get_width(), 960); Ok(()) } From a32210dcc209b65baa0f259f07d941eec9012e6e Mon Sep 17 00:00:00 2001 From: 72374 <250991390+72374@users.noreply.github.com> Date: Wed, 4 Feb 2026 01:00:04 +0100 Subject: [PATCH 3/3] feat: Use the available file-size for avatar-images better The resolution-limits for avatar-images are currently 512x512 or 128x128. Reducing the resolution further, to 2/3 each step, can reduce the quality much more than is necessary to fit within the file-size-limits, which are currently 60 kB or 20 kB. An image made entirely of noise (which results in unusually large file-sizes), encoded with jpeg-quality 75, and 4:2:2-colour-subsampling (the format currently used for encoding images), can be below 60 kB at 227x227. For the lower file-size-limit of 20 kB, such images with a resolution of 128x128 already fit. More normal images will have a lower file-size at the same resolution. Before this change, the target-resolutions for resizing were: 512x512 -> 341x341 -> 227x227. And for the lower file-size-limit: 128x128 (does already fit). After this change, the target-resolutions for resizing will be: 512x512 -> 448x448 -> 392x392 -> 343x343 -> 300x300 -> 263x263 -> 230x230. And for the lower file-size-limit, those will be: 256x256 -> 224x224 -> 196x196 -> 172x172 -> 150x150 -> 131x131. The resolution-limit has been increased to 256x256, because the file-size of many images is still smaller than 20 kB when encoded at 256x256, and it can be a large improvement in quality. --- src/blob.rs | 3 ++- src/constants.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index 2e55b9e5c2..9411b2e198 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -483,7 +483,8 @@ impl<'a> BlobObject<'a> { )); } - target_wh = target_wh * 2 / 3; + // Note: This is only done for avatar-images. + target_wh = target_wh * 7 / 8; } else { info!( context, diff --git a/src/constants.rs b/src/constants.rs index 28eb4c0fed..6850cf243f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -199,7 +199,7 @@ pub const WORSE_IMAGE_BYTES: usize = 130_000; // max. width/height and bytes of an avatar pub(crate) const BALANCED_AVATAR_SIZE: u32 = 512; pub(crate) const BALANCED_AVATAR_BYTES: usize = 60_000; -pub(crate) const WORSE_AVATAR_SIZE: u32 = 128; +pub(crate) const WORSE_AVATAR_SIZE: u32 = 256; pub(crate) const WORSE_AVATAR_BYTES: usize = 20_000; // this also fits to Outlook servers don't allowing headers larger than 32k. // max. width/height of images scaled down because of being too huge