feat: Optimize avatar size multiplier for 2 << n px avatars

Instead of 2/3 which is not optimal for 512 px avatars usually passed to Core, use the sequence 7/8,
6/8, 11/16, 10/16... to do "smaller" downscaling steps and reduce aliasing effects.

Before, it was discussed that just 3/4 can be used. However:
- If we repeat the downscaling step, we get `3 ** n` as a numerator and this way increase aliasing
  effects on each step. The same for 7/8. Better limit the numerator value.
- As we do downscaling in a loop only for avatars which aren't changed frequently, let's do
  "smaller" steps and start from 7/8.
This commit is contained in:
iequidoo
2026-02-02 01:09:32 -03:00
parent ef718bb869
commit f48792b5c2
3 changed files with 26 additions and 3 deletions

View File

@@ -435,6 +435,8 @@ impl<'a> BlobObject<'a> {
});
if do_scale {
let (mut m, mut d) = (8, 8);
let wh = target_wh;
loop {
if mem::take(&mut add_white_bg) {
self::add_white_bg(&mut img);
@@ -467,8 +469,11 @@ impl<'a> BlobObject<'a> {
"Failed to scale image to below {max_bytes}B.",
));
}
target_wh = target_wh * 2 / 3;
(m, d) = match m > 6 {
true => (m - 1, d),
false => (11, d * 2),
};
target_wh = wh * m / d;
} else {
info!(
context,

View File

@@ -4,7 +4,9 @@ use super::*;
use crate::message::{Message, Viewtype};
use crate::param::Param;
use crate::sql;
use crate::test_utils::{self, AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext};
use crate::test_utils::{
self, AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext, TestContextManager,
};
use crate::tools::SystemTime;
fn check_image_size(path: impl AsRef<Path>, width: u32, height: u32) -> image::DynamicImage {
@@ -239,6 +241,22 @@ async fn test_selfavatar_in_blobdir() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_huge_selfavatar() -> Result<()> {
let mut tcm = TestContextManager::new();
let t = &tcm.unconfigured().await;
let avatar_src = t.get_blobdir().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/noise400x400.png");
fs::write(&avatar_src, bytes).await?;
t.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
.await?;
let avatar_cfg = t.get_config(Config::Selfavatar).await?.unwrap();
// At 6/8 the avatar is still huge, so it's downscaled to 11/16.
check_image_size(avatar_cfg, 275, 275);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_selfavatar_copy_without_recode() {
let t = TestContext::new().await;

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB