Cómo crear MS clon de pintura con Python y Pygame
Pregunta
Tal como lo veo, hay dos maneras de manejar los eventos del ratón para dibujar una imagen.
La primera es para detectar cuando el ratón se mueve y dibuja una línea a donde está el ratón, se muestra aquí . Sin embargo, el problema con esto es que con un tamaño cepillo grande, aparecen muchos vacíos entre cada "línea" que no es recta, ya que está utilizando tamaño de trazo de la línea para crear líneas gruesas.
La otra manera es dibujar círculos cuando el ratón se mueve como se muestra aquí . El problema con esto es que aparecen vacíos entre cada círculo si el ratón se mueve más rápido que el ordenador detecta la entrada del ratón.
Aquí hay una captura de pantalla con mis problemas con ambos:
¿Cuál es la mejor manera de implementar un cepillo como MS Paint, con un tamaño decente-cepillo grande y sin espacios en el trazo de la línea o sin espacios entre cada círculo?
Solución
¿Por qué no hacer las dos cosas?
Dibuje un círculo en cada punto final y una línea entre los dos.
Editar rofl, simplemente no podía evitarlo.
En realidad, usted no desea utilizar pygame.draw.line
porque hace trampa. Se llena una amplia fila 1 pixel o columna (en función del ángulo de ataque) de píxeles. Si tienes que ir a un ángulo de aproximadamente perpendicular, 0 grados o 90 grados, esto no es un problema, pero al 45 de, verás que hay una especie de habichuela efecto.
La única solución es dibujar un círculo en la distancia a cada píxel. Aquí ...
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()
Otros consejos
No blitting en cada paso de bucle puede mejorar la velocidad del dibujo (usando el código adaptado de la anterior permitirá eliminar problema lag en mi máquina)
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()
Para el primer problema, es necesario tener un fondo, incluso si es sólo un color. Yo tenía el mismo problema con un juego pong réplica que hice. Este es un ejemplo de un programa de dibujo que hice réplica, a la izquierda haga clic para dibujar, haga clic derecho de borrar, haga clic en la imagen en color para elegir un color, y hasta botón para borrar la pantalla:
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)
Aquí hay una versión simplificada de ejemplo el cual es, por desgracia, no ejecutable.
Cuando se mueve el ratón, eventos pygame.MOUSEMOTION
se añaden a la cola de eventos que contienen la posición y el movimiento relativo. Usted puede utilizar éstos para calcular la posición anterior y luego pasar a los dos puntos a pygame.draw.line
.
eventos pygame.MOUSEMOTION
también tienen un atributo buttons
que se puede utilizar para comprobar qué botón del ratón está actualmente abajo.
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()