首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何用Python在屏幕上绘制空矩形

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

Stack Overflow用户
提问于 2020-06-12 09:11:17
回答 4查看 12.4K关注 0票数 4

我不是专家,我试图在屏幕上显示一个矩形,它跟随鼠标从一个固定的起点移动,就像你在文字或绘画中选择什么一样。我带着这个密码来的:

代码语言:javascript
运行
复制
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解决了更新问题。然而,我发现只设置我需要设置的点的颜色比要求一个矩形更便宜。第一种解决方案相当干净,而第二种解决方案仍然有一点小问题。最后,对我来说最有效的代码是:

代码语言:javascript
运行
复制
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相同。

这将只有当你保持低沉和右从你的最初立场,但这正是我所需要的。不难改进,接受任何次要职位。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2020-06-12 09:34:05

为了刷新旧的绘制区域,您需要调用win32gui.UpdateWindow或类似的东西来更新您的特定窗口,但是因为技术上不是在表面上绘图,而是整个监视器。您需要使监视器的整个区域失效,以便告诉windows在其上重新绘制任何内容(至少我理解它)。

为了克服速度慢的问题,您可以使用win32ui.Rectangle一次完成绘制,而不是使用for循环创建边界,这将需要X个循环才能完成矩形:

代码语言:javascript
运行
复制
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并没有强迫重新绘制,它只是要求窗口来完成它,然后它取决于窗口是否尊重它。InvalidateRect在性能上要好一些,因为它要求事件处理程序在有空闲时间时清除rect。但我还没有找到比RedrawWindow更激进的方法,即使是这样的方式也是相当温和的。例如,隐藏桌面图标,下面的代码将无法工作。

代码语言:javascript
运行
复制
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)

关于立场和解决的问题?请注意,较高的新闻部系统往往会引起一系列问题。除了使用OpenGL解决方案或使用框架(如wxPython或OpenCV )之外,除了本文:将Python程序标记为高度新闻部感知的无缝Windows之外,我还没有找到其他方法。

或将窗口显示缩放更改为100%

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

我在“清理旧画”中唯一能找到的参考是这篇文章:win32内容已更改,但除非移动窗口,否则不会显示更新标记的c++ win winapi。希望这能避免一些人在找到一个好例子之前进行搜索。

票数 7
EN

Stack Overflow用户

发布于 2020-06-19 10:07:34

在使用@Torex解决方案一段时间后,我遇到了一些问题,特别是,我用该方法绘制的矩形没有在全屏模式下显示,而且如果我得到正在绘制它们的部分的屏幕快照,它们仍然是可见的(自相矛盾的),所以解决方案的更新部分在全屏模式下不能工作。

贝娄也许是一个更复杂的解决方案,有一些优点和相反之处:

优点:

  1. 它允许使用pygame功能来轻松地将函数添加到绘制矩形的基本思想中,包括很容易地更改其颜色、宽度等。
  2. 它可以在任何屏幕模式下工作。
  3. 它不会在工作时出故障

contras:

  1. 当pygame.display()被调用和杀死时,它确实会在乞讨时发生故障,最后也会发生故障。
  2. 它需要创建一个独立的窗口。
  3. 您需要对其进行调整,以防止单击事件将您从要创建的透明窗口中带出。

工作守则:

代码语言:javascript
运行
复制
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])))
票数 2
EN

Stack Overflow用户

发布于 2020-06-12 09:25:01

如果您使用OpenCV,那么可以这样做

代码语言:javascript
运行
复制
#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文档这里

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62341134

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档