[office/crow-translate] src: Fix Windows multi-monitor capture area with per-monitor DPI
Mauritius Clemens <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit da9ff89d35c1f2c26c2e067029b3f4e0888790c4 by Mauritius Clemens.
Committed on 16/08/2026 at 22:20.
Pushed by pillowtrucker into branch 'master'.
Fix Windows multi-monitor capture area with per-monitor DPI
Request PER_MONITOR_AWARE_V2 so Qt reports consistent geometry across a
scaled main monitor and a second monitor. Map the capture selection from the
overlay's logical coordinates into device pixels per-screen in
SnippingArea::selectedPixmap, so the grabbed image is correctly offset and
scaled for OCR on 200%-scaled or mixed-DPI setups.
M +12 -0 src/main.cpp
M +52 -4 src/ocr/snippingarea.cpp
M +4 -0 src/ocr/snippingarea.h
https://invent.kde.org/office/crow-translate/-/commit/da9ff89d35c1f2c26c2e067029b3f4e0888790c4
diff --git a/src/main.cpp b/src/main.cpp
index ab9c57a3..f892e1d8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -18,6 +18,12 @@
#include <KIconTheme>
#endif
+#ifdef Q_OS_WIN
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <windows.h>
+#endif
+
#ifdef Q_OS_UNIX
#include "ocr/ocr.h"
@@ -69,6 +75,12 @@ int launchGui(int argc, char *argv[])
#if defined(Q_OS_WIN)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+ // Request per-monitor DPI awareness (V2) explicitly so that the screen
+ // geometry and grabbed images reported by Qt use a single, consistent scale
+ // regardless of any application manifest. Without this, on a scaled main
+ // monitor alongside a second monitor the capture area maps its coordinates
+ // incorrectly and the grabbed pixmap is offset or clipped.
+ SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
#endif
#if defined(Q_OS_LINUX)
QGuiApplication::setDesktopFileName(QStringLiteral(DESKTOP_FILE_BASENAME));
diff --git a/src/ocr/snippingarea.cpp b/src/ocr/snippingarea.cpp
index 9ae3a789..a2dd324c 100644
--- a/src/ocr/snippingarea.cpp
+++ b/src/ocr/snippingarea.cpp
@@ -449,7 +449,7 @@ int SnippingArea::boundsLeft(int newTopLeftX, bool mouse)
int SnippingArea::boundsRight(int newTopLeftX, bool mouse)
{
// The max X coordinate of the top left point
- const int realMaxX = qRound((width() - m_selection.width()) * devicePixelRatioF());
+ const int realMaxX = width() - m_selection.width();
const int xOffset = newTopLeftX - realMaxX;
if (xOffset > 0) {
if (mouse)
@@ -763,16 +763,62 @@ SnippingArea::MouseState SnippingArea::mouseLocation(QPointF pos) const
QPixmap SnippingArea::selectedPixmap() const
{
- return m_screenPixmap.copy(m_selection);
+ // The selection is stored in the overlay's logical (device-independent)
+ // coordinates, but each screen image is captured at its native
+ // (device-pixel) resolution. Extract the selection at full resolution by
+ // intersecting it with every screen and scaling by that screen's
+ // devicePixelRatio, preserving the pixels for OCR.
+ const QRect deviceSelection = toDeviceRect(m_selection);
+ QPixmap result(deviceSelection.size());
+ result.fill(Qt::transparent);
+
+ QPainter painter(&result);
+ for (auto it = m_images.constBegin(); it != m_images.constEnd(); ++it) {
+ const QRect screenArea = deviceGeometry(it.key()).translated(-m_deviceScreensRect.topLeft());
+ const QRect matched = screenArea.intersected(deviceSelection);
+ if (matched.isEmpty())
+ continue;
+
+ const QRect sourceRect = matched.translated(-screenArea.topLeft());
+ painter.drawImage(matched.translated(-deviceSelection.topLeft()), it.value(), sourceRect);
+ }
+ return result;
+}
+
+QRect SnippingArea::deviceGeometry(const QScreen *screen)
+{
+ const qreal dpr = screen->devicePixelRatio();
+ const QRect geometry = screen->geometry();
+ return QRect(qRound(geometry.x() * dpr),
+ qRound(geometry.y() * dpr),
+ qRound(geometry.width() * dpr),
+ qRound(geometry.height() * dpr));
+}
+
+QRect SnippingArea::toDeviceRect(const QRect &rect) const
+{
+ // Map a rectangle expressed in the overlay's logical coordinates to the
+ // device-pixel space of the composited screenshot. Use the screen under the
+ // rectangle's center so that a single consistent scale factor is applied.
+ const QPointF overlayCenter = rect.center() + m_screensRect.topLeft();
+ const QScreen *screen = QGuiApplication::screenAt(overlayCenter.toPoint());
+ if (screen == nullptr)
+ screen = QGuiApplication::primaryScreen();
+
+ const qreal dpr = screen->devicePixelRatio();
+ const QPointF screenRelativeTopLeft = rect.topLeft() + m_screensRect.topLeft() - screen->geometry().topLeft();
+ const QPointF deviceTopLeft = screenRelativeTopLeft * dpr + deviceGeometry(screen).topLeft() - m_deviceScreensRect.topLeft();
+ return QRect(deviceTopLeft.toPoint(), QSizeF(rect.width() * dpr, rect.height() * dpr).toSize());
}
void SnippingArea::createPixmapFromScreens()
{
- m_screenPixmap = QPixmap(m_screensRect.width(), m_screensRect.height());
+ m_screenPixmap = QPixmap(m_screensRect.size());
QPainter painter(&m_screenPixmap);
// Geometry can have negative coordinates, so it is necessary to subtract the upper left point, because coordinates on the widget are counted from 0
+ // Each screen image is grabbed at its native resolution, so it must be scaled down to the screen's logical geometry when composited
for (auto it = m_images.constBegin(); it != m_images.constEnd(); ++it)
- painter.drawImage(it.key()->geometry().topLeft() - m_screensRect.topLeft(), it.value());
+ painter.drawImage(it.key()->geometry().translated(-m_screensRect.topLeft()), it.value());
}
void SnippingArea::setGeometryToScreenPixmap()
@@ -880,8 +926,10 @@ void SnippingArea::setBottomHelpText()
void SnippingArea::preparePaint()
{
m_screensRect = {};
+ m_deviceScreensRect = {};
for (auto it = m_images.constBegin(); it != m_images.constEnd(); ++it) {
m_screensRect = m_screensRect.united(it.key()->geometry());
+ m_deviceScreensRect = m_deviceScreensRect.united(deviceGeometry(it.key()));
}
}
diff --git a/src/ocr/snippingarea.h b/src/ocr/snippingarea.h
index daaa153c..ccc491bf 100644
--- a/src/ocr/snippingarea.h
+++ b/src/ocr/snippingarea.h
@@ -104,6 +104,9 @@ private:
static bool isPointInsideCircle(QPointF circleCenter, qreal radius, QPointF point);
static bool isInRange(qreal low, qreal high, qreal value);
static bool isWithinThreshold(qreal offset, qreal threshold);
+ static QRect deviceGeometry(const QScreen *screen);
+
+ QRect toDeviceRect(const QRect &rect) const;
static constexpr int s_handleRadiusMouse = 9;
static constexpr int s_handleRadiusTouch = 12;
@@ -146,6 +149,7 @@ private:
QPointF m_startPos;
QPointF m_initialTopLeft;
QRect m_screensRect;
+ QRect m_deviceScreensRect;
QMap<const QScreen *, QImage> m_images;