“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)
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)])
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:
pygame.Surface
object with a per-pixel alpha format large enough to cover the shape.blit()
by default blends 2 SurfacesFor 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)
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
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
Related Stack Overflow questions:
How can I draw a rectangular outline (not filled) with PyGame?
Why did drawing a PyGame rectangle with very thick borders draw a plus shape instead?
What is the difference between pygame.draw.rect and screen_surface.blit()?
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:
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)
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
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
📁 Minimal example - rotate rectangle
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 round
ed 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))
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)
Related Stack Overflow questions:
Related Stack Overflow questions:
How do you create a polygon that fills the area between 2 circles?
How to translate and rotate the coordinate axis about a point in pygame screen?
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)
Related Stack Overflow questions:
Why am I not getting appropriate values for the outline I am creating - mask with pygame
Related Stack Overflow questions:
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
Related Stack Overflow questions:
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))
Related Stack Overflow questions:
Related Stack Overflow questions: