Fill pygame font with custom pattern - python

I'm currently working on a (first) project in Python/Pygame and I'm trying to display text with a pattern overlay. The pattern consists vertical lines (1 pixel width), 2 alternating colors .
I'm creating this pattern using pygame.draw.line(), and I can create rectangles with a custom function I created. But now I want this pattern on my font.
I'm new to Python and am wondering: is there a way to create some sort of mask so that the pattern will appear on the font characters only, not outside of them? I've searched the web for some time but cannot find anything.
Many thanks in advance!

You could make use of the different blend modes to get the effect you desire.
Here's a simple example (where t.bmp is the image with your pattern, but of course you can just use any other surface):
import pygame
pygame.init()
screen = pygame.display.set_mode((490, 160))
font = pygame.font.SysFont('Arial', 150)
pattern = pygame.image.load('t.bmp').convert()
text = font.render('FooBar', True, (255, 255, 255), (0, 0, 0))
pattern.blit(text, (0, 0), special_flags = pygame.BLEND_MULT)
screen.blit(pattern, (0, 0))
pygame.display.flip();
while True:
if pygame.event.get(pygame.QUIT):
break
Result:

Related

How to get the Difference blending mode?

I'm trying to make a night effect for my game in Pygame. So I'm gonna blit a black image on the screen then, change its blending mode to Difference just like in Photoshop in order to make the screen look darker. However, I still don't know how to do that as I haven't used the blending modes in Pygame yet. Any help ?
The blending mode can be changed by setting the optional special_flags argument of pygame.Surface.blit:
blit(source, dest, area=None, special_flags=0) -> Rect
[...]
New in pygame 1.8: Optional special_flags: BLEND_ADD, BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX.
New in pygame 1.8.1: Optional special_flags: BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX, BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX.
New in pygame 1.9.2: Optional special_flags: BLEND_PREMULTIPLIED
New in pygame 2.0.0: Optional special_flags: BLEND_ALPHA_SDL2 [...]
e.g.:
screen.blit(image, (x, y), special_flags = pygame.BLEND_RGBA_SUB)
Unfortunately, Pygame doesn't have a blending mode that gives the absolute difference of 2 images. However it can be achieved with
MAX(SUB(image1, imgage2), SUB(image2, image1))
e.g.:
image1 = pygame.image.load('image2.png')
image2 = pygame.image.load('image1.png')
temp_image = image1.copy()
temp_image.blit(image2, (0, 0), special_flags = pygame.BLEND_RGBA_SUB)
final_image = image2.copy()
final_image.blit(image1, (0, 0), special_flags = pygame.BLEND_RGBA_SUB)
final_image.blit(temp_image, (0, 0), special_flags = pygame.BLEND_RGBA_MAX)

How to create a grid using pygame

I have created a surface but i am not sure how to make a grid because i have tried using the rectangle function but do not fully understand the parameter.
import pygame
pygame.init()
Background = pygame.display.set_mode((900,900))
pygame.draw.rect(Background,green,_____)
I don't understand how i am suppose to use this to create grid because even if i can get the code to work, I don't know how to position the item and there must be a more efficient way to create a grid rather then manually creating and positioning each box.
There is a better way - you should use loops to create grids. The third argument for the pygame.draw.rect is a rectangle, meaning four values that are packed together (say in a tuple): start x, start y, width and height - but lines are more useful for grids usually. This will draw a simple grid, with spaces of 50 pixels, and then keep the display active with a loop:
import pygame
pygame.init()
Background = pygame.display.set_mode((900 ,900))
for i in range(0, 900, 50):
pygame.draw.line(Background, (255, 255, 255), (0, i), (900, i))
pygame.draw.line(Background, (255, 255, 255), (i, 0), (i, 900))
pygame.display.update()
while pygame.event.wait().type != pygame.QUIT:
pass
Really the only slightly complex part here is the lines. The first to arguments are the same as they are for rectangles. The last two arguments are a starting point for the line and an ending point, and optionally you could add width, which I didn't - in which case the get the default width of 1 pixel.

Is there a way to copy a certain section of a surface using Surface.copy()?

I am new to dirty rect animation and I am currently trying to store a snapshot of the main display surface window, however I would only like to store the area where my item is going to be blit so the next frame I can call this stored snapshot instead of re blitting the whole background.
I looked at the documentation for Surface.copy() but it doesn't take arguments and I couldn't find anything similar other than pygame.pixelcopy() which from what I understand is not what I am looking for. If Surface.copy() isn't what I am looking for, please let me know of alternatives.
import pygame, time
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen.fill((128, 128, 128))
pygame.display.update()
#immagine a complex pattern being blit to the screen here
pygame.draw.rect(screen, (128, 0, 0), (0, 0, 50, 50))
pygame.draw.rect(screen, (0, 128, 0), (50, 0, 50, 50))
pygame.draw.rect(screen, (0, 0, 128), (200, 0, 50, 50))
#my complex background area that i want to save ()
area_to_save = pygame.Rect(0, 0, 100, 50)
rest_of_background = pygame.Rect(200, 0, 50, 50)
#updating for demo purposes
dirty_rects = [area_to_save, rest_of_background]
for rect in dirty_rects:
pygame.display.update(rect)
temp_screen = screen.copy()
time.sleep(3)
#after some events happen and I draw the item thats being animated onto the background
item_to_animate = pygame.Rect(35, 10, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
pygame.display.update(item_to_animate)
time.sleep(3)
item_to_animate = pygame.Rect(50, 60, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
#now that the item has moved, draw back old frame, which draws over the whole surface
screen.blit(temp_screen, (0, 0))
pygame.display.update()
#I understand swapping the drawing of the new item location to after temp_surface blit
#will provide me the desired outcome in this scenario but this is a compressed version of my problem
#so for simplicity sake, is there a way of not saving the whole surface, only those rects defined?
I expect the output of this code to be displaying my background for 3 seconds, then the black square overlaying the patter and then after another 3 seconds, the black square appearing below my pattern.
P.S.:I am new to this site, let me know if I did something wrong please!
Edit: For anyone wondering if this solution (of saving the background before blitting an item over it and then redrawing the saved background before the new item location is blit over) is more efficient than redrawing the whole background and then blitting the item, using a simple square animation over a chequered pattern with redrawing the whole background each time reduced my overall fps by around 50% from 1000 (before redrawing the background) to 500 average. While using dirty rects and this method above I get around 900 fps.
What you want to do can be achieved by pygame.Surface.blit().
Create a surface with the decided size and blit an area of the screen to this surface. Note, the 3rd argument to bilt is an optional parameter which selects a rectangular area of the source surface:
# create a surface with size 'area_to_save.size'
temp_screen = pygame.Surface(area_to_save.size)
# blit the rectangular area 'area_to_save' from 'screen' to 'temp_screen' at (0, 0)
temp_screen.blit(screen, (0, 0), area_to_save)
You could use subsurface to specify the area you want to copy, and then call copy on the returned Surface.
But note that it may happend that this will not increase the performance of your game at all, since copying a lot of Surfaces around does not come free. Just try and check yourself if it actually better then drawing a background surface every frame.
Also note that you should never use time.sleep in your game. While sleep is blocking your game's process, your game can't process events, so you e.g. can't quit the game in this time. Also, it may happen that your game's window will just not be redrawn if you don't process events by calling pygame.event.get; and once the event queue fills up because you never call pygame.event.get, your game will freeze.

How to make a circle semi-transparent in pygame?

I'm somewhat new to pygame and trying to figure out how to make a circle semi-transparent. The trick however is that the background for the circle also has to be transparent. Here is the code I'm talking about:
size = 10
surface = pygame.Surface(size, size), pygame.SRCALPHA, 32)
pygame.draw.circle(
surface,
pygame.Color("black"),
(int(size/2), int(size/2)),
int(size/2), 2)
I tried using surface.set_alpha(127) but that didn't work. I'm assuming because the surface is already transparent.
Any help is appreciated.
A couple things. First, your surface definition should crash, as it missing a parenthesis. It should be:
surface = pygame.Surface((size, size), pygame.SRCALPHA, 32)
I assume that somewhere later in your code, you have something to the effect of:
mainWindow.blit(surface, (x, y))
pygame.display.update() #or flip
Here is your real problem:
>>> import pygame
>>> print pygame.Color("black")
(0, 0, 0, 255)
Notice that 255 at the end. That means that pygame.Color("black") returns a fully opaque color. Whereas (0, 0, 0, 0) would be fully transparent. If you want to set the transparency, define the color directly. That would make your draw function look like:
pygame.draw.circle(
surface,
(0, 0, 0, transparency),
(int(size/2), int(size/2)),
int(size/2), 2)

PyGame: translucent sprites with per pixel alpha

Is it possible to display PyGame surfaces with controllable alpha? I would like to take a surface with its own per pixel alpha and display it with variable level of translucency without affecting the surface data and keep the transparency intact i.e. the objects on the surface would keep their shapes but their "contents" becoming more or less translucent.
In other words I want to combine per-pixel alpha from the source image with per-surface alpha calculated at the runtime.
The Pygame documentation says that you can't combine surface alpha with per-pixel alpha, but if you're using Pygame 1.8.1 or newer, you can work around this by using the special_flags parameter to .blit().
Here's how I did it:
# Load an image with per-pixel alpha
s = pygame.image.load('spam.png')
s = s.convert_alpha()
# Simulate s.set_alpha(alpha)
alpha_img = pygame.Surface(s.get_rect().size, pygame.SRCALPHA)
alpha_img.fill((255, 255, 255, alpha))
s.blit(alpha_img, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
The way this works is to create a second surface the same size as the first, and blit it on to the first using RGBA multiplication. By having the R, G and B components of the second image equal to 255, the multiplication won't affect the colour of the image, but it will scale the alpha channel by the given alpha factor.
Note that the method above differs from calling set_alpha() in that set_alpha() can be reversed by calling set_alpha(255). If you use the method above it will result in the pixel alphas of every pixel being changed, so the process cannot be straightforwardly reversed.
Yes you can. The documentation of the Surface class explains how to do it. It boils down to two cases only:
Either you set a flag during the creation of the Surface object:
s = pygame.Surface((16, 16), flags=pygame.SRCALPHA)
Or you give an alpha channel to a surface that doesn't have one yet:
s = pygame.image.load('spam.png')
s.convert_alpha()
The documentation of the pygame.image module says that applying convert_alpha is necessary for PNGs with transparency.
If you want you can modify the alpha value of each pixel with the modules draw and surfarray. When specifying a color, use then a tuple of four integers: (red, green, blue, alpha). The alpha parameter ranges from 0 (totally transparent) to 255 (totally opaque).
import pygame
pygame.init()
screen = pygame.display.set_mode((320, 200))
screen.fill((200, 100, 200))
s = pygame.Surface((64, 64), flags=pygame.SRCALPHA)
for i in range(64):
pygame.draw.line(s, (0, 0, 0, i*4), (0, i), (64, i))
screen.blit(s, (50, 30))
pygame.display.flip()
After checking both PyGame and SDL documentations I came to conclusion that what I asked wasn't doable in PyGame using standard functions.
SDL docs state that per-pixel alpha and per-surface alpha cannot be combined with the former always taking the precedence. The only way to achieve the effect I want is by writing a code which updates per-pixel alpha values of the source surface before the blit.
If your input image has per-pixel alpha enabled, but is only using a single bit for transparency (e.g., .png sprites with a transparent background or text), you can convert the transparent background to a colorkey-alpha background pretty easily, and then use set_alpha() to blend the whole texture onto whatever.
This is a quick and dirty helper function I used for fading text effects and other things:
def convert_to_colorkey_alpha(surf, colorkey=pygame.color.Color("magenta")):
newsurf = surface.Surface(surf.get_size())
newsurf.fill(colorkey)
newsurf.blit(surf, (0, 0))
newsurf.set_colorkey(colorkey)
return newsurf
A couple of other tricks potentially solve this problem might be:
Doing per-pixel editing to modify the alpha channel before blitting
Blitting a white or black-filled, semi-transparent surface with special_flags=BLEND_RGBA_MULT or BLEND_RGBA_SUB onto your source surface.

Categories