feat: Don't scale down huge non-JPEGs, recode them first (#7977)

If after recoding to JPEG an image isn't huge anymore, don't scale it down, this preserves quality
of most screenshots. For JPEGs however we don't try to recode them w/o scaling down:
- They are already JPEG-encoded, maybe with higher quality, but anyway.
- We don't want extra CPU work for most photos.
This commit is contained in:
iequidoo
2026-03-15 09:53:40 -03:00
parent 942172a31a
commit 2e7e8c35da
5 changed files with 40 additions and 6 deletions

View File

@@ -416,6 +416,17 @@ impl<'a> BlobObject<'a> {
// also `Viewtype::Gif` (maybe renamed to `Animation`) should be used for animated
// images.
let do_scale = exceeds_max_bytes
// Don't recode huge JPEGs w/o resizing:
// - It may be huge because of high JPEG quality, but we don't know that.
// - We don't want extra CPU work for most photos.
&& (fmt == ImageFormat::Jpeg
|| encoded_img_exceeds_bytes(
context,
&img,
ofmt.clone(),
max_bytes,
&mut encoded,
)?)
|| is_avatar
&& (exceeds_wh
|| exif.is_some() && {
@@ -477,8 +488,7 @@ impl<'a> BlobObject<'a> {
}
}
}
if do_scale || exif.is_some() {
if !encoded.is_empty() || exif.is_some() {
// The file format is JPEG/PNG now, we may have to change the file extension
if !matches!(fmt, ImageFormat::Jpeg)
&& matches!(ofmt, ImageOutputFormat::Jpeg { .. })

View File

@@ -462,6 +462,26 @@ async fn test_recode_image_balanced_png() {
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_recode_image_balanced_png_huge() {
let bytes = include_bytes!("../../test-data/image/screenshot-huge.png");
SendImageCheckMediaquality {
viewtype: Viewtype::Image,
media_quality_config: "0",
bytes,
extension: "jpg",
original_width: 1618,
original_height: 949,
compressed_width: 1618,
compressed_height: 949,
..Default::default()
}
.test()
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sticker_with_exif() {
let bytes = include_bytes!("../../test-data/image/logo-exif.png");

View File

@@ -34,7 +34,8 @@ 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!(msg.get_text().starts_with("test [Image "));
assert!(msg.get_text().ends_with(" KiB]"));
Ok(())
}

View File

@@ -6,6 +6,7 @@ use crate::EventType;
use crate::chat;
use crate::chat::send_msg;
use crate::config::Config;
use crate::constants;
use crate::contact;
use crate::download::{DownloadState, PRE_MSG_ATTACHMENT_SIZE_THRESHOLD, PostMsgMetadata};
use crate::message::{Message, MessageState, Viewtype, delete_msgs, markseen_msgs};
@@ -402,9 +403,11 @@ 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);
let n_bytes: usize = msg.get_filebytes(bob).await?.unwrap().try_into().unwrap();
assert!(100_000 < n_bytes);
assert!(n_bytes <= constants::BALANCED_IMAGE_BYTES);
assert_eq!(msg.get_height(), 1920);
assert_eq!(msg.get_width(), 1080);
Ok(())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB