@@ -58,6 +58,7 @@ pub struct AndroidFrame {
5858
5959pub struct AndroidGrpcFrameStream {
6060 inner : tonic:: Streaming < grpc:: Image > ,
61+ target_size : Option < ( u32 , u32 ) > ,
6162}
6263
6364pub fn is_android_id ( id : & str ) -> bool {
@@ -516,21 +517,25 @@ impl AndroidBridge {
516517 display : 0 ,
517518 transport : None ,
518519 } ;
519- if let Ok ( serial) = self . resolve_serial ( & avd_name) {
520- if let Ok ( ( width, height) ) = self . screen_size_for_serial ( & serial) {
521- let mut target_width = width;
522- let mut target_height = height;
523- if let Some ( max_edge) = max_edge {
524- let max_edge = max_edge. clamp ( 240 , 2400 ) as f64 ;
525- let largest = width. max ( height) ;
526- if largest > max_edge {
527- let scale = max_edge / largest;
528- target_width = ( width * scale) . round ( ) . max ( 1.0 ) ;
529- target_height = ( height * scale) . round ( ) . max ( 1.0 ) ;
530- }
520+ let target_size = self
521+ . resolve_serial ( & avd_name)
522+ . ok ( )
523+ . and_then ( |serial| self . screen_size_for_serial ( & serial) . ok ( ) )
524+ . map ( |( width, height) | {
525+ (
526+ width. round ( ) . max ( 1.0 ) as u32 ,
527+ height. round ( ) . max ( 1.0 ) as u32 ,
528+ )
529+ } ) ;
530+ if let ( Some ( max_edge) , Some ( ( width, height) ) ) = ( max_edge, target_size) {
531+ let max_edge = max_edge. clamp ( 240 , 2400 ) ;
532+ let largest = width. max ( height) ;
533+ if largest > max_edge {
534+ if width >= height {
535+ format. width = max_edge;
536+ } else {
537+ format. height = max_edge;
531538 }
532- format. width = target_width. round ( ) . max ( 1.0 ) as u32 ;
533- format. height = target_height. round ( ) . max ( 1.0 ) as u32 ;
534539 }
535540 }
536541
@@ -568,6 +573,7 @@ impl AndroidBridge {
568573 } ) ?;
569574 Ok ( AndroidGrpcFrameStream {
570575 inner : response. into_inner ( ) ,
576+ target_size,
571577 } )
572578 }
573579
@@ -965,6 +971,8 @@ impl AndroidGrpcFrameStream {
965971 grpc:: image_format:: ImgFormat :: try_from ( format. format )
966972 . unwrap_or ( grpc:: image_format:: ImgFormat :: Rgba8888 ) ,
967973 ) ?;
974+ let ( width, height, rgba) =
975+ normalize_android_frame_orientation ( width, height, rgba, self . target_size ) ;
968976 Ok ( Some ( AndroidFrame {
969977 width,
970978 height,
@@ -974,6 +982,42 @@ impl AndroidGrpcFrameStream {
974982 }
975983}
976984
985+ fn normalize_android_frame_orientation (
986+ width : u32 ,
987+ height : u32 ,
988+ rgba : Vec < u8 > ,
989+ target_size : Option < ( u32 , u32 ) > ,
990+ ) -> ( u32 , u32 , Vec < u8 > ) {
991+ let Some ( ( target_width, target_height) ) = target_size else {
992+ return ( width, height, rgba) ;
993+ } ;
994+ if width == 0
995+ || height == 0
996+ || target_width == 0
997+ || target_height == 0
998+ || ( width > height) == ( target_width > target_height)
999+ {
1000+ return ( width, height, rgba) ;
1001+ }
1002+ ( height, width, rotate_rgba_clockwise ( & rgba, width, height) )
1003+ }
1004+
1005+ fn rotate_rgba_clockwise ( rgba : & [ u8 ] , width : u32 , height : u32 ) -> Vec < u8 > {
1006+ let width = width as usize ;
1007+ let height = height as usize ;
1008+ let mut out = vec ! [ 0 ; rgba. len( ) ] ;
1009+ for y in 0 ..height {
1010+ for x in 0 ..width {
1011+ let src = ( y * width + x) * 4 ;
1012+ let dst_x = height - 1 - y;
1013+ let dst_y = x;
1014+ let dst = ( dst_y * height + dst_x) * 4 ;
1015+ out[ dst..dst + 4 ] . copy_from_slice ( & rgba[ src..src + 4 ] ) ;
1016+ }
1017+ }
1018+ out
1019+ }
1020+
9771021fn run_command_text < const N : usize > ( program : PathBuf , args : [ & str ; N ] ) -> Result < String , AppError > {
9781022 let output = run_command ( program, args) ?;
9791023 String :: from_utf8 ( output)
@@ -1633,6 +1677,38 @@ mod tests {
16331677 } )
16341678 ) ;
16351679 }
1680+
1681+ #[ test]
1682+ fn android_frame_orientation_rotates_when_stream_aspect_is_swapped ( ) {
1683+ let rgba = vec ! [
1684+ 1 , 0 , 0 , 255 , 2 , 0 , 0 , 255 , 3 , 0 , 0 , 255 , //
1685+ 4 , 0 , 0 , 255 , 5 , 0 , 0 , 255 , 6 , 0 , 0 , 255 ,
1686+ ] ;
1687+
1688+ let ( width, height, rotated) =
1689+ normalize_android_frame_orientation ( 3 , 2 , rgba, Some ( ( 2 , 3 ) ) ) ;
1690+
1691+ assert_eq ! ( ( width, height) , ( 2 , 3 ) ) ;
1692+ assert_eq ! (
1693+ rotated,
1694+ vec![
1695+ 4 , 0 , 0 , 255 , 1 , 0 , 0 , 255 , //
1696+ 5 , 0 , 0 , 255 , 2 , 0 , 0 , 255 , //
1697+ 6 , 0 , 0 , 255 , 3 , 0 , 0 , 255 ,
1698+ ]
1699+ ) ;
1700+ }
1701+
1702+ #[ test]
1703+ fn android_frame_orientation_keeps_matching_aspect ( ) {
1704+ let rgba = vec ! [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ;
1705+
1706+ let ( width, height, out) =
1707+ normalize_android_frame_orientation ( 1 , 2 , rgba. clone ( ) , Some ( ( 1080 , 2400 ) ) ) ;
1708+
1709+ assert_eq ! ( ( width, height) , ( 1 , 2 ) ) ;
1710+ assert_eq ! ( out, rgba) ;
1711+ }
16361712}
16371713
16381714mod grpc {
0 commit comments