@@ -9,6 +9,7 @@ use async_std::{fs, io};
99
1010use anyhow:: format_err;
1111use anyhow:: Error ;
12+ use image:: DynamicImage ;
1213use image:: GenericImageView ;
1314use num_traits:: FromPrimitive ;
1415use thiserror:: Error ;
@@ -19,6 +20,7 @@ use crate::constants::{
1920 WORSE_IMAGE_SIZE ,
2021} ;
2122use crate :: context:: Context ;
23+ use crate :: dc_tools:: count_bytes;
2224use crate :: events:: EventType ;
2325use crate :: message;
2426
@@ -430,34 +432,64 @@ impl<'a> BlobObject<'a> {
430432 } ) ?;
431433 let orientation = self . get_exif_orientation ( context) ;
432434
435+ fn exceeds_bytes (
436+ context : & Context ,
437+ img : & DynamicImage ,
438+ max_bytes : Option < usize > ,
439+ ) -> anyhow:: Result < bool > {
440+ if let Some ( max_bytes) = max_bytes {
441+ let size = count_bytes ( img) ?;
442+ if size > max_bytes {
443+ info ! (
444+ context,
445+ "image size {}B ({}x{}px) exceeds {}B, need to scale down" ,
446+ size,
447+ img. width( ) ,
448+ img. height( ) ,
449+ max_bytes,
450+ ) ;
451+ return Ok ( true ) ;
452+ }
453+ }
454+ Ok ( false )
455+ } ;
433456 let exceeds_width = img. width ( ) > img_wh || img. height ( ) > img_wh;
457+
458+ let do_scale = exceeds_width || exceeds_bytes ( context, & img, max_bytes) ?;
434459 let do_rotate = matches ! ( orientation, Ok ( 90 ) | Ok ( 180 ) | Ok ( 270 ) ) ;
435- let exceeds_bytes = if let Some ( max_bytes) = max_bytes {
436- img. as_bytes ( ) . len ( ) > max_bytes
437- } else {
438- false
439- } ;
440460
441- if exceeds_width || do_rotate || exceeds_bytes {
442- let mut scaled_img = None ;
443- if exceeds_width {
444- scaled_img = Some ( img. thumbnail ( img_wh, img_wh) ) ;
445- }
446- if let Some ( max_bytes) = max_bytes {
447- while scaled_img. as_ref ( ) . unwrap_or ( & img) . as_bytes ( ) . len ( ) > max_bytes {
448- img_wh = img_wh * 2 / 3 ;
449- if img_wh < 20 {
450- Err ( format_err ! (
451- "Image width is <20, but size is still {}" ,
452- img. as_bytes( ) . len( )
453- ) ) ?
461+ if do_scale || do_rotate {
462+ if do_scale {
463+ if !exceeds_width {
464+ // The image is already smaller than img_wh, but exceeds max_bytes
465+ // We can directly start with trying to scale down to 2/3 of its current width
466+ img_wh = img. width ( ) * 2 / 3 // TODO should be {max or min}(img.width(), img.height())
467+ }
468+
469+ img = loop {
470+ let new_img = img. thumbnail ( img_wh, img_wh) ;
471+
472+ if exceeds_bytes ( context, & new_img, max_bytes) ? {
473+ if img_wh < 20 {
474+ return Err ( format_err ! (
475+ "Failed to scale image to below {}B" ,
476+ max_bytes. unwrap_or_default( )
477+ )
478+ . into ( ) ) ;
479+ }
480+
481+ img_wh = img_wh * 2 / 3 ;
482+ } else {
483+ info ! (
484+ context,
485+ "Final scaled-down image size: {}B ({}px)" ,
486+ count_bytes( & new_img) ?,
487+ img_wh
488+ ) ; // TODO dbg (?)
489+ break new_img;
454490 }
455- scaled_img = Some ( img. thumbnail ( img_wh, img_wh) ) ;
456491 }
457492 }
458- if let Some ( scaled) = scaled_img {
459- img = scaled;
460- }
461493
462494 if do_rotate {
463495 img = match orientation {
@@ -768,9 +800,25 @@ mod tests {
768800 assert_eq ! ( img. width( ) , 1000 ) ;
769801 assert_eq ! ( img. height( ) , 1000 ) ;
770802
771- let img = image:: open ( avatar_blob) . unwrap ( ) ;
803+ let img = image:: open ( & avatar_blob) . unwrap ( ) ;
772804 assert_eq ! ( img. width( ) , BALANCED_AVATAR_SIZE ) ;
773805 assert_eq ! ( img. height( ) , BALANCED_AVATAR_SIZE ) ;
806+
807+ async fn file_size ( path_buf : & PathBuf ) -> u64 {
808+ let file = File :: open ( path_buf) . await . unwrap ( ) ;
809+ file. metadata ( ) . await . unwrap ( ) . len ( )
810+ }
811+
812+ let blob = BlobObject :: new_from_path ( & t, & avatar_blob) . await . unwrap ( ) ;
813+
814+ blob. recode_to_size ( & t, blob. to_abs_path ( ) , 1000 , Some ( 3000 ) )
815+ . await
816+ . unwrap ( ) ;
817+ assert ! ( file_size( & avatar_blob) . await <= 3000 ) ;
818+ assert ! ( file_size( & avatar_blob) . await > 2000 ) ;
819+ let img = image:: open ( & avatar_blob) . unwrap ( ) ;
820+ assert ! ( img. width( ) > 130 ) ;
821+ assert_eq ! ( img. width( ) , img. height( ) ) ;
774822 }
775823
776824 #[ async_std:: test]
0 commit comments