Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/data/zsh-completion/_scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ arguments=(
'--camera-fps=[Specify the camera capture frame rate]'
'--camera-size=[Specify an explicit camera capture size]'
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the client]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display-id=[Specify the display id to mirror]'
Expand Down
2 changes: 1 addition & 1 deletion app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Default is 0.

.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
Crop the device screen on the client.

The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet).

Expand Down
2 changes: 1 addition & 1 deletion app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ static const struct sc_option options[] = {
.longopt_id = OPT_CROP,
.longopt = "crop",
.argdesc = "width:height:x:y",
.text = "Crop the device screen on the server.\n"
.text = "Crop the device screen on the client.\n"
"The values are expressed in the device natural orientation "
"(typically, portrait for a phone, landscape for a tablet).",
},
Expand Down
6 changes: 3 additions & 3 deletions app/src/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,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, const SDL_Rect *crop) {
SDL_RenderClear(display->renderer);

if (display->pending.flags) {
Expand All @@ -307,7 +307,7 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
SDL_Texture *texture = display->texture;

if (orientation == SC_ORIENTATION_0) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
int ret = SDL_RenderCopy(renderer, texture, crop, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
Expand All @@ -331,7 +331,7 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
? SDL_FLIP_HORIZONTAL : 0;

int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
int ret = SDL_RenderCopyEx(renderer, texture, crop, dstrect, angle,
NULL, flip);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
Expand Down
2 changes: 1 addition & 1 deletion app/src/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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, const SDL_Rect *crop);

#endif
2 changes: 1 addition & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ scrcpy(struct scrcpy_options *options) {
.video_source = options->video_source,
.audio_source = options->audio_source,
.camera_facing = options->camera_facing,
.crop = options->crop,
.port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
Expand Down Expand Up @@ -813,6 +812,7 @@ scrcpy(struct scrcpy_options *options) {
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.crop = options->crop,
};

if (!sc_screen_init(&s->screen, &screen_params)) {
Expand Down
87 changes: 83 additions & 4 deletions app/src/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "icon.h"
#include "options.h"
#include "util/log.h"
#include "util/str.h"

#define DISPLAY_MARGINS 96

Expand All @@ -26,6 +27,37 @@ get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
return oriented_size;
}

static bool
parse_crop(const char *s, SDL_Rect *crop) {
long values[4];
size_t count = sc_str_parse_integers(s, ':', 4, values);
if (count != 4) {
LOGE("Crop must contain 4 values separated by colons: %s", s);
return false;
}
long w = values[0];
long h = values[1];
long x = values[2];
long y = values[3];
if (w <= 0 || h <= 0) {
LOGE("Invalid crop size: %ldx%ld", w, h);
return false;
}
if (x < 0 || y < 0) {
LOGE("Invalid crop offset: %ld:%ld", x, y);
return false;
}
if (w > 0xFFFF || h > 0xFFFF || x > 0xFFFF || y > 0xFFFF) {
LOGE("Crop values too large");
return false;
}
crop->x = (int) x;
crop->y = (int) y;
crop->w = (int) w;
crop->h = (int) h;
return true;
}

// get the window size in a struct sc_size
static struct sc_size
get_window_size(const struct sc_screen *screen) {
Expand Down Expand Up @@ -213,15 +245,18 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
sc_screen_update_content_rect(screen);
}

const SDL_Rect *crop = screen->has_crop ? &screen->crop_rect : NULL;
enum sc_display_result res =
sc_display_render(&screen->display, &screen->rect, screen->orientation);
sc_display_render(&screen->display, &screen->rect, screen->orientation,
crop);
(void) res; // any error already logged
}

static void
sc_screen_render_novideo(struct sc_screen *screen) {
const SDL_Rect *crop = screen->has_crop ? &screen->crop_rect : NULL;
enum sc_display_result res =
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0, crop);
(void) res; // any error already logged
}

Expand Down Expand Up @@ -270,6 +305,14 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
// event acts as a memory barrier so it is safe without mutex
screen->frame_size.width = ctx->width;
screen->frame_size.height = ctx->height;
if (!screen->has_crop) {
screen->crop_rect.x = 0;
screen->crop_rect.y = 0;
screen->crop_rect.w = ctx->width;
screen->crop_rect.h = ctx->height;
screen->crop_size.width = ctx->width;
screen->crop_size.height = ctx->height;
}

// Post the event on the UI thread (the texture must be created from there)
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
Expand Down Expand Up @@ -333,6 +376,9 @@ sc_screen_init(struct sc_screen *screen,
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
screen->has_crop = false;
screen->crop_size.width = 0;
screen->crop_size.height = 0;

screen->video = params->video;

Expand All @@ -358,6 +404,15 @@ sc_screen_init(struct sc_screen *screen,
LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
}

if (params->crop) {
if (!parse_crop(params->crop, &screen->crop_rect)) {
goto error_destroy_fps_counter;
}
screen->crop_size.width = screen->crop_rect.w;
screen->crop_size.height = screen->crop_rect.h;
screen->has_crop = true;
}
}

uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
Expand Down Expand Up @@ -610,8 +665,18 @@ sc_screen_init_size(struct sc_screen *screen) {

// The requested size is passed via screen->frame_size

if (screen->has_crop) {
if (screen->crop_rect.x + screen->crop_rect.w > screen->frame_size.width ||
screen->crop_rect.y + screen->crop_rect.h > screen->frame_size.height) {
LOGE("Crop exceeds the frame dimensions");
return false;
}
}

struct sc_size base_size = screen->has_crop ? screen->crop_size
: screen->frame_size;
struct sc_size content_size =
get_oriented_size(screen->frame_size, screen->orientation);
get_oriented_size(base_size, screen->orientation);
screen->content_size = content_size;

enum sc_display_result res =
Expand All @@ -632,8 +697,22 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
// frame dimension changed
screen->frame_size = new_frame_size;

if (!screen->has_crop) {
screen->crop_rect.x = 0;
screen->crop_rect.y = 0;
screen->crop_rect.w = new_frame_size.width;
screen->crop_rect.h = new_frame_size.height;
screen->crop_size = new_frame_size;
} else if (screen->crop_rect.x + screen->crop_rect.w > new_frame_size.width ||
screen->crop_rect.y + screen->crop_rect.h > new_frame_size.height) {
LOGE("Crop exceeds the frame dimensions");
return SC_DISPLAY_RESULT_ERROR;
}

struct sc_size base_size = screen->has_crop ? screen->crop_size
: new_frame_size;
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
get_oriented_size(base_size, screen->orientation);
set_content_size(screen, new_content_size);

sc_screen_update_content_rect(screen);
Expand Down
7 changes: 6 additions & 1 deletion app/src/screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ struct sc_screen {

SDL_Window *window;
struct sc_size frame_size;
struct sc_size content_size; // rotated frame_size
struct sc_size crop_size; // cropped frame size
SDL_Rect crop_rect; // cropping area in frame coordinates
bool has_crop;
struct sc_size content_size; // rotated crop_size

bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or
Expand Down Expand Up @@ -100,6 +103,8 @@ struct sc_screen_params {

bool fullscreen;
bool start_fps_counter;

const char *crop; // width:height:x:y
};

// initialize screen, create window, renderer and texture (window is hidden)
Expand Down
4 changes: 0 additions & 4 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,6 @@ execute_server(struct sc_server *server,
if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=true");
}
if (params->crop) {
VALIDATE_STRING(params->crop);
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
// By default, control is true
ADD_PARAM("control=false");
Expand Down
1 change: 0 additions & 1 deletion app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct sc_server_params {
enum sc_video_source video_source;
enum sc_audio_source audio_source;
enum sc_camera_facing camera_facing;
const char *crop;
const char *video_codec_options;
const char *audio_codec_options;
const char *video_encoder;
Expand Down
5 changes: 2 additions & 3 deletions doc/video.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ scrcpy --capture-orientation=@flip180 # locked to hflip + 180°
scrcpy --capture-orientation=@flip270 # locked to hflip + 270° clockwise
```

The capture orientation transform is applied after `--crop`, but before
`--angle`.
The capture orientation transform is applied before `--angle`.

To orient the video (on the client side):

Expand Down Expand Up @@ -191,7 +190,7 @@ scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0)
The values are expressed in the device natural orientation (portrait for a
phone, landscape for a tablet).

Cropping is performed before `--capture-orientation` and `--angle`.
Cropping is performed on the client after `--capture-orientation` and `--angle`.

For display mirroring, `--max-size` is applied after cropping. For camera,
`--max-size` is applied first (because it selects the source size rather than
Expand Down
28 changes: 0 additions & 28 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.genymobile.scrcpy.video.VideoSource;
import com.genymobile.scrcpy.wrappers.WindowManager;

import android.graphics.Rect;
import android.util.Pair;

import java.util.List;
Expand All @@ -37,7 +36,6 @@ public class Options {
private float maxFps;
private float angle;
private boolean tunnelForward;
private Rect crop;
private boolean control = true;
private int displayId;
private String cameraId;
Expand Down Expand Up @@ -140,9 +138,6 @@ public boolean isTunnelForward() {
return tunnelForward;
}

public Rect getCrop() {
return crop;
}

public boolean getControl() {
return control;
Expand Down Expand Up @@ -376,11 +371,6 @@ public static Options parse(String... args) {
case "tunnel_forward":
options.tunnelForward = Boolean.parseBoolean(value);
break;
case "crop":
if (!value.isEmpty()) {
options.crop = parseCrop(value);
}
break;
case "control":
options.control = Boolean.parseBoolean(value);
break;
Expand Down Expand Up @@ -526,24 +516,6 @@ public static Options parse(String... args) {
return options;
}

private static Rect parseCrop(String crop) {
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Invalid crop size: " + width + "x" + height);
}
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Invalid crop offset: " + x + ":" + y);
}
return new Rect(x, y, x + width, y + height);
}

private static Size parseSize(String size) {
// input format: "<width>x<height>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public class CameraCapture extends SurfaceCapture {
private final CameraAspectRatio aspectRatio;
private final int fps;
private final boolean highSpeed;
private final Rect crop;
private final Orientation captureOrientation;
private final float angle;

Expand All @@ -86,7 +85,6 @@ public CameraCapture(Options options) {
this.aspectRatio = options.getCameraAspectRatio();
this.fps = options.getCameraFps();
this.highSpeed = options.getCameraHighSpeed();
this.crop = options.getCrop();
this.captureOrientation = options.getCaptureOrientation();
assert captureOrientation != null;
this.angle = options.getAngle();
Expand Down Expand Up @@ -125,10 +123,6 @@ public void prepare() throws IOException {

VideoFilter filter = new VideoFilter(captureSize);

if (crop != null) {
filter.addCrop(crop, false);
}

if (captureOrientation != Orientation.Orient0) {
filter.addOrientation(captureOrientation);
}
Expand Down
Loading
Loading