我不是专家,我试图在屏幕上显示一个矩形,该矩形跟随鼠标从稳定的起点移动,就像您选择文字或绘画中的某些内容一样。我带有以下代码:
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)
除法和乘以四是避免闪烁所必需的,但在视觉上与使用DrawFocusRect相同。
这仅在您保持波纹管且从初始位置向右的情况下才有效,但这正是我所需要的。改善它以接受任何次要职位并不困难。
答案 0 :(得分:1)
为了刷新旧的绘制区域,您需要调用win32gui.UpdateWindow或类似方法来更新您的特定窗口,但是由于从技术上讲您不是在表面上绘制,而是在整个显示器上绘制。您需要使监视器的整个区域无效,以便告诉Windows在其上重新绘制任何内容(或者,据我所知)。
要克服速度慢的问题,您可以使用win32ui.Rectangle
一次绘制它,而不是使用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系统往往会引起很多问题。除了这篇文章之外,我没有找到其他解决方法,除了使用OpenGL解决方案或使用wxPython或OpenCV之类的框架之外,Marking Your Python Program as High DPI Aware Seamlessly Windows
或将Windows显示比例更改为100%
:
这导致定位问题消失了,也许可以通过向操作系统查询比例并进行补偿来解决这个问题。
我在“清除旧图纸”上可以找到的唯一参考是这篇文章:win32 content changed but doesn't show update unless window is moved标记为c++
胜利winapi
。希望这可以使一些人在找到好的榜样之前不必进行搜索。
答案 1 :(得分:0)
如果您使用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
答案 2 :(得分:0)
使用@Torex解决方案一段时间后,我遇到了一些问题,特别是,用该方法绘制的矩形在全屏模式下不显示,如果我截取了该部分的屏幕截图,则这些矩形仍然是可见的(矛盾的是)它们正在绘制,因此解决方案的更新部分在全屏模式下不起作用。
下面是一个可能更复杂的解决方案,但有一些利弊:
优点:
它可以使用pygame功能轻松地将功能添加到绘制矩形的基本概念中,包括轻松更改其颜色,宽度等。
它可以在任何屏幕模式下工作。
工作时不会出现故障
相反:
在pegame.display()被调用并杀死时,它在开始时并最终出现故障。
它需要创建一个独立的窗口。
您需要对其进行调整,以防止单击事件将您带出您要创建的透明窗口。
工作代码:
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])))