Search code examples
python-3.xperformancepygamedrawpixel

efficient way to draw pixel art in pygame


I'm making a very simple pixel art software in pygame. My logic was creating a grid class, which has a 2D list, containing 0's. When I click, the grid approximates the row and column selected, and mark the cell with a number, corresponding to the color. For simplicity, let's say '1'.

The code works correctly, but It's slow. If the number of rows and columns is less or equal than 10, It works perfectly, but if It's more, it's very laggy.

I think the problem is that I'm updating the entire screen everytime, and, since the program has to check EVERY cell, It can't handle a bigger list

import pygame
from grid import Grid
from pincel import Pincel
from debugger import Debugger
from display import Display
from pygame.locals import *

pygame.init()

pygame.mixer_music.load("musica/musica1.wav")
pygame.mixer_music.play(-1)

width = 1300
height = 1300
screen = pygame.display.set_mode((1366, 768), pygame.RESIZABLE)
pygame.display.set_caption("SquareDraw")

#Grid Creator
numberOfRows = 25
numberOfColumns = 25
grid = Grid(numberOfRows, numberOfColumns)

# Medidas
basicX = width / numberOfColumns
basicY = height / numberOfRows

#Tool Creator
pincel = Pincel(2)


#variáveis de controle
running = 1

#Initial values
grid.equipTool(pincel)

#variáveis de controle de desenho
clicking = 0


def drawScreen(screen, grid, rows, columns, basicX, basicY):
    for i in range(rows):
        for j in range(columns):
            if grid[i][j]:
                print('yes')
                print(i, j)
                pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))



while running:

    screen.fill((255, 255, 255))
    Display.drawScreen(screen, grid.board, grid.getRows(), grid.getColumns(), basicX, basicY)
    pygame.display.flip()


    events = pygame.event.get()
    for event in events:
        if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            running = 0
        if event.type == pygame.MOUSEBUTTONDOWN or clicking:
            clicking = 1
            x, y = pygame.mouse.get_pos()
            Debugger.printArray2D(grid.board)
            print('')
            xInGrid = int(x / basicX)
            yInGrid = int(y / basicY)
            grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
            Debugger.printArray2D(grid.board)
            print('')
        if event.type == pygame.MOUSEBUTTONUP:
            clicking = 0
        if event.type == pygame.VIDEORESIZE:
            width = event.w
            height = event.h
            basicX = width / numberOfColumns
            basicY = height / numberOfRows
            print(width, height)


pygame.quit()

The class grid contains the 2D list. The class "Pincel" marks the cells and The class "Debugger" is just for printing lists or anything related to debugging.

Is there a way to update only the part of the screen that was changed? If so, how can I apply that in my logic?

Thanks in advance :)


Solution

  • A few things:

    • Use the grid array to store the on\off blocks of the screen. It only gets read when the screen is resized and needs a full redraw
    • When a new rectangle is turned on, draw the rectangle directly in the event handler and update the grid array. There is no need to redraw the entire screen here.
    • In the resize event, reset the screen mode to the new size then redraw the entire screen using the grid array. This is the only time you need to do a full redraw.

    Here is the updated code:

    import pygame
    #from grid import Grid
    #from pincel import Pincel
    #from debugger import Debugger
    #from display import Display
    from pygame.locals import *
    
    pygame.init()
    
    #pygame.mixer_music.load("musica/musica1.wav")
    #pygame.mixer_music.play(-1)
    
    width = 1000
    height = 1000
    screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
    pygame.display.set_caption("SquareDraw")
    
    #Grid Creator
    numberOfRows = 250  
    numberOfColumns = 250
    #grid = Grid(numberOfRows, numberOfColumns)
    grid = [[0 for x in range(numberOfRows)] for y in range(numberOfColumns)]  # use array for grid: 0=white, 1=black
    
    # Medidas
    basicX = width / numberOfColumns
    basicY = height / numberOfRows
    
    #Tool Creator
    #pincel = Pincel(2)
    
    #xx
    running = 1
    
    #Initial values
    #grid.equipTool(pincel)
    
    #xx
    clicking = 0
    
    def drawScreen(screen, grid, basicX, basicY):  # draw rectangles from grid array
        for i in range(numberOfColumns):
            for j in range(numberOfRows):
                if grid[i][j]:
                    #print('yes')
                    #print(i, j)
                    pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))
    
    screen.fill((255, 255, 255))  # start screen
    
    while running:
        events = pygame.event.get()
        for event in events:
            if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                running = 0
            if event.type == pygame.MOUSEBUTTONDOWN or clicking:  # mouse button down
                clicking = 1
                x, y = pygame.mouse.get_pos()
                #Debugger.printArray2D(grid.board)
                #print('')
                xInGrid = int(x / basicX)
                yInGrid = int(y / basicY)
                grid[yInGrid][xInGrid] = 1  # save this point = 1, for screen redraw (if resize)
                pygame.draw.rect(screen, (0, 0, 0), (xInGrid * basicX, yInGrid * basicY, basicX, basicY))  # draw rectangle
                #grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
                #Debugger.printArray2D(grid.board)
                #print('')
                pygame.display.flip()  # update screen
            if event.type == pygame.MOUSEBUTTONUP:
                clicking = 0
            if event.type == pygame.VIDEORESIZE:  # screen resized, must adjust grid height, width
                width = event.w
                height = event.h
                basicX = width / numberOfColumns
                basicY = height / numberOfRows
                #print(width, height)
                screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)  # reset screen with new height, width
                screen.fill((255, 255, 255))  # clear screen
                drawScreen(screen, grid, basicX, basicY)  # redraw rectangles
                pygame.display.flip()  # update screen
    
    
    pygame.quit()