我有一个 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 缩放相关的问题..
一点点数学:
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)