Comment créer clone MS Paint avec Python et Pygame
Question
Comme je le vois, il y a deux façons de gérer les événements de la souris pour dessiner une image.
La première consiste à détecter le moment où la souris passe et tracer une ligne à l'endroit où la souris est représenté ici . Cependant, le problème est que, avec une grande taille du pinceau, de nombreuses lacunes apparaissent entre chaque « ligne » qui ne sont pas directement car il utilise la taille de la course de la ligne pour créer des lignes épaisses.
L'autre façon consiste à dessiner des cercles lorsque la souris se déplace comme cela est montré ici . Le problème est que semblent écarts entre chaque cercle si la souris se déplace plus vite que l'ordinateur détecte une entrée de la souris.
Voici une capture d'écran avec mes problèmes à la fois:
Quelle est la meilleure façon de mettre en œuvre un pinceau comme MS Paint de, avec une taille de pinceau décemment grande sans lacunes dans la course de la ligne ou pas d'espace entre chaque cercle?
La solution
Pourquoi ne pas faire les deux?
Tracer un cercle au niveau de chaque point d'extrémité et une ligne entre les deux.
EDIT rofl, ne pouvait pas me arrêter.
En fait, vous ne voulez pas utiliser pygame.draw.line
parce qu'il triche. Il remplit un 1 pixel de large ligne ou de colonne (en fonction de l'angle d'attaque) des pixels. Si vous allez à un angle sensiblement perpendiculaire, 0 ° ou 90 °, ce n'est pas un problème, mais à 45 de, vous remarquerez une sorte de haricot effet.
La seule solution est de tracer un cercle à la distance de chaque pixel. Ici ...
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()
Autres conseils
Non blitting à chaque étape de la boucle peut améliorer la vitesse du dessin (en utilisant ce code adapté de la précédente permet de retirer problème de retard sur ma machine)
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()
Pour le premier problème, vous devez avoir un arrière-plan, même si son juste une couleur. J'ai eu le même problème avec un jeu de pong réplique je l'ai fait. Voici un exemple d'un programme de peinture réplique que j'ai fait, à gauche cliquez pour dessiner, clic droit pour effacer, cliquez sur l'image couleur pour choisir une couleur, et jusqu'à bouton pour effacer l'écran:
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)
Voici une version simplifiée du exemple qui est malheureusement pas runnable.
Lorsque la souris est déplacée, les événements de pygame.MOUSEMOTION
sont ajoutés à la file d'attente qui contient la position et le mouvement relatif. Vous pouvez les utiliser pour calculer la position précédente, puis passer les deux points à pygame.draw.line
.
événements pygame.MOUSEMOTION
ont également un attribut buttons
que vous pouvez utiliser pour vérifier quel bouton souris est actuellement vers le bas.
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()