I am trying to make a logic in which i can subtract the geometrical shapes. Lets say i have to subtract a circle from a rectangle.
The idea is to draw each point of the rectangle as a single individual point using win.set_at(). And then when we need to subtract the circle from the rectangle we an simply calculate that the point on which we are drawing and completing our square lies or not in the circle. If it lies then don't set pixels else draw the pixel. This is my code:
import pygame
WIDTH, HEIGHT = 700, 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
def drawer(window, x, y, radius, s_l, s_w, s_x, s_y):
for i in range(0, HEIGHT):
for j in range(0, WIDTH):
distance = (x - j)**2 + (y - i)**2
if not distance <= radius**2 and i >= s_y and j >= s_x and i <= s_w + s_x and j <= s_l + s_y:
window.set_at((j, i), BLACK)
c_x, c_y = 130, 150
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
drawer(win, c_x, c_y, 30, 100, 100, 100, 100)
pygame.display.flip()
pygame.quit()
But is it is quite slow. Can you suggest a better way (more optimized algorithm) of doing it.
For a good performance I suggest to use pygame.mask. Draw the shapes on pygame.Surface objects and create masks from the surfaces:
mask1 = pygame.mask.from_surface(shape1)
mask2 = pygame.mask.from_surface(shape2)
Invert the mask of the second shape and create the overlapping mask. Finally, create a new _Surface: from the overlapping mask:
mask2.invert()
subtract_maks = mask1.overlap_mask(mask2, (0, 0))
subtract_shape = subtract_maks.to_surface(setcolor = "red", unsetcolor=(0, 0, 0, 0))
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
shape1 = pygame.Surface((200, 200), pygame.SRCALPHA)
pygame.draw.circle(shape1, "white", (100, 100), 100)
shape2 = pygame.Surface((200, 200), pygame.SRCALPHA)
pygame.draw.rect(shape2, "white", (25, 50, 150, 100))
mask1 = pygame.mask.from_surface(shape1)
mask2 = pygame.mask.from_surface(shape2)
mask2.invert()
subtract_maks = mask1.overlap_mask(mask2, (0, 0))
subtract_shape = subtract_maks.to_surface(setcolor = "red", unsetcolor=(0, 0, 0, 0))
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *background.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background, color, rect) for rect, color in tiles]
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
print(pygame.key.name(event.key))
window.blit(background, (0, 0))
window.blit(subtract_shape, (50, 50))
pygame.display.flip()
pygame.quit()
exit()
Related
When i create a mask in Pygame, for example:
self.mask = pygame.mask.from_surface(self.image)
Can i see the result, the grid with Boolean values?
And question 2, can I make an overlap visible when two mask overlap?
Can i see the result, the grid with Boolean values.
You can only see what you are drawing on the screen. A mask cannot be drawn on the screen so you cannot see it.
Can I make an overlap visible when two mask overlap?
Create a Mask containing the overlapping set bits between 2 Masks with pygame.mask.Mask.overlap_mask.
Convert the Mask to a Surface with pygame.mask.Mask.to_surface. e.g.:
overlap_mask = mask1.overlap_mask(mask2, (offset_x, offset_y))
overlap_surf = overlap_mask.to_surface(setcolor= (255, 0, 0))
overlap_surf.set_colorkey((0, 0, 0))
Minimal example:
The overlapping pixels are colored red.
import pygame, math
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
image1 = pygame.image.load("Banana.png")
image2 = pygame.image.load("Bird.png")
rect1 = image1.get_rect(center = (165, 150))
rect2 = image1.get_rect(center = (135, 150))
mask1 = pygame.mask.from_surface(image1)
mask2 = pygame.mask.from_surface(image2)
angle = 0
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
angle += 0.01
rect1.centery = 150 + round(60 * math.sin(angle))
offset_x = rect2.x - rect1.x
offset_y = rect2.y - rect1.y
overlap_mask = mask1.overlap_mask(mask2, (offset_x, offset_y))
overlap_surf = overlap_mask.to_surface(setcolor = (255, 0, 0))
overlap_surf.set_colorkey((0, 0, 0))
window.fill(0)
window.blit(image1, rect1)
window.blit(image2, rect2)
window.blit(overlap_surf, rect1)
pygame.display.flip()
pygame.quit()
exit()
How can i make this kind of countdown in Pygame? (i'm looking for how could i make the circle's perimeter decrease, that's the issue, because displaying the time isn't hard )
Keep in mind that how long the perimeter of the circle is and the displayed time should be in proportion with each other.
Just use pygame.draw.arc and specify the stop_angle argument depending on the counter:
percentage = counter/100
end_angle = 2 * math.pi * percentage
pygame.draw.arc(window, (255, 0, 0), arc_rect, 0, end_angle, 10)
Minimal example:
import pygame
import math
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
counter = 100
text = font.render(str(counter), True, (0, 128, 0))
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, 1000)
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)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
counter -= 1
text = font.render(str(counter), True, (0, 128, 0))
if counter == 0:
pygame.time.set_timer(timer_event, 0)
window.fill((255, 255, 255))
text_rect = text.get_rect(center = window.get_rect().center)
window.blit(text, text_rect)
drawArc(window, (255, 0, 0), (100, 100), 90, 10, 2*math.pi*counter/100)
pygame.display.flip()
pygame.quit()
exit()
Sadly the quality of pygame.draw.arc with a width > 1 is poor. However this can be improved, using cv2 and cv2.ellipse:
import pygame
import cv2
import numpy
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
counter = 100
text = font.render(str(counter), True, (0, 128, 0))
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, 1000)
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))
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
counter -= 1
text = font.render(str(counter), True, (0, 128, 0))
if counter == 0:
pygame.time.set_timer(timer_event, 0)
window.fill((255, 255, 255))
text_rect = text.get_rect(center = window.get_rect().center)
window.blit(text, text_rect)
drawArcCv2(window, (255, 0, 0), (100, 100), 90, 10, 360*counter/100)
pygame.display.flip()
pygame.quit()
exit()
So I read the documentation of pygame but I could not understand it clearly. I recently asked a question about bitmap fonts and I got some code as my answer; here is the code:
import pygame
pygame.init()
win = pygame.display.set_mode((800, 600))
font = pygame.font.Font("freesansbold.ttf", 32)
i = 0
text = "hello how are you?"
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
letter = text[i]
text_1 = font.render(letter, True, (255, 255, 255))
bw, bh = font.size(letter)
glyph_rect = pygame.mask.from_surface(text_1).get_bounding_rects()
# print(glyph_rect)
if glyph_rect:
gh = glyph_rect[0].height
print(f'letter {letter} bitmap height: {bh} glyph height: {gh}')
win.fill((0, 0, 0))
win.blit(text_1, (0, 0))
pygame.display.update()
i += 1
run = i < len(text)
pygame.quit()
So, my questions are on the line glyph_rect = pygame.mask.from_surface(text_1).get_bounding_rects().
What does the pygame.mask.from_surface() function do?
What does the line glyph_rect = pygame.mask.from_surface(text_1).get_bounding_rects() do?
What arguments does the variable glyph_rect return, and what is the meaning of those arguments?
pygame.mask.from_surface creates a pygame.mask.Mask object form a pygame.Surface.
A Surface is bitmap. A Mask is an 2 dimensional array with Boolean values. The Mask created is the size of the _Surface. A field is True if the corresponding pixel in the surface is not transparent, and False if it is transparent.
pygame.mask.Mask.get_bounding_rects creates a list of pygame.Rect objects. Each rectangle describes a bounding area of connected pixles.
If the Surface contains exactly 1 connected image, you will get exactly 1 rectangle surrounding the image.
See the example. The black rectangle is the Surface rectangle and the red rectangle is the bound rectangle of the connected component:
repl.it/#Rabbid76/ImageHitbox
import pygame
def getMaskRect(surf, top = 0, left = 0):
surf_mask = pygame.mask.from_surface(surf)
rect_list = surf_mask.get_bounding_rects()
surf_mask_rect = rect_list[0].unionall(rect_list)
surf_mask_rect.move_ip(top, left)
return surf_mask_rect
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
try:
my_image = pygame.image.load('Bomb-256.png')
except:
my_image = pygame.Surface((200, 200), pygame.SRCALPHA)
pygame.draw.circle(my_image, (0, 128, 0), (60, 60), 40)
pygame.draw.circle(my_image, (0, 0, 128), (100, 150), 40)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pos = window.get_rect().center
my_image_rect = my_image.get_rect(center = pos)
my_image_mask_rect = getMaskRect(my_image, *my_image_rect.topleft)
window.fill((255, 255, 255))
window.blit(my_image, my_image_rect)
pygame.draw.rect(window, (0, 0, 0), my_image_rect, 3)
pygame.draw.rect(window, (255, 0, 0), my_image_mask_rect, 3)
pygame.display.flip()
pygame.quit()
exit()
I am using pygame.font.Font.render() to render some text. I'd like the text to be translucent, ie have an alpha value other than 255, so I tried passing a color argument with an alpha value (eg (255, 0, 0, 150)) as the color argument for pygame.font.Font.render() but it didn't have any effect. I also tried using pygame.Surface.convert_alpha() on the resulting Surface object, but that didn't do anything either. Any ideas?
I'm not sure why, but after some experimentation I have discovered that the surface created with font.render cannot have it's alpha value changed. Just blit that surface to another surface, and change the alpha value of the new surface.
textsurface=font.render('Test', True, (0, 0, 0))
surface=pygame.Surface((100, 30))
surface.fill((255, 255, 255))
surface.blit(textsurface, pygame.Rect(0, 0, 10, 10))
surface.set_alpha(50)
window.blit(surface, pygame.Rect(0, 30, 10, 10))
When using the pygame.font module, the alpha channel of the text color is not taken into account when rendering a text, but see pygame.font.Font.render:
Antialiased images are rendered to 24-bit RGB images. If the background is transparent a pixel alpha will be included.
and pygame.Surface.set_alpha
Changed in pygame 2.0: per-surface alpha can be combined with per-pixel alpha.
Hence it is completely sufficient to set the transparency after rendering the text with set_alpha. This even works for anti-aliased text:
font = pygame.font.SysFont(None, 150)
text_surf = font.render('test text', True, (255, 0, 0))
text_surf.set_alpha(127)
window.blit(text_surf, (x, y))
Minimal example: repl.it/#Rabbid76/PyGame-TransparentText
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 150)
text_surf = font.render('test text', True, (255, 0, 0))
text_surf.set_alpha(127)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
window.blit(text_surf, text_surf.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
By using the pygame.freetype module, you can use a transparent color directly when creating a text surface:
ft_font = pygame.freetype.SysFont('Times New Roman', 150)
text_surf2, text_rect2 = ft_font.render('test text', (255, 0, 0, 128))
window.blit(text_surf2, (x, y))
Or if you are rendering the text directly onto a surface:
ft_font = pygame.freetype.SysFont('Times New Roman', 150)
ft_font.render_to(window, (x, y), 'test text', (255, 0, 0, 128))
Minimal example: repl.it/#Rabbid76/PyGame-TransparentFreeTypeText
import pygame
import pygame.freetype
pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()
ft_font = pygame.freetype.SysFont('Times New Roman', 150)
text_surf2, text_rect2 = ft_font.render('test text', (255, 0, 0, 128))
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
text_rect = ft_font.get_rect('test text')
text_rect.center = (window.get_width() // 2, window.get_height() // 2 - 70)
ft_font.render_to(window, text_rect.topleft, 'test text', (255, 0, 0, 128))
text_rect2.center = (window.get_width() // 2, window.get_height() // 2 + 70)
window.blit(text_surf2, text_rect2)
pygame.display.flip()
pygame.quit()
exit()
See also Text and font
You can change the transparency of the entire surface using a individual alpha value.
Use surf=pygame.Surface((size,pygame.SRCALPHA). Now if you change the alpha value by setting surf.set_alpha(alpha) of the screen, the surface around the text wont be black anymore.
import pygame
pygame.init()
width = 800
height = 600
running = True
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Dijkstra's Path-Finding Algorithm Solver")
icon = pygame.image.load('icon.jpg')
pygame.display.set_icon(icon)
def title():
button_font = pygame.font.Font('TrajanPro-Regular.otf', 40)
rect_display = button_font.render('Dijkstra Path-Finding Algorithm', True, (255, 255, 255))
# display global total deaths
screen.blit(rect_display, (12, 10))
def title_underline():
# create the button
rect = pygame.Rect(0, 60, 800, 3)
rect_display = pygame.draw.rect(screen, [255, 255, 255], rect)
button_font = pygame.font.Font('TrajanPro-Regular.otf', 100)
rect_display = button_font.render('', True, (255, 255, 255))
# display global total deaths
screen.blit(rect_display, (270, 198))
def grid():
blockSize = 20 #Set the size of the grid block
for x in range(width):
for y in range(height):
rect = pygame.Rect(x*blockSize, y*blockSize, blockSize, blockSize)
pygame.draw.rect(screen, (200,200,200), rect, 1)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
title()
title_underline()
grid()
pygame.display.update()
I have created a title for my project and I created a grid for which I want to run my application, but the grid is being made in every single x, y location on my screen. But I want the grid to only be made under the title and not over it.
Define the top left coordinates of the grid (grid_x, grid_y) and add the coordinates when constructing the rectangle of a cell. For instance:
def grid():
grid_x = 12
grid_y = 30
blockSize = 20 #Set the size of the grid block
for x in range(width):
for y in range(height):
rect = pygame.Rect(
grid_x + x*blockSize, grid_y + y*blockSize,
blockSize, blockSize)
pygame.draw.rect(screen, (200,200,200), rect, 1)