How to blit image in multilayer pygame without blending - python

I'm trying to create a chessgame using pygame. I got a screen with two layers blitted on it. One being the chessboard and the other one with the figures. In order to update the screen, I'm blitting the layers onto the Screen. First the chessboard, then the figure layer. However I couldn't find a way of doing this successfully without using a blend option as a special flag. This causes the figures to look different depending on which square they are (black, white background).
I tried doing it without the blending option but that results into me not being able to draw the chessboard layer.
The chessboard layer is drawn by using pygame.draw.rect().
The figures are drawn by using surface.blit().
Following code is the setup of the layers.
self.WIN = pygame.display.set_mode((self.WIDTH, self.HEIGHT))
self.CHESSBOARD_LAYER = pygame.Surface((self.WIDTH,self.HEIGHT))
self.FIGURE_LAYER = pygame.Surface((self.WIDTH,self.HEIGHT))
Following code is being used in order to draw the chessboard and the figure layer.
self.WIN.fill("#000000")
self.WIN.blit(self.CHESSBOARD_LAYER,(0,0), special_flags=pygame.BLEND_RGB_ADD)
self.WIN.blit(self.FIGURE_LAYER,(0,0), special_flags=pygame.BLEND_RGB_ADD)
pygame.display.update()
Not using the .BLEND_RGB_ADD or other blend parameters results in a black screen with the image of the figure ontop of it (not drawing the chessboard).
I've also tried different blending options but didn't find one to work for me.
Image looks like this while the pawn on the black background looks as it should be but the other ones are affected by the background.

The problem is that your layer surface have no alpha channel. You create a Surface without alpha channel (RGB). You have to use the SRCALPHA flag to create a Surface with an alpha channel (RGBA). Also see pygame.Surface:
self.CHESSBOARD_LAYER = pygame.Surface((self.WIDTH, self.HEIGHT), pygame.SRCALPHA)
self.FIGURE_LAYER = pygame.Surface((self.WIDTH, self.HEIGHT), pygame.SRCALPHA)
Now you don't need any special blending flag at all:
self.WIN.fill("#000000")
self.WIN.blit(self.CHESSBOARD_LAYER, (0, 0))
self.WIN.blit(self.FIGURE_LAYER, (0, 0))
pygame.display.update()
Note, with the flag SRCALPHA each pixel has its own alpha value (RGBA format), without the flag the surfaces hast just 1 global alpha value and can not store different alpha values for the pixels (RGB format).

Related

How to blit image without background in pygame

I want to blit only green triangle of this image on pygame surface.
My code:
import pygame
win = pygame.display.set_mode((800,800))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255,255,255))
Image = pygame.image.load('sample.jpg').convert()
Image.set_colorkey((0,0,0))
win.blit(Image,(80,80))
pygame.display.update()
pygame.quit()
When I run that code it blit image like this.
How can I do this?
If you load your original triangle bitmap into a graphics editor like The GIMP, selecting the colours in the area around your triangle giving problems shows that these colours are not exactly black, but very very dark grey, e.g. ( 0, 0, 3 ). This is probably because the image is saved as a JPEG, where there is non-pixel-perfect image storage, and the colours have become corrupt.
Try this re-draw image, a .PNG - which does have pixel-perfect compression:
sample.png
But if you're going to the trouble, you may as well just make the unwanted sections transparent with an alpha channel, then PyGame will hide the background automatically.
Unfortunately, pygame doesn't do a good job in color filtering. Instead you can try to blit a png image which has alpha transparency built in and you can use tools such as GIMP or photoshop to make the transparent image.
But if you have a lot of images and you want to do the process automatically you can use opencv to filter colors seamlessly; here is a good tutorial on this:
https://realpython.com/python-opencv-color-spaces/
After filtering the specified color, You can also use morphological operations to achieve a better result:
https://docs.opencv.org/trunk/d9/d61/tutorial_py_morphological_ops.html

Is There a Way to Make a Rect Transparent in Pygame?

Is it possible to make a rect transparent in pygame?
I need it because I'm using rects as particles for my game. :P
pygame.draw functions will not draw with alpha. The documentation says:
Most of the arguments accept a color argument that is an RGB triplet. These can also accept an RGBA quadruplet. The alpha value will be written directly into the Surface if it contains pixel alphas, but the draw function will not draw transparently.
What you can do is create a second surface and then blit it to the screen. Blitting will do alpha blending and color keys. Also, you can specify alpha at the surface level (faster and less memory) or at the pixel level (slower but more precise). You can do either:
s = pygame.Surface((1000,750)) # the size of your rect
s.set_alpha(128) # alpha level
s.fill((255,255,255)) # this fills the entire surface
windowSurface.blit(s, (0,0)) # (0,0) are the top-left coordinates
or,
s = pygame.Surface((1000,750), pygame.SRCALPHA) # per-pixel alpha
s.fill((255,255,255,128)) # notice the alpha value in the color
windowSurface.blit(s, (0,0))
Keep in mind in the first case, that anything else you draw to s will get blitted with the alpha value you specify. So if you're using this to draw overlay controls for example, you might be better off using the second alternative.
Also, consider using pygame.HWSURFACE to create the surface hardware-accelerated.
Check the Surface docs at the pygame site, especially the intro.
Draw a transparent rectangle in pygame
I have had this question as a pygame user before, and this is a method of solving your problem.

A bit confused with blitting (Pygame)

I've just started learning some pygame (quite new to programming overall), and I have some very basic questions about how it works.
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen. For example, when drawing a circle:
circle = pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
I don't need to do screen.blit(circle), but when displaying text:
text = font.render("TEXT", 1, (10, 10, 10))
textpos = text.get_rect()
textpos.centerx = screen.get_rect().centerx
screen.blit(text, textpos)
If I don't blit, the text won't appear.
To be honest, I really don't know what blitting is supposed to do, apart from "pasting" the desired surface onto the screen. I hope I have been clear enough.
The short answer
I haven't found a place yet that explains when I need to blit or not to include a certain surface on the screen.
Each operation will behave differently, and you'll need to read the documentation for the function you're working with.
The long answer
What Is Blitting?
First, you need to realize what blitting is doing. Your screen is just a collection of pixels, and blitting is doing a complete copy of one set of pixels onto another. For example, you can have a surface with an image that you loaded from the hard drive, and can display it multiple times on the screen in different positions by blitting that surface on top of the screen surface multiple times.
So, you often have code like this...
my_image = load_my_image()
screen.blit(my_image, position)
screen.blit(my_image, another_position)
In two lines of code, we copied a ton of pixels from the source surface (my_image) onto the screen by "blitting".
How do the pygame.draw.* functions blit?
Technically, the pygame.draw.* methods could have been written to do something similar. So, instead of your example...
pygame.draw.circle(screen, COLOR, POS, RADIUS, WIDTH)
...they COULD have had you do this...
circle_surface = pygame.draw.circle(COLOR, RADIUS, WIDTH)
screen.blit(circle_surface, POS)
If this were the case, you would get the same result. Internally, though, the pygame.draw.circle() method directly manipulates the surface you pass to it rather than create a new surface. This might have been chosen as the way to do things because they could have it run faster or with less memory than creating a new surface.
So which do I do?
So, to your question of "when to blit" and "when not to", basically, you need to read the documentation to see what the function actually does.
Here is the pygame.draw.circle() docs:
pygame.draw.circle():
draw a circle around a point
circle(Surface, color, pos, radius, width=0) -> Rect
Draws a circular shape on the Surface. The pos argument is the center of the circle, and radius is the size. The width argument is the thickness to draw the outer edge. If width is zero then the circle will be filled.
Note that it says that "draws a shape on the surface", so it has already done the pixel changes for you. Also, it doesn't return a surface (it returns a Rect, but that just tells you where the pixel changes were done).
Now let's look at the pygame.font.Font.render() documentation:
draw text on a new Surface
render(text, antialias, color, background=None) -> Surface
This creates a new Surface with the specified text rendered on it. Pygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface.
...
As you can see, it specifically says that the text is drawn on a NEW Surface, which is created and returned to you. This surface is NOT your screen's surface (it can't be, you didn't even tell the render() function what your screen's surface is). That's a pretty good indication that you will need to actually blit this surface to the screen.
Blit means 'BL'ock 'I'mage 'T'ranfser
When you are displaying things on the screen you will, in some way, use screen because that's where you are putting it.
When you do:
pygame.draw.circle(screen, (0, 0, 0), (100, 100), 15, 1)
you are still using screen but you are just not blitting because pygame is drawing it for you.
And when you use text, pygame renders it into an image then you have to blit it.
So basically you blit images, but you can also have pygame draw them for you. But remember when you blit an image, say over a background, you need to loop it back and fourth; so that it blits the background, then the image, then the background etc...
You dont need to know much more than that, but you can read all about it here Pygame Blit
I hope this helped. Good Luck!
Imagine that you are a painter:
You have a canvas, and a brush.
Let's say that your main screen surface will be your canvas, and all the other surfaces, are "in your head" - you know how to draw them already.
When you call blit, you paint on top of the surface, covering any pixels that were overlapped. That is why you need to repaint the whole screen black so that you won't have any smudges on the painting while moving an object.
As Mark already said, you can draw a circle with a function, or first blit it to a new surface, and blit that on the screen surface.
If you have a more complicated surface - curves, text etc. you wouldn't need to have a surface for that, so you don't have to do any expensive calculations, just drawing. The setback is that your program takes up more memory, so you have to choose between those 2.

Pygame rotate causes image to stutter

I honestly have no idea why this doesn't work. The rotate cause the image to scale up and down constantly. I looked around and haven't found a solution to my problem.
Main http://tinypaste.com/1c5025fa
Module http://tinypaste.com/f42f9c58
Also can someone explain why this program's box abruptly stops rotating?
Etc 'http://tinypaste.com/82b3b30e' (remove the quotes, I'm not allowed to post more than 2 hyperlinks)
From what I can tell, the scaling that you're seeing is a sort of artifact of how the rotation operation works. As a rectangle is rotated, the bounding box will necessarily be larger than the original rectangle. See for example, the blue rectangle in the image, below. R is the radius of the rectangle...so when it's rotated, the rectangle sweeps out the area covered by the red circle in the second image. The bounding box for the rotation is the now the gray rectangle. pygame has to fill in both the red area and the gray area. What color does pygame use to fill in the padding area?
The pygame.transform.rotate docs say...
"Unless rotating by 90 degree increments, the image will be padded larger to hold the new size. If the image has pixel alphas, the padded area will be transparent. Otherwise pygame will pick a color that matches the Surface colorkey or the topleft pixel value."
So, the solution is to explicitly set the color key or alpha value for the image (in your case, when you construct your saved_image surface.) Then, when the saved_image is rotated, the newly produced image will have the padding area filled with the appropriate color.
Give it a go and see if that works.

How do I clear a pygame alpha layer efficiently?

I'm trying to write a 2D game using python / pygame that blits several layers on top of one another every screen refresh. My basic setup is (from bottom to top):
Background: surface (non-transparent), scrolls at a different rate than rest
Midground: SRCALPHA transparent surface (static)
Player / Sprites / Enemies: sprite group
Forground: SRCALPHA transparent surface (static)
Right now, I'm blitting these four layers one on top of another every screen. The background scrolls at a different rate than the other three layers, which is why I have it separate from midground. As I have the game structured now, it runs on my fairly modest laptop at 60fps.
-BUT- I'm having trouble with the sprite group, which I'm blitting directly to the screen. Having to adjust the rect for every sprite according to my current viewport seems like an ugly way to program things, and I'd like a more elegant solution.
I'd love to blit the sprites to another transparent surface which I could manage, but therin lies my problem: I can't find a way of clearing a transparent layer that doesn't half my performance. Some of the setups I've tried:
I've tried filling the layer with a white surface with blend mode rgba_sub (surf.fill((255,255,255,255), area, BLEND_RGBA_SUB)) -- this is super, super slow
I've tried surface.copy() of a blank surface - this is faster, but still halves my fps
I've tried combining the sprites with the midground layer and using pygame.sprite.LayeredUpdates to update the sprites. This has no effect on performance, but does not work where the midground is transparent. I get trails of sprites over the background layer.
The best solution I've found so far is my current setup of drawing sprites directly to the screen. It looks great, runs fast, but is a pain to manage, as I have to make sure each sprites' rect is adjusted according to the viewport every frame. Its also making collision detection difficult.
Is there another quick way to clear a pygame transparent surface? Quick as in, can be done 60+ times a second? Alternately, is there a setup for my layers that would still accomplish the same effect?
I figured out a fast way of clearing a sprites only transparent layer by applying Peter's solution selectively to the layer:
for s in self.level.sprites:
spritelayer.fill((0), s.rect)
This seems to be working fine (erasing everything each frame) and still runs at 60fps.
The Surface.fill() will clear all R, G, B, and A values.
>>> img = pygame.image.load("hasalpha.png")
>>> print img.get_at((300, 300))
(238, 240, 239, 255)
>>> surface.fill(0)
>>> print img.get_at((300, 300))
(0, 0, 0, 0)
It sounds like this will do what you are describing. If you are trying to do something more specific with the alpha values the pygame.surfarray.pixel functions can give you directly editable values. That will be quick, but requires numpy as a dependency.

Categories