How do you clip a circle (or any non-Rect) from an image in pygame?

Question:

I am using Pygame and have an image. I can clip a rectangle from it:

image = pygame.transform.scale(pygame.image.load('example.png'), (32, 32))
handle_surface = image.copy()
handle_surface.set_clip(pygame.Rect(0, 0, 32, 16))
clipped_image = surface.subsurface(handle_surface.get_clip())

I have tried to use subsurface by passing a Surface:

handle_surface = image.copy()
hole = pygame.Surface((32, 32))
pygame.draw.circle(hole, (255, 255, 255), (0, 0), 32)
handle_surface.set_clip(hole)
image = surface.subsurface(handle_surface.get_clip())
surf = image.copy()

But I get the error:

ValueError: invalid rectstyle object

This error is because despite its name, subsurface expects a Rect, not a Surface. Is there a way to clip another shape from this image and have collidepoint work correctly?

Asked By: Anonymous

||

Answers:

You cannot use pygame.Surface.subsurface because a Surface is always rectangular and cannot have a circular shape. pygame.Rect.collidepoint detects if a point is inside a rectangular area and therefore cannot help you either.


Collision detection between a circle and a point can be calculated using the distance between the pointer and the center of the circle. Calculate the square of the Euclidean distance (dx*dx + dy*dy) from the point to the center of the circle. Check that the square of the distance is less than the square of the radius. In the following code the coordinates of the point are (px, py) and the circle is defined by its center (cx, cy) and its radius (radius).

dx = px - cx
dy = py - cy
if dx*dx + dy*dy <= radius*radius:
    print('hit')

An alternative solution could be PyGame collision with masks. Also pygame.sprite.collide_circle could help, but then you would have to create a pygame.sprite.Sprite object for the point with radius 1, which seems to overcomplicate the problem.


If you want to clip a circular area from a pygame.Surface, see:

Short instruction:

  • Create a rectangular partial area from the image (only if the circle does not fill the whole image). However, the image must have an alpha channel. If it does not have one, this can be achieved with convert_alpha.
  • Create a transparent (pygame.SRCALPHA) mask with the size of the image
  • Draw a white opaque circle on the mask (I use pygame.draw.ellipse here, because it is easier to set the dimension)
  • Blend the circular mask with the image using the blend mode pygame.BLEND_RGBA_MIN. (see pygame.Surface.blit)
sub_image = image.subsurface(pygame.Rect(x, y, w, h)).convert_alpha()
mask_image = pygame.Surface(sub_image.get_size(), pygame.SRCALPHA)
pygame.draw.ellipse(mask_image, (255, 255, 255, 255), sub_image.get_rect())
sub_image.blit(mask_image, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
Answered By: Rabbid76