fix(capture): map selections across high-DPI displays

This commit is contained in:
2026-09-18 15:14:37 +08:00
parent b45a757982
commit 419eb0d005
+41 -11
View File
@@ -74,13 +74,44 @@ class ScreenCaptureOverlay(QtWidgets.QWidget):
self.activateWindow() self.activateWindow()
self.raise_() self.raise_()
@staticmethod
def _scale_rect(rect, logical_size, native_size):
"""把 Qt 逻辑坐标选区映射到抓屏位图的原生像素坐标。"""
if (logical_size.width() <= 0 or logical_size.height() <= 0
or native_size.width() <= 0 or native_size.height() <= 0):
return QtCore.QRect()
logical_bounds = QtCore.QRect(QtCore.QPoint(), logical_size)
clipped = rect.normalized().intersected(logical_bounds)
if clipped.isEmpty():
return QtCore.QRect()
scale_x = native_size.width() / logical_size.width()
scale_y = native_size.height() / logical_size.height()
left = round(clipped.x() * scale_x)
top = round(clipped.y() * scale_y)
right = round((clipped.x() + clipped.width()) * scale_x)
bottom = round((clipped.y() + clipped.height()) * scale_y)
left = max(0, min(native_size.width(), left))
top = max(0, min(native_size.height(), top))
right = max(left, min(native_size.width(), right))
bottom = max(top, min(native_size.height(), bottom))
return QtCore.QRect(left, top, right - left, bottom - top)
def _native_rect(self, rect):
"""返回选区在当前抓屏位图中的原生像素矩形。"""
if self._full_pixmap is None or self._full_pixmap.isNull():
return QtCore.QRect()
return self._scale_rect(rect, self.size(), self._full_pixmap.size())
def paintEvent(self, event): def paintEvent(self, event):
if not self._full_pixmap: if not self._full_pixmap:
return return
painter = QtGui.QPainter(self) painter = QtGui.QPainter(self)
# 1. 绘制屏幕截图作为背景 # 1. 绘制屏幕截图作为背景
painter.drawPixmap(0, 0, self._full_pixmap) painter.drawPixmap(self.rect(), self._full_pixmap, self._full_pixmap.rect())
# 2. 半透明遮罩 # 2. 半透明遮罩
painter.fillRect(self.rect(), QtGui.QColor(0, 0, 0, 100)) painter.fillRect(self.rect(), QtGui.QColor(0, 0, 0, 100))
@@ -92,15 +123,17 @@ class ScreenCaptureOverlay(QtWidgets.QWidget):
rect = self._current_rect rect = self._current_rect
if rect.width() > 0 and rect.height() > 0: if rect.width() > 0 and rect.height() > 0:
native_rect = self._native_rect(rect)
# 3. 选区内重绘原图(去掉遮罩,形成高亮效果) # 3. 选区内重绘原图(去掉遮罩,形成高亮效果)
painter.drawPixmap(rect, self._full_pixmap, rect) painter.drawPixmap(rect, self._full_pixmap, native_rect)
# 4. 蓝色边框 # 4. 蓝色边框
pen = QtGui.QPen(QtGui.QColor(0, 120, 215), 2) pen = QtGui.QPen(QtGui.QColor(0, 120, 215), 2)
painter.setPen(pen) painter.setPen(pen)
painter.setBrush(QtCore.Qt.BrushStyle.NoBrush) painter.setBrush(QtCore.Qt.BrushStyle.NoBrush)
painter.drawRect(rect) painter.drawRect(rect)
# 5. 尺寸标注 # 5. 尺寸标注
size_text = f"{rect.width()} x {rect.height()}" # 标签显示最终文件的真实像素,而不是受系统缩放影响的逻辑尺寸。
size_text = f"{native_rect.width()} x {native_rect.height()} px"
font = painter.font() font = painter.font()
font.setPointSize(9) font.setPointSize(9)
painter.setFont(font) painter.setFont(font)
@@ -177,14 +210,11 @@ class ScreenCaptureOverlay(QtWidgets.QWidget):
def _on_confirm(self): def _on_confirm(self):
"""确认截图:裁剪并发射信号""" """确认截图:裁剪并发射信号"""
if self._full_pixmap and self._current_rect.width() > 5 and self._current_rect.height() > 5: if self._full_pixmap and self._current_rect.width() > 5 and self._current_rect.height() > 5:
dpr = self._full_pixmap.devicePixelRatio() native_rect = self._native_rect(self._current_rect)
phys_rect = QtCore.QRect( if not native_rect.isEmpty():
int(self._current_rect.x() * dpr), captured = self._full_pixmap.toImage().copy(native_rect)
int(self._current_rect.y() * dpr), # 截图是普通位图;避免下游再次按桌面 DPR 缩小显示。
int(self._current_rect.width() * dpr), captured.setDevicePixelRatio(1.0)
int(self._current_rect.height() * dpr),
)
captured = self._full_pixmap.toImage().copy(phys_rect)
self.screenshot_captured.emit(captured) self.screenshot_captured.emit(captured)
self.close() self.close()