From 71ae87ddd303e4f06c7db5b34725c687979796a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norbert=20Musia=C5=82?= <54953461+normusF7@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:24:19 +0200 Subject: [PATCH 1/2] Mirror only left half of screen --- .../scrcpy/video/NewDisplayCapture.java | 55 ++++--------------- .../scrcpy/video/ScreenCapture.java | 26 ++++----- 2 files changed, 21 insertions(+), 60 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 792b3a8a7a..f0fdb6dcf8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -122,47 +122,13 @@ public void prepare() { displayRotation = displayInfo.getRotation(); } - VideoFilter filter = new VideoFilter(displaySize); - - if (crop != null) { - boolean transposed = (displayRotation % 2) != 0; - filter.addCrop(crop, transposed); - } - - filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation); - filter.addAngle(angle); - - Size filteredSize = filter.getOutputSize(); - if (!filteredSize.isMultipleOf8() || (maxSize != 0 && filteredSize.getMax() > maxSize)) { - if (maxSize != 0) { - filteredSize = filteredSize.limit(maxSize); - } - filteredSize = filteredSize.round8(); - filter.addResize(filteredSize); - } - - eventTransform = filter.getInverseTransform(); - - // DisplayInfo gives the oriented size (so videoSize includes the display rotation) - videoSize = filter.getOutputSize(); - - // But the virtual display video always remains in the origin orientation (the video itself is not rotated, so it must rotated manually). - // This additional display rotation must not be included in the input events transform (the expected coordinates are already in the - // physical display size) - if ((displayRotation % 2) == 0) { - physicalSize = displaySize; - } else { - physicalSize = displaySize.rotate(); - } - VideoFilter displayFilter = new VideoFilter(physicalSize); - displayFilter.addRotation(displayRotation); - AffineMatrix displayRotationMatrix = displayFilter.getInverseTransform(); - - // Take care of multiplication order: - // displayTransform = (FILTER_MATRIX * DISPLAY_FILTER_MATRIX)⁻¹ - // = DISPLAY_FILTER_MATRIX⁻¹ * FILTER_MATRIX⁻¹ - // = displayRotationMatrix * eventTransform - displayTransform = AffineMatrix.multiplyAll(displayRotationMatrix, eventTransform); + // Mirror only the left half of the screen, no crop filter or OpenGL + int halfWidth = displaySize.getWidth() / 2; + Size halfSize = new Size(halfWidth, displaySize.getHeight()); + videoSize = halfSize.limit(maxSize).round8(); + physicalSize = displaySize; + displayTransform = null; + eventTransform = null; } public void startNew(Surface surface) { @@ -189,9 +155,9 @@ public void startNew(Surface surface) { } } virtualDisplay = ServiceManager.getDisplayManager() - .createNewVirtualDisplay("scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi, surface, flags); + .createNewVirtualDisplay("scrcpy", videoSize.getWidth(), videoSize.getHeight(), dpi, surface, flags); virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); - Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); + Ln.i("New display: " + videoSize.getWidth() + "x" + videoSize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); if (displayImePolicy != -1) { ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy); @@ -220,7 +186,8 @@ public void start(Surface surface) throws IOException { } if (vdListener != null) { - PositionMapper positionMapper = PositionMapper.create(videoSize, eventTransform, displaySize); + Size cropSize = new Size(displaySize.getWidth() / 2, displaySize.getHeight()); + PositionMapper positionMapper = PositionMapper.create(videoSize, eventTransform, cropSize); vdListener.onNewVirtualDisplay(virtualDisplay.getDisplay().getDisplayId(), positionMapper); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index 5f4e1803f9..3b25b95a2a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -85,19 +85,11 @@ public void prepare() throws ConfigurationException { captureOrientation = Orientation.fromRotation(displayInfo.getRotation()); } - VideoFilter filter = new VideoFilter(displaySize); - - if (crop != null) { - boolean transposed = (displayInfo.getRotation() % 2) != 0; - filter.addCrop(crop, transposed); - } - - boolean locked = captureOrientationLock != Orientation.Lock.Unlocked; - filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation); - filter.addAngle(angle); - - transform = filter.getInverseTransform(); - videoSize = filter.getOutputSize().limit(maxSize).round8(); + // Mirror only the left half of the screen without using VideoFilter.crop + int halfWidth = displaySize.getWidth() / 2; + Size halfSize = new Size(halfWidth, displaySize.getHeight()); + videoSize = halfSize.limit(maxSize).round8(); + transform = null; // no OpenGL transform } @Override @@ -133,8 +125,9 @@ public void start(Surface surface) throws IOException { display = createDisplay(); Size deviceSize = displayInfo.getSize(); + Rect deviceRect = new Rect(0, 0, deviceSize.getWidth() / 2, deviceSize.getHeight()); int layerStack = displayInfo.getLayerStack(); - setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack); + setDisplaySurface(display, surface, deviceRect, inputSize.toRect(), layerStack); Ln.d("Display: using SurfaceControl API"); } catch (Exception surfaceControlException) { Ln.e("Could not create display using DisplayManager", displayManagerException); @@ -147,9 +140,10 @@ public void start(Surface surface) throws IOException { int virtualDisplayId; PositionMapper positionMapper; if (virtualDisplay == null || displayId == 0) { - // Surface control or main display: send all events to the original display, relative to the device size + // Surface control or main display: send all events to the original display, relative to the cropped area Size deviceSize = displayInfo.getSize(); - positionMapper = PositionMapper.create(videoSize, transform, deviceSize); + Size cropSize = new Size(deviceSize.getWidth() / 2, deviceSize.getHeight()); + positionMapper = PositionMapper.create(videoSize, transform, cropSize); virtualDisplayId = displayId; } else { // The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!) From ada69da7c109f60da6543775d9cc077f012f51e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norbert=20Musia=C5=82?= <54953461+normusF7@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:49:48 +0200 Subject: [PATCH 2/2] Crop left half without filter --- .../scrcpy/video/NewDisplayCapture.java | 55 +++++++++--- .../scrcpy/video/ScreenCapture.java | 83 ++++--------------- 2 files changed, 62 insertions(+), 76 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index f0fdb6dcf8..792b3a8a7a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -122,13 +122,47 @@ public void prepare() { displayRotation = displayInfo.getRotation(); } - // Mirror only the left half of the screen, no crop filter or OpenGL - int halfWidth = displaySize.getWidth() / 2; - Size halfSize = new Size(halfWidth, displaySize.getHeight()); - videoSize = halfSize.limit(maxSize).round8(); - physicalSize = displaySize; - displayTransform = null; - eventTransform = null; + VideoFilter filter = new VideoFilter(displaySize); + + if (crop != null) { + boolean transposed = (displayRotation % 2) != 0; + filter.addCrop(crop, transposed); + } + + filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation); + filter.addAngle(angle); + + Size filteredSize = filter.getOutputSize(); + if (!filteredSize.isMultipleOf8() || (maxSize != 0 && filteredSize.getMax() > maxSize)) { + if (maxSize != 0) { + filteredSize = filteredSize.limit(maxSize); + } + filteredSize = filteredSize.round8(); + filter.addResize(filteredSize); + } + + eventTransform = filter.getInverseTransform(); + + // DisplayInfo gives the oriented size (so videoSize includes the display rotation) + videoSize = filter.getOutputSize(); + + // But the virtual display video always remains in the origin orientation (the video itself is not rotated, so it must rotated manually). + // This additional display rotation must not be included in the input events transform (the expected coordinates are already in the + // physical display size) + if ((displayRotation % 2) == 0) { + physicalSize = displaySize; + } else { + physicalSize = displaySize.rotate(); + } + VideoFilter displayFilter = new VideoFilter(physicalSize); + displayFilter.addRotation(displayRotation); + AffineMatrix displayRotationMatrix = displayFilter.getInverseTransform(); + + // Take care of multiplication order: + // displayTransform = (FILTER_MATRIX * DISPLAY_FILTER_MATRIX)⁻¹ + // = DISPLAY_FILTER_MATRIX⁻¹ * FILTER_MATRIX⁻¹ + // = displayRotationMatrix * eventTransform + displayTransform = AffineMatrix.multiplyAll(displayRotationMatrix, eventTransform); } public void startNew(Surface surface) { @@ -155,9 +189,9 @@ public void startNew(Surface surface) { } } virtualDisplay = ServiceManager.getDisplayManager() - .createNewVirtualDisplay("scrcpy", videoSize.getWidth(), videoSize.getHeight(), dpi, surface, flags); + .createNewVirtualDisplay("scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi, surface, flags); virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); - Ln.i("New display: " + videoSize.getWidth() + "x" + videoSize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); + Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); if (displayImePolicy != -1) { ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy); @@ -186,8 +220,7 @@ public void start(Surface surface) throws IOException { } if (vdListener != null) { - Size cropSize = new Size(displaySize.getWidth() / 2, displaySize.getHeight()); - PositionMapper positionMapper = PositionMapper.create(videoSize, eventTransform, cropSize); + PositionMapper positionMapper = PositionMapper.create(videoSize, eventTransform, displaySize); vdListener.onNewVirtualDisplay(virtualDisplay.getDisplay().getDisplayId(), positionMapper); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index 3b25b95a2a..c9fd2640b1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -8,9 +8,6 @@ import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.Orientation; import com.genymobile.scrcpy.device.Size; -import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; -import com.genymobile.scrcpy.opengl.OpenGLFilter; -import com.genymobile.scrcpy.opengl.OpenGLRunner; import com.genymobile.scrcpy.util.AffineMatrix; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.LogUtils; @@ -18,7 +15,6 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Rect; -import android.hardware.display.VirtualDisplay; import android.os.Build; import android.os.IBinder; import android.view.Surface; @@ -41,10 +37,8 @@ public class ScreenCapture extends SurfaceCapture { private final DisplaySizeMonitor displaySizeMonitor = new DisplaySizeMonitor(); private IBinder display; - private VirtualDisplay virtualDisplay; private AffineMatrix transform; - private OpenGLRunner glRunner; public ScreenCapture(VirtualDisplayListener vdListener, Options options) { this.vdListener = vdListener; @@ -85,11 +79,9 @@ public void prepare() throws ConfigurationException { captureOrientation = Orientation.fromRotation(displayInfo.getRotation()); } - // Mirror only the left half of the screen without using VideoFilter.crop int halfWidth = displaySize.getWidth() / 2; - Size halfSize = new Size(halfWidth, displaySize.getHeight()); - videoSize = halfSize.limit(maxSize).round8(); - transform = null; // no OpenGL transform + videoSize = new Size(halfWidth, displaySize.getHeight()).limit(maxSize).round8(); + transform = null; } @Override @@ -98,68 +90,33 @@ public void start(Surface surface) throws IOException { SurfaceControl.destroyDisplay(display); display = null; } - if (virtualDisplay != null) { - virtualDisplay.release(); - virtualDisplay = null; - } - Size inputSize; - if (transform != null) { - // If there is a filter, it must receive the full display content - inputSize = displayInfo.getSize(); - assert glRunner == null; - OpenGLFilter glFilter = new AffineOpenGLFilter(transform); - glRunner = new OpenGLRunner(glFilter); - surface = glRunner.start(inputSize, videoSize, surface); - } else { - // If there is no filter, the display must be rendered at target video size directly - inputSize = videoSize; - } + Size inputSize = videoSize; try { - virtualDisplay = ServiceManager.getDisplayManager() - .createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface); - Ln.d("Display: using DisplayManager API"); - } catch (Exception displayManagerException) { - try { - display = createDisplay(); - - Size deviceSize = displayInfo.getSize(); - Rect deviceRect = new Rect(0, 0, deviceSize.getWidth() / 2, deviceSize.getHeight()); - int layerStack = displayInfo.getLayerStack(); - setDisplaySurface(display, surface, deviceRect, inputSize.toRect(), layerStack); - Ln.d("Display: using SurfaceControl API"); - } catch (Exception surfaceControlException) { - Ln.e("Could not create display using DisplayManager", displayManagerException); - Ln.e("Could not create display using SurfaceControl", surfaceControlException); - throw new AssertionError("Could not create display"); - } + display = createDisplay(); + + Size deviceSize = displayInfo.getSize(); + Rect deviceRect = new Rect(0, 0, deviceSize.getWidth() / 2, deviceSize.getHeight()); + int layerStack = displayInfo.getLayerStack(); + setDisplaySurface(display, surface, deviceRect, inputSize.toRect(), layerStack); + Ln.d("Display: using SurfaceControl API"); + } catch (Exception e) { + Ln.e("Could not create display", e); + throw new AssertionError("Could not create display"); } if (vdListener != null) { - int virtualDisplayId; - PositionMapper positionMapper; - if (virtualDisplay == null || displayId == 0) { - // Surface control or main display: send all events to the original display, relative to the cropped area - Size deviceSize = displayInfo.getSize(); - Size cropSize = new Size(deviceSize.getWidth() / 2, deviceSize.getHeight()); - positionMapper = PositionMapper.create(videoSize, transform, cropSize); - virtualDisplayId = displayId; - } else { - // The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!) - positionMapper = PositionMapper.create(videoSize, transform, inputSize); - virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); - } - vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper); + Size deviceSize = displayInfo.getSize(); + Size cropSize = new Size(deviceSize.getWidth() / 2, deviceSize.getHeight()); + PositionMapper positionMapper = PositionMapper.create(videoSize, transform, cropSize); + vdListener.onNewVirtualDisplay(displayId, positionMapper); } } @Override public void stop() { - if (glRunner != null) { - glRunner.stopAndRelease(); - glRunner = null; - } + // no-op } @Override @@ -170,10 +127,6 @@ public void release() { SurfaceControl.destroyDisplay(display); display = null; } - if (virtualDisplay != null) { - virtualDisplay.release(); - virtualDisplay = null; - } } @Override