[FFmpeg-devel] gdigrab: fix HIDPI support for multi-monitor setup with different scale settings

Submitted by Dilshod Mukhtarov on July 5, 2019, 7:45 p.m.

Details

Message ID 32df4703-873d-88a7-c492-14f6cf4ccc99@gmail.com
State New
Headers show

Commit Message

Dilshod Mukhtarov July 5, 2019, 7:45 p.m.
Hi,

In Windows multi-monitor setup if there are different scales settings on 
different monitors then coordinates for videorecording  calculated not 
correct.

Here is the patch that fixes this problem

Patch hide | download patch | download mbox

From 34ac7244b23ac2347a8e48b2ea2966cf69e1cf27 Mon Sep 17 00:00:00 2001
From: Dilshod Muktharov <dilshodm@gmail.com>
Date: Thu, 4 Jul 2019 21:52:09 +0400
Subject: [PATCH 1/2] HighDPI support for recording Windows multi-monitor mode

In windows multi-monitor mode with different UI scales it was not correctly calculated total area of recording on non-primary monitor
---
 libavdevice/gdigrab.c | 210 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 195 insertions(+), 15 deletions(-)

diff --git a/libavdevice/gdigrab.c b/libavdevice/gdigrab.c
index f4444406fa..276b448692 100644
--- a/libavdevice/gdigrab.c
+++ b/libavdevice/gdigrab.c
@@ -212,6 +212,165 @@  gdigrab_region_wnd_update(AVFormatContext *s1, struct gdigrab *gdigrab)
     }
 }
 
+typedef struct tagRESOLUTION {
+    int x;
+    int y;
+} RESOLUTION;
+
+typedef struct tagMONITOR {
+    RESOLUTION logical;
+    RESOLUTION physical;
+    RECT rect;
+} MONITOR;
+
+#define MY_MAX_MONITORS 4
+static MONITOR monitors[MY_MAX_MONITORS];
+static int monitorsNum = 0;
+static RECT rcCombined;
+
+/**
+ * Callback function which is used by EnumDisplayMonitors()
+ *
+ * @param hMon
+ * @param hdc
+ * @param lprcMonitor - rectangle of the given monitor
+ * @param pData - arbitrary user data passed to EnumDisplayMonitors
+ * @return TRUE
+ */
+static BOOL CALLBACK
+monitor_enum(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData)
+{
+    monitors[monitorsNum].rect = *lprcMonitor;
+    UnionRect(&rcCombined, &rcCombined, lprcMonitor);
+
+    MONITORINFOEXA monitor_info;
+    monitor_info.cbSize = sizeof (monitor_info);
+    GetMonitorInfoA(hMon, &monitor_info);
+
+    HDC monitor_hdc = CreateDCA(NULL, monitor_info.szDevice, NULL, NULL);
+
+    monitors[monitorsNum].logical.x  = GetDeviceCaps(monitor_hdc, HORZRES);
+    monitors[monitorsNum].logical.y  = GetDeviceCaps(monitor_hdc, VERTRES);
+    monitors[monitorsNum].physical.x = GetDeviceCaps(monitor_hdc, DESKTOPHORZRES);
+    monitors[monitorsNum].physical.y = GetDeviceCaps(monitor_hdc, DESKTOPVERTRES);
+
+    DeleteDC(monitor_hdc);
+
+    ++monitorsNum;
+    return TRUE;
+}
+
+/**
+ * Returns the monitor id by given logical coordinates
+ * of a pixel
+ *
+ * @param x Logical coordinate x
+ * @param y Logical coordinate y
+ * @return id of monitor (in monitors) on success, -1 on error
+ */
+static int
+get_monitor_id_by_logical_point(int x, int y)
+{
+    for (int i = 0; i < monitorsNum; ++i) {
+        if (monitors[i].rect.left <= x && x < monitors[i].rect.right &&
+                monitors[i].rect.top  <= y && y < monitors[i].rect.bottom)
+            return i;
+    }
+
+    return -1;
+}
+
+/**
+ * Returns the monitor id by given logical coordinates
+ * of a rectangle. The center of rectangle is used to get monitor
+ *
+ * @param rect Logical coordinates of rect
+ * @return id of monitor (in monitors) on success, -1 on error
+ */
+static int
+get_monitor_id_by_logical_rectangle(const RECT *rect)
+{
+    int x = rect->left + (rect->right  - rect->left) / 2;
+    int y = rect->top  + (rect->bottom - rect->top)  / 2;
+    return get_monitor_id_by_logical_point(x, y);
+}
+
+/**
+ * Returns the monitor id by given x coordinate
+ *
+ * @param x Logical coordinate x
+ * @return id of monitor (in monitors) on success, -1 on error
+ */
+static int
+get_monitor_id_by_logical_x(int x)
+{
+    for (int i = 0; i < monitorsNum; ++i) {
+        if (monitors[i].rect.left <= x && x < monitors[i].rect.right)
+            return i;
+    }
+
+    return -1;
+}
+
+/**
+ * Returns the monitor id by given y coordinate
+ *
+ * @param y Logical coordinate y
+ * @return id of monitor (in monitors) on success, -1 on error
+ */
+static int
+get_monitor_id_by_logical_y(int y)
+{
+    for (int i = 0; i < monitorsNum; ++i) {
+        if (monitors[i].rect.top <= y && y < monitors[i].rect.bottom)
+            return i;
+    }
+
+    return -1;
+}
+
+#define LOGICAL_TO_PHYSICAL_X(val, i) ((val) * monitors[i].physical.x / monitors[i].logical.x)
+#define LOGICAL_TO_PHYSICAL_Y(val, i) ((val) * monitors[i].physical.y / monitors[i].logical.y)
+
+/**
+ * Converts given rect from logical to physical pixel coordinates
+ *
+ * @param rect in logical coordinates, it will be modified to physical coordinates
+ * @return void
+ */
+static void
+convert_logical_rect_to_physical(RECT *rect)
+{
+    int indX;
+    int indY;
+    int ind;
+
+    /* convert top-left corner */
+    ind = get_monitor_id_by_logical_point(rect->left, rect->top);
+    if (ind >= 0) {
+        indX = indY = ind;
+    } else {
+        /* monitor not found, let's search by x and y separately */
+        indX = get_monitor_id_by_logical_x(rect->left);
+        indY = get_monitor_id_by_logical_y(rect->top);
+    }
+    rect->left = LOGICAL_TO_PHYSICAL_X(rect->left, indX);
+    rect->top  = LOGICAL_TO_PHYSICAL_Y(rect->top,  indY);
+
+    /* convert bottom-right corner, we have to make -1 because bottom-right
+     * corner is not incluse */
+    ind = get_monitor_id_by_logical_point(rect->right - 1, rect->bottom - 1);
+    if (ind >= 0) {
+        indX = indY = ind;
+    } else {
+        /* monitor not found, let's search by x and y separately */
+        indX = get_monitor_id_by_logical_x(rect->right  - 1);
+        indY = get_monitor_id_by_logical_y(rect->bottom - 1);
+    }
+    rect->right  = LOGICAL_TO_PHYSICAL_X(rect->right,  indX);
+    rect->bottom = LOGICAL_TO_PHYSICAL_Y(rect->bottom, indY);
+}
+
 /**
  * Initializes the gdi grab device demuxer (public device demuxer API).
  *
@@ -235,10 +394,6 @@  gdigrab_read_header(AVFormatContext *s1)
     AVStream   *st       = NULL;
 
     int bpp;
-    int horzres;
-    int vertres;
-    int desktophorzres;
-    int desktopvertres;
     RECT virtual_rect;
     RECT clip_rect;
     BITMAP bmp;
@@ -277,24 +432,49 @@  gdigrab_read_header(AVFormatContext *s1)
     }
     bpp = GetDeviceCaps(source_hdc, BITSPIXEL);
 
-    horzres = GetDeviceCaps(source_hdc, HORZRES);
-    vertres = GetDeviceCaps(source_hdc, VERTRES);
-    desktophorzres = GetDeviceCaps(source_hdc, DESKTOPHORZRES);
-    desktopvertres = GetDeviceCaps(source_hdc, DESKTOPVERTRES);
+    /* Get resolution and coordinates for all monitors */
+    SetRectEmpty(&rcCombined);
+    monitorsNum = 0;
+    EnumDisplayMonitors(0, 0, monitor_enum, 0);
+
+    for (int i = 0; i < monitorsNum; ++i) {
+        av_log(s1, AV_LOG_DEBUG,
+               "Monitor %i (%li,%li) (%li,%li), logical res:(%li,%li), physical res:(%li,%li)\n",
+               i, monitors[i].rect.left, monitors[i].rect.top,
+               monitors[i].rect.right, monitors[i].rect.bottom,
+               monitors[i].logical.x, monitors[i].logical.y,
+               monitors[i].physical.x, monitors[i].physical.y);
+    }
 
     if (hwnd) {
+        /* get actual window coordinates to retrieve it's monitor index */
+        GetWindowRect(hwnd, &virtual_rect);
+        int ind = get_monitor_id_by_logical_rectangle(&virtual_rect);
+
         GetClientRect(hwnd, &virtual_rect);
+        av_log(s1, AV_LOG_DEBUG, "Window rect logical (%li,%li)x(%li,%li)",
+               virtual_rect.left, virtual_rect.top, virtual_rect.right, virtual_rect.bottom);
+
         /* window -- get the right height and width for scaling DPI */
-        virtual_rect.left   = virtual_rect.left   * desktophorzres / horzres;
-        virtual_rect.right  = virtual_rect.right  * desktophorzres / horzres;
-        virtual_rect.top    = virtual_rect.top    * desktopvertres / vertres;
-        virtual_rect.bottom = virtual_rect.bottom * desktopvertres / vertres;
+        virtual_rect.left   = LOGICAL_TO_PHYSICAL_X(virtual_rect.left,   ind);
+        virtual_rect.right  = LOGICAL_TO_PHYSICAL_X(virtual_rect.right,  ind);
+        virtual_rect.top    = LOGICAL_TO_PHYSICAL_Y(virtual_rect.top,    ind);
+        virtual_rect.bottom = LOGICAL_TO_PHYSICAL_Y(virtual_rect.bottom, ind);
+        av_log(s1, AV_LOG_DEBUG, ", physical (%li,%li)x(%li,%li)\n",
+               virtual_rect.left, virtual_rect.top, virtual_rect.right, virtual_rect.bottom);
     } else {
-        /* desktop -- get the right height and width for scaling DPI */
         virtual_rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
         virtual_rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
-        virtual_rect.right = (virtual_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN)) * desktophorzres / horzres;
-        virtual_rect.bottom = (virtual_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN)) * desktopvertres / vertres;
+        virtual_rect.right = (virtual_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN)) ;
+        virtual_rect.bottom = (virtual_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN)) ;
+
+        av_log(s1, AV_LOG_DEBUG, "Virtual desktop logical (%li,%li)x(%li,%li)",
+               virtual_rect.left, virtual_rect.top, virtual_rect.right, virtual_rect.bottom);
+
+        /* desktop -- get the right height and width for scaling DPI */
+        convert_logical_rect_to_physical(&virtual_rect);
+        av_log(s1, AV_LOG_DEBUG, ", physical (%li,%li)x(%li,%li)\n",
+               virtual_rect.left, virtual_rect.top, virtual_rect.right, virtual_rect.bottom);
     }
 
     /* If no width or height set, use full screen/window area */
-- 
2.21.0.windows.1