Trouble adding text to screen in pygame - python

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.

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?

Pygame drawing antialiased shape and then blitting to main window

I want to draw antialiased shapes. I know you can use the pygame's gfxdraw module to accomplish this. However, it does seem to work only when drawing directly on the main window which is not suitable for me because I intend to use masks for collision checking.
Therefore a different surface is needed to create a mask that represents the circle.
How can you achieve this in pygame?
Minimal working example:
import pygame as pg
from pygame import gfxdraw
WIDHT, HEIGHT = 1200, 800
WIN = pg.display.set_mode((WIDHT, HEIGHT))
RADIUS = 80
WHITE = (255, 255, 255)
GREEN = (0, 200, 0)
RED = (200, 0, 0)
TRANS = (1, 1, 1)
class Circle(pg.sprite.Sprite):
def __init__(self,
radius: int,
pos: tuple[int, int],
color: tuple[int, int, int]):
super().__init__()
self.radius = radius
self.color = color
self.image = pg.surface.Surface((radius*2, radius*2))
self.image.fill(TRANS)
self.image.set_colorkey(TRANS)
self.rect = self.image.get_rect(center=(pos[0], pos[1]))
pg.gfxdraw.aacircle(self.image, self.rect.width//2, self.rect.height//2, radius, color)
pg.gfxdraw.filled_circle(self.image, self.rect.width//2, self.rect.height//2, radius, color)
self.mask = pg.mask.from_surface(self.image)
def draw(self):
WIN.blit(self.image, self.rect)
def main():
circle_1 = Circle(RADIUS, (500, 500), GREEN)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
WIN.fill(WHITE)
circle_1.draw()
pg.gfxdraw.filled_circle(WIN, 700, 500, RADIUS, RED)
pg.gfxdraw.aacircle(WIN, 700, 500, RADIUS, RED)
pg.display.update()
if __name__ == "__main__":
main()
You need to create a transparent pygame.Surface with the per pixel alpha format. Use the SRCALPHA flag:
self.image = pg.surface.Surface((radius*2, radius*2))
self.image = pg.surface.Surface((radius*2, radius*2), pg.SRCALPHA)
However, for the highest quality, I suggest using OpenCV/cv2 (e.g. How to make a circular countdown timer in Pygame?)

How do you write a function in pygame that draws one or more objects?

I am a relative newbie in both python and pygame. I am trying to learn with a visual calculator project. In it, I draw a rectangle which works fine:
pygame.draw.rect(screen, COLOR3, Rect = pygame.Rect(65, 450, 400, 100))
but if I do the following:
def draw_rect():
pygame.draw.rect(screen, COLOR3, Rect = pygame.Rect(65, 450, 400, 100))
and I try to invoke it with draw_rect, I get nothing.
I've tried adding some parameters in the () of the function but i couldn't get that to work.
To draw one or more, you just update positions, then loop through all your objects in a draw routine. Hard to say where yer having trouble without seeing more of what you've written. It's possible rectangles are displaying, but they're outside of the screen area, so you don't see them. Best thing to do is look at examples 'til you get a better idea of the desired syntax.
#! /usr/bin/env python3
import pygame
from random import uniform
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pygame .init()
origin = 0, 0
size = width, height = 400, 300
screen = pygame .display .set_mode( size )
pygame .display .set_caption( 'Jitter' )
white = 255, 255, 255
red = 255, 0, 0
green = 0, 255, 0
blue = 0, 0, 255
black = 0, 0, 0
background = pygame .Surface( screen .get_size() )
background = background .convert()
background .fill( white )
red_pos, red_size = ( 50, 50 ), ( 50, 50 ) ## left, top, width, height
green_pos, green_size = ( 190, 130 ), ( 20, 20 )
blue_pos, blue_size = ( 300, 50 ), ( 50, 50 )
black_pos, black_size = ( 50, 220 ), ( 300, 20 )
red_rect = pygame .Rect( red_pos, red_size )
green_rect = pygame .Rect( green_pos, green_size )
blue_rect = pygame .Rect( blue_pos, blue_size )
black_rect = pygame .Rect( black_pos, black_size )
def update_positions(): ## move "in-place", as opposed to creating a new object
red_rect .move_ip( uniform(-2, 2), 0 )
green_rect .move_ip( 0, uniform(-2, 2) )
blue_rect .move_ip( uniform(-2, 2), 0 )
black_rect .move_ip( uniform(-2, 2), 0 )
def draw_stuff():
pygame .draw .rect( screen, red, red_rect )
pygame .draw .rect( screen, green, green_rect )
pygame .draw .rect( screen, blue, blue_rect )
pygame .draw .rect( screen, black, black_rect )
run = True
while run:
for event in pygame .event .get():
if event .type == pygame .QUIT:
pygame .quit()
run = False
screen .blit( background, origin )
update_positions()
draw_stuff()
pygame .display .flip()
## eof ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I know I have had this problem (or similar one) billions of times before and it is SO annoying! My guess is that you have forgotten pygame.display.update() at the end of your main loop or draw_rect().
I hope this solves your problem :)
Rect = is not necessary, Remove it and try:
This is how you have to draw a rectangle:
pygame.draw.rect(screen, COLOR3, pygame.Rect(65, 450, 400, 100))
Use this code in the function like this(don't forget to call it):
def draw_rect():
pygame.draw.rect(screen, COLOR3, pygame.Rect(65, 450, 400, 100))
draw_rect()
and call the method inside the loop like so:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
run = False
draw_rect()
pygame.display.update()
Use a list. Create a list of colors and rectangles:
rects = [
(COLOR1, pygame.Rect(65, 150, 400, 100)),
(COLOR2, pygame.Rect(65, 300, 400, 100)),
(COLOR3, pygame.Rect(65, 450, 400, 100))]
Draw the objects in a loop:
def draw_rects(rects):
for color, rect in rects:
pygame.draw.rect(screen, color, rect)
draw_rects(rects)

Have an outline of text in Pygame

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.

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