如何用Python在屏幕上绘制空矩形

如何用Python在屏幕上绘制空矩形,python,drawing,pywin32,updating,win32gui,Python,Drawing,Pywin32,Updating,Win32gui,我不是专家,我试图在屏幕上显示一个矩形,它从一个固定的起点跟随鼠标移动,就像在word或paint中选择某个对象一样。我带着这个密码来的: 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.S

我不是专家,我试图在屏幕上显示一个矩形,它从一个固定的起点跟随鼠标移动,就像在word或paint中选择某个对象一样。我带着这个密码来的:

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.com解决了更新问题。但是,我发现只设置需要设置的点的颜色比要求设置矩形便宜。第一个解决方案渲染得相当干净,而第二个解决方案仍然有点小瑕疵。最后,最适合我的代码是:

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相同


这只会在你从最初的位置向右转的时候起作用,但这正是我所需要的。改善它以接受任何次要职位并不困难。

如果您使用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文档中获取的

为了刷新旧的绘制区域,您需要调用或类似的方法来更新特定的窗口,但从技术上讲,您不是在曲面上绘制,而是在整个监视器上绘制。您需要使监视器的整个区域无效,以便告诉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
。或者更多的控制,甚至使用

显然,
setPixel
是最快的,所以这里是我最后一个关于颜色和速度的例子,虽然它并不完美,因为
重画窗口
强制重画,它只是要求windows去做,然后由windows来兑现它
invalidate
在性能上有点好,因为它要求事件处理程序在有空闲时间时清除rect。但我还没有找到一种方法比重新绘制窗口更具侵略性,即使它仍然相当温和。例如,隐藏桌面图标,下面的代码将不起作用

导入win32gui、win32ui、win32api、win32con
从win32api导入GetSystemMetrics
dc=win32gui.GetDC(0)
dcObj=win32ui.CreateDCFromHandle(dc)
hwnd=win32gui.WindowFromPoint((0,0))
监视器=(0,0,GetSystemMetrics(0),GetSystemMetrics(1))
红色=win32api.RGB(255,0,0)#红色
过去的坐标=监视器
尽管如此:
m=win32gui.GetCursorPos()
rect=win32gui.CreateRoundRectRgn(*过去的坐标,2,2)
win32gui.RedrawWindow(hwnd、过去坐标、rect、win32con.RDW\u无效)
对于范围(10)内的x:
win32gui.SetPixel(直流,m[0]+x,m[1],红色)
win32gui.SetPixel(直流,m[0]+x,m[1]+10,红色)
对于范围(10)内的y:
win32gui.SetPixel(直流,m[0],m[1]+y,红色)
win32gui.SetPixel(直流,m[0]+10,m[1]+y,红色)
过去_坐标=(m[0]-20,m[1]-20,m[0]+20,m[1]+20)

与立场和解决方案有关的问题?请注意,高DPI系统往往会导致一系列问题。除了使用OpenGL解决方案或使用诸如或OpenCV之类的框架之外,我还没有找到很多方法来解决这个问题:

或将Windows显示比例更改为
100%

这会导致定位问题消失,也许可以通过查询操作系统的比例和补偿来考虑这一点



我能在“清除旧图形”上找到的唯一参考是这篇文章:taged
c++
win
winapi
。希望这能让一些人在找到一个好的例子之前避免搜索。

在使用@Torex解决方案一段时间后,我遇到了一些问题,特别是,我用这种方法绘制的矩形不会在全屏模式下显示,如果我得到绘制它们的部分的屏幕截图,它们仍然可见(矛盾的是),因此,解决方案的更新部分不能在全屏模式下工作

下面是一个可能更复杂的解决方案,有一些优点和缺点:

优点:

  • 它可以使用pygame功能轻松地为绘制矩形的基本思想添加函数,包括轻松更改其颜色、宽度等

  • 它可以在任何屏幕模式下工作

  • 工作时不会出现故障

  • 相反:

  • 它会在最后调用并杀死pygame.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])))
    

    谢谢你的回答,但是我正在寻找一种在主屏幕上画画的方法,而不是在一个新的屏幕上one@Jos通过重新渲染整个场景,我修复了更新问题。就闪烁而言,这是一个短暂的“小故障”,但这是因为windows没有硬件加速(我知道这一点),即桌面应该尽可能快地渲染。因此,必须进行一些优化以获得完美的体验。非常感谢,您的回答确实解决了我的问题。我只想补充一点,很有趣