Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
319 views
in Technique[技术] by (71.8m points)

python - Why is my airbrush function not working in pygame?

I am creating a basic paint app and I want to create an airbrush function, and have other functions for different styles of brushes. Basically, the user clicks an icon to select the style of brush, and then when they click and hold over the canvas, the respective brush will paint on the canvas. Here is my code: The main loop:

def paintScreen():
    intro = True
    gameDisplay.fill(cyan)
    message_to_screen('Welcome to PyPaint', black, -300, 'large')
    cur = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()
    while intro:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        pygame.draw.rect(gameDisplay, white, (50, 120, displayWidth - 100, displayHeight - 240))

        button('X', 20, 20, 50, 50, red, lightRed, action = 'quit')
        icon(airbrushIcon, white, 50, displayHeight - 101, 51, 51, white, grey, 'airbrush')
        icon(pencilIcon, white, 140, displayHeight - 101, 51, 51, white, grey, 'pencil')
        icon(calligraphyIcon, white, 230, displayHeight - 101, 51, 51, white, grey, 'calligraphy')
        pygame.display.update()
        if cur[0] > 50 < displayWidth - 50 and cur [1] > 120 < displayHeight - 120:
            if airbrush == True:
                airbrush()

the icon function:

def icon(icon, colour, x, y, width, height, inactiveColour, activeColour, action = None):
    cur = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()
    if x + width > cur[0] > x and y + height > cur[1] > y:#if the cursor is over the button
        pygame.draw.rect(gameDisplay, activeColour, (x, y, width, height))
        gameDisplay.blit(icon, (x, y))
        if click[0] == 1 and action != None:
            if action == 'quit':
                pygame.quit()
                quit()
            elif action == 'pencil':
                pencil = True
                return pencil
            elif action == 'airbrush':
                airbrush = True
                return airbrush
            elif action == 'calligraphy':
                calligraphy = True
                return calligraphy
            elif action == 'erase':
                eraser = True
                return eraser
    else:
        pygame.draw.rect(gameDisplay, inactiveColour, (x, y, width, height))
        gameDisplay.blit(icon, (x, y))

the airbrush function:

def airbrush(brushSize = 3):
    airbrush = True
    cur = pygame.mouse.get_pos() #cur[0] is x location, cur[1] is y location
    click = pygame.mouse.get_pressed()
    while airbrush == True:
        if click[0] == True:
            if cur[0] > 50 < displayWidth - 50 and cur[1] > 120 < displayHeight - 120: #if the cursor is above the canvas
                #the area of the canvas is x(50, width-50) y(120, width-120)
                pygame.draw.circle(gameDisplay, black, (cur[0] + random.randrange(brushSize), cur[1] + random.randrange(brushSize)), random.randrange(1, 5))
                pygame.display.update()
            clock.tick(60)

What can I do to make the airbrush function work? When running the app, it doesn't return any errors, it just doesn't work at all

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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()

enter image description here

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...