由于显示器缩放和双显示器设置,PySide 截图工具坐标不匹配

问题描述 投票:0回答:1

我有一个 Python 应用程序,它使用 PySide6 和 PIL 库捕获屏幕区域。 该应用程序在 1980x1080 分辨率的显示器上运行良好。然而,当我在分辨率为 2560x1600、缩放比例为 175% 的主显示器上使用它时,捕获的区域与实际屏幕截图之间存在明显的不匹配。

我怀疑这种差异是由于显示器缩放因子影响坐标计算造成的。如何调整坐标以适应显示器缩放系数?

此外,我想扩展屏幕捕获功能以在双显示器上工作,同时捕获两个显示器的区域。我应该采取什么方法来实现这个功能?

# source from https://github.com/harupy/snipping-tool
import sys
from PySide6 import QtCore, QtGui, QtWidgets
import tkinter as tk
from PIL import ImageGrab
import numpy as np
import cv2
import pygetwindow as gw

class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        root = tk.Tk()
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        root.withdraw()  # Hide the root window
        screen_geometry = gw.getWindowsWithTitle(gw.getActiveWindow().title)[0]
        virtual_screen_width = screen_geometry.width
        virtual_screen_height = screen_geometry.height
        self.setGeometry(0, 0, virtual_screen_width, virtual_screen_height)
        self.setWindowTitle(' ')
        self.begin = QtCore.QPoint()
        self.end = QtCore.QPoint()
        self.setWindowOpacity(0.3)
        self.num_snip = 0
        self.is_snipping = False
        QtWidgets.QApplication.setOverrideCursor(
            QtGui.QCursor(QtCore.Qt.CrossCursor)
        )
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        print('Capture the screen...')
        print('Press q if you want to quit...')
        self.show()

    def paintEvent(self, event):
        if self.is_snipping:
            brush_color = (0, 0, 0, 0)
            lw = 0
            opacity = 0
        else:
            brush_color = (128, 128, 255, 128)
            lw = 3
            opacity = 0.3

        self.setWindowOpacity(opacity)
        qp = QtGui.QPainter(self)
        qp.setPen(QtGui.QPen(QtGui.QColor('black'), lw))
        qp.setBrush(QtGui.QColor(*brush_color))
        qp.drawRect(QtCore.QRectF(self.begin, self.end))

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Q:
            print('Quit')
            self.close()
        event.accept()

    def mousePressEvent(self, event):
        self.begin = event.position()
        self.end = self.begin
        self.update()

    def mouseMoveEvent(self, event):
        self.end = event.position()
        self.update()

    def mouseReleaseEvent(self, event):
        self.num_snip += 1
        x1 = min(self.begin.x(), self.end.x())
        y1 = min(self.begin.y(), self.end.y())
        x2 = max(self.begin.x(), self.end.x())
        y2 = max(self.begin.y(), self.end.y())

        self.is_snipping = True
        self.repaint()
        QtWidgets.QApplication.processEvents()
        img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
        self.is_snipping = False
        self.repaint()
        QtWidgets.QApplication.processEvents()
        img_name = 'snip{}.png'.format(self.num_snip)
        img.save(img_name)
        print(img_name, 'saved')
        img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MyWidget()
    window.show()
    app.aboutToQuit.connect(app.deleteLater)
    sys.exit(app.exec())

我目前正在探索

screeninfo
库来解决与屏幕分辨率和 DPI 缩放相关的问题..

python pyqt screenshot pyside6 pyqt6
1个回答
0
投票

一点点数学:

img = ImageGrab.grab()
# crop with correct scale
screen_width = self.master.winfo_screenwidth()
screen_height = self.master.winfo_screenheight()
x1 = x1 / screen_width * img.width
x2 = x2 / screen_width * img.width
y1 = y1 / screen_height * img.height
y2 = y2 / screen_height * img.height
img = img.crop(box=(x1, y1, x2, y2))

另外,不要使用

bbox
,因为它会降低质量。请使用
img.crop()
来代替。

顺便说一句,你的代码在 Mac 上不可用。但我有一个跨平台版本的截图工具:

"""
Ver 1.0

StackOverflow answer: https://stackoverflow.com/a/79166810/18598080

Bruh. Finally made it.
it mainly supports Mac(tested) and Windows(not tested but supposed to work). In Linux (not tested), the dragging view will not be totally transparent.

Example:

import tkinter as tk
import TkDragScreenshot as dshot

root = tk.Tk()
root.withdraw()

def callback(img):
    img.save("a.png")
    quit()
def cancel_callback():
    print("User clicked / dragged 0 pixels.")
    quit()

dshot.drag_screen_shot(root, callback, cancel_callback)

root.mainloop()
"""

import platform
import tkinter as tk
from PIL import ImageGrab

using_debug_mode = None

class DragScreenshotPanel:  
    def __init__(self, root: tk.Tk, master: tk.Toplevel | tk.Tk, callback = None, cancel_callback = None):
        self.root = root
        self.master = master
        self.callback = callback
        self.cancel_callback = cancel_callback
        self.start_x = None
        self.start_y = None
        self.rect = None
        self.canvas = tk.Canvas(master, cursor="cross", background="black")
        self.canvas.pack(fill=tk.BOTH, expand=True)  
        self.canvas.config(bg=master["bg"])
        self.canvas.bind("<Button-1>", self.on_button_press)
        self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)

    def on_button_press(self, event):
        self.start_x = event.x
        self.start_y = event.y
        self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline='white', width=2)  

    def on_mouse_drag(self, event):  
        self.canvas.coords(self.rect, self.start_x, self.start_y, event.x, event.y)

    def on_button_release(self, event):
        x1 = min(self.start_x, event.x)
        y1 = min(self.start_y, event.y)
        x2 = max(self.start_x, event.x)
        y2 = max(self.start_y, event.y)
        self.canvas.delete(self.rect)
        dy = abs(y2-y1)
        dx = abs(x2-x1)
        if dy*dx != 0:
            self.master.withdraw()
            img = ImageGrab.grab()
            screen_width = self.master.winfo_screenwidth()
            screen_height = self.master.winfo_screenheight()
            x1 = x1 / screen_width * img.width
            x2 = x2 / screen_width * img.width
            y1 = y1 / screen_height * img.height
            y2 = y2 / screen_height * img.height
            img = img.crop(box=(x1, y1, x2, y2))
            if using_debug_mode: print("Screenshot taken!")
            self.root.after(1, self.callback(img))
            self.master.deiconify()
            self.master.focus_force()
        else:
            if using_debug_mode: print("Screenshot canceled!")
            self.root.after(1, self.cancel_callback())
        
        self.master.destroy()
        


def set_bg_transparent(toplevel:tk.Toplevel, invisible_color_Windows_OS_Only= '#100101'):
    if platform.system() == "Windows":
        toplevel.attributes("-transparentcolor", invisible_color_Windows_OS_Only)
        toplevel.config(bg=invisible_color_Windows_OS_Only)
    elif platform.system() == "Darwin":
        toplevel.attributes("-transparent", True)
        toplevel.config(bg="systemTransparent")
    else:
        if using_debug_mode: print(f"Total transparency is not supported on this OS. platform.system() -> '{platform.system()}'")
        window_alpha_channel = 0.3
        toplevel.attributes('-alpha', window_alpha_channel)
        toplevel.lift()
        toplevel.attributes("-topmost", True)
        toplevel.attributes("-transparent", True)


def drag_screen_shot(root:tk.Tk, callback = None, cancel_callback = None, debug_logging = False):
    global using_debug_mode

    using_debug_mode = debug_logging

    top = tk.Toplevel(root)
    top.geometry(f"{root.winfo_screenwidth()}x{root.winfo_screenheight()}+0+0")
    top.overrideredirect(True)
    top.lift()
    top.attributes("-topmost", True)

    set_bg_transparent(top)
    DragScreenshotPanel(root, top, callback, cancel_callback)

只需使用

tk
创建根,然后调用
drag_screen_shot(root, on_capture, on_cancel)

© www.soinside.com 2019 - 2024. All rights reserved.