@@ -368,61 +368,64 @@ impl<'a> BlobObject<'a> {
368
368
) -> Result < Option < String > > {
369
369
tokio:: task:: block_in_place ( move || {
370
370
let mut img = image:: open ( & blob_abs) . context ( "image recode failure" ) ?;
371
- let orientation = self . get_exif_orientation ( context) ;
371
+ let exif = self . get_exif ( ) . ok ( ) ;
372
+ let orientation = exif. as_ref ( ) . map ( |exif| exif_orientation ( exif, context) ) ;
372
373
let mut encoded = Vec :: new ( ) ;
373
374
let mut changed_name = None ;
374
375
375
376
let exceeds_width = img. width ( ) > img_wh || img. height ( ) > img_wh;
376
377
377
378
let do_scale =
378
379
exceeds_width || encoded_img_exceeds_bytes ( context, & img, max_bytes, & mut encoded) ?;
379
- let do_rotate = matches ! ( orientation, Ok ( 90 ) | Ok ( 180 ) | Ok ( 270 ) ) ;
380
-
381
- if do_scale || do_rotate {
382
- if do_rotate {
383
- img = match orientation {
384
- Ok ( 90 ) => img. rotate90 ( ) ,
385
- Ok ( 180 ) => img. rotate180 ( ) ,
386
- Ok ( 270 ) => img. rotate270 ( ) ,
387
- _ => img,
388
- }
380
+ let do_rotate = matches ! ( orientation, Some ( 90 ) | Some ( 180 ) | Some ( 270 ) ) ;
381
+
382
+ if do_rotate {
383
+ img = match orientation {
384
+ Some ( 90 ) => img. rotate90 ( ) ,
385
+ Some ( 180 ) => img. rotate180 ( ) ,
386
+ Some ( 270 ) => img. rotate270 ( ) ,
387
+ _ => img,
389
388
}
389
+ }
390
390
391
- if do_scale {
392
- if !exceeds_width {
393
- // The image is already smaller than img_wh, but exceeds max_bytes
394
- // We can directly start with trying to scale down to 2/3 of its current width
395
- img_wh = max ( img. width ( ) , img. height ( ) ) * 2 / 3
396
- }
391
+ if do_scale {
392
+ if !exceeds_width {
393
+ // The image is already smaller than img_wh, but exceeds max_bytes
394
+ // We can directly start with trying to scale down to 2/3 of its current width
395
+ img_wh = max ( img. width ( ) , img. height ( ) ) * 2 / 3
396
+ }
397
+
398
+ loop {
399
+ let new_img = img. thumbnail ( img_wh, img_wh) ;
400
+
401
+ if encoded_img_exceeds_bytes ( context, & new_img, max_bytes, & mut encoded) ? {
402
+ if img_wh < 20 {
403
+ return Err ( format_err ! (
404
+ "Failed to scale image to below {}B" ,
405
+ max_bytes. unwrap_or_default( )
406
+ ) ) ;
407
+ }
397
408
398
- loop {
399
- let new_img = img. thumbnail ( img_wh, img_wh) ;
400
-
401
- if encoded_img_exceeds_bytes ( context, & new_img, max_bytes, & mut encoded) ? {
402
- if img_wh < 20 {
403
- return Err ( format_err ! (
404
- "Failed to scale image to below {}B" ,
405
- max_bytes. unwrap_or_default( )
406
- ) ) ;
407
- }
408
-
409
- img_wh = img_wh * 2 / 3 ;
410
- } else {
411
- if encoded. is_empty ( ) {
412
- encode_img ( & new_img, & mut encoded) ?;
413
- }
414
-
415
- info ! (
416
- context,
417
- "Final scaled-down image size: {}B ({}px)" ,
418
- encoded. len( ) ,
419
- img_wh
420
- ) ;
421
- break ;
409
+ img_wh = img_wh * 2 / 3 ;
410
+ } else {
411
+ if encoded. is_empty ( ) {
412
+ encode_img ( & new_img, & mut encoded) ?;
422
413
}
414
+
415
+ info ! (
416
+ context,
417
+ "Final scaled-down image size: {}B ({}px)" ,
418
+ encoded. len( ) ,
419
+ img_wh
420
+ ) ;
421
+ break ;
423
422
}
424
423
}
424
+ }
425
425
426
+ // We also need to rewrite the file to remove metadata such as location, camera model,
427
+ // etc. if any
428
+ if do_rotate || do_scale || exif. is_some ( ) {
426
429
// The file format is JPEG now, we may have to change the file extension
427
430
if !matches ! ( ImageFormat :: from_path( & blob_abs) , Ok ( ImageFormat :: Jpeg ) ) {
428
431
blob_abs = blob_abs. with_extension ( "jpg" ) ;
@@ -443,23 +446,26 @@ impl<'a> BlobObject<'a> {
443
446
} )
444
447
}
445
448
446
- pub fn get_exif_orientation ( & self , context : & Context ) -> Result < i32 > {
449
+ pub fn get_exif ( & self ) -> Result < exif :: Exif > {
447
450
let file = std:: fs:: File :: open ( self . to_abs_path ( ) ) ?;
448
451
let mut bufreader = std:: io:: BufReader :: new ( & file) ;
449
452
let exifreader = exif:: Reader :: new ( ) ;
450
- let exif = exifreader. read_from_container ( & mut bufreader) ?;
451
- if let Some ( orientation) = exif. get_field ( exif:: Tag :: Orientation , exif:: In :: PRIMARY ) {
452
- // possible orientation values are described at http://sylvana.net/jpegcrop/exif_orientation.html
453
- // we only use rotation, in practise, flipping is not used.
454
- match orientation. value . get_uint ( 0 ) {
455
- Some ( 3 ) => return Ok ( 180 ) ,
456
- Some ( 6 ) => return Ok ( 90 ) ,
457
- Some ( 8 ) => return Ok ( 270 ) ,
458
- other => warn ! ( context, "exif orientation value ignored: {:?}" , other) ,
459
- }
453
+ Ok ( exifreader. read_from_container ( & mut bufreader) ?)
454
+ }
455
+ }
456
+
457
+ fn exif_orientation ( exif : & exif:: Exif , context : & Context ) -> i32 {
458
+ if let Some ( orientation) = exif. get_field ( exif:: Tag :: Orientation , exif:: In :: PRIMARY ) {
459
+ // possible orientation values are described at http://sylvana.net/jpegcrop/exif_orientation.html
460
+ // we only use rotation, in practise, flipping is not used.
461
+ match orientation. value . get_uint ( 0 ) {
462
+ Some ( 3 ) => return 180 ,
463
+ Some ( 6 ) => return 90 ,
464
+ Some ( 8 ) => return 270 ,
465
+ other => warn ! ( context, "exif orientation value ignored: {:?}" , other) ,
460
466
}
461
- Ok ( 0 )
462
467
}
468
+ 0
463
469
}
464
470
465
471
impl < ' a > fmt:: Display for BlobObject < ' a > {
@@ -796,12 +802,22 @@ mod tests {
796
802
async fn test_recode_image_1 ( ) {
797
803
let bytes = include_bytes ! ( "../test-data/image/avatar1000x1000.jpg" ) ;
798
804
// BALANCED_IMAGE_SIZE > 1000, the original image size, so the image is not scaled down:
799
- send_image_check_mediaquality ( Some ( "0" ) , bytes, 1000 , 1000 , 0 , 1000 , 1000 )
800
- . await
801
- . unwrap ( ) ;
805
+ send_image_check_mediaquality (
806
+ Some ( "0" ) ,
807
+ bytes,
808
+ true , // has Exif
809
+ 1000 ,
810
+ 1000 ,
811
+ 0 ,
812
+ 1000 ,
813
+ 1000 ,
814
+ )
815
+ . await
816
+ . unwrap ( ) ;
802
817
send_image_check_mediaquality (
803
818
Some ( "1" ) ,
804
819
bytes,
820
+ true , // has Exif
805
821
1000 ,
806
822
1000 ,
807
823
0 ,
@@ -819,6 +835,7 @@ mod tests {
819
835
let img_rotated = send_image_check_mediaquality (
820
836
Some ( "0" ) ,
821
837
bytes,
838
+ true , // has Exif
822
839
2000 ,
823
840
1800 ,
824
841
270 ,
@@ -842,6 +859,7 @@ mod tests {
842
859
let img_rotated = send_image_check_mediaquality (
843
860
Some ( "0" ) ,
844
861
& bytes2,
862
+ false , // no Exif
845
863
BALANCED_IMAGE_SIZE * 1800 / 2000 ,
846
864
BALANCED_IMAGE_SIZE ,
847
865
0 ,
@@ -856,6 +874,7 @@ mod tests {
856
874
let img_rotated = send_image_check_mediaquality (
857
875
Some ( "1" ) ,
858
876
& bytes,
877
+ false , // no Exif
859
878
BALANCED_IMAGE_SIZE * 1800 / 2000 ,
860
879
BALANCED_IMAGE_SIZE ,
861
880
0 ,
@@ -872,15 +891,33 @@ mod tests {
872
891
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
873
892
async fn test_recode_image_3 ( ) {
874
893
let bytes = include_bytes ! ( "../test-data/image/rectangle200x180-rotated.jpg" ) ;
875
- let img_rotated = send_image_check_mediaquality ( Some ( "0" ) , bytes, 200 , 180 , 270 , 180 , 200 )
876
- . await
877
- . unwrap ( ) ;
894
+ let img_rotated = send_image_check_mediaquality (
895
+ Some ( "0" ) ,
896
+ bytes,
897
+ true , // has Exif
898
+ 200 ,
899
+ 180 ,
900
+ 270 ,
901
+ 180 ,
902
+ 200 ,
903
+ )
904
+ . await
905
+ . unwrap ( ) ;
878
906
assert_correct_rotation ( & img_rotated) ;
879
907
880
908
let bytes = include_bytes ! ( "../test-data/image/rectangle200x180-rotated.jpg" ) ;
881
- let img_rotated = send_image_check_mediaquality ( Some ( "1" ) , bytes, 200 , 180 , 270 , 180 , 200 )
882
- . await
883
- . unwrap ( ) ;
909
+ let img_rotated = send_image_check_mediaquality (
910
+ Some ( "1" ) ,
911
+ bytes,
912
+ true , // has Exif
913
+ 200 ,
914
+ 180 ,
915
+ 270 ,
916
+ 180 ,
917
+ 200 ,
918
+ )
919
+ . await
920
+ . unwrap ( ) ;
884
921
assert_correct_rotation ( & img_rotated) ;
885
922
}
886
923
@@ -901,9 +938,11 @@ mod tests {
901
938
assert_eq ! ( luma, 0 ) ;
902
939
}
903
940
941
+ #[ allow( clippy:: too_many_arguments) ]
904
942
async fn send_image_check_mediaquality (
905
943
media_quality_config : Option < & str > ,
906
944
bytes : & [ u8 ] ,
945
+ has_exif : bool ,
907
946
original_width : u32 ,
908
947
original_height : u32 ,
909
948
orientation : i32 ,
@@ -923,7 +962,13 @@ mod tests {
923
962
check_image_size ( & file, original_width, original_height) ;
924
963
925
964
let blob = BlobObject :: new_from_path ( & alice, & file) . await ?;
926
- assert_eq ! ( blob. get_exif_orientation( & alice) . unwrap_or( 0 ) , orientation) ;
965
+ let exif = blob. get_exif ( ) ;
966
+ if has_exif {
967
+ let exif = exif. unwrap ( ) ;
968
+ assert_eq ! ( exif_orientation( & exif, & alice) , orientation) ;
969
+ } else {
970
+ assert ! ( exif. is_err( ) ) ;
971
+ }
927
972
928
973
let mut msg = Message :: new ( Viewtype :: Image ) ;
929
974
msg. set_file ( file. to_str ( ) . unwrap ( ) , None ) ;
@@ -944,7 +989,7 @@ mod tests {
944
989
let file = bob_msg. get_file ( & bob) . unwrap ( ) ;
945
990
946
991
let blob = BlobObject :: new_from_path ( & bob, & file) . await ?;
947
- assert_eq ! ( blob. get_exif_orientation ( & bob ) . unwrap_or ( 0 ) , 0 ) ;
992
+ assert ! ( blob. get_exif ( ) . is_err ( ) ) ;
948
993
949
994
let img = check_image_size ( file, compressed_width, compressed_height) ;
950
995
Ok ( img)
0 commit comments