Stretch an Image in Pygame while preserving the corners - python

The title says it all really. The effect I'm desiring is going to be used for UI, since UI bubbles will appear, and I want to animate them stretching.
Chat bubbles in iOS messaging apps are a good example of this behavior, see
here for example. Here's the main image reproduced:
Notice the last chat bubbles wonky behavior. This is not normal in messaging apps, and the proper stretching is what I want to achieve with Pygame.
Is there any easy way to reproduce this specific kind of stretching in Pygame? Even if there are some constraints, like, all corners have to be the same size or something. I'd just like to know what is possible.
Thanks!

Based on what I had suggested in the comments, here is an implementation of the SliceSprite class that creates and renders a 9-sliced sprite in pygame. I have also included a sample to show how it might be used. It is definitely rough around the edges (does not check for invalid input like when you resize the sprite with a width less than your defined left and right slice sizes) but should still be a useful start. This code has been updated and polished to handle these edge cases and does not recreate nine subsurfaces on every draw call as suggested by #skrx in the comments.
slicesprite.py
import pygame
class SliceSprite(pygame.sprite.Sprite):
"""
SliceSprite extends pygame.sprite.Sprite to allow for 9-slicing of its contents.
Slicing of its image property is set using a slicing tuple (left, right, top, bottom).
Values for (left, right, top, bottom) are distances from the image edges.
"""
width_error = ValueError("SliceSprite width cannot be less than (left + right) slicing")
height_error = ValueError("SliceSprite height cannot be less than (top + bottom) slicing")
def __init__(self, image, slicing=(0, 0, 0, 0)):
"""
Creates a SliceSprite object.
_sliced_image is generated in _generate_slices() only when _regenerate_slices is True.
This avoids recomputing the sliced image whenever each SliceSprite parameter is changed
unless absolutely necessary! Additionally, _rect does not have direct #property access
since updating properties of the rect would not be trigger _regenerate_slices.
Args:
image (pygame.Surface): the original surface to be sliced
slicing (tuple(left, right, top, bottom): the 9-slicing margins relative to image edges
"""
pygame.sprite.Sprite.__init__(self)
self._image = image
self._sliced_image = None
self._rect = self.image.get_rect()
self._slicing = slicing
self._regenerate_slices = True
#property
def image(self):
return self._image
#image.setter
def image(self, new_image):
self._image = new_image
self._regenerate_slices = True
#property
def width(self):
return self._rect.width
#width.setter
def width(self, new_width):
self._rect.width = new_width
self._regenerate_slices = True
#property
def height(self):
return self._rect.height
#height.setter
def height(self, new_height):
self._rect.height = new_height
self._regenerate_slices = True
#property
def x(self):
return self._rect.x
#x.setter
def x(self, new_x):
self._rect.x = new_x
self._regenerate_slices = True
#property
def y(self):
return self._rect.y
#y.setter
def y(self, new_y):
self._rect.y = new_y
self._regenerate_slices = True
#property
def slicing(self):
return self._slicing
#slicing.setter
def slicing(self, new_slicing=(0, 0, 0, 0)):
self._slicing = new_slicing
self._regenerate_slices = True
def get_rect(self):
return self._rect
def set_rect(self, new_rect):
self._rect = new_rect
self._regenerate_slices = True
def _generate_slices(self):
"""
Internal method required to generate _sliced_image property.
This first creates nine subsurfaces of the original image (corners, edges, and center).
Next, each subsurface is appropriately scaled using pygame.transform.smoothscale.
Finally, each subsurface is translated in "relative coordinates."
Raises appropriate errors if rect cannot fit the center of the original image.
"""
num_slices = 9
x, y, w, h = self._image.get_rect()
l, r, t, b = self._slicing
mw = w - l - r
mh = h - t - b
wr = w - r
hb = h - b
rect_data = [
(0, 0, l, t), (l, 0, mw, t), (wr, 0, r, t),
(0, t, l, mh), (l, t, mw, mh), (wr, t, r, mh),
(0, hb, l, b), (l, hb, mw, b), (wr, hb, r, b),
]
x, y, w, h = self._rect
mw = w - l - r
mh = h - t - b
if mw < 0: raise SliceSprite.width_error
if mh < 0: raise SliceSprite.height_error
scales = [
(l, t), (mw, t), (r, t),
(l, mh), (mw, mh), (r, mh),
(l, b), (mw, b), (r, b),
]
translations = [
(0, 0), (l, 0), (l + mw, 0),
(0, t), (l, t), (l + mw, t),
(0, t + mh), (l, t + mh), (l + mw, t + mh),
]
self._sliced_image = pygame.Surface((w, h))
for i in range(num_slices):
rect = pygame.rect.Rect(rect_data[i])
surf_slice = self.image.subsurface(rect)
stretched_slice = pygame.transform.smoothscale(surf_slice, scales[i])
self._sliced_image.blit(stretched_slice, translations[i])
def draw(self, surface):
"""
Draws the SliceSprite onto the desired surface.
Calls _generate_slices only at draw time only if necessary.
Note that the final translation occurs here in "absolute coordinates."
Args:
surface (pygame.Surface): the parent surface for blitting SliceSprite
"""
x, y, w, h, = self._rect
if self._regenerate_slices:
self._generate_slices()
self._regenerate_slices = False
surface.blit(self._sliced_image, (x, y))
Example usage (main.py):
import pygame
from slicesprite import SliceSprite
if __name__ == "__main__":
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
done = False
outer_points = [(0, 20), (20, 0), (80, 0), (100, 20), (100, 80), (80, 100), (20, 100), (0, 80)]
inner_points = [(10, 25), (25, 10), (75, 10), (90, 25), (90, 75), (75, 90), (25, 90), (10, 75)]
image = pygame.Surface((100, 100), pygame.SRCALPHA)
pygame.draw.polygon(image, (20, 100, 150), outer_points)
pygame.draw.polygon(image, (0, 60, 120), inner_points)
button = SliceSprite(image, slicing=(25, 25, 25, 25))
button.set_rect((50, 100, 500, 200))
#Alternate version if you hate using rects for some reason
#button.x = 50
#button.y = 100
#button.width = 500
#button.height = 200
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill((0, 0, 0))
button.draw(screen)
pygame.display.flip()
clock.tick()

Here's a solution in which I create an enlarged version of the surface by splitting it into three parts and blitting the middle line repeatedly. Vertical enlargement would work similarly.
import pygame as pg
def enlarge_horizontal(image, width=None):
"""A horizontally enlarged version of the image.
Blit the middle line repeatedly to enlarge the image.
Args:
image (pygame.Surface): The original image/surface.
width (int): Desired width of the scaled surface.
"""
w, h = image.get_size()
# Just return the original image, if the desired width is too small.
if width is None or width < w:
return image
mid_point = w//2
# Split the image into 3 parts (left, mid, right).
# `mid` is just the middle vertical line.
left = image.subsurface((0, 0, w//2, h))
mid = image.subsurface((mid_point, 0, 1, h))
right = image.subsurface((mid_point, 0, w//2, h))
surf = pg.Surface((width, h), pg.SRCALPHA)
# Join the parts (blit them onto the new surface).
surf.blit(left, (0, 0))
for i in range(width-w+1):
surf.blit(mid, (mid_point+i, 0))
surf.blit(right, (width-w//2, 0))
return surf
def main():
screen = pg.display.set_mode((800, 800))
clock = pg.time.Clock()
image = pg.Surface((100, 100), pg.SRCALPHA)
pg.draw.circle(image, (20, 100, 150), (50, 50), 50)
pg.draw.circle(image, (0, 60, 120), (50, 50), 45)
surfs = [enlarge_horizontal(image, width=i) for i in range(0, 701, 140)]
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
screen.fill((30, 30, 40))
for i, surf in enumerate(surfs):
screen.blit(surf, (20, i*109 + 5))
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

Related

In Pygame, how would I fill a drawn circle with an image instead of a colour? [duplicate]

I need to create a surface that has a bounding circle. Anything drawn on that surface should not be visible outside that bounding circle. I've tried using masks, subsurfaces, srcalpha, etc., but nothing seems to work.
My attempt:
w = ss.get_width ()
h = ss.get_height ()
TRANSPARENT = (255, 255, 255, 0)
OPAQUE = ( 0, 0, 0, 225)
crop = pygame.Surface ((w, h), pygame.SRCALPHA, ss)
crop.fill (TRANSPARENT)
c = round (w / 2), round (h / 2)
r = 1
pygame.gfxdraw. aacircle (crop, *c, r, OPAQUE)
pygame.gfxdraw.filled_circle (crop, *c, r, OPAQUE)
ss = crop.subsurface (crop.get_rect ())
App.set_subsurface (self, ss)
Later...
self.ss.fill (BLACK)
self.ss.blit (self.background, ORIGIN)
The background is a square image. It should be cropped into the shape of a circle and rendered on screen
Solution based on notes from Rabbid76:
def draw_scene (self, temp=None):
if temp is None: temp = self.ss
# 1. Draw everything on a surface with the same size as the window (background and scene).
size = temp.get_size ()
temp = pygame.Surface (size)
self.draw_cropped_scene (temp)
# 2. create the surface with the white circle.
self.cropped_background = pygame.Surface (size, pygame.SRCALPHA)
self.crop ()
# 3. blit the former surface on the white circle.
self.cropped_background.blit (temp, ORIGIN, special_flags=pygame.BLEND_RGBA_MIN)
# 4. blit the whole thing on the window.
self.ss.blit (self.cropped_background, ORIGIN)
def draw_cropped_scene (self, temp): App.draw_scene (self, temp)
An example implementation of crop() is:
def crop (self):
o, bounds = self.bounds
bounds = tr (bounds) # round elements of tuple
pygame.gfxdraw. aaellipse (self.cropped_background, *bounds, *bounds, OPAQUE)
pygame.gfxdraw.filled_ellipse (self.cropped_background, *bounds, *bounds, OPAQUE)
The background is a square image. It should be cropped into the shape of a circle and rendered on screen
You can achieve this by using the blend mode BLEND_RGBA_MIN (see pygame.Surface.blit).
Create a transparent pygame.Surface with the same size as self.background. Draw a whit circle in the middle of the Surface and blend the background on this Surface using the blend mode BLEND_RGBA_MIN. Finally you can blit it on the screen:
size = self.background.get_size()
self.cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(self.cropped_background, (255, 255, 255, 255), (0, 0, *size))
self.cropped_background.blit(self.background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
self.ss.fill(BLACK)
self.ss.blit(self.cropped_background, ORIGIN)
Minimal example: repl.it/#Rabbid76/PyGame-ClipCircularRegion-1
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
background = pygame.Surface(window.get_size())
for x in range(5):
for y in range(5):
color = (255, 255, 255) if (x+y) % 2 == 0 else (255, 0, 0)
pygame.draw.rect(background, color, (x*50, y*50, 50, 50))
size = background.get_size()
cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(cropped_background, (255, 255, 255, 255), (0, 0, *size))
cropped_background.blit(background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(cropped_background, (0, 0))
pygame.display.flip()
See Also How to fill only certain circular parts of the window in pygame?

The particle is not moving so what is the problem?

I follow a youtube video 'https://www.youtube.com/watch?v=cCiXqK9c18g' and in the video he make a class that represent a ball and he used pymunk to make a body and added it to the space and after that he created a method inside the ball class that will use pygame to draw the ball and I did almost like him
import pygame
import pymunk
pygame.init()
fps = 60
dt = 1/fps
dsX = 800 # screen width
dsY = 500 # screen height
display = pygame.display.set_mode((dsX, dsY))
space = pymunk.Space()
clock = pygame.time.Clock()
def convert_cor(point): # convet the coordinates from pymunk to pygame coordinates
return point[0], dsY - point[1]
class Particle: # v: velocity, pos: position[x, y], r: radius of particle(Circle)
def __init__(self, pos = [0, 0], v = [0, 0], r = 10, color = (255, 0, 0)):
self.pos = pos
self.v = v
self.r = r
self.color = color
self.body = pymunk.Body()
self.body.position = self.pos
self.body.velocity = self.v # this is the veclocity
self.shape = pymunk.Circle(self.body, self.r)
self.shape.dencity = 1
self.shape.elasticity = 1
space.add(self.body, self.shape)
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.pos), self.r)
class Box: # thickness of the sides of the box and L1, L2, L3, L4 are the sides of the box
def __init__(self, thickness, color):
self.thickness = thickness
self.color = color
L1 = pymunk.Body(body_type = pymunk.Body.STATIC)
L2 = pymunk.Body(body_type = pymunk.Body.STATIC)
L3 = pymunk.Body(body_type = pymunk.Body.STATIC)
L4 = pymunk.Body(body_type = pymunk.Body.STATIC)
L1_shape = pymunk.Segment(L1, (0, 0), (dsX, 0), self.thickness)
L2_shape = pymunk.Segment(L2, (dsX, 0), (dsX, dsY), self.thickness)
L3_shape = pymunk.Segment(L3, (dsX, dsY), (0, dsY), self.thickness)
L4_shape = pymunk.Segment(L4, (0, dsY), (0, 0), self.thickness)
space.add(L1, L1_shape)
space.add(L2, L2_shape)
space.add(L3, L3_shape)
space.add(L4, L4_shape)
def draw(self):
pygame.draw.line(display, self.color, convert_cor((0, 0)), convert_cor((dsX, 0)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((dsX, 0)), convert_cor((dsX, dsY)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((dsX, dsY)), convert_cor((0, dsY)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((0, dsY)), convert_cor((0, 0)), self.thickness * 2)
def Sim(): # the infinite while loop as a function
box = Box(2, (0, 255, 255))
particle = Particle(pos =[dsX/2, dsY/2], v = [-200, 500]) # here i gave the position and the velocity
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
display.fill((255, 255, 255))
box.draw()
particle.draw()
clock.tick(fps)
space.step(dt)
pygame.display.update()
Sim()
pygame.quit()
The thing is, I did also a class that will add a rigid sides for the display and i drew the sides from the Box class using the method 'draw' The problem is in the time 5:58 in the video he gave the ball velocity and it start moving and in my code it does not move. any idea why it doen't move?
note: I called the ball particle in my code
You error is both a typo and using the wrong variable.
Inside your particles draw function...
# OLD
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.pos), self.r)
# New
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.body.position), self.r)
You have to use the body's position cause that is the position of the physics body in pymunk's space.
Secondly...
class Particle: # v: velocity, pos: position[x, y], r: radius of particle(Circle)
def __init__(self, pos, v, r=10, color=(255, 0, 0)):
...
# Old
self.shape.dencity = 1
# New
self.shape.density = 1
Since density was not set to anything Pymunk was having a divide by zero error so it wouldn't update the body's position.
self.body.position = self.pos
To be clear about the problem:
From what I could find in the documentation, pymunk.Body.position is a property; it expects you to pass either an ordinary Python tuple or a Vec2d (not a list, although anything it's relying on internally to handle a tuple probably handles a list just fine), and calls some internal code written in another programming language. The effect is that, instead of storing your list object and then making changes to the list (which would be visible in your class, since it would be the same object), it just gets the initial values out of your list, and then doesn't use the list object.
This means that when Pymunk applies physics calculations, self.body.position changes, but self.pos does not. So the current self.pos is useless; we can't use it to check the object position.
If you don't need to do that, then there is no need to create a self.pos at all - just feed self.body.position = pos directly, and make sure to use self.body.position when drawing.
If you do, I recommend using your own property instead of trying to set self.pos. Do the above, and then add to the class:
#property
def pos(self):
return self.body.position
And if you want to change the position from your code (but you probably shouldn't! Why else are you using the physics engine in the first place?), also add:
#pos.setter
def pos(self, value):
self.body.position = value

Can I use an image on a moving object within Pygame as opposed to to a color?

def star1():
global angle
x = int(math.cos(angle) * 100) + 800
y = int(math.sin(angle) * 65) + 400
pygame.draw.circle(screen, white, (x, y), 17)
angle += 0.02
The parameters pygame gives for the .draw.circle here only allow for an RGB color to be used to color the figure. I am wondering if there is a way I can pass an image as a color, or use a different method that allows me to place an image on the object/circle. The circles move around so the image also needs to stay with the circle as it moves.
Create a transparent pygame.Surface with the desired size
Draw a white circle in the middle of the Surface
Blend the image (my_image) on this Surface using the blend mode BLEND_RGBA_MIN:
size = 100
circular_image = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.circle(circular_image, (255, 255, 255), (size//2, size//2), size//2)
image_rect = my_image.get_rect(center = circular_image.get_rect().center)
circular_image.blit(my_image, image_rect, special_flags=pygame.BLEND_RGBA_MIN)
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
def create_circular_image(size, image):
clip_image = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.circle(clip_image, (255, 255, 255), (size//2, size//2), size//2)
image_rect = my_image.get_rect(center = clip_image.get_rect().center)
clip_image.blit(my_image, image_rect, special_flags=pygame.BLEND_RGBA_MIN)
return clip_image
def create_test_image():
image = pygame.Surface((100, 100))
ts, w, h, c1, c2 = 25, 100, 100, (255, 64, 64), (32, 64, 255)
[pygame.draw.rect(image, c1 if (x+y) % 2 == 0 else c2, (x*ts, y*ts, ts, ts))
for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
return image
my_image = create_test_image()
circular_image = create_circular_image(100, my_image)
rect = circular_image.get_rect(center = window.get_rect().center)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 5
rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 5
window.fill((64, 64, 64))
window.blit(circular_image, rect)
pygame.display.flip()
pygame.quit()
exit()
See also:
how to make circular surface in PyGame
How to fill only certain circular parts of the window in PyGame?
Clipping
Write a function that draws an image on a given x, y and pass these x, y values used in the circle function.
Basicly it's just win.blit(image, (x,y)). Don't forget to subtruct radius value. So the image matchs with circle.

Need to pass tuples to subclass to draw triangle

I want to draw a triangle from a class, so I call the function
pygame.draw.polygon()
Now, the problem is that I need to pass the points in a manner that will allow me to calculate the centre of the triangle.
I was trying to pass the tuples one by one in this way
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
so that I can then access the single tuple values.
Then pass the three points like this
self.position = [self.first_point, self.second_point, self.third_point]
But for some reason, it doesn't work.
This is the error I get
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 178, in <module>
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
[(100, 100), (200, 200), (300, 300)]
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 132, in __init__
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
TypeError: points must be number pairs
By number of pairs, the Pygame documentation gives as an example
e.g. [(x1, y1), (x2, y2), (x3, y3)]
In fact, when I print the position I pass I get, as you can see from the error above
[(100, 100), (200, 200), (300, 300)]
Anyone can help with this?
Is there another manner to calculate the centre without accessing the xs and ys like that?
Full code here
import pygame
import sys
from coordinator import coordinator
# set up the display
pygame.init()
window_size = (800, 800)
game_window = pygame.display.set_mode(size=window_size)
pygame.display.set_caption('My Game')
# defines classes and related methods
class WhiteSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.white_square = pygame.Surface((self.height, self.width))
self.white_square.fill((255, 255, 255))
class BlackSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.black_square = pygame.Surface((self.height, self.width))
self.black_square.fill((0, 0, 0))
class ChessBoard:
def __init__(self):
self.ws = ws
self.bs = bs
self.white_columns = white_columns
self.black_columns = black_columns
def draw(self):
for w_columns in self.white_columns:
game_window.blit(self.ws.white_square, w_columns)
for b_columns in self.black_columns:
game_window.blit(self.bs.black_square, b_columns)
# declare letters and numbers
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
numbers = ['1', '2', '3', '4', '5', '6', '7', '8']
# create coordinates
coordinates = []
for item_letter in letters:
letter = item_letter
for item_number in numbers:
number = item_number
coordinates.append(letter + number)
# create coordinates values components
x_values = []
for number in range(0, 800, 100):
x = number
x_values.append(x)
y_values = []
for number in range(0, 800, 100):
y = number
y_values.append(y)
# create coordinate values
coordinate_values = []
for x in x_values:
for y in y_values:
coordinate_values.append((x, y))
# assign values to coordinates
squares_coordinates = dict(zip(coordinates, coordinate_values))
# Background for units
class CircleSurface:
def __init__(self):
self.circle_surface = pygame.Surface((100, 100), flags=pygame.SRCALPHA)
pygame.draw.circle(self.circle_surface, (255, 0, 0), (50, 50), 45)
# define colours
black = (0, 0, 0)
white = (255, 255, 255)
gold = (153, 153, 0)
class Unit:
def __init__(self, colour, position):
# define Unit colour
self.colour = colour
# define Unit position
self.position = position
class Knight(Unit):
def __init__(self, colour, position):
# draw circle, inline, and outline
super().__init__(colour, position)
self.center_x = position[0]
self.center_y = position[1]
self.colour = colour
self.position = position
circle_radius = 40
self.circle = pygame.draw.circle(game_window, colour, self.position, circle_radius)
self.circle_outline = pygame.draw.circle(game_window, gold, self.position, circle_radius, 5)
self.circle_inline = pygame.draw.circle(game_window, gold, self.position, (circle_radius - 10), 5)
# draw letter
pygame.font.init()
my_font_size = 50
my_font = pygame.font.SysFont('Time New Roman', my_font_size)
text_surface = my_font.render('K', 1, gold)
center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
game_window.blit(text_surface, center_text)
class Archer(Unit):
def __init__(self, colour, position):
super().__init__(colour, position)
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
self.position = [self.first_point, self.second_point, self.third_point]
print(position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
# draw letter
# pygame.font.init()
# my_font_size = 50
# my_font = pygame.font.SysFont('Time New Roman', my_font_size)
# text_surface = my_font.render('A', 1, gold)
# center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
# game_window.blit(text_surface, center_text)
# Sets and gets the coordinates for black and white squares
coordinator = coordinator()
black_columns = coordinator[2] + coordinator[3]
white_columns = coordinator[0] + coordinator[1]
# Creates needed objects
ws = WhiteSquare()
bs = BlackSquare()
cb = ChessBoard()
cs = CircleSurface()
# Event loop (outer)
while 1:
# Event loop (inner)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Draws the chessboard
cb.draw()
# Draws white pieces in their initial position
white_knight_1 = Knight(white, (150, 650))
white_knight_2 = Knight(white, (650, 650))
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
pygame.display.update()
Thank you
OK, I managed to make it work.
You guys are both right, I should know by now that I can't pass placeholders, so I figured out the solution to my problem as follow:
class Archer(Unit):
def __init__(self, colour, first_point, second_point, third_point):
self.first_point = first_point
self.second_point = second_point
self.third_point = third_point
position = [self.first_point, self.second_point, self.third_point]
super().__init__(colour, position)
print(self.position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
Basically I have to declare the three points self variables, as well as position before the super function so that I can then pass them as 'position' to the parent class initializer. This has been really useful!!
Thanks both
you could also do this: self.first_point = (int(0), int(0)) as int is not a placeholder but to declare that a variable is an integer str('0') will print '0' you could also input this
zero = 0
str(zero) #'0'
int(zero) #0
and you dont need to put rgb tupels because you can store them in a variable like this
black = (0, 0, 0)
Display = pygame.display.set_mode((800, 600))
Display.fill(black)

How to draw outline on the font(Pygame)

How can I draw an outline on the font?
I want to use black font, but the background has to be blackish so it's hard to see the font.
I assume myfont.render doesn't support drawing outline on the font.
Is there other way?
Pygame doesn't support this out of the box, but one way to do it is render the text in the outline color and blit it to the result surface shifted multiple times, then render the text in the desired color on top of it.
pgzero uses this technique; a trimmed down version of its code is shown below:
import pygame
_circle_cache = {}
def _circlepoints(r):
r = int(round(r))
if r in _circle_cache:
return _circle_cache[r]
x, y, e = r, 0, 1 - r
_circle_cache[r] = points = []
while x >= y:
points.append((x, y))
y += 1
if e < 0:
e += 2 * y - 1
else:
x -= 1
e += 2 * (y - x) - 1
points += [(y, x) for x, y in points if x > y]
points += [(-x, y) for x, y in points if x]
points += [(x, -y) for x, y in points if y]
points.sort()
return points
def render(text, font, gfcolor=pygame.Color('dodgerblue'), ocolor=(255, 255, 255), opx=2):
textsurface = font.render(text, True, gfcolor).convert_alpha()
w = textsurface.get_width() + 2 * opx
h = font.get_height()
osurf = pygame.Surface((w, h + 2 * opx)).convert_alpha()
osurf.fill((0, 0, 0, 0))
surf = osurf.copy()
osurf.blit(font.render(text, True, ocolor).convert_alpha(), (0, 0))
for dx, dy in _circlepoints(opx):
surf.blit(osurf, (dx + opx, dy + opx))
surf.blit(textsurface, (opx, opx))
return surf
def main():
pygame.init()
font = pygame.font.SysFont(None, 64)
screen = pygame.display.set_mode((350, 100))
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill((30, 30, 30))
screen.blit(render('Hello World', font), (20, 20))
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
If you are a beginer and are not looking for very complex code, and you are only using a simple font such as arial, helvetica, calibri, etc...
You can just blit the text in the outline colour 4 times in the 'corners', then blit the actual text over it:
white = pygame.Color(255,255,255)
black = pygame.Color(0,0,0)
def draw_text(x, y, string, col, size, window):
font = pygame.font.SysFont("Impact", size )
text = font.render(string, True, col)
textbox = text.get_rect()
textbox.center = (x, y)
window.blit(text, textbox)
x = 300
y = 300
# TEXT OUTLINE
# top left
draw_text(x + 2, y-2 , "Hello", black, 40, win)
# top right
draw_text(x +2, y-2 , "Hello", black, 40, win)
# btm left
draw_text(x -2, y +2 , "Hello", black, 40, win)
# btm right
draw_text(x-2, y +2 , "Hello", black, 40, win)
# TEXT FILL
draw_text(x, y, "Hello", white, 40, win)
You can use masks to add an outline to your text. Here is an example:
import pygame
def add_outline_to_image(image: pygame.Surface, thickness: int, color: tuple, color_key: tuple = (255, 0, 255)) -> pygame.Surface:
mask = pygame.mask.from_surface(image)
mask_surf = mask.to_surface(setcolor=color)
mask_surf.set_colorkey((0, 0, 0))
new_img = pygame.Surface((image.get_width() + 2, image.get_height() + 2))
new_img.fill(color_key)
new_img.set_colorkey(color_key)
for i in -thickness, thickness:
new_img.blit(mask_surf, (i + thickness, thickness))
new_img.blit(mask_surf, (thickness, i + thickness))
new_img.blit(image, (thickness, thickness))
return new_img
And here is how I use this function to create a white text with a 2px black outline:
text_surf = myfont.render("test", False, (255, 255, 255)).convert()
text_with_ouline = add_outline_to_image(text_surf, 2, (0, 0, 0))

Categories