Merging two images in to one in pygame - python

I have a program that takes two different images - one a tile texture (32x32) that has no blank/transparent spots and the second one is an effect (cracks effect) that has transparent pixels and it is also 32x32 .
From those two images i want to make one.
I tried placing the second image on the first one but it did not work (i thing i don't let the transparency work in it)
First of all - how do i get the transparency to work on an image in pygame(any color key) and different opacity's.
and Second thing is - How do i merge them?

To "merge" two images, just blit the second one onto the first one.
Given these two images (1.png, 2.png)
here's an example:
import pygame
pygame.init()
screen = pygame.display.set_mode((200, 50))
image = pygame.image.load("1.png")
shadow = pygame.image.load("2.png")
merged = image.copy()
merged.blit(shadow, (0, 0))
while True:
screen.fill(pygame.color.Color('white'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
raise
screen.blit(image, (0, 0))
screen.blit(shadow, (50, 0))
screen.blit(image, (100, 0))
screen.blit(shadow, (100, 0))
screen.blit(merged, (150, 0))
pygame.display.flip()
Result:
If that does not work for you, your surfaces may have different pixel formats. Fix that by calling convert_alpha on each surface.
If your problem is about mixing surfaces with a color key and surfaces with per-pixel transparency, take a look at this answer.

Related

Trying to make sections of sprite change colour, but whole sprite changes instead

I have a few sprites in my game that need specific parts to be able to change colour.
My process I am trying to to have a pure white sprite image that is transparent everywhere the colour does not need to be. I am blitting a coloured square on top of that, and then that on top of the main sprite, however the main sprite then changes colour everywhere, but while respecting the main sprite transparency. The part that confuses me most is that the masked colour image does look correct when I put it on the main screen.
# Load main sprite and mask sprite
self.image = pygame.image.load("Enemy.png").convert_alpha()
self.mask = pygame.image.load("EnemyMask.png").convert_alpha()
# Create coloured image the size of the entire sprite
self.coloured_image = pygame.Surface([self.width, self.height])
self.coloured_image.fill(self.colour)
# Mask off the coloured image with the transparency of the masked image, this part works
self.masked = self.mask.copy()
self.masked.blit(self.coloured_image, (0, 0), None, pygame.BLEND_RGBA_MULT)
# Put the masked image on top of the main sprite
self.image.blit(self.masked, (0, 0), None, pygame.BLEND_MULT)
Enemy.png
EnemyMask.png (It's white so can't be seen)
Masked colour Masked Colour
Final Failed Sprite Failed Sprite
Can't post images, not enough reputation
I get no error, but only the white part of the shield is supposed to be green
self.image is the loaded image, where you want to change specific regions by a certain color and self.mask is a mask which defines the regions.
And you create an image masked, which contains the regions which are specified in mask tinted in a specific color.
So all you've to do is to .blit the tinted mask (masked) on the image without any special_flags set:
self.image.blit(self.masked, (0, 0))
See the example, where the red rectangle is changed to a blue rectangle:
repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea
Minimal example: repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea-3
Sprite:
Mask:
import pygame
def changColor(image, maskImage, newColor):
colouredImage = pygame.Surface(image.get_size())
colouredImage.fill(newColor)
masked = maskImage.copy()
masked.set_colorkey((0, 0, 0))
masked.blit(colouredImage, (0, 0), None, pygame.BLEND_RGBA_MULT)
finalImage = image.copy()
finalImage.blit(masked, (0, 0), None)
return finalImage
pygame.init()
window = pygame.display.set_mode((404, 84))
image = pygame.image.load('avatar64.png').convert_alpha()
maskImage = pygame.image.load('avatar64mask.png').convert_alpha()
colors = []
for hue in range (0, 360, 60):
colors.append(pygame.Color(0))
colors[-1].hsla = (hue, 100, 50, 100)
images = [changColor(image, maskImage, c) for c in colors]
clock = pygame.time.Clock()
nextColorTime = 0
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((255, 255, 255))
for i, image in enumerate(images):
window.blit(image, (10 + i * 64, 10))
pygame.display.flip()
pygame.quit()
exit()

Blitting a Surface onto another without combining alpha

In a pygame project I'm working on, sprites of characters and objects cast a shadow onto the terrain. Both the shadow and the terrain are normal pygame surfaces so, to show them, the shadow is blitted onto the terrain. When there's no other shadow (only one shadow and the terrain) everything works fine, but when the character walks into the area of a shadow, while casting its own shadow, both shadows combine their alpha values, obscuring the terrain even more.
What I want is to avoid this behaviour, keeping the alpha value stable. Is there any way to do it?
EDIT: This is an image, that I made in Photoshop, to show the issue
EDIT2: #sloth's answer is ok, but I neglected to comment that my project is more complicated than that. The shadows are not whole squares, but more akin to “stencils”. Like real shadows, they are silhouettes of the objects they are cast from, and therefore they need per pixel alphas which are not compatible with colorkey and whole alpha values.
Here is a YouTube video that shows the issue a bit more clearly.
An easy way to solve this is to blit your shadows on another Surface first which has an alpha value, but no per pixel alpha. Then blit that Surface to your screen instead.
Here's a simple example showing the result:
from pygame import *
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# we create two "shadow" surfaces, a.k.a. black with alpha channel set to something
# we use these to illustrate the problem
shadow = pygame.Surface((128, 128), pygame.SRCALPHA)
shadow.fill((0, 0, 0, 100))
shadow2 = shadow.copy()
# a helper surface we use later for the fixed shadows
shadow_surf = pygame.Surface((800, 600))
# we set a colorkey to easily make this surface transparent
colorkey_color = (2,3,4)
shadow_surf.set_colorkey(colorkey_color)
# the alpha value of our shadow
shadow_surf.set_alpha(100)
# just something to see the shadow effect
test_surface = pygame.Surface((800, 100))
test_surface.fill(pygame.Color('cyan'))
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
screen.fill(pygame.Color('white'))
screen.blit(test_surface, (0, 150))
# first we blit the alpha channel shadows directly to the screen
screen.blit(shadow, (100, 100))
screen.blit(shadow2, (164, 164))
# here we draw the shadows to the helper surface first
# since the helper surface has no per-pixel alpha, the shadows
# will be fully black, but the alpha value for the full Surface image
# is set to 100, so we still have transparent shadows
shadow_surf.fill(colorkey_color)
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow2, (164, 164))
screen.blit(shadow_surf, (400, 0))
pygame.display.update()
You could create a function that tests for shadow collision and adjust the blend values of the shadows accordingly.
You can combine per-pixel alpha shadows by blitting them onto a helper surface and then fill this surface with a transparent white and pass the pygame.BLEND_RGBA_MIN flag as the special_flags argument. The alpha value of the fill color should be equal or lower than the alphas of the shadows. Passing the pygame.BLEND_RGBA_MIN flag means that for each pixel the lower value of each color channel will be taken, so it will reduce the increased alpha of the overlapping shadows to the fill color alpha.
import pygame as pg
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
shadow = pg.image.load('shadow.png').convert_alpha()
# Shadows will be blitted onto this surface.
shadow_surf = pg.Surface((800, 600), pg.SRCALPHA)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill((130, 130, 130))
screen.blit(shadow, (100, 100))
screen.blit(shadow, (154, 154))
shadow_surf.fill((0, 0, 0, 0)) # Clear the shadow_surf each frame.
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow, (154, 154))
# Now adjust the alpha values of each pixel by filling the `shadow_surf` with a
# transparent white and passing the pygame.BLEND_RGBA_MIN flag. This will take
# the lower value of each channel, therefore the alpha should be lower than
# the shadow alphas.
shadow_surf.fill((255, 255, 255, 120), special_flags=pg.BLEND_RGBA_MIN)
# Finally, blit the shadow_surf onto the screen.
screen.blit(shadow_surf, (300, 0))
pg.display.update()
clock.tick(60)
Here's the shadow.png.

How does one stretch an image to the shape of a polygon in Pygame?

Alternatively, how does one fill a polygon in Pygame with an image?
I've tried searching documentation but didn't turn up much, so I'm asking here to see if I missed out anything. I can use transformation matrices if needed.
Here's an example of what I want to achieve:
You might be looking for pygame's alternative drawing functions from pygame.gfxdraw, what your looking is textured polygon
Note: You need to import it separately as gfxdraw doesn't import as default so you need to import it separately e.g.
import pygame
import pygame.gfxdraw
I think the concept you are looking for is a bitmap mask. Using a special blending mode, pygame.BLEND_RGBA_MULT, we can blend the contents of two surfaces together. Here, by blending, I mean that we can simply multiply the color values of two corresponding pixels together to get the new color value. This multiplication is done on normalized RGB colors, so white (which is (255, 255, 255) would actually be converted to (1.0, 1.0, 1.0), then multiplication between the masking surface and the masked surface's pixels would be performed, and then you would convert back to the non-normalized RGB colors.
In this case, all that means is that we need to draw the polygon to one surface, draw the image of interest to another surface, and blend the two together and render the final result to the screen. Here is the code that does just that:
import pygame
import sys
(width, height) = (800, 600)
pygame.init()
screen = pygame.display.set_mode((width, height))
image = pygame.image.load("test.png").convert_alpha()
masked_result = image.copy()
white_color = (255, 255, 255)
polygon = [(0, 0), (800, 600), (0, 600)]
mask_surface = pygame.Surface((width, height))
pygame.draw.polygon(mask_surface, white_color, polygon)
pygame.draw.aalines(mask_surface, white_color, True, polygon)#moderately helps with ugly aliasing
masked_result.blit(mask_surface, (0, 0), None, pygame.BLEND_RGBA_MULT)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.blit(masked_result, (0, 0))
pygame.display.update()
The result looks like this using a simple triangle as the mask.
Some things to note:
This answer is based off of this, which you might be interested in if you want more complicated masks than polygons (such as pretty bitmap masks).
This solution is not using the pygame.mask module!
I have not fully tested this code yet, but I suspect that this will only work for convex polygons, which may be a significant limitation. I am not sure if pygame can draw concave polygons, but I know PyOpenGL can using stencil buffer tricks or tesselation. Let me know if you are interested in that approach instead.
While I have attempted to get rid of aliasing, the results are still not completely satisfactory.

How to draw a list of sprites in Pygame

I am asking if it is possible to draw a list of sprites in Pygame, and if so how?
I am attempting to draw from a two dimensional list, that will draw the map
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])
pygame.init()
image = pygame.image.load("Textures(Final)\Grass_Tile.bmp").convert()
imagerect = image.get_rect()
tilemap = [
[image, image, image],
[image, image, image]
]
done = False
clock = pygame.time.Clock()
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(BLACK)
screen.blit(tilemap, imagerect)
clock.tick(20)
pygame.display.flip()
pygame.quit()
I know I can do something much simpler, like drawing each image. But, I am wondering if I can do this, so in the future when I have more sprites I can just add to the list, and create the map.
Thank you!
I don't believe this is quite possible as you describe it, but if these images aren't going to change often I would suggest pre-blitting all your map images to a pygame Surface which can later be used, using something like:
map_surface = pygame.Surface(( len(tilemap[0])*TILE_WIDTH, len(tilemap)*TILE_HEIGHT ))
for y,row in enumerate(tilemap):
for x,tile_surface in enumerate(row):
map_surface.blit(tile_surface,(x*TILE_WIDTH,y*TILE_HEIGHT))
and then later you can simply use:
screen.blit(map_surface)
One thing to note is that even if your map does change, you will only have to blit the changed tiles onto the map_surface surface, and not have to recreate the whole thing.

Pygame Erasing Images on Backgrounds

You blit an image onto your surface to use as your background. Then you press button X to blit an image on the same surface, how do you erase the image? I have this so far, but then I end up with a white rectangle in the middle of my background.
screen = pygame.display.set_mode(1280, 512)
screen.blit(background, (0,0))
while True:
pygame.display.flip() #flip is same as update
for event in pygame.event.get():
if (event.type == pygame.KEYDOWN):
if event.key == pygame.K_SPACE:
screen.blit(player, (x, y))
if event.key == pygame.K_BACKSPACE:
pygame.draw.rect(screen, [255,255,255], (x, y, 62,62))
There are basically two things you can do here. You can go the simple route and just draw the background again...
if event.key == pygame.K_BACKSPACE:
screen.blit(background, (0,0))
Or, if you want it to be a little more efficient, you can have the blit method only draw the part of the background that the player image is covering up. You can do that like this...
screen.blit(background, (x, y), pygame.Rect(x, y, 62, 62))
The Rect in the third argument will cause the blit to only draw the 62x62 pixel section of 'background' located at position x,y on the image.
Of course this assumes that the player image is always inside the background. If the player image only partially overlaps the background I'm not entirely sure what will happen. It'll probably throw an exception, or it'll just wrap around to the other side of the image.
Really the first option should be just fine if this is a fairly small project, but if you're concerned about performance for some reason try the second option.
Normally the blit workflow is just to blit the original background to screen.
In your code you would use screen.blit(background, (0,0))
When you start combining lots of surfaces you will probably want either a variable or a list of variables to keep track of screen state.
E.g.
Initialise Background
objectsonscreen = []
objectsonscreen.append(background)
screen.blit(background, (0,0))
Add Sprite
objectsonscreen.append(player)
screen.blit(player, (x, y))
Clear Sprite
objectsonscreen = [background]
screen.blit(background, (0,0))
See here for more information on sprites in Pygame. Note that if your background isn't an image background and is just a solid color you could also use screen.fill([0, 0, 0]) instead.
I personally use
pygame.draw.rect(screen, (RgbColorHere), (x, y, width, height))

Categories

Resources