Your code has several flaws:
- You mix up the business logic and the drawing, which clutters up your code. We'll fix it later
- In your
icon
function, you're setting local variables (like airbrush
and pencil
etc), which does not affect global variables with the same name. Also you return their value after setting them and never use that value.
- If your
airbrush
function would be called, you could never leave that function, because there's no way to set airbrush
to false
- The function would also not work because you don't handle events in its loop, which would result in your window no longer responding once the event queue fills up
You should make use of polymorphism, something like this:
import pygame
import pygame.freetype
import random
def bresenham_line(start, end):
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
is_steep = abs(dy) > abs(dx)
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
dx = x2 - x1
dy = y2 - y1
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
if swapped:
points.reverse()
return points
class Brush(pygame.sprite.Sprite):
def __init__(self, pos, font, canvas, tmpcanvas, icon, brushes, offset):
super().__init__(brushes)
self.image = pygame.Surface((32, 32))
self.image.fill(pygame.Color('grey'))
font.render_to(self.image, (8, 7), icon)
self.other_image = self.image.copy()
pygame.draw.rect(self.other_image, pygame.Color('red'), self.other_image.get_rect(), 3)
self.rect = self.image.get_rect(topleft=pos)
self.active = False
self.canvas = canvas
self.tmpcanvas = tmpcanvas
self.brushes = brushes
self.offset = offset
self.mouse_pos = None
def translate(self, pos):
return pos[0] - self.offset[0], pos[1] - self.offset[1]
def draw_to_canvas(self):
pass
def flip(self):
self.active = not self.active
self.image, self.other_image = self.other_image, self.image
def update(self, events):
for e in events:
if e.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(e.pos):
for brush in self.brushes:
if brush.active:
brush.flip()
self.flip()
self.mouse_pos = self.translate(pygame.mouse.get_pos())
if self.active:
self.draw_to_canvas()
class Pencil(Brush):
def __init__(self, pos, font, canvas, tmpcanvas, brushes, offset):
super().__init__(pos, font, canvas, tmpcanvas, 'P', brushes, offset)
self.prev_pos = None
def draw_to_canvas(self):
pressed = pygame.mouse.get_pressed()
if pressed[0] and self.prev_pos:
pygame.draw.line(self.canvas, pygame.Color('red'), self.prev_pos, self.mouse_pos)
self.prev_pos = self.mouse_pos
class Calligraphy(Brush):
def __init__(self, pos, font, canvas, tmpcanvas, brushes, offset):
super().__init__(pos, font, canvas, tmpcanvas, 'C', brushes, offset)
self.prev_pos = None
def draw_to_canvas(self):
pressed = pygame.mouse.get_pressed()
if pressed[0] and self.prev_pos:
for x, y in bresenham_line(self.prev_pos, self.mouse_pos):
pygame.draw.rect(self.canvas, pygame.Color('orange'), (x, y, 5, 15))
self.prev_pos = self.mouse_pos
class Airbrush(Brush):
def __init__(self, pos, font, canvas, tmpcanvas, brushes, offset):
super().__init__(pos, font, canvas, tmpcanvas, 'A', brushes, offset)
def draw_to_canvas(self):
pressed = pygame.mouse.get_pressed()
if pressed[0]:
pygame.draw.circle(self.canvas, pygame.Color('green'),
(self.mouse_pos[0] + random.randrange(-13, 13), self.mouse_pos[1] + random.randrange(-13, 13)),
random.randrange(1, 5))
class LineTool(Brush):
def __init__(self, pos, font, canvas, tmpcanvas, brushes, offset):
super().__init__(pos, font, canvas, tmpcanvas, 'L', brushes, offset)
self.start = None
def draw_to_canvas(self):
pressed = pygame.mouse.get_pressed()
if pressed[0]:
if not self.start:
self.start = self.mouse_pos
pygame.draw.line(self.tmpcanvas, pygame.Color('yellow'), self.start, self.mouse_pos)
else:
if self.start:
pygame.draw.line(self.canvas, pygame.Color('yellow'), self.start, self.mouse_pos)
self.start = None
class Clear(Brush):
def __init__(self, pos, font, canvas, tmpcanvas, brushes, offset):
super().__init__(pos, font, canvas, tmpcanvas, '*', brushes, offset)
def draw_to_canvas(self):
pressed = pygame.mouse.get_pressed()
if pressed[0]:
self.canvas.fill((1, 1, 1))
self.flip()
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
sprites = pygame.sprite.Group()
clock = pygame.time.Clock()
font = pygame.freetype.SysFont(None, 26)
offset = 0, 50
canvas = pygame.Surface((500, 450))
canvas.set_colorkey((1,1,1))
canvas.fill((1,1,1))
tmpcanvas = canvas.copy()
x=10
for tool in (Pencil, Calligraphy, Airbrush, LineTool, Clear):
tool((x, 10), font, canvas, tmpcanvas, sprites, offset)
x+= 40
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
tmpcanvas.fill((1, 1, 1))
sprites.update(events)
screen.fill((30, 30, 30))
screen.blit(canvas, offset)
screen.blit(tmpcanvas, offset)
sprites.draw(screen)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
In this example, we have a Brush
base class that handles drawing of the icon and keeping track of the active
-state of the brush, and the subclasses handle the actual drawing.
This way, we can easily add new brushes/tools quite easily by creating a new class and implementing the draw_to_canvas
function.