如何使用 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
How to create MS Paint clone with Python and pygame
提问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:
这是我的问题的屏幕截图:
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.line
because 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.MOUSEMOTION
events 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.MOUSEMOTION
events also have a buttons
attribute 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()