[英]How to draw an empty rectangle on screen with Python
我不是专家,我试图在屏幕上显示一个矩形,该矩形从一个固定的起点开始跟随鼠标移动,就像你 select 用文字或绘画一样。 我附带了这段代码:
import win32gui
m=win32gui.GetCursorPos()
while True:
n=win32gui.GetCursorPos()
for i in range(n[0]-m[0]):
win32gui.SetPixel(dc, m[0]+i, m[1], 0)
win32gui.SetPixel(dc, m[0]+i, n[1], 0)
for i in range(n[1]-m[1]):
win32gui.SetPixel(dc, m[0], m[1]+i, 0)
win32gui.SetPixel(dc, n[0], m[1]+i, 0)
如您所见,代码将绘制矩形,但之前的矩形将一直保留到屏幕更新为止。
我提出的唯一解决方案是在将它们设置为黑色之前获取我将绘制的像素值,然后每次都重新绘制它们,但这会使我的代码非常慢。 有没有一种简单的方法可以更快地更新屏幕以防止这种情况发生?
...
用解决方案编辑。
正如@Torxed 所建议的,使用 win32gui.InvalidateRect 解决了更新问题。 但是,我发现只设置我需要设置的点的颜色比要求矩形便宜。 第一个解决方案非常干净,而第二个仍然有点小故障。 最后,最适合我的代码是:
import win32gui
m=win32gui.GetCursorPos()
dc = win32gui.GetDC(0)
while True:
n=win32gui.GetCursorPos()
win32gui.InvalidateRect(hwnd, (m[0], m[1], GetSystemMetrics(0), GetSystemMetrics(1)), True)
back=[]
for i in range((n[0]-m[0])//4):
win32gui.SetPixel(dc, m[0]+4*i, m[1], 0)
win32gui.SetPixel(dc, m[0]+4*i, n[1], 0)
for i in range((n[1]-m[1])//4):
win32gui.SetPixel(dc, m[0], m[1]+4*i, 0)
win32gui.SetPixel(dc, n[0], m[1]+4*i, 0)
4 的除法和乘法是避免闪烁所必需的,但在视觉上与使用 DrawFocusRect 相同。
这只有在您从最初的 position 开始向右下方时才有效,但这正是我所需要的。 改进它以接受任何辅助 position 并不难。
为了刷新旧的绘制区域,您需要调用win32gui.UpdateWindow或类似的方法来更新您的特定 window,但因为从技术上讲,您不是在表面上绘制,而是在整个显示器上绘制。 您需要使显示器的整个区域无效,以便告诉 windows 在其上重新绘制任何内容(或者我理解它) 。
为了克服缓慢的问题,您可以使用win32ui.Rectangle
在一个 go 中绘制它,而不是使用 for 循环创建在完成矩形之前需要 X 个循环来迭代的边界:
import win32gui, win32ui
from win32api import GetSystemMetrics
dc = win32gui.GetDC(0)
dcObj = win32ui.CreateDCFromHandle(dc)
hwnd = win32gui.WindowFromPoint((0,0))
monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
while True:
m = win32gui.GetCursorPos()
dcObj.Rectangle((m[0], m[1], m[0]+30, m[1]+30))
win32gui.InvalidateRect(hwnd, monitor, True) # Refresh the entire monitor
可以在这里进行进一步的优化,比如不更新整个显示器,只更新你绘制的部分等等。 但这是基本概念:)
并且要创建一个没有填充的矩形,例如,您可以将Rectangle
DrawFocusRect
。 或者为了更多的控制,甚至使用win32gui.PatBlt
显然setPixel
是最快的,所以这是我最后一个关于颜色和速度的例子,尽管它并不完美,因为RedrawWindow
不会强制重绘,它只是要求 windows 去做,然后取决于 windows 是否尊重它。 InvalidateRect
在性能上要好一些,因为它要求事件处理程序在有空闲时间时清除矩形。 但是我还没有找到比RedrawWindow
更具侵略性的方法,即使那仍然很温和。 一个例子是,隐藏桌面图标,下面的代码将不起作用。
import win32gui, win32ui, win32api, win32con
from win32api import GetSystemMetrics
dc = win32gui.GetDC(0)
dcObj = win32ui.CreateDCFromHandle(dc)
hwnd = win32gui.WindowFromPoint((0,0))
monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
red = win32api.RGB(255, 0, 0) # Red
past_coordinates = monitor
while True:
m = win32gui.GetCursorPos()
rect = win32gui.CreateRoundRectRgn(*past_coordinates, 2 , 2)
win32gui.RedrawWindow(hwnd, past_coordinates, rect, win32con.RDW_INVALIDATE)
for x in range(10):
win32gui.SetPixel(dc, m[0]+x, m[1], red)
win32gui.SetPixel(dc, m[0]+x, m[1]+10, red)
for y in range(10):
win32gui.SetPixel(dc, m[0], m[1]+y, red)
win32gui.SetPixel(dc, m[0]+10, m[1]+y, red)
past_coordinates = (m[0]-20, m[1]-20, m[0]+20, m[1]+20)
职位和决议的问题? 请注意,高 DPI 系统往往会导致一系列问题。 And I haven't found many ways around this other than going over to a OpenGL solution or using frameworks such as wxPython or OpenCV other than this post: Marking Your Python Program as High DPI Aware Seamlessly Windows
或者将 Windows 显示比例更改为100%
:
这会导致 go 的定位问题,也许通过向操作系统查询比例和补偿来考虑这一点。
我能在“清除旧图纸”中找到的唯一参考是这篇文章: win32 content changed but doesn't show update unless window ismoved tagged c++
win winapi
希望这可以避免一些人在找到一个好例子之前进行搜索。
如果您使用 OpenCV 那么可以这样做
#draw either rectangles or circles by dragging the mouse like we do in Paint application
import cv2
import numpy as np
drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle. Press 'm' to toggle to curve
ix,iy = -1,-1
#mouse callback function
def draw_circle(event,x,y,flags,param):
global ix,iy,drawing,mode
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x,y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),5,(0,0,255),-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),5,(0,0,255),-1)
#bind this mouse callback function to OpenCV window
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_circle)
while(1):
cv2.imshow('image',img)
k = cv2.waitKey(1) & 0xFF
if k == ord('m'):
mode = not mode
elif k == 27:
break
cv2.destroyAllWindows()
这取自官方 opencv 文档here
在使用@Torex 解决方案一段时间后,我遇到了一些问题,特别是,我使用该方法绘制的矩形不会在全屏模式下显示,并且如果我获得它们所在部分的屏幕截图,它们仍然可见(自相矛盾)绘制,因此解决方案的更新部分在全屏模式下不起作用。
下面是一个可能更复杂的解决方案,有一些优缺点:
优点:
它可以使用 pygame 功能轻松地为绘制矩形的基本思想添加功能,包括轻松更改其颜色、宽度等。
它可以在任何屏幕模式下工作。
工作时不会出现故障
对比:
它在开始时确实出现故障,最后因为 pygame.display() 被调用并被杀死。
它需要创建一个独立的window。
您需要对其进行调整以防止点击事件将您带出您要创建的透明 window。
工作代码:
import win32api
from win32api import GetSystemMetrics
import win32con
import pygame
import win32gui
import pyautogui
pygame.init()
screen = pygame.display.set_mode((GetSystemMetrics(0), GetSystemMetrics(1)), pygame.FULLSCREEN, pygame.NOFRAME) # For borderless, use pygame.NOFRAME
done = False
fuchsia = (255, 0, 128) # Transparency color
dark_red = (139, 0, 0)
# Set window transparency color
hwnd = pygame.display.get_wm_info()["window"]
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(*fuchsia), 0, win32con.LWA_COLORKEY)
#Some controls
block=0
block1=0
#You can render some text
white=(255,255,255)
blue=(0,0,255)
font = pygame.font.Font('freesansbold.ttf', 32)
texto=font.render('press "z" to define one corner and again to define the rectangle, it will take a screenshot', True, white, blue)
while not done:
keys= pygame.key.get_pressed()
pygame.time.delay(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
#This controls the actions at starting and end point
if block1==0:
if keys[pygame.K_z]:
if block==0:
block=1
n=win32gui.GetCursorPos()
else:
done=True
break
#this prevents double checks, can be handle also by using events
block1=10
else:
m=win32gui.GetCursorPos()
else:
block1-=1
screen.fill(fuchsia) # Transparent background
#this will render some text
screen.blit(texto,(0,0))
#This will draw your rectangle
if block==1:
pygame.draw.line(screen,dark_red,(n[0],n[1]),(m[0],n[1]),1)
pygame.draw.line(screen,dark_red,(n[0],m[1]),(m[0],m[1]),1)
pygame.draw.line(screen,dark_red,(n[0],n[1]),(n[0],m[1]),1)
pygame.draw.line(screen,dark_red,(m[0],n[1]),(m[0],m[1]),1)
# Drawing the independent lines is still a little faster than drawing a rectangle
pygame.draw.rect(screen,dark_red,(min(n[0],m[0]),min(n[1],m[1]),abs(m[0]-n[0]),abs(m[1]-n[1])),1)
pygame.display.update()
pygame.display.quit()
pyautogui.screenshot(region=(min(n[0],m[0]),min(n[1],m[1]),abs(m[0]-n[0]),abs(m[1]-n[1])))
根据这里的一些答案,我已经为此工作了一段时间,这是我的解决方案。
这等待鼠标被按下和拖动,并创建一个从按下的 position 到拖动的 position 的矩形。 在鼠标释放时,它将清除矩形和 output 单击和释放的位置并关闭所有挂钩。
我想解决一些问题(一些轻微的闪烁和边框太薄)所以如果有人知道这些我会很感激一些帮助(win32ui 文档真的很糟糕)
如果您想要纯色,只需将FrameRect((x,y,a,b),brush)
更改为FillRect((x,y,a,b), brush)
from win32gui import GetDC, WindowFromPoint, SetPixel, InvalidateRect
from win32ui import CreateDCFromHandle, CreateBrush
from win32api import GetSystemMetrics, GetSysColor
from PyHook3 import HookManager
import ctypes
class Draw_Screen_Rect:
def __init__(self):
self.pos = [0, 0, 0, 0]
dc = GetDC(0)
self.dcObj = CreateDCFromHandle(dc)
self.hwnd = WindowFromPoint((0,0))
self.monitor = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
self.clicked = False
self.b1 = CreateBrush()
self.b1.CreateSolidBrush(GetSysColor(255))
self.final_rect = None
self.refresh_frames = 0
self.refresh_after = 10
def _draw_rect_func(self):
self.dcObj.FrameRect(tuple(self.pos), self.b1)
def _refresh_rect(self):
InvalidateRect(self.hwnd, self.monitor, True)
def _OnMouseEvent(self, event):
if event.Message == 513:
self.clicked = True
self.pos[0], self.pos[1] = event.Position
elif event.Message == 514:
self.clicked = False
self.pos[2], self.pos[3] = event.Position
self._draw_rect_func()
self._refresh_rect()
self.final_rect = self.pos
self._destroy_hooks()
elif event.Message == 512:
if self.clicked:
self.pos[2], self.pos[3] = event.Position
if self.refresh_frames%2 ==0:
self._draw_rect_func()
self.refresh_frames+=1
if self.refresh_frames > self.refresh_after:
self.refresh_frames = 0
self._refresh_rect()
return True
def create_hooks(self):
self.hm = HookManager()
self.hm.MouseLeftDown = self._OnMouseEvent
self.hm.MouseLeftUp = self._OnMouseEvent
self.hm.MouseMove = self._OnMouseEvent
self.hm.HookMouse()
self.hm.HookKeyboard()
def _destroy_hooks(self):
self.hm.UnhookMouse()
ctypes.windll.user32.PostQuitMessage(0)
def output(self):
return self.final_rect
if __name__ == '__main__':
app = Draw_Screen_Rect()
app.create_hooks()
from pythoncom import PumpMessages
PumpMessages()
out = app.output()
print(out)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.