I want to draw a solid black rectangle with only the middle part(have an accurate position for the hollow place) being fully transparent. How can I do that?
Use pygame.draw.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)
Related
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)
I have a problem. I want to animate an area, which is saved in a pygame rectangle (pygame.Rect).
Is there a function like:
for pixel in pygame.Rect:
Use a nested loop to iterate through all pixels in a rectangle:
rect = pygame.Rect(x, y, w, h)
for x in range(rect.left, rect.right):
for y in range(rect.top, rect.bottom):
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?
I'm trying to select the center of my image to make a hitbox some pixels around it.
I was able to inflate() the hitbox to make it the right size, but it always uses the top left corner of the image. This is fine when i'm moving left, but when i turn right it goes away from the character (in the image the character is dragging a sword, so it goes way off center).
I've been reading about Vector2, pos and offset, but i can't get it to work.
In conclusion i need to learn a way to find the center of my image in order to place it's hitbox a few pixels to each side. Either that or how to "shift" the corner the hitbox uses so it's always in the front of the char.
Use a pygame.Rect object. Get the rectangle from the pygame.Surface object (image) and set the top left corner position (x, y) of the rectangle. e.g.:
rect = image.get_rect()
rect.topleft = (x, y)
respectively
rect = image.get_rect(topleft = (x, y))
pygame.Rect provides a lot of virtual attributes, which can be used to retrieve the corners, borders, center and size of the rectangle. .center returns the center of the rectangle:
center_x, center_y = rect.center
I was writing a simple program that gradually fills (using floodfill) an image (black background with random white rectangles) to become totally white by clicking different areas of the image. Completed without a hitch.
So I thought of making it more interesting by toggling between filling with white color and black color. As in, if the pixel I click on is part of a white region, fill it to become black. Otherwise if it is part of a black region, fill it to become white.
However, after I change some boxes to become white, it refuses to change to black after subsequent clicking (unable to toggle the color back). Furthermore, because my rectangles are drawn using 3 or 4 pixels thick lines, after changing all the lines to black, it seems to still 'remember' that those lines exists, such that when I click on certain dark regions, occasionally the region (bounded by those invisible 'previous' lines) would become white color.
I have tried printing the pixel color to confirm that the color picked up is indeed white or black, but yet the floodfill is not filling it with the correct alternate color (written by my if/else loops)
import numpy as np
import cv2 as cv
import random
width = 800
height = 500
img = np.zeros((height, width), np.uint8)
mask = np.zeros((height+2, width+2), np.uint8)
def click_event(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDOWN:
font = cv.FONT_HERSHEY_PLAIN
strxy = "X: {0} Y: {1}".format(x,y)
print(strxy)
fillmeup(x, y)
cv.imshow("test", img)
def fillmeup(x, y):
print(img[y,x])
if img[y,x] == 0:
cv.floodFill(img, mask, (x, y), 255)
elif img[y,x] == 255:
cv.floodFill(img, mask, (x, y), 0)
def drawboxes(qty):
global img
for _ in range(qty):
w = int(random.random()*width)
h = int(random.random()*height)
x = random.randrange(0, width-w)
y = random.randrange(0, height-h)
img = cv.rectangle(img, (x, y), (x+w, y+h), 255, 2)
drawboxes(7)
cv.imshow("test", img)
cv.setMouseCallback("test", click_event)
cv.waitKey(0)
cv.destroyAllWindows()
Well, I would expect that every subsequent click on a black region would produce white, and vice versa, but it is not happening.
And when it does switch back to white, it seems to be bounded by invisible lines that have been turned black already.
Below is an attached sample outcome.
01_start
02_selecting 2 boxes
03_selecting one of the thin white lines changes them to black: correct outcome
04_selecting some random random black space, but yet bounded white rectangles appear. the boundaries were the original white lines. weird outcome
floodFill() updates not only the image but also the mask.
On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the a value specified in flags as described below. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap.
def fillmeup(x, y):
print(img[y,x])
mask = np.zeros((height+2, width+2), np.uint8)
if img[y,x] == 0:
cv.floodFill(img, mask, (x, y), 255)
else:
cv.floodFill(img, mask, (x, y), 0)
This works for me as you describe. If you do not need mask at all, you possibly can write
cv.floodFill(img, None, (x, y), ...)
This works for me too, but I didn't find any evidence that None mask argument is legal for floodFill(). Please notify my to update the answer if you find if it is legal or not in any authoritative source.