pygame: mask non-image type surfaces - python

I know how to mask images. But if I simply want to create a circle and mask it, it would be annoying to create an image of a simple circle outside of my code, especially when I need to create different kinds of circles. Appearantly you can create Rect objects in pygame but there is no class Circle.
pygame.mask.from_surface requires a surface. Can I pass a non-image type surface as a parameter? If so how can I mask circles and/or other objects?
Here's something I imagined which obviously throws an error:
circle = pygame.Circle((10, 10), 5) # (center coordinates), radius
pygame.mask.from_surface(circle)

There is no way to create a circular mask directly. See pygame.mask. You need to draw a circle on a Surface. Create a function that creates a Surface and draw a circle on the Surface:
def circleSurface(color, radius):
shape_surf = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
pygame.draw.circle(shape_surf, color, (radius, radius), radius)
return shape_surf
Create the Mask from the Surface:
circle = circleSurface((255, 255, 255), 5) # (center coordinates), radius
pygame.mask.from_surface(circle)
However, if you want to use a circle to clip an area of the display, see the answers to the following questions:
how to make circular surface in PyGame
How to fill only certain circular parts of the window in PyGame?

Related

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

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?
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:
how to make circular surface in PyGame
How do I focus light or how do I only draw certain circular parts of the window in pygame?
How do I display a large black rectangle with a moveable transparent circle in pygame?
Can I use an image on a moving object within Pygame as opposed to to a color?
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)

How to get the surface from a rect/line

I am trying to find the point where a line collides with a brick in the arkanoid that i am making. The most logical way i found is getting the mask from the line and use collidemask as it returns the point. Well as i tried with this:
linemask = pygame.mask.from_surface(pygame.draw.line(screen, (0,0,0), bola.line[0], bola.line[1], 2))
it gave me this error:
TypeError: argument 1 must be pygame.Surface, not pygame.Rect
meaning that the input(in this case the line) can't be a rect but needs to be a surface. Do you know how to get the surface from a rect or any alternative solution ?
pygame.draw.line draws on a Surface and returns the affected area in form of a Rect object.
The Surface you drew on is screen. So it's screen you want to create a mask from. Alternatively, create a new Surface that you use pygame.draw on and create a mask from it. Or create a mask from the subsurface of the screen (so you don't have to create a mask from the whole screen), like this:
rect = pygame.draw.line(screen, (0,0,0), bola.line[0], bola.line[1], 2)
surface = screen.subsurface(rect)
mask = pygame.mask.from_surface(surface)

Draw a polygon using draw.polygon

import PIL.ImageDraw as ImageDraw,PIL.Image as Image, PIL.ImageShow as ImageShow
#that s my class Point(2D)
class Pnt(namedtuple('Pnt', 'x y')):
__slots__ = ()
def __init__(self, *args):
super(Pnt, self).__init__(*args)
Here is the vector of the vertices(convex polygon)
vertix = []
vertix.append(Pnt(50, 100))
vertix.append(Pnt(100, 200))
vertix.append(Pnt(200, 200))
vertix.append(Pnt(300, 0))
vertix.append(Pnt(250, -200))
vertix.append(Pnt(100, -100))
Here I want to draw the polygon. The problem is it is not centered, so almost half the polynom is outside the frame.
im = Image.new("RGB", (600,800))
draw = ImageDraw.Draw(im)
draw.polygon(vertix, fill=230, outline=255)
im.show()
If you want to center your polygon in the Image you can
1) determine the bounding box of your polygon,
2) calculate the coordinates of the center of the bounding box,
3) calculate the vector required to translate the center of the bounding box to the center of the Image rectangle,
4) create a new polygon by translating the coords of the vertices of the old poly by the vector found in step 3.

Pygame circle collision?

I am using pygame to make a simple game. I am having issues with circle collisions. I am getting the following error:
"AttributeError: 'pygame.Rect' object has no attribute 'rect'"
Here is the particular code I am having issues with below:
if pygame.sprite.collide_circle(hero_circle, enemy_circle):
gameover()
Use pygame.mask to create a collision mesh for your objects and use the mesh to do collision detections.
In more detail:
Create an image file for both of your circles and set the bg color to something you will not use anywhere else.
Set that color to "transparent" in your image editor.
Import the images.
Create a mesh for them with pygame.mask and set it to make transparent pixels non-collidable.
Use the generated mask as your collision detection mesh.
PROFIT
(Technically this is just doing collision detection of a circle shaped area on a rectangle, but who cares!)
pygame.draw.rect()
draw a rectangle shape
rect(Surface, color, Rect, width=0) -> Rect
Draws a rectangular shape on the Surface. The given Rect is the area of the rectangle. The width argument is the thickness to draw the outer edge. If width is zero then the rectangle will be filled.
Keep in mind the Surface.fill() method works just as well for drawing filled rectangles. In fact the Surface.fill() can be hardware accelerated on some platforms with both software and hardware display modes.
The best way I've found to check circle collision detection is to calculate the distance between the center points of two circles. If the distance is less than the sum of the two circle's radii, then you've collided.
Just like how gmk said it but if your are using circles instead of rectangles, you should use this pygame function :
pygame.draw.circle(surface, color, center_point, radius, width)
This draws a circle on your surface (which would go in the surface area). Clearly the color requires a list of numbers (RGB anyone?). Your center_point decides the location of your circle since it will be the location of the center of your circle. The radius will need a number to set the radius of the circle (using the number like 25 will set your radius at 25 pixels/diameter at 50 pixels). the width section is optional as it sets the thickness of the perimeter of your circle (having 0 will have none at all). If you are not using circles, you should change your title... But anyways, I hope this helps you!

How can I crop an image with Pygame?

I am learning pygame and want a graphic for a button with the three states: normal, hover, and pressed. I have an image like this one ...
... and I want to get a new Surface using a portion of it.
I'm loading the image with this code:
buttonStates = pygame.image.load(os.path.join('image','button.png'))
How can I make a new surface using just a portion of that graphic?
cropped = pygame.Surface((80, 80))
cropped.blit(buttonStates, (0, 0), (30, 30, 80, 80))
The blit method on a surface 'pastes' another surface on to it. The first argument to blit is the source surface. The second is the location to paste to (in this case, the top left corner). The third (optional) argument is the area of the source image to paste from -- in this case an 80x80 square 30px from the top and 30px from the left.
You can also use the pygame.Surface.subsurface method to create subsurfaces that share their pixels with their parent surface. However, you have to make sure that the rect is inside of the image area or a ValueError: subsurface rectangle outside surface area will be raised.
subsurface = a_surface.subsurface((x, y, width, height))
There are 2 possibilities.
The blit method allows to specify a rectangular sub-area of the source _Surface:
[...] An optional area rectangle can be passed as well. This represents a smaller portion of the source Surface to draw. [...]
In this way you can blit an area of the source surface directly onto a target:
cropped_region = (x, y, width, height)
target.blit(source_surf, (posx, posy), cropped_region)
Alternatively, you can define a subsurface that is directly linked to the source surface with the subsurface method:
Returns a new Surface that shares its pixels with its new parent. The new Surface is considered a child of the original. Modifications to either Surface pixels will effect each other.
As soon as a subsurface has been created, it can be used as a normal surface at any time:
cropped_region = (x, y, width, height)
cropped_subsurf = source_surf.subsurface(cropped_region)
target.blit(cropped_subsurf, (posx, posy))
I think the best way to do it is crop the image of these 3 kind of buttons in a external program and load in different surface instead use pygame to crop it

Categories

Resources