@@ -371,61 +371,64 @@ impl<'a> BlobObject<'a> {
371
371
) -> Result < Option < String > > {
372
372
tokio:: task:: block_in_place ( move || {
373
373
let mut img = image:: open ( & blob_abs) . context ( "image recode failure" ) ?;
374
- let orientation = self . get_exif_orientation ( context) ;
374
+ let exif = self . get_exif ( ) . ok ( ) ;
375
+ let orientation = exif. as_ref ( ) . map ( |exif| exif_orientation ( exif, context) ) ;
375
376
let mut encoded = Vec :: new ( ) ;
376
377
let mut changed_name = None ;
377
378
378
379
let exceeds_width = img. width ( ) > img_wh || img. height ( ) > img_wh;
379
380
380
381
let do_scale =
381
382
exceeds_width || encoded_img_exceeds_bytes ( context, & img, max_bytes, & mut encoded) ?;
382
- let do_rotate = matches ! ( orientation, Ok ( 90 ) | Ok ( 180 ) | Ok ( 270 ) ) ;
383
-
384
- if do_scale || do_rotate {
385
- if do_rotate {
386
- img = match orientation {
387
- Ok ( 90 ) => img. rotate90 ( ) ,
388
- Ok ( 180 ) => img. rotate180 ( ) ,
389
- Ok ( 270 ) => img. rotate270 ( ) ,
390
- _ => img,
391
- }
383
+ let do_rotate = matches ! ( orientation, Some ( 90 ) | Some ( 180 ) | Some ( 270 ) ) ;
384
+
385
+ if do_rotate {
386
+ img = match orientation {
387
+ Some ( 90 ) => img. rotate90 ( ) ,
388
+ Some ( 180 ) => img. rotate180 ( ) ,
389
+ Some ( 270 ) => img. rotate270 ( ) ,
390
+ _ => img,
392
391
}
392
+ }
393
393
394
- if do_scale {
395
- if !exceeds_width {
396
- // The image is already smaller than img_wh, but exceeds max_bytes
397
- // We can directly start with trying to scale down to 2/3 of its current width
398
- img_wh = max ( img. width ( ) , img. height ( ) ) * 2 / 3
399
- }
394
+ if do_scale {
395
+ if !exceeds_width {
396
+ // The image is already smaller than img_wh, but exceeds max_bytes
397
+ // We can directly start with trying to scale down to 2/3 of its current width
398
+ img_wh = max ( img. width ( ) , img. height ( ) ) * 2 / 3
399
+ }
400
400
401
- loop {
402
- let new_img = img. thumbnail ( img_wh, img_wh) ;
403
-
404
- if encoded_img_exceeds_bytes ( context, & new_img, max_bytes, & mut encoded) ? {
405
- if img_wh < 20 {
406
- return Err ( format_err ! (
407
- "Failed to scale image to below {}B" ,
408
- max_bytes. unwrap_or_default( )
409
- ) ) ;
410
- }
411
-
412
- img_wh = img_wh * 2 / 3 ;
413
- } else {
414
- if encoded. is_empty ( ) {
415
- encode_img ( & new_img, & mut encoded) ?;
416
- }
417
-
418
- info ! (
419
- context,
420
- "Final scaled-down image size: {}B ({}px)." ,
421
- encoded. len( ) ,
422
- img_wh
423
- ) ;
424
- break ;
401
+ loop {
402
+ let new_img = img. thumbnail ( img_wh, img_wh) ;
403
+
404
+ if encoded_img_exceeds_bytes ( context, & new_img, max_bytes, & mut encoded) ? {
405
+ if img_wh < 20 {
406
+ return Err ( format_err ! (
407
+ "Failed to scale image to below {}B." ,
408
+ max_bytes. unwrap_or_default( )
409
+ ) ) ;
425
410
}
411
+
412
+ img_wh = img_wh * 2 / 3 ;
413
+ } else {
414
+ if encoded. is_empty ( ) {
415
+ encode_img ( & new_img, & mut encoded) ?;
416
+ }
417
+
418
+ info ! (
419
+ context,
420
+ "Final scaled-down image size: {}B ({}px)." ,
421
+ encoded. len( ) ,
422
+ img_wh
423
+ ) ;
424
+ break ;
426
425
}
427
426
}
427
+ }
428
428
429
+ // We also need to rewrite the file to remove metadata such as location, camera model,
430
+ // etc. if any
431
+ if do_rotate || do_scale || exif. is_some ( ) {
429
432
// The file format is JPEG now, we may have to change the file extension
430
433
if !matches ! ( ImageFormat :: from_path( & blob_abs) , Ok ( ImageFormat :: Jpeg ) ) {
431
434
blob_abs = blob_abs. with_extension ( "jpg" ) ;
@@ -446,23 +449,26 @@ impl<'a> BlobObject<'a> {
446
449
} )
447
450
}
448
451
449
- pub fn get_exif_orientation ( & self , context : & Context ) -> Result < i32 > {
452
+ pub fn get_exif ( & self ) -> Result < exif :: Exif > {
450
453
let file = std:: fs:: File :: open ( self . to_abs_path ( ) ) ?;
451
454
let mut bufreader = std:: io:: BufReader :: new ( & file) ;
452
455
let exifreader = exif:: Reader :: new ( ) ;
453
- let exif = exifreader. read_from_container ( & mut bufreader) ?;
454
- if let Some ( orientation) = exif. get_field ( exif:: Tag :: Orientation , exif:: In :: PRIMARY ) {
455
- // possible orientation values are described at http://sylvana.net/jpegcrop/exif_orientation.html
456
- // we only use rotation, in practise, flipping is not used.
457
- match orientation. value . get_uint ( 0 ) {
458
- Some ( 3 ) => return Ok ( 180 ) ,
459
- Some ( 6 ) => return Ok ( 90 ) ,
460
- Some ( 8 ) => return Ok ( 270 ) ,
461
- other => warn ! ( context, "Exif orientation value ignored: {other:?}." ) ,
462
- }
456
+ Ok ( exifreader. read_from_container ( & mut bufreader) ?)
457
+ }
458
+ }
459
+
460
+ fn exif_orientation ( exif : & exif:: Exif , context : & Context ) -> i32 {
461
+ if let Some ( orientation) = exif. get_field ( exif:: Tag :: Orientation , exif:: In :: PRIMARY ) {
462
+ // possible orientation values are described at http://sylvana.net/jpegcrop/exif_orientation.html
463
+ // we only use rotation, in practise, flipping is not used.
464
+ match orientation. value . get_uint ( 0 ) {
465
+ Some ( 3 ) => return 180 ,
466
+ Some ( 6 ) => return 90 ,
467
+ Some ( 8 ) => return 270 ,
468
+ other => warn ! ( context, "Exif orientation value ignored: {other:?}." ) ,
463
469
}
464
- Ok ( 0 )
465
470
}
471
+ 0
466
472
}
467
473
468
474
impl < ' a > fmt:: Display for BlobObject < ' a > {
@@ -880,12 +886,22 @@ mod tests {
880
886
async fn test_recode_image_1 ( ) {
881
887
let bytes = include_bytes ! ( "../test-data/image/avatar1000x1000.jpg" ) ;
882
888
// BALANCED_IMAGE_SIZE > 1000, the original image size, so the image is not scaled down:
883
- send_image_check_mediaquality ( Some ( "0" ) , bytes, 1000 , 1000 , 0 , 1000 , 1000 )
884
- . await
885
- . unwrap ( ) ;
889
+ send_image_check_mediaquality (
890
+ Some ( "0" ) ,
891
+ bytes,
892
+ true , // has Exif
893
+ 1000 ,
894
+ 1000 ,
895
+ 0 ,
896
+ 1000 ,
897
+ 1000 ,
898
+ )
899
+ . await
900
+ . unwrap ( ) ;
886
901
send_image_check_mediaquality (
887
902
Some ( "1" ) ,
888
903
bytes,
904
+ true , // has Exif
889
905
1000 ,
890
906
1000 ,
891
907
0 ,
@@ -903,6 +919,7 @@ mod tests {
903
919
let img_rotated = send_image_check_mediaquality (
904
920
Some ( "0" ) ,
905
921
bytes,
922
+ true , // has Exif
906
923
2000 ,
907
924
1800 ,
908
925
270 ,
@@ -926,6 +943,7 @@ mod tests {
926
943
let img_rotated = send_image_check_mediaquality (
927
944
Some ( "0" ) ,
928
945
& bytes2,
946
+ false , // no Exif
929
947
BALANCED_IMAGE_SIZE * 1800 / 2000 ,
930
948
BALANCED_IMAGE_SIZE ,
931
949
0 ,
@@ -940,6 +958,7 @@ mod tests {
940
958
let img_rotated = send_image_check_mediaquality (
941
959
Some ( "1" ) ,
942
960
& bytes,
961
+ false , // no Exif
943
962
BALANCED_IMAGE_SIZE * 1800 / 2000 ,
944
963
BALANCED_IMAGE_SIZE ,
945
964
0 ,
@@ -956,15 +975,33 @@ mod tests {
956
975
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
957
976
async fn test_recode_image_3 ( ) {
958
977
let bytes = include_bytes ! ( "../test-data/image/rectangle200x180-rotated.jpg" ) ;
959
- let img_rotated = send_image_check_mediaquality ( Some ( "0" ) , bytes, 200 , 180 , 270 , 180 , 200 )
960
- . await
961
- . unwrap ( ) ;
978
+ let img_rotated = send_image_check_mediaquality (
979
+ Some ( "0" ) ,
980
+ bytes,
981
+ true , // has Exif
982
+ 200 ,
983
+ 180 ,
984
+ 270 ,
985
+ 180 ,
986
+ 200 ,
987
+ )
988
+ . await
989
+ . unwrap ( ) ;
962
990
assert_correct_rotation ( & img_rotated) ;
963
991
964
992
let bytes = include_bytes ! ( "../test-data/image/rectangle200x180-rotated.jpg" ) ;
965
- let img_rotated = send_image_check_mediaquality ( Some ( "1" ) , bytes, 200 , 180 , 270 , 180 , 200 )
966
- . await
967
- . unwrap ( ) ;
993
+ let img_rotated = send_image_check_mediaquality (
994
+ Some ( "1" ) ,
995
+ bytes,
996
+ true , // has Exif
997
+ 200 ,
998
+ 180 ,
999
+ 270 ,
1000
+ 180 ,
1001
+ 200 ,
1002
+ )
1003
+ . await
1004
+ . unwrap ( ) ;
968
1005
assert_correct_rotation ( & img_rotated) ;
969
1006
}
970
1007
@@ -985,9 +1022,11 @@ mod tests {
985
1022
assert_eq ! ( luma, 0 ) ;
986
1023
}
987
1024
1025
+ #[ allow( clippy:: too_many_arguments) ]
988
1026
async fn send_image_check_mediaquality (
989
1027
media_quality_config : Option < & str > ,
990
1028
bytes : & [ u8 ] ,
1029
+ has_exif : bool ,
991
1030
original_width : u32 ,
992
1031
original_height : u32 ,
993
1032
orientation : i32 ,
@@ -1007,7 +1046,13 @@ mod tests {
1007
1046
check_image_size ( & file, original_width, original_height) ;
1008
1047
1009
1048
let blob = BlobObject :: new_from_path ( & alice, & file) . await ?;
1010
- assert_eq ! ( blob. get_exif_orientation( & alice) . unwrap_or( 0 ) , orientation) ;
1049
+ let exif = blob. get_exif ( ) ;
1050
+ if has_exif {
1051
+ let exif = exif. unwrap ( ) ;
1052
+ assert_eq ! ( exif_orientation( & exif, & alice) , orientation) ;
1053
+ } else {
1054
+ assert ! ( exif. is_err( ) ) ;
1055
+ }
1011
1056
1012
1057
let mut msg = Message :: new ( Viewtype :: Image ) ;
1013
1058
msg. set_file ( file. to_str ( ) . unwrap ( ) , None ) ;
@@ -1028,7 +1073,7 @@ mod tests {
1028
1073
let file = bob_msg. get_file ( & bob) . unwrap ( ) ;
1029
1074
1030
1075
let blob = BlobObject :: new_from_path ( & bob, & file) . await ?;
1031
- assert_eq ! ( blob. get_exif_orientation ( & bob ) . unwrap_or ( 0 ) , 0 ) ;
1076
+ assert ! ( blob. get_exif ( ) . is_err ( ) ) ;
1032
1077
1033
1078
let img = check_image_size ( file, compressed_width, compressed_height) ;
1034
1079
Ok ( img)
0 commit comments