PyGameExamplesAndAnswers

StackOverflow            reply.it reply.it

“Every time you write a comment, you should grimace and feel the failure of your ability of expression.”
Robert C. Martin, The Robert C. Martin Clean Code Collection (Collection)


Shape and contour

pygame.draw pygame.gfxdraw

Shapes

Related Stack Overflow questions:

With the module pygame.draw shapes like rectangles, circles, polygons or liens can be drawn.

pygame.draw.rect draws filled rectangular shapes or outlines. The arguments are the target Surface (i.s. the display), the color, the rectangle and the optional outline width. The rectangle argument is a tuple with the 4 components (x, y, width, height), where (x, y) is the upper left point of the rectangle. Alternatively, the argument can be a pygame.Rect object:

pygame.draw.rect(window, color, (x, y, width, height))
rectangle = pygame.Rect(x, y, width, height)
pygame.draw.rect(window, color, rectangle)

pygame.draw.circle draws filled circles or outlines. The arguments are the target Surface (i.s. the display), the color, the center, the radius and the optional outline width. The center argument is a tuple with the 2 components (x, y):

pygame.draw.circle(window, color, (x, y), radius)

pygame.draw.polygon draws filled polygons or contours. The arguments are the target Surface (i.s. the display), the color, a list of points and the optional contour width. Each point is a tuple with the 2 components (x, y):

pygame.draw.polygon(window, color, [(x1, y1), (x2, y2), (x3, y3)])

Transparent shapes

Related Stack Overflow questions:

Unfortunately there is no good way to draw a transparent shape. See pygame.draw module:

A color’s alpha value will be written directly into the surface […], but the draw function will not draw transparently.

Therefor you hve to do a workaround:

  1. Create a pygame.Surface object with a per-pixel alpha format large enough to cover the shape.
  2. Draw the shape on the _Surface.
  3. Blend the Surface with the target Surface. blit() by default blends 2 Surfaces

For example 3 functions, which can draw transparent rectangles, circles and polygons:

def draw_rect_alpha(surface, color, rect):
    shape_surf = pygame.Surface(pygame.Rect(rect).size, pygame.SRCALPHA)
    pygame.draw.rect(shape_surf, color, shape_surf.get_rect())
    surface.blit(shape_surf, rect)
def draw_circle_alpha(surface, color, center, radius):
    target_rect = pygame.Rect(center, (0, 0)).inflate((radius * 2, radius * 2))
    shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
    pygame.draw.circle(shape_surf, color, (radius, radius), radius)
    surface.blit(shape_surf, target_rect)
def draw_polygon_alpha(surface, color, points):
    lx, ly = zip(*points)
    min_x, min_y, max_x, max_y = min(lx), min(ly), max(lx), max(ly)
    target_rect = pygame.Rect(min_x, min_y, max_x - min_x, max_y - min_y)
    shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
    pygame.draw.polygon(shape_surf, color, [(x - min_x, y - min_y) for x, y in points])
    surface.blit(shape_surf, target_rect)

Draw single Pixel

Related Stack Overflow questions:

The usual method of drawing a point on a Surface or the display is to use [`pygame.Surface.set_at’]:

window_surface.set_at((x, y), my_color)

However, this function is very slow and leads to a massive lack of performance if more than 1 point is to be drawn.

📁 Minimal example - Draw pixels with set_at

repl.it/@Rabbid76/PyGame-DrawPixel-1

Pygame: Draw single pixel

Another option is to use a “pygame.PixelArray” object. This object enables direct pixel access to Surface objects. A PixelArray pixel item can be assigned directly. The pixel can be accessed by subscription. The PixelArray locks the Surface, You have to close() it when you have changed the pixel:

pixel_array = pygame.PixelArray(window_surface)

pixel_array[x, y] = my_color
pixel_array[start_x:end_x, start_y:end_y] = my_color

pixel_array.close()

📁 Minimal example - Draw pixels with pygame.PixelArray

repl.it/@Rabbid76/PyGame-DrawPixel-2

Pygame: Draw single pixel

Draw rectangle

Related Stack Overflow questions:

Use pygame.draw.rect() to draw a rectangle. The 3rd argument of pygame.draw.rect has to be a tuple with 4 elements:

pygame.draw.rect(win, (255, 0, 0),(x, y, width, height))

Alternatively it can be a pygame.Rect object, too:

rect = pygame.Rect(x, y, width, height)
pygame.draw.rect(win, (255, 0, 0), rect)

The last parameter of pygame.draw.rect is the thickness of line the outline. If the parameter is 0 (or default), then the rectangle is filled, else a rectangle with the specified line thickness is drawn. e.g:

How can I draw a rectangular outline (not filled) with PyGame?

pygame.draw.rect(surf, color, (x, y, w, h), outlineThickness)

The corners of the rectangle are jagged. However, the corner radius can be set (border_radius) to get a better result:

pygame.draw.rect(surf, color, (x, y, w, h), outlineThickness, border_radius=1)

How can I draw a rectangular outline (not filled) with PyGame?

You can achieve what you want by setting the key word argument border_radius of the function pygame.draw.rect.

Create a rectangle the same size as the image and and per pixel alpha (SRCALPHA) and draw a completely white, opaque image with round corners on it:

size = self.original_image.get_size()
self.rect_image = pg.Surface(size, pg.SRCALPHA)
pg.draw.rect(self.rect_image, (255, 255, 255), (0, 0, *size), border_radius=roundness)

Copy the original image and use the BLEND_RGBA_MIN blending mode to blend the rectangle with the image (see pygame.Surface.blit):

self.image = self.original_image.copy().convert_alpha()
self.image.blit(self.rect_image, (0, 0), None, pg.BLEND_RGBA_MIN)

Note, the keyword attribute border_radius is a new feature. You have to use the most recent pygame version (2.0.0.dev10).

If you can’t use version 2.0.0.dev10, you’ll need to stick the rounded rectangle together yourself:

class Rectangle(pg.sprite.Sprite):
    # [...]

    def set_rounded(self, roundness):
        size = self.original_image.get_size()
        self.rect_image = pg.Surface(size, pg.SRCALPHA)

        #pg.draw.rect(self.rect_image, (255, 255, 255), (0, 0, *size), border_radius=roundness)

        r, c = roundness, (255, 255, 255)
        pg.draw.rect(self.rect_image, c, (r, 0, size[0]-2*r, size[1]))
        pg.draw.rect(self.rect_image, c, (0, r, size[0], size[1]-2*r))
        for cpt in [(r, r), (size[0]-r, r), (r, size[1]-r), (size[0]-r, size[1]-r)]:  
            pg.draw.circle(self.rect_image, c, cpt, r)

        self.image = self.original_image.copy().convert_alpha()
        self.image.blit(self.rect_image, (0, 0), None, pg.BLEND_RGBA_MIN) 

📁 Minimal example - draw a rectangle with rounded corners

scene

Use pygame.Rect.normalize() to handle rectangles with negative width and height:

normalize() -> None
This will flip the width or height of a rectangle if it has a negative size. The rectangle will remain in the same place, with only the sides swapped.

Create a pygame.Rect object and normalize it:

rect = pygame.Rect(rpos[0], rpos[1], pos2[0]-rpos[0], pos2[1]-rpos[1])
rect.normalize()
pygame.draw.rect(window, (100, 200, 100), rect)

A pygame.Rect stores a position and a size. It is always axis aligned and cannot represent a rotated rectangle. Use pygame.math.Vector2.rotate() to compute the corner points of the rotated rectangle. Draw the rotated rectangle with pygame.draw.polygon():

def draw_rect_angle(surf, rect, pivot, angle, width=0):
    pts = [rect.topleft, rect.topright, rect.bottomright, rect.bottomleft]
    pts = [(pygame.math.Vector2(p) - pivot).rotate(-angle) + pivot for p in pts]
    pygame.draw.polygon(surf, (255, 255, 0), pts, width)

📁 Minimal example - draw rotated rectangle

repl.it/@Rabbid76/PyGame-RotatedRectangle

Getting rotated rect of rotated image in Pygame

📁 Minimal example - rotate rectangle

rotating a rectangle in pygame

Draw circle

Related Stack Overflow questions:

The center argument of pygame.draw.circle() has to be a tuple with 2 integral components. If the coordinates are of a floating point type, they must rounded to integer values, otherwise a warning will be generated (TypeError: integer argument expected, got float):

pygame.draw.circle(screen, (0, 0, 0), (round(circleX), round(circleY)), size)

Use the center attribute of a rectangle to center a circle on the rectangle:

self.rect = self.image.get_rect()
pygame.draw.circle(self.image, color, self.rect.center, min(self.rect.center))

Antialiased circle

Related Stack Overflow questions:

You can try to stitch the circle with a pygame.draw.circle() for the body and pygame.gfxdraw.circle() on the edges. However, the quality is low and can depend on the system:

📁 Minimal example - Antialiased circle

def drawAACircle(surf, color, center, radius, width):
    pygame.gfxdraw.aacircle(surf, *center, 100, color)  
    pygame.gfxdraw.aacircle(surf, *center, 100-width, color)  
    pygame.draw.circle(surf, color, center, radius, width) 

I recommend drawing a image with an antialiased circle and blit the image. You can create the image using OpenCV (opencv-python). See OpenCV - Drawing Functions.

📁 Minimal example - Antialiased circle with OpenCV

import cv2
import numpy

def drawAACircle(surf, color, center, radius, width):
    circle_image = numpy.zeros((radius*2+4, radius*2+4, 4), dtype = numpy.uint8)
    circle_image = cv2.circle(circle_image, (radius+2, radius+2), radius-width//2, (*color, 255), width, lineType=cv2.LINE_AA)  
    circle_surface = pygame.image.frombuffer(circle_image.flatten(), (radius*2+4, radius*2+4), 'RGBA')
    surf.blit(circle_surface, circle_surface.get_rect(center = center))

Another approach is to construct the circle with numpy.fromfunction.

📁 Minimal example - Antialiased circle with NumPy

import pygame
import numpy

def drawAACircle(surf, color, center, radius):
    f_circle = lambda i, j: numpy.clip(radius - numpy.hypot(i-radius-1, j-radius-1), 0, 1) * 255
    shape = (radius*2+4, radius*2+4)
    circle_rgb = numpy.full((*shape, len(color)), color, dtype = numpy.uint8)
    circle_alpha = numpy.fromfunction(f_circle, shape).astype(numpy.uint8).reshape((*shape, 1))
    circle_array = numpy.concatenate((circle_rgb, circle_alpha), 2)
    circle_surface = pygame.image.frombuffer(circle_array.flatten(), shape, 'RGBA')
    surf.blit(circle_surface, circle_surface.get_rect(center = center))
def drawAACircle(surf, color, center, radius, width, angle):
    circle_image = np.zeros((radius*2, radius*2, 4), dtype = np.uint8)
    circle_image = cv2.ellipse(circle_image, (radius, radius), (radius-width, radius-width), (angle*-.5)-90 , 0, angle, (*color, 255), width, lineType=cv2.LINE_AA)  
    circle_surf = pygame.image.frombuffer(circle_image.tobytes(), circle_image.shape[1::-1], "RGBA")
    pos = (center[0]-radius, center[1]-radius) 
    surf.blit(circle_surf, pos, special_flags=pygame.BLEND_PREMULTIPLIED)

Draw ellipse

Related Stack Overflow questions:

Draw lines and polygons

Related Stack Overflow questions:

If you want to draw a polygon line with different colors for each segment, you need to draw each line segment separately. Write a function that uses a list of points and colors to draw the line:

def draw_colorful_line(surf, colors, closed, points, width=1):
    for i in range(len(points)-1):
        pygame.draw.line(surf, colors[i], points[i], points[i+1], width)
    if closed:
        pygame.draw.line(surf, colors[-1], points[-1], points[0], width)

Use the function to draw the line:

colors = ['green', 'blue', 'red']
points = [(10, 100), (20, 200), (30, 100)]
draw_colorful_line(screen, colors, True, points)

Outline

Related Stack Overflow questions:

Line with round corners

Related Stack Overflow questions:

Dashed line

Related Stack Overflow questions:

To draw a dashed line, write a function that operates similar as pygame.draw.line() but draws a dashed straight line. The function has an additional argument prev_line_len which indicates where the line segment is within a consecutive curve. Compute the Euclidean distance between the points and the Unit vector that points from the beginning of the line segment to its end. Distribute the strokes along the line:

def draw_dashed_line(surf, color, p1, p2, prev_line_len, dash_length=8):
    dx, dy = p2[0]-p1[0], p2[1]-p1[1]
    if dx == 0 and dy == 0:
        return 
    dist = math.hypot(dx, dy)
    dx /= dist
    dy /= dist

    step = dash_length*2
    start = (int(prev_line_len) // step) * step
    end = (int(prev_line_len + dist) // step + 1) * step
    for i in range(start, end, dash_length*2):
        s = max(0, start - prev_line_len)
        e = min(start - prev_line_len + dash_length, dist)
        if s < e:
            ps = p1[0] + dx * s, p1[1] + dy * s 
            pe = p1[0] + dx * e, p1[1] + dy * e 
            pygame.draw.line(surf, color, pe, ps

Write another function that behaves similarly to pygame.draw.lines(), but uses the former function (draw_dashed_line) to draw the dashed curve. Calculate the length from the beginning of the curve to the beginning of each line segment and pass it to the function:

def draw_dashed_lines(surf, color, points, dash_length=8):
    line_len = 0
    for i in range(1, len(points)):
        p1, p2 = points[i-1], points[i]
        dist = math.hypot(p2[0]-p1[0], p2[1]-p1[1])
        draw_dashed_line(surf, color, p1, p2, line_len, dash_length)
        line_len += dist

Draw arc

Related Stack Overflow questions:

Creating an arc between two points pygame

Antialiased arc

Related Stack Overflow questions:

An arc can be drawn using pygame.draw.arc:

def drawArc(surf, color, center, radius, width, end_angle):
    arc_rect = pygame.Rect(0, 0, radius*2, radius*2)
    arc_rect.center = center
    pygame.draw.arc(surf, color, arc_rect, 0, end_angle, width)

Sadly the quality of pygame.draw.arc with a width > 1 is poor. However this can be improved, using cv2 and cv2.ellipse:

import cv2
import numpy

def drawArcCv2(surf, color, center, radius, width, end_angle):
    circle_image = numpy.zeros((radius*2+4, radius*2+4, 4), dtype = numpy.uint8)
    circle_image = cv2.ellipse(circle_image, (radius+2, radius+2),
        (radius-width//2, radius-width//2), 0, 0, end_angle, (*color, 255), width, lineType=cv2.LINE_AA) 
    circle_surface = pygame.image.frombuffer(circle_image.flatten(), (radius*2+4, radius*2+4), 'RGBA')
    surf.blit(circle_surface, circle_surface.get_rect(center = center))

Bezier

Related Stack Overflow questions:

Wireframe shape

Related Stack Overflow questions: