diff --git a/src/blob.rs b/src/blob.rs index bd426a35c0..b03c5c5856 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 `Config::MediaQuality`. 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,20 +429,43 @@ impl<'a> BlobObject<'a> { }); if do_scale { + let n_px_longest_side = max(img.width(), img.height()); + let n_all_px = img.width() * img.height(); + + // 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. + let mut target_wh = if !is_avatar { + let n_all_px_sqrt = f64::from(n_all_px).sqrt(); + // 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. + let mut resolution_limit = + (f64::from(n_px_longest_side) * (f64::from(max_wh) / n_all_px_sqrt)) as u32; + // Align (at least) two sides of the resampled image to a multiple of 8 pixels, + // to have fewer partially used JPEG-blocks (which represent 8x8 pixels each). + while !resolution_limit.is_multiple_of(8) { + resolution_limit -= 1 + } + resolution_limit + } else { + max_wh + }; + + if target_wh > n_px_longest_side { + target_wh = n_px_longest_side; + }; + loop { if mem::take(&mut add_white_bg) { self::add_white_bg(&mut img); } - // resize() results in often slightly better quality, - // however, comes at high price of being 4+ times slower than thumbnail(). - // for a typical camera image that is sent, this may be a change from "instant" (500ms) to "long time waiting" (3s). - // as we do not have recoding in background while chat has already a preview, - // we vote for speed. - // exception is the avatar image: this is far more often sent than recoded, - // usually has less pixels by cropping, UI that needs to wait anyways, - // and also benefits from slightly better (5%) encoding of Triangle-filtered images. - let new_img = if is_avatar { + // resize() results in better quality than thumbnail(), + // however, comes at the price of being 4+ times slower than thumbnail(). + // For a typical camera-image that is sent (often more than 8 megapixels), + // this may be a change from "instant" (500ms) to "long time waiting" (3s). + // As we do not have recoding in the background while the chat already has a preview, + // we vote for speed, if the original image has a high resolution (> 2.56 megapixels). + let new_img = if is_avatar || n_all_px <= 1600 * 1600 { img.resize(target_wh, target_wh, image::imageops::FilterType::Triangle) } else { img.thumbnail(target_wh, target_wh) @@ -467,7 +485,7 @@ impl<'a> BlobObject<'a> { )); } - target_wh = target_wh * 2 / 3; + target_wh = target_wh * 7 / 8; } else { info!( context, diff --git a/src/blob/blob_tests.rs b/src/blob/blob_tests.rs index ed5a2b0017..63763f54a6 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: 848, + compressed_height: 477, ..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: 848, + compressed_height: 477, ..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: 1704, + compressed_height: 959, ..Default::default() } .test() 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 diff --git a/src/tests/pre_messages/additional_text.rs b/src/tests/pre_messages/additional_text.rs index 0af33ec8c6..40204a9c97 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 – 210.38 KiB]".to_owned()); Ok(()) } diff --git a/src/tests/pre_messages/receiving.rs b/src/tests/pre_messages/receiving.rs index 0657f523fd..3f7e38467d 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(215424)); + assert_eq!(msg.get_height(), 1704); + assert_eq!(msg.get_width(), 959); Ok(()) }