From 9d7f89415b7eb9ff414204a2a4d615af99424393 Mon Sep 17 00:00:00 2001 From: "Willy A. Kuster" Date: Thu, 11 Jan 2024 09:22:38 +0100 Subject: [PATCH 1/4] Add --rotation-offset parameter to control rotation --- app/src/cli.c | 26 +++++++++++++++++++++++ app/src/display.c | 54 ++++++++++++++++++++++------------------------- app/src/display.h | 2 +- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/screen.c | 3 ++- app/src/screen.h | 2 ++ doc/video.md | 15 +++++++++++++ 9 files changed, 74 insertions(+), 31 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index f7d7e390af..22a2736a7f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -93,6 +93,7 @@ enum { OPT_DISPLAY_ORIENTATION, OPT_RECORD_ORIENTATION, OPT_ORIENTATION, + OPT_ROTATION_OFFSET, }; struct sc_option { @@ -837,6 +838,13 @@ static const struct sc_option options[] = { .text = "Set the initial window height.\n" "Default is 0 (automatic).", }, + { + .longopt_id = OPT_ROTATION_OFFSET, + .longopt = "rotation-offset", + .argdesc = "value", + .text = "Set the initial display rotation offset.\n" + "Default is 0 (automatic).", + }, }; static const struct sc_shortcut shortcuts[] = { @@ -1937,6 +1945,19 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) { } +static bool +parse_rotation_offset(const char *s, int16_t *rotation_offset) { + long value; + bool ok = parse_integer_arg(s, &value, false, -360, 360, + "display rotation offset"); + if (!ok) { + return false; + } + + *rotation_offset = (int16_t) value; + return true; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -2359,6 +2380,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_CAMERA_HIGH_SPEED: opts->camera_high_speed = true; break; + case OPT_ROTATION_OFFSET: + if (!parse_rotation_offset(optarg, &opts->rotation_offset)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/display.c b/app/src/display.c index 906b5d657b..378cc3259a 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -234,7 +234,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - enum sc_orientation orientation) { + enum sc_orientation orientation, int16_t rotation_offset) { SDL_RenderClear(display->renderer); if (display->pending.flags) { @@ -247,37 +247,33 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, SDL_Renderer *renderer = display->renderer; SDL_Texture *texture = display->texture; - if (orientation == SC_ORIENTATION_0) { - int ret = SDL_RenderCopy(renderer, texture, NULL, geometry); - if (ret) { - LOGE("Could not render texture: %s", SDL_GetError()); - return SC_DISPLAY_RESULT_ERROR; - } - } else { - unsigned cw_rotation = sc_orientation_get_rotation(orientation); - double angle = 90 * cw_rotation; + if (rotation_offset < 0) { + rotation_offset = rotation_offset + 360; + } - const SDL_Rect *dstrect = NULL; - SDL_Rect rect; - if (sc_orientation_is_swap(orientation)) { - rect.x = geometry->x + (geometry->w - geometry->h) / 2; - rect.y = geometry->y + (geometry->h - geometry->w) / 2; - rect.w = geometry->h; - rect.h = geometry->w; - dstrect = ▭ - } else { - dstrect = geometry; - } + unsigned cw_rotation = sc_orientation_get_rotation(orientation); + double angle = (90 * cw_rotation) + rotation_offset; - SDL_RendererFlip flip = sc_orientation_is_mirror(orientation) - ? SDL_FLIP_HORIZONTAL : 0; + const SDL_Rect *dstrect = NULL; + SDL_Rect rect; + if (sc_orientation_is_swap(orientation)) { + rect.x = geometry->x + (geometry->w - geometry->h) / 2; + rect.y = geometry->y + (geometry->h - geometry->w) / 2; + rect.w = geometry->h; + rect.h = geometry->w; + dstrect = ▭ + } else { + dstrect = geometry; + } + + SDL_RendererFlip flip = sc_orientation_is_mirror(orientation) + ? SDL_FLIP_HORIZONTAL : 0; - int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle, - NULL, flip); - if (ret) { - LOGE("Could not render texture: %s", SDL_GetError()); - return SC_DISPLAY_RESULT_ERROR; - } + int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle, + NULL, flip); + if (ret) { + LOGE("Could not render texture: %s", SDL_GetError()); + return SC_DISPLAY_RESULT_ERROR; } SDL_RenderPresent(display->renderer); diff --git a/app/src/display.h b/app/src/display.h index 643ce73c63..682183aee0 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -55,6 +55,6 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame); enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - enum sc_orientation orientation); + enum sc_orientation orientation, int16_t rotation_offset); #endif diff --git a/app/src/options.c b/app/src/options.c index a13df585a6..ee64406645 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -41,6 +41,7 @@ const struct scrcpy_options scrcpy_options_default = { .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .display_orientation = SC_ORIENTATION_0, .record_orientation = SC_ORIENTATION_0, + .rotation_offset = 0, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, diff --git a/app/src/options.h b/app/src/options.h index 11e64fa19e..2411b81459 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -221,6 +221,7 @@ struct scrcpy_options { enum sc_lock_video_orientation lock_video_orientation; enum sc_orientation display_orientation; enum sc_orientation record_orientation; + int16_t rotation_offset; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index cf2e7e4786..4ba64a0253 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -702,6 +702,7 @@ scrcpy(struct scrcpy_options *options) { .window_height = options->window_height, .window_borderless = options->window_borderless, .orientation = options->display_orientation, + .rotation_offset = options->rotation_offset, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, .start_fps_counter = options->start_fps_counter, diff --git a/app/src/screen.c b/app/src/screen.c index 091001bcbf..6adea5edde 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -251,7 +251,7 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { } enum sc_display_result res = - sc_display_render(&screen->display, &screen->rect, screen->orientation); + sc_display_render(&screen->display, &screen->rect, screen->orientation, screen->rotation_offset); (void) res; // any error already logged } @@ -380,6 +380,7 @@ sc_screen_init(struct sc_screen *screen, } screen->orientation = params->orientation; + screen->rotation_offset = params->rotation_offset; if (screen->orientation != SC_ORIENTATION_0) { LOGI("Initial display orientation set to %s", sc_orientation_get_name(screen->orientation)); diff --git a/app/src/screen.h b/app/src/screen.h index 46591be5cc..14c3e1fc80 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -52,6 +52,7 @@ struct sc_screen { // client orientation enum sc_orientation orientation; + int16_t rotation_offset; // rectangle of the content (excluding black borders) struct SDL_Rect rect; bool has_frame; @@ -88,6 +89,7 @@ struct sc_screen_params { bool window_borderless; enum sc_orientation orientation; + int16_t rotation_offset; bool mipmaps; bool fullscreen; diff --git a/doc/video.md b/doc/video.md index ed92cb221b..b2d3f4843b 100644 --- a/doc/video.md +++ b/doc/video.md @@ -141,6 +141,21 @@ to the MP4 or MKV target file. Flipping is not supported, so only the 4 first values are allowed when recording. +## Rotation Offset + +Rotation offset to be applied (positive values will rotate clockwise): + +```bash +scrcpy --rotation-offset=45 +``` + +This is useful for Meta Quest 3 tilted screen setup: + +```bash +scrcpy --crop=2064:2208:0:0 --rotation-offset=21 +``` + + ## Crop The device screen may be cropped to mirror only part of the screen. From b840ac09fc858a7dbbd8a19a19ac03a66fd0e34d Mon Sep 17 00:00:00 2001 From: "Willy A. Kuster" Date: Thu, 11 Jan 2024 17:44:24 +0100 Subject: [PATCH 2/4] Add --scale, --position-x-offset and --position-y-offset --- app/src/cli.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++ app/src/coords.h | 6 ++++++ app/src/display.c | 33 +++++++++++++++++++++--------- app/src/display.h | 2 +- app/src/options.c | 3 +++ app/src/options.h | 3 +++ app/src/scrcpy.c | 3 +++ app/src/screen.c | 7 +++++-- app/src/screen.h | 9 ++++++-- doc/video.md | 20 +++++++++++++++++- 10 files changed, 122 insertions(+), 16 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 22a2736a7f..50c8825c73 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -94,6 +94,9 @@ enum { OPT_RECORD_ORIENTATION, OPT_ORIENTATION, OPT_ROTATION_OFFSET, + OPT_SCALE, + OPT_POSITION_X_OFFSET, + OPT_POSITION_Y_OFFSET, }; struct sc_option { @@ -845,6 +848,27 @@ static const struct sc_option options[] = { .text = "Set the initial display rotation offset.\n" "Default is 0 (automatic).", }, + { + .longopt_id = OPT_SCALE, + .longopt = "scale", + .argdesc = "value", + .text = "Set the initial display scale.\n" + "Default is 100 (automatic).", + }, + { + .longopt_id = OPT_POSITION_X_OFFSET, + .longopt = "position-x-offset", + .argdesc = "value", + .text = "Set the initial display horizontal position offset.\n" + "Default is 0 (automatic).", + }, + { + .longopt_id = OPT_POSITION_Y_OFFSET, + .longopt = "position-y-offset", + .argdesc = "value", + .text = "Set the initial display vertical position offset.\n" + "Default is 0 (automatic).", + }, }; static const struct sc_shortcut shortcuts[] = { @@ -1958,6 +1982,19 @@ parse_rotation_offset(const char *s, int16_t *rotation_offset) { return true; } +static bool +parse_scale(const char *s, uint16_t *scale) { + long value; + bool ok = parse_integer_arg(s, &value, false, 1, 1000, + "display scale"); + if (!ok) { + return false; + } + + *scale = (uint16_t) value; + return true; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -2385,6 +2422,21 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_SCALE: + if (!parse_scale(optarg, &opts->scale)) { + return false; + } + break; + case OPT_POSITION_X_OFFSET: + if (!parse_window_position(optarg, &opts->position_x_offset)) { + return false; + } + break; + case OPT_POSITION_Y_OFFSET: + if (!parse_window_position(optarg, &opts->position_y_offset)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/coords.h b/app/src/coords.h index cdabb782c2..30fb1b3025 100644 --- a/app/src/coords.h +++ b/app/src/coords.h @@ -21,4 +21,10 @@ struct sc_position { struct sc_point point; }; +struct sc_transform { + int16_t rotation; + uint16_t scale; + struct sc_point position; +}; + #endif diff --git a/app/src/display.c b/app/src/display.c index 378cc3259a..7a8153eed8 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -234,7 +234,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - enum sc_orientation orientation, int16_t rotation_offset) { + enum sc_orientation orientation, struct sc_transform *transform_offsets) { SDL_RenderClear(display->renderer); if (display->pending.flags) { @@ -247,6 +247,7 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, SDL_Renderer *renderer = display->renderer; SDL_Texture *texture = display->texture; + int16_t rotation_offset = transform_offsets->rotation; if (rotation_offset < 0) { rotation_offset = rotation_offset + 360; } @@ -255,17 +256,29 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, double angle = (90 * cw_rotation) + rotation_offset; const SDL_Rect *dstrect = NULL; - SDL_Rect rect; - if (sc_orientation_is_swap(orientation)) { - rect.x = geometry->x + (geometry->w - geometry->h) / 2; - rect.y = geometry->y + (geometry->h - geometry->w) / 2; - rect.w = geometry->h; - rect.h = geometry->w; - dstrect = ▭ + SDL_Rect rect; + + rect.x = geometry->x + transform_offsets->position.x; + rect.y = geometry->y + transform_offsets->position.y; + + if (transform_offsets->scale != 100) { + rect.w = (int)(geometry->w * (float)transform_offsets->scale / 100); + rect.h = (int)(geometry->h * (float)transform_offsets->scale / 100); } else { - dstrect = geometry; + rect.w = geometry->w; + rect.h = geometry->h; + } + + if (sc_orientation_is_swap(orientation)) { + rect.x = rect.x + (rect.w - rect.h) / 2; + rect.y = rect.y + (rect.h - rect.w) / 2; + int width = rect.w; + rect.w = rect.h; + rect.h = width; } - + + dstrect = ▭ + SDL_RendererFlip flip = sc_orientation_is_mirror(orientation) ? SDL_FLIP_HORIZONTAL : 0; diff --git a/app/src/display.h b/app/src/display.h index 682183aee0..2d8fde371e 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -55,6 +55,6 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame); enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - enum sc_orientation orientation, int16_t rotation_offset); + enum sc_orientation orientation, struct sc_transform *transform_offsets); #endif diff --git a/app/src/options.c b/app/src/options.c index ee64406645..762f309dd4 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -42,6 +42,9 @@ const struct scrcpy_options scrcpy_options_default = { .display_orientation = SC_ORIENTATION_0, .record_orientation = SC_ORIENTATION_0, .rotation_offset = 0, + .scale = 100, + .position_x_offset = 0, + .position_y_offset = 0, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, diff --git a/app/src/options.h b/app/src/options.h index 2411b81459..abcbdeb3d2 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -222,6 +222,9 @@ struct scrcpy_options { enum sc_orientation display_orientation; enum sc_orientation record_orientation; int16_t rotation_offset; + uint16_t scale; + int16_t position_x_offset; + int16_t position_y_offset; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4ba64a0253..f05e9384ee 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -703,6 +703,9 @@ scrcpy(struct scrcpy_options *options) { .window_borderless = options->window_borderless, .orientation = options->display_orientation, .rotation_offset = options->rotation_offset, + .scale = options->scale, + .position_x_offset = options->position_x_offset, + .position_y_offset = options->position_y_offset, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, .start_fps_counter = options->start_fps_counter, diff --git a/app/src/screen.c b/app/src/screen.c index 6adea5edde..ed08d0b5b6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -251,7 +251,7 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { } enum sc_display_result res = - sc_display_render(&screen->display, &screen->rect, screen->orientation, screen->rotation_offset); + sc_display_render(&screen->display, &screen->rect, screen->orientation, &screen->transform_offsets); (void) res; // any error already logged } @@ -380,7 +380,10 @@ sc_screen_init(struct sc_screen *screen, } screen->orientation = params->orientation; - screen->rotation_offset = params->rotation_offset; + screen->transform_offsets.rotation = params->rotation_offset; + screen->transform_offsets.scale = params->scale; + screen->transform_offsets.position.x = params->position_x_offset; + screen->transform_offsets.position.y = params->position_y_offset; if (screen->orientation != SC_ORIENTATION_0) { LOGI("Initial display orientation set to %s", sc_orientation_get_name(screen->orientation)); diff --git a/app/src/screen.h b/app/src/screen.h index 14c3e1fc80..b604ae4856 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -52,7 +52,6 @@ struct sc_screen { // client orientation enum sc_orientation orientation; - int16_t rotation_offset; // rectangle of the content (excluding black borders) struct SDL_Rect rect; bool has_frame; @@ -65,6 +64,8 @@ struct sc_screen { SDL_Keycode mouse_capture_key_pressed; AVFrame *frame; + + struct sc_transform transform_offsets; }; struct sc_screen_params { @@ -89,11 +90,15 @@ struct sc_screen_params { bool window_borderless; enum sc_orientation orientation; - int16_t rotation_offset; bool mipmaps; bool fullscreen; bool start_fps_counter; + + int16_t rotation_offset; + uint16_t scale; + int16_t position_x_offset; + int16_t position_y_offset; }; // initialize screen, create window, renderer and texture (window is hidden) diff --git a/doc/video.md b/doc/video.md index b2d3f4843b..18b5a8bfd1 100644 --- a/doc/video.md +++ b/doc/video.md @@ -141,7 +141,7 @@ to the MP4 or MKV target file. Flipping is not supported, so only the 4 first values are allowed when recording. -## Rotation Offset +## Rotation offset Rotation offset to be applied (positive values will rotate clockwise): @@ -156,6 +156,24 @@ scrcpy --crop=2064:2208:0:0 --rotation-offset=21 ``` +## Scale + +Scale to be applied (default is 100): + +```bash +scrcpy --scale=150 # 150% scale +``` + +## Position offset + +Position offset to be applied both horizontally and/or vertically +(default is 0 for both): + +```bash +scrcpy --position-x-offset=150 +scrcpy --position-y-offset=150 +``` + ## Crop The device screen may be cropped to mirror only part of the screen. From de7d21d369bb097edc2030cb80d4a593b4c52c52 Mon Sep 17 00:00:00 2001 From: "Willy A. Kuster" Date: Sat, 3 Feb 2024 21:04:02 +0100 Subject: [PATCH 3/4] Transform inputs with --scale, --position-x-offset, --position-y-offset and --rotation-offset --- app/src/display.c | 4 +-- app/src/screen.c | 82 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/app/src/display.c b/app/src/display.c index 7a8153eed8..7a3a973522 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -262,8 +262,8 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, rect.y = geometry->y + transform_offsets->position.y; if (transform_offsets->scale != 100) { - rect.w = (int)(geometry->w * (float)transform_offsets->scale / 100); - rect.h = (int)(geometry->h * (float)transform_offsets->scale / 100); + rect.w = (int)(geometry->w * (float32_t)transform_offsets->scale / 100); + rect.h = (int)(geometry->h * (float32_t)transform_offsets->scale / 100); } else { rect.w = geometry->w; rect.h = geometry->h; diff --git a/app/src/screen.c b/app/src/screen.c index ed08d0b5b6..1e2736ea76 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "events.h" #include "icon.h" @@ -845,6 +846,23 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { return true; } +static void +sc_rotate_point(struct sc_point *point, + struct sc_point *pivot, + int16_t angle_in_degrees) { + float32_t angle_in_radians = (float32_t)angle_in_degrees * M_PI / 180.0; + float32_t cosine = (float32_t)cos(angle_in_radians); + float32_t sine = (float32_t)sin(angle_in_radians); + + int32_t x = point->x; + int32_t y = point->y; + int32_t pivot_x = pivot->x; + int32_t pivot_y = pivot->y; + + point->x = (int32_t)(((x - pivot_x) * cosine) + ((y - pivot_y) * -sine) + pivot_x); + point->y = (int32_t)(((x - pivot_x) * sine) + ((y - pivot_y) * cosine) + pivot_y); +} + struct sc_point sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t x, int32_t y) { @@ -852,50 +870,78 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; + int32_t w_half = (int32_t) w * 0.5; + int32_t h_half = (int32_t) h * 0.5; + struct sc_point pivot = { + .x = w_half, + .y = h_half, + }; + int8_t flip_factor = -1; + float32_t scale_factor = 100 / (float32_t)screen->transform_offsets.scale; // screen->rect must be initialized to avoid a division by zero assert(screen->rect.w && screen->rect.h); - x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; - y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; + float32_t w_factor = (float32_t)w / screen->rect.w; + float32_t h_factor = (float32_t)h / screen->rect.h; + x = (int64_t) (x - screen->rect.x) * w_factor; + y = (int64_t) (y - screen->rect.y) * h_factor; + int32_t x_offset = screen->transform_offsets.position.x * w_factor; + int32_t y_offset = screen->transform_offsets.position.y * h_factor; struct sc_point result; switch (orientation) { case SC_ORIENTATION_0: - result.x = x; - result.y = y; + result.x = (x - x_offset) * scale_factor; + result.y = (y - y_offset) * scale_factor; break; case SC_ORIENTATION_90: - result.x = y; - result.y = w - x; + result.x = (y - y_offset) * scale_factor; + result.y = w + ((x_offset - x) * scale_factor); + pivot.x = h_half; + pivot.y = w_half; break; case SC_ORIENTATION_180: - result.x = w - x; - result.y = h - y; + result.x = w + ((x_offset - x) * scale_factor); + result.y = h + ((y_offset - y) * scale_factor); break; case SC_ORIENTATION_270: - result.x = h - y; - result.y = x; + result.x = h + ((y_offset - y) * scale_factor); + result.y = (x - x_offset) * scale_factor; + pivot.x = h_half; + pivot.y = w_half; break; case SC_ORIENTATION_FLIP_0: - result.x = w - x; - result.y = y; + result.x = w + ((x_offset - x) * scale_factor); + result.y = (y - y_offset) * scale_factor; + flip_factor = 1; break; case SC_ORIENTATION_FLIP_90: - result.x = h - y; - result.y = w - x; + result.x = h + ((y_offset - y) * scale_factor); + result.y = w + ((x_offset - x) * scale_factor); + pivot.x = h_half; + pivot.y = w_half; + flip_factor = 1; break; case SC_ORIENTATION_FLIP_180: - result.x = x; - result.y = h - y; + result.x = (x - x_offset) * scale_factor; + result.y = h + ((y_offset - y) * scale_factor); + flip_factor = 1; break; default: assert(orientation == SC_ORIENTATION_FLIP_270); - result.x = y; - result.y = x; + result.x = (y - y_offset) * scale_factor; + result.y = (x - x_offset) * scale_factor; + pivot.x = h_half; + pivot.y = w_half; + flip_factor = 1; break; } + if (screen->transform_offsets.rotation != 0) { + sc_rotate_point(&result, &pivot, flip_factor * screen->transform_offsets.rotation); + } + return result; } From 16f93ac5fdac3439dc473f944dc5e8fea821ba7d Mon Sep 17 00:00:00 2001 From: "Willy A. Kuster" Date: Fri, 9 Feb 2024 09:12:12 +0100 Subject: [PATCH 4/4] Refactor and optimize --scale and --rotation-offset --- app/src/cli.c | 10 ++++++---- app/src/display.c | 4 ++-- app/src/screen.c | 13 +++++++------ doc/video.md | 15 ++++++++++++--- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 50c8825c73..32a579e876 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -845,28 +845,30 @@ static const struct sc_option options[] = { .longopt_id = OPT_ROTATION_OFFSET, .longopt = "rotation-offset", .argdesc = "value", - .text = "Set the initial display rotation offset.\n" + .text = "Set the display rotation offset in degrees.\n" + "Positive values rotate clockwise, negative values " + "rotate counter-clockwise.\n" "Default is 0 (automatic).", }, { .longopt_id = OPT_SCALE, .longopt = "scale", .argdesc = "value", - .text = "Set the initial display scale.\n" + .text = "Set the display scale in integer percentage.\n" "Default is 100 (automatic).", }, { .longopt_id = OPT_POSITION_X_OFFSET, .longopt = "position-x-offset", .argdesc = "value", - .text = "Set the initial display horizontal position offset.\n" + .text = "Set the display horizontal position offset.\n" "Default is 0 (automatic).", }, { .longopt_id = OPT_POSITION_Y_OFFSET, .longopt = "position-y-offset", .argdesc = "value", - .text = "Set the initial display vertical position offset.\n" + .text = "Set the display vertical position offset.\n" "Default is 0 (automatic).", }, }; diff --git a/app/src/display.c b/app/src/display.c index 7a3a973522..8fda7f0074 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -262,8 +262,8 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, rect.y = geometry->y + transform_offsets->position.y; if (transform_offsets->scale != 100) { - rect.w = (int)(geometry->w * (float32_t)transform_offsets->scale / 100); - rect.h = (int)(geometry->h * (float32_t)transform_offsets->scale / 100); + rect.w = geometry->w * transform_offsets->scale / 100; + rect.h = geometry->h * transform_offsets->scale / 100; } else { rect.w = geometry->w; rect.h = geometry->h; diff --git a/app/src/screen.c b/app/src/screen.c index 1e2736ea76..1174b9f86f 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -850,7 +850,8 @@ static void sc_rotate_point(struct sc_point *point, struct sc_point *pivot, int16_t angle_in_degrees) { - float32_t angle_in_radians = (float32_t)angle_in_degrees * M_PI / 180.0; + const double deg_to_rad = M_PI / 180.0; + float32_t angle_in_radians = (float32_t)angle_in_degrees * deg_to_rad; float32_t cosine = (float32_t)cos(angle_in_radians); float32_t sine = (float32_t)sin(angle_in_radians); @@ -870,20 +871,20 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; - int32_t w_half = (int32_t) w * 0.5; - int32_t h_half = (int32_t) h * 0.5; + int32_t w_half = w >> 1; + int32_t h_half = h >> 1; struct sc_point pivot = { .x = w_half, .y = h_half, }; int8_t flip_factor = -1; - float32_t scale_factor = 100 / (float32_t)screen->transform_offsets.scale; + float32_t scale_factor = 100.0 / screen->transform_offsets.scale; // screen->rect must be initialized to avoid a division by zero assert(screen->rect.w && screen->rect.h); - float32_t w_factor = (float32_t)w / screen->rect.w; - float32_t h_factor = (float32_t)h / screen->rect.h; + float32_t w_factor = w / (float32_t)screen->rect.w; + float32_t h_factor = h / (float32_t)screen->rect.h; x = (int64_t) (x - screen->rect.x) * w_factor; y = (int64_t) (y - screen->rect.y) * h_factor; int32_t x_offset = screen->transform_offsets.position.x * w_factor; diff --git a/doc/video.md b/doc/video.md index 18b5a8bfd1..08947fe198 100644 --- a/doc/video.md +++ b/doc/video.md @@ -143,7 +143,8 @@ values are allowed when recording. ## Rotation offset -Rotation offset to be applied (positive values will rotate clockwise): +Rotation offset in degrees to be applied (positive values rotate clockwise, +negative values rotate counter-clockwise): ```bash scrcpy --rotation-offset=45 @@ -152,18 +153,23 @@ scrcpy --rotation-offset=45 This is useful for Meta Quest 3 tilted screen setup: ```bash -scrcpy --crop=2064:2208:0:0 --rotation-offset=21 +scrcpy --crop=2064:2208:0:0 --rotation-offset=22 ``` +This is not supported when recording. + ## Scale -Scale to be applied (default is 100): +Scale in integer percentage to be applied (default is 100): ```bash scrcpy --scale=150 # 150% scale ``` +This is not supported when recording. + + ## Position offset Position offset to be applied both horizontally and/or vertically @@ -174,6 +180,9 @@ scrcpy --position-x-offset=150 scrcpy --position-y-offset=150 ``` +This is not supported when recording. + + ## Crop The device screen may be cropped to mirror only part of the screen.