Have an outline of text in Pygame - python

So I have this function in which it puts text on the screen:
def text_speech(font : str ,size : int,text : str,color,x,y, bold : bool):
SCREEN = width, height = 900, 600
font = pygame.font.Font(font,size)
font.set_bold(bold)
text = font.render(text, True, color)
textRect = text.get_rect()
textRect.center = (x,y)
screen.blit(text,textRect)
If I do this:
screen.fill((0,0,0))
text_speed('arialnarrow.ttf', 40, 'Hello', (255,255,255), (width/2), (height/2), False)
It generates the world 'Hello' on a black screen with white text. Is it possible that if the user hovers their mouse over this, it creates a red (255,0,0) outline?

To accomplish an outline you have to blit the multiple times. Render the text in the outline color (red):
outlineSurf = font.render(text, True, (255, 0, 0))
outlineSize = outlineSurf.get_size()
Create a surface which is grater than the text surface. The width and the height have to be increased by the doubled outline thickness:
textSurf = pygame.Surface((outlineSize[0] + outline*2, outlineSize[1] + 2*outline))
textRect = textSurf.get_rect()
Blit the outline surface 8 times on the text surface, shifted by the outline thickness (horizontal, vertical and diagonal:
offsets = [(ox, oy)
for ox in range(-outline, 2*outline, outline)
for oy in range(-outline, 2*outline, outline)
if ox != 0 or ox != 0]
for ox, oy in offsets:
px, py = textRect.center
textSurf.blit(outlineSurf, outlineSurf.get_rect(center = (px+ox, py+oy)))
Render the text with the text color and convert the surface to a per pixel alpha format (convert_alpha):
innerText = font.render(text, True, color).convert_alpha()
Blit the text in the middle of textSurf:
textSurf.blit(innerText, innerText.get_rect(center = textRect.center))
Blit textSurf onto the window:
textRect.center = (x,y)
screen.blit(textSurf, textRect)
See the example:
import pygame
import pygame.font
pygame.init()
width, height = 400, 300
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
textRect = pygame.Rect(0, 0, 0, 0)
def text_speech(font : str, size : int, text : str, color, x, y, bold : bool, outline: int):
global textRect
# font = pygame.font.Font(font,size)
font = pygame.font.SysFont(None, size)
font.set_bold(True)
if outline > 0:
outlineSurf = font.render(text, True, (255, 0, 0))
outlineSize = outlineSurf.get_size()
textSurf = pygame.Surface((outlineSize[0] + outline*2, outlineSize[1] + 2*outline))
textRect = textSurf.get_rect()
offsets = [(ox, oy)
for ox in range(-outline, 2*outline, outline)
for oy in range(-outline, 2*outline, outline)
if ox != 0 or ox != 0]
for ox, oy in offsets:
px, py = textRect.center
textSurf.blit(outlineSurf, outlineSurf.get_rect(center = (px+ox, py+oy)))
innerText = font.render(text, True, color).convert_alpha()
textSurf.blit(innerText, innerText.get_rect(center = textRect.center))
else:
textSurf = font.render(text, True, color)
textRect = textSurf.get_rect()
textRect.center = (x,y)
screen.blit(textSurf, textRect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
hover = textRect.collidepoint(pygame.mouse.get_pos())
outlineSize = 3 if hover else 0
screen.fill((0,0,0))
text_speech('arialnarrow.ttf', 40, 'Hello', (255,255,255), (width/2), (height/2), False, outlineSize)
pygame.display.flip()

Assuming that by "outline" you mean a stroke around it, I've got and easy solution. Simply render the same text, centered around the same position as the text you've already written, a bit bigger and in red. Then, just check when the mouse is hovering over the rect of your initial text, and if so, blit the outline.
In order to do this, we need to extract the rect of your first text. I changed your function so that it outputs the rendered surface, and rect.
I also made a few other adjustments :
You don't need to generate the font and render the text each time, this wastes CPU cycles. I recommend setting each of your fonts as global constants, for each size/typeface
You define a screen within your function, but never use it. I changed the function so that it no longer does the job of rendering.
When you call text_speech (I assume your second usage is a typo), width and height don't refer to anything. I also defined them as global constants, which I set to be your display size.
You haven't included any display code, so I wrote the bare minimum for a running concept.
import pygame
pygame.init()
# Font constants
ARIALNARROW_40 = font = pygame.font.Font('arialnarrow.ttf', 40)
ARIALNARROW_42 = font = pygame.font.Font('arialnarrow.ttf', 42)
# Screen size
WIDTH = 900
HEIGHT = 600
def text_speech(font, text, color, x, y, bold):
font.set_bold(bold)
rendered_text = font.render(text, True, color)
# Directly center the rect upon its creation
text_rect = rendered_text.get_rect(center=(x,y))
return text_rect, rendered_text
screen = pygame.display.set_mode((WIDTH, HEIGHT))
inner_rect, inner_text = text_speech(
ARIALNARROW_40, 'Hello', (255, 255, 255),
(WIDTH / 2), (HEIGHT / 2), False
)
# For your outline
outline_rect, outline_text = text_speech(
ARIALNARROW_42, 'Hello', (255, 0, 0),
(WIDTH / 2), (HEIGHT / 2), False
)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
# Paint our screen
screen.fill((0,0,0))
if inner_rect.collidepoint(pygame.mouse.get_pos()):
# Touching our text! Render outline
screen.blit(outline_text, outline_rect)
screen.blit(inner_text, inner_rect)
# Enact our display changes
pygame.display.update()
Note This does add the potentially unwanted affected of having a "side zoom". However getting around this would mean that'd you either have to mess around with font kerning (with the pygame.freetype module) but that could get very messy very fast, or you could prerender a stroke image ahead of time (and blit it using the same logic I used) but that would require you to rerender every time you changed the text, for all your text surfaces.

Related

Padding for text using pygame SysFont

I'm trying to use pygame's SysFont to display some text on the screen but I want the background rectangle to be slightly wider than the actual text.
When I use the following code the background seems to start exactly where the first letter starts and finishes exactly where the last letter ends.
font = pygame.font.SysFont("Helvetica", 30)
img = font.render("ABC", True, "white", "blue1")
img_width = img.get_width()
img_height = img.get_height()
screen.blit(img, (200, 200, img_width, img_height))
Is there a way to have some padding to the left and right of the text in the background colour? I thought that perhaps adding something to img_width might help but it didn't.
You need to render the text on Surface larger than the text Surface:
Render the text with a transparent background
Create a Surface larger the the text Surface (see pygame.Rect.inflate)
Fill the Surface with the background color
blit the text in the center of the Surface
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
def create_text_box(font, text, text_color, box_color, margin_x, margin_y):
text_surf = font.render("ABC", True, text_color, "blue1")
box_surf = pygame.Surface(text_surf.get_rect().inflate(margin_x, margin_y).size)
box_surf.fill(box_color)
box_surf.blit(text_surf, text_surf.get_rect(center = box_surf.get_rect().center))
return box_surf
font = pygame.font.SysFont(None, 100)
text_surf = create_text_box(font, "ABC", "white", "blue1", 10, 0)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(text_surf, text_surf.get_rect(center = window.get_rect().center))
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()
See also How to separately change the opacity of a text on a button pygame?

how to center text in a rectangle in pygame [duplicate]

This question already has answers here:
How to Center Text in Pygame
(6 answers)
Closed 1 year ago.
im making a game in pygame and I can't figure out how I can center some text in a rectangle. I draw the rectangle by using pygame.draw_rect(win, color, (x, y, w, h)) and text with
text = font.render("text", 1, color).
i know I can use text_rect = text.get_Rect(), to get a rect object of the text. but I don't know how to use those to center the text. I was searching for a solution online and couldn't find it
You can get a rect from the surface generated by rendering your font. Then set the centre that rect to the centre of your rectangle. Then blit your font surface to the main surface.
Here's a small example that uses the mousewheel to enlarge and shrink the border rectangle to show that the font remains centred. Pressing a key randomises the text.
import random
import pygame
WIDTH, HEIGHT = 320, 240
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# use the first available font
font = pygame.font.SysFont(pygame.font.get_fonts()[0], 60)
pygame.display.set_caption("Centering Font Rect")
text = "Initial Text"
widget = font.render(text, True, pygame.Color("seagreen"))
border = pygame.Rect(10, 10, WIDTH - 40, HEIGHT - 40)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
# change the text
text = random.choice(["Short", "Long Looooong", ".", "+++", "ABC"])
widget = font.render(text, True, pygame.Color("purple"))
elif event.type == pygame.MOUSEWHEEL:
# enlarge or shrink the border rect
border.inflate_ip(event.y, event.y)
screen.fill(pygame.Color("turquoise"))
# draw border rect
pygame.draw.rect(screen, pygame.Color("red"), border, width=1)
# get the current font rect
font_rect = widget.get_rect()
# move rect to be centered on border rect
font_rect.center = border.center
screen.blit(widget, font_rect)
# update display
pygame.display.update()
pygame.quit()
This shows:
Then after some resizing:

How to fade in a text or an image with PyGame

Can someone help me on this ? :
I try to make a title that appear smoothly on the user screen :
def image(name,x,y,u):
screen.blit(name,(x,y))
if u = 1
pygame.display.update()
Window == 'main'
While windows = 'main':
image(background,0,0)
image(title, 640, 120)
pygame.display.update()
But texte appaers suddently and not as I would like...
You want to use transparency, and pygame uses three different types:
There are three types of transparency supported in pygame: colorkeys, surface alphas, and pixel alphas. Surface alphas can be mixed with colorkeys, but an image with per pixel alphas cannot use the other modes. Colorkey transparency makes a single color value transparent. Any pixels matching the colorkey will not be drawn. The surface alpha value is a single value that changes the transparency for the entire image. A surface alpha of 255 is opaque, and a value of 0 is completely transparent.
Per pixel alphas are different because they store a transparency value for every pixel. This allows for the most precise transparency effects, but it also the slowest. Per pixel alphas cannot be mixed with surface alpha and colorkeys.
So let's use colorkey to create a transparent Surface and blit some text to it, and then use surface alpha to create the fade in effect by calling set_alpha and the Surface:
import pygame
def main():
screen = pygame.display.set_mode((300, 100))
FONT = pygame.font.SysFont(None, 64)
text = FONT.render('Hello World', False, pygame.Color('darkorange'))
surf = pygame.Surface(text.get_rect().size)
surf.set_colorkey((1,1,1))
surf.fill((1,1,1))
surf.blit(text, (0, 0))
clock = pygame.time.Clock()
alpha = 0
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
return
alpha = (alpha + 1) % 256
surf.set_alpha(alpha)
screen.fill(pygame.Color('dodgerblue'))
screen.blit(surf, (20, 20))
clock.tick(120)
print(alpha)
pygame.display.update()
if __name__ == '__main__':
pygame.init()
main()
pygame.Surface.blit() already does what you want. It bends a surface to an other surface, depended on its alpha channel. All you have to do is to set the alpha channel by pygame.Surface.set_alpha():
image.set_alpha(min(1.0,alpha)*255)
Write a function, which "blends" a surface to the screen. In the following function BlendSurface, the parameter alpha is the opacity value of the surface in range[0, 1]. 0.0 would generate a completely transparent (invisible) surface. 1.0 would generate a completely opaque surface:
def BlendSurface(image, pos, alpha):
image.set_alpha(min(1.0,alpha)*255)
screen.blit(image, pos)
See the simple demo program:
import pygame
import pygame.font
pygame.init()
BLUE = ( 0, 0, 255)
YELLOW = (255, 255, 0)
size = (800,600)
screen = pygame.display.set_mode(size)
def BlendSurface(image, pos, alpha):
image.set_alpha(min(1.0,alpha)*255)
screen.blit(blendImage, pos)
clock = pygame.time.Clock()
font = pygame.font.SysFont('Times New Roman', 100)
text = font.render('blend text', False, YELLOW)
i = 0
done = False
while not done:
clock.tick(60)
i += 1
if i > 200:
i = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(BLUE)
BlendSurface(text, (100, 100), i/200)
pygame.display.flip()

Trouble adding text to screen in pygame

When I run this code, I get the error "ValueError: size needs to be (int width, int height)".
display_width = 800
display_height = 600
welcomeScreen = pygame.display.set_mode((display_width, display_height))
red = (255, 0, 0)
white = (255, 255, 255)
welcomeScreen.fill(white, rect = None, special_flags = 0)
welcomeMessageBackground = pygame.draw.rect(welcomeScreen, red, [200, 100, 400, 75])
welcomeFont = pygame.font.Font(None, 50)
welcomeMessage = welcomeFont.render('Welcome! Click a button to get started.', True, white)
pygame.Surface.blit(pygame.Surface(display_width, display_height), welcomeMessageBackground, area = None, special_flags = 0)
pygame.display.update()
The line causing problems is
pygame.Surface.blit(pygame.Surface(display_width, display_height), welcomeMessageBackground, area = None, special_flags = 0)
I've tried substituting the variables for the values 800 and 600, same error. What I want is for the text in welcomeMessage to appear over the rect welcomeMessageBackground. Any help is appreciated!
This line doesn't make sense.
pygame.Surface.blit(pygame.Surface(display_width, display_height), welcomeMessageBackground, area = None, special_flags = 0)
You shouldn't call pygame.Surface.blit() but the blit method of the surface onto which you want to blit the other surface, e.g.:
welcomeScreen.blit(welcomeMessage, (70, 100))
welcomeFont.render returns a surface which you can blit onto the screen or another surface.
To create a pygame.Surface instance, you have to pass a tuple for the width and height, pygame.Surface((100, 200)), but in your case you don't have to create another surface at all.
Also, if you want to add a background color behind the text, you can just pass the color to the render method of your font:
welcomeMessage = welcomeFont.render(
'Welcome! Click a button to get started.', True, white, red)
You need to use a tuple for the value:
pygame.Surface.blit(pygame.Surface((display_width, display_height)), welcomeMessageBackground, area = None, special_flags = 0)
So wrapping your display_width and display_height inside () should work. Check it out the documentation.

Pygame creating surfaces

Code:
#!/usr/bin/python
import pygame, time, sys, random, os
from pygame.locals import *
from time import gmtime, strftime
pygame.init()
w = 640
h = 400
screen = pygame.display.set_mode((w, h),RESIZABLE)
clock = pygame.time.Clock()
x = y = 100
def starting():
basicfont = pygame.font.SysFont(None, 48)
text = basicfont.render('Starting...', True, (255, 255, 255), (0, 0, 255))
textrect = text.get_rect()
textrect.centerx = screen.get_rect().centerx
textrect.centery = screen.get_rect().centery
screen.fill((0, 0, 255))
screen.blit(text, textrect)
def taskbar():
basicfont = pygame.font.SysFont(None, 24)
taskbarrect = pygame.Rect((0, int(h-40)), (int(w), int(h)))
text = basicfont.render(strftime("%Y-%m-%d", gmtime()), True, (0, 0, 0))
text2 = basicfont.render(strftime("%H:%M:%S", gmtime()), True, (0, 0, 0))
taskbarrect.blit(text, (w - 100, h - 37))
taskbarrect.blit(text2, (w - 100, h - 17))
pygame.display.update()
starting()
screen.fill((255,255,255))
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type==VIDEORESIZE:
w = event.dict['size'][0]
h = event.dict['size'][1]
screen=pygame.display.set_mode(event.dict['size'],RESIZABLE)
taskbar()
Because I want to make my code run faster, I want to create surfaces and blit rects on top of them, so that I can do pygame.display.update(taskbarrect) and speed up my code. However, I don't know how to create multiple surfaces. I have tried taskbarrect=(xcoordinate, ycoordinate, width, length) then blitting an image or text or whatever, but trying it says tuple object has no attribute blit. Trying the method in the code (suggested by #elegent) gives 'pygame.Rect' object has no attribute 'blit'.
What am I doing wrong?
Pygame uses surfaces to represent any form of image. This could be either
your main screen Surface, which is returned by the pygame.display.set_mode() function
myDisplay = pygame.display.set_mode((500, 300))
or created object of the pygame.Surface class:
#create a new Surface
myNewSurface = Surface((500, 300))
#change its background color
myNewSurface.fill((55,155,255))
#blit myNewSurface onto the main screen at the position (0, 0)
myDisplay.blit(myNewSurface, (0, 0))
#update the screen to display the changes
display.update() #or display.flip()
Pygame's display.update() has a method that allows one to update only some portions of the screen by passing one object or a list of pygame.Rect objects to it. Therefore, we could also call:
myUpdateRect= pygame.Rect((500, 300), (0, 0))
display.update(myUpdateRect)
Anyway, I recommend to using the pygame.draw module to draw simple shapes like rectangles, circles and polygons onto a surface, because all of these functions return a rectangle representing the bounding area of changed pixels.
This enables you to pass this information to the update() function, so Pygame only updates the pixels of the just drawn shapes:
myDisplay = pygame.display.set_mode((500, 300))
myRect= pygame.Rect((100, 200), (50, 100))
pygame.display.update(pygame.draw.rect(myDisplay, (55, 155, 255), myRect))
Update:
def taskbar():
basicfont = pygame.font.SysFont(None, 24)
text = basicfont.render(strftime("%Y-%m-%d", gmtime()), True, (0, 0, 0))
text2 = basicfont.render(strftime("%H:%M:%S", gmtime()), True, (0, 0, 0))
screen.fill((55, 155, 255))
screen.blit(text, (w - 100, h - 37))
screen.blit(text2, (w - 100, h - 17))
pygame.display.update()
Hope this helps :)

Categories