如何使用 Python 和 pygame 创建 MS Paint 克隆

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/597369/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 20:25:36  来源:igfitidea点击:

How to create MS Paint clone with Python and pygame

pythonmousepygamedrawpaint

提问by Johnston

As I see it, there are two ways to handle mouse events to draw a picture.

在我看来,有两种方法可以处理鼠标事件来绘制图片。

The first is to detect when the mouse moves and draw a line to where the mouse is, shown here. However, the problem with this is that with a large brush size, many gaps appear between each "line" that is not straight since it is using the line's stroke size to create thick lines.

第一种是当检测到鼠标移动和画一条线,其中鼠标,示出在这里。然而,这样做的问题是,对于较大的画笔尺寸,每条不直的“线”之间会出现许多间隙,因为它使用线的笔触尺寸来创建粗线。

The other way is to draw circles when the mouse moves as is shown here. The problem with this is that gaps appear between each circle if the mouse moves faster than the computer detects mouse input.

另一种方法是,当鼠标移动如图所示,以画圈圈这里。这样做的问题是,如果鼠标移动的速度比计算机检测到的鼠标输入速度快,则每个圆圈之间会出现间隙。

Here's a screenshot with my issues with both:

这是我的问题的屏幕截图:

http://imgur.com/32DXN.jpg

http://imgur.com/32DXN.jpg

What is the best way to implement a brush like MS Paint's, with a decently-big brush size with no gaps in the stroke of the line or no gaps between each circle?

实现像 MS Paint 这样的画笔的最佳方法是什么,画笔大小适中,线条的笔划没有间隙或每个圆圈之间没有间隙?

回答by SingleNegationElimination

Why not do both?

为什么不两者都做?

Draw a circle at each endpoint and a line between the two.

在每个端点画一个圆,并在两者之间画一条线。

EDITrofl, just couldn't stop myself.

编辑rofl,就是无法阻止自己。

Actually, you don't want to use pygame.draw.linebecause it cheats. It fills a 1 pixel wide row or column (depending on angle of attack) of pixels. If you do go at a roughly perpendicular angle, 0 deg or 90 deg, this isn't an issue, but at 45's, you'll notice a sort of string beaneffect.

实际上,您不想使用,pygame.draw.line因为它会作弊。它填充 1 像素宽的像素行或列(取决于迎角)。如果您确实以大致垂直的角度(0 度或 90 度)前进,这不是问题,但是在 45 度时,您会注意到一种四季豆效应。

The only solution is to draw a circle at every pixel's distance. Here...

唯一的解决方案是在每个像素的距离上画一个圆。这里...

import pygame, random

screen = pygame.display.set_mode((800,600))

draw_on = False
last_pos = (0, 0)
color = (255, 128, 0)
radius = 10

def roundline(srf, color, start, end, radius=1):
    dx = end[0]-start[0]
    dy = end[1]-start[1]
    distance = max(abs(dx), abs(dy))
    for i in range(distance):
        x = int( start[0]+float(i)/distance*dx)
        y = int( start[1]+float(i)/distance*dy)
        pygame.draw.circle(srf, color, (x, y), radius)

try:
    while True:
        e = pygame.event.wait()
        if e.type == pygame.QUIT:
            raise StopIteration
        if e.type == pygame.MOUSEBUTTONDOWN:
            color = (random.randrange(256), random.randrange(256), random.randrange(256))
            pygame.draw.circle(screen, color, e.pos, radius)
            draw_on = True
        if e.type == pygame.MOUSEBUTTONUP:
            draw_on = False
        if e.type == pygame.MOUSEMOTION:
            if draw_on:
                pygame.draw.circle(screen, color, e.pos, radius)
                roundline(screen, color, e.pos, last_pos,  radius)
            last_pos = e.pos
        pygame.display.flip()

except StopIteration:
    pass

pygame.quit()

回答by anonym..

Not blitting at each loop step can improve the speed of the drawing (using this code adapted from the previous one allow to remove lag problem on my machine)

不在每个循环步骤中进行 blitting 可以提高绘图速度(使用改编自前一个代码的代码可以消除我机器上的延迟问题)

import pygame, random

screen = pygame.display.set_mode((800,600))

draw_on = False
last_pos = (0, 0)
color = (255, 128, 0)
radius = 10

def roundline(srf, color, start, end, radius=1):
    dx = end[0]-start[0]
    dy = end[1]-start[1]
    distance = max(abs(dx), abs(dy))
    for i in range(distance):
        x = int( start[0]+float(i)/distance*dx)
        y = int( start[1]+float(i)/distance*dy)
        pygame.display.update(pygame.draw.circle(srf, color, (x, y), radius))

try:
    while True:
        e = pygame.event.wait()
        if e.type == pygame.QUIT:
            raise StopIteration
        if e.type == pygame.MOUSEBUTTONDOWN:
            color = (random.randrange(256), random.randrange(256), random.randrange(256))
            pygame.draw.circle(screen, color, e.pos, radius)
            draw_on = True
        if e.type == pygame.MOUSEBUTTONUP:
            draw_on = False
        if e.type == pygame.MOUSEMOTION:
            if draw_on:
                pygame.display.update(pygame.draw.circle(screen, color, e.pos, radius))
                roundline(screen, color, e.pos, last_pos,  radius)
            last_pos = e.pos
        #pygame.display.flip()

except StopIteration:
    pass

pygame.quit()

回答by Matthew

For the first problem, you need have a background, even if its just a color. I had the same problem with a replica pong game i made. Here is an example of a replica paint program I made, left click to draw, right click to erase, click on the color image to choose a color, and up button to clear screen:

对于第一个问题,您需要有一个背景,即使它只是一种颜色。我制作的复制乒乓球游戏也遇到了同样的问题。这是我制作的复制绘画程序的示例,左键单击绘制,右键单击擦除,单击彩色图像选择颜色,向上按钮清除屏幕:

import os
os.environ['SDL_VIDEO_CENTERED'] = '1'
from pygamehelper import *
from pygame import *
from pygame.locals import *
from vec2d import *
from math import e, pi, cos, sin, sqrt
from random import uniform

class Starter(PygameHelper):
    def __init__(self):
        self.w, self.h = 800, 600
        PygameHelper.__init__(self, size=(self.w, self.h), fill=((255,255,255)))

        self.img= pygame.image.load("colors.png")
        self.screen.blit(self.img, (0,0))

        self.drawcolor= (0,0,0)
        self.x= 0

    def update(self):
        pass

    def keyUp(self, key):
        if key==K_UP:
            self.screen.fill((255,255,255))
            self.screen.blit(self.img, (0,0))




    def mouseUp(self, button, pos):
        pass

    def mouseMotion(self, buttons, pos, rel):
        if pos[1]>=172: 
            if buttons[0]==1:
                #pygame.draw.circle(self.screen, (0,0,0), pos, 5)
                pygame.draw.line(self.screen, self.drawcolor, pos, (pos[0]-rel[0], pos[1]-rel[1]),5)                
            if buttons[2]==1:
                pygame.draw.circle(self.screen, (255,255,255), pos, 30)
            if buttons[1]==1:
                #RAINBOW MODE
                color= self.screen.get_at((self.x, 0))
                pygame.draw.line(self.screen, color, pos, (pos[0]-rel[0], pos[1]-rel[1]), 5)

                self.x+= 1
                if self.x>172: self.x=0

        else:
            if pos[0]<172:
                if buttons[0]==1:
                    self.drawcolor= self.screen.get_at(pos)
                    pygame.draw.circle(self.screen, self.drawcolor, (250, 100), 30)

    def draw(self):
        pass
        #self.screen.fill((255,255,255))
        #pygame.draw.circle(self.screen, (0,0,0), (50,100), 20)

s = Starter()
s.mainLoop(40)

回答by skrx

Here's a simplified version of Matthew's examplewhich is unfortunately not runnable.

这是 Matthew示例的简化版本,不幸的是它无法运行。

When the mouse is moved, pygame.MOUSEMOTIONevents are added to the event queue which contain the position and the relative movement. You can use these to calculate the previous position and then pass the two points to pygame.draw.line.

当鼠标移动时,pygame.MOUSEMOTION事件被添加到包含位置和相对移动的事件队列中。您可以使用这些来计算前一个位置,然后将两个点传递给pygame.draw.line

pygame.MOUSEMOTIONevents also have a buttonsattribute which you can use to check which mouse button is currently down.

pygame.MOUSEMOTION事件还有一个buttons属性,您可以使用它来检查当前按下的是哪个鼠标按钮。

import os
import random

import pygame as pg


class App:

    def __init__(self):
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pg.init()
        self.w, self.h = 800, 600
        self.screen = pg.display.set_mode((self.w, self.h))
        self.screen.fill(pg.Color('white'))
        self.clock = pg.time.Clock()
        self.drawcolor = (0, 0, 0)

    def mainloop(self):
        while True:
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    return
                elif event.type == pg.MOUSEBUTTONDOWN:
                    if event.button == 2:  # Color picker (middle mouse button).
                        self.drawcolor = self.screen.get_at(pos)
                        # Pick a random color.
                        # self.drawcolor = [random.randrange(256) for _ in range(3)]
                elif event.type == pg.MOUSEMOTION:
                    pos, rel = event.pos, event.rel
                    if event.buttons[0]:  # If the left mouse button is down.
                        # Draw a line from the pos to the previous pos.
                        pg.draw.line(self.screen, self.drawcolor, pos, (pos[0]-rel[0], pos[1]-rel[1]), 5)
                    elif event.buttons[2]:  # If the right mouse button is down.
                        # Erase by drawing a circle.
                        pg.draw.circle(self.screen, (255, 255, 255), pos, 30)

            pg.display.flip()
            self.clock.tick(30)


if __name__ == '__main__':
    app = App()
    app.mainloop()
    pg.quit()