Transparent spritesheet has black background - python

I'm working on a game using Python and Pygame. I created a sprite sheet for one of the enemies and got my code for it working. The problem is that the image appears to have a black background even though it is a transparent image. The code for it is this:
enemySheet = pygame.image.load("resources/Alien.png").convert_alpha()
transColor = (255,255,255)
cells = []
for n in range(3):
width, height=(36,32)
rect = pygame.Rect(n * width, 0, width, height)
image = pygame.Surface(rect.size).convert_alpha()
image.blit(enemySheet, (0,0), rect)
cells.append(image)
enemyImg = cells[0]
enemyImg.set_colorkey(transColor)
enemy = enemyImg.get_rect()
enemy.center = (216,216)
I have already tried a few things but nothing has worked. Any ideas are welcome.

New surfaces are filled with black by default. If you want to make it transparent you can either add a fourth number to the transColor (the alpha value) and then fill the image,
transColor = (255,255,255,0)
# In the for loop.
image = pygame.Surface(rect.size).convert_alpha()
image.fill(transColor)
or just pass the pygame.SRCALPHA flag:
image = pygame.Surface(rect.size, pygame.SRCALPHA)
A nicer solution would be to use pygame.Surface.subsurface to cut the sheet:
for n in range(3):
width, height = (36, 32)
rect = pygame.Rect(n * width, 0, width, height)
image = enemySheet.subsurface(rect)
cells.append(image)

Related

Is it possible to change sprite colours in Pygame?

I'm making a game in Python using Pygame that includes a small avatar maker before the game starts, but instead of creating a big sprite sheet with 88 different combinations of hairstyles and colours, is there a way that I can just use a generic .png image of each hairstyle and apply colour to it in-game?
The hairstyles are saved as .png images with alpha and anti-aliasing, so they are not just one shade of colour. I've got 8 different hairstyles and 11 different colours. It wouldn't be a problem to load them in as a sprite sheet and clip them in-game, but if there was a way to apply colour (or hue) in the game then not only would it be easier on the memory, but would open it up to more possibilities.
If the image is a "mask" image, with a transparent background and a white (255, 255, 255) mask, then you can "tint" the image with ease.
Load the image:
image = pygame.image.load(imageName)
Generate a uniform colored image with an alpha channel and the same size:
colorImage = pygame.Surface(image.get_size()).convert_alpha()
colorImage.fill(color)
Blend the image with maskImage, by using the filter BLEND_RGBA_MULT:
image.blit(colorImage, (0,0), special_flags = pygame.BLEND_RGBA_MULT)
A sprite class may look like this:
class MySprite(pygame.sprite.Sprite):
def __init__(self, imageName, color):
super().__init__()
self.image = pygame.image.load(imageName)
self.rect = self.image.get_rect()
colorImage = pygame.Surface(self.image.get_size()).convert_alpha()
colorImage.fill(color)
self.image.blit(colorImage, (0,0), special_flags = pygame.BLEND_RGBA_MULT)
Minimal example: repl.it/#Rabbid76/PyGame-ChangeColorOfSurfaceArea-4
import pygame
def changColor(image, color):
colouredImage = pygame.Surface(image.get_size())
colouredImage.fill(color)
finalImage = image.copy()
finalImage.blit(colouredImage, (0, 0), special_flags = pygame.BLEND_MULT)
return finalImage
pygame.init()
window = pygame.display.set_mode((300, 160))
image = pygame.image.load('CarWhiteDragon256.png').convert_alpha()
hue = 0
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
color = pygame.Color(0)
color.hsla = (hue, 100, 50, 100)
hue = hue + 1 if hue < 360 else 0
color_image = changColor(image, color)
window.fill((96, 96, 64))
window.blit(color_image, color_image.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Sprite:

How to remove black background from sprite in sprite sheet in pygame

I am trying to remove the black background from this sprite taken from a sprite sheet. As you'll see in the following is the code and suggested by this post (Transparent spritesheet has black background), I've tried to use the pygame.SRCALPHA flag but this does not seem to work. I've even tried to use the same sprite sheet after converting it with a transparent background and I get the same black border around the Mario sprite.
import pygame
pygame.init()
def imgcolorkey(image, colorkey):
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
return image
class SpriteSheet(object):
sprite_sheet = None
def __init__(self, file_name):
#self.sprite_sheet = pygame.image.load(file_name).convert()
self.sprite_sheet = pygame.image.load(file_name,pygame.SRCALPHA)
#self.sheet = load_image(filename)
def imgat(self, rect, colorkey = None):
rect = pygame.Rect(rect)
#image = pygame.Surface(rect.size).convert()
#image = pygame.Surface(rect.size, pygame.SRCALPHA ).convert()
image = pygame.Surface(rect.size, pygame.SRCALPHA)
#image.fill(transColor)
image.blit(self.sprite_sheet, (0, 0), rect)
return imgcolorkey(image, colorkey)
def imgsat(self, rects, colorkey = None):
imgs = []
for rect in rects:
imgs.append(self.imgat(rect, colorkey))
return imgs
Found the problem.
I was not sending the colorkey argument of -1 when building the image in my main class.
I changed:
mario_image = SPRITE_SHEET.imgsat(MARIO_COORD)
to:
images = SPRITE_SHEET.imgsat(MARIO_COORD,-1)

Python: Image resizing: keep proportion - add white background

I would like to create a Python script to resize images, but not changing its proportions, just by adding a white background
(So, a : 500*700 px image would transform to a 700*700 px image by adding 100 px of a white band on each side)
The three image types I use are .PNG, .JPG and .GIF. I am not even sure it is possible for Gifs, PNG and JPG would already be awesome.
In my case, they have to be squares. But if any of you manage to do it for adaptable to any proportion, it would benefit the maximum number of people that see this thread and you would be even more awesome !
I saw same threads for other languages but not python, do you guys know how you do this ?
PS : I am using Python 3
What I tried :
Combining 3 images together.
If we take our 500*700 px image :
Creating two white images of 100*700px and put one on each side of the image. Inspired by :
Combine several images horizontally with Python
But, I am kind of new on python, and I haven't succeded.
Finally did it :
def Reformat_Image(ImageFilePath):
from PIL import Image
image = Image.open(ImageFilePath, 'r')
image_size = image.size
width = image_size[0]
height = image_size[1]
if(width != height):
bigside = width if width > height else height
background = Image.new('RGBA', (bigside, bigside), (255, 255, 255, 255))
offset = (int(round(((bigside - width) / 2), 0)), int(round(((bigside - height) / 2),0)))
background.paste(image, offset)
background.save('out.png')
print("Image has been resized !")
else:
print("Image is already a square, it has not been resized !")
Thanks to #Blotosmetek for the suggestion, pasting a centered image is definitely simpler than creating images and combining them !
PS : If you don't have PIL yet, the library's name to install it with pip is "pillow", not PIL. But still, you use it as PIL in the code.
Thanks #Jay D., here a bit more general version:
from PIL import Image
def resize(image_pil, width, height):
'''
Resize PIL image keeping ratio and using white background.
'''
ratio_w = width / image_pil.width
ratio_h = height / image_pil.height
if ratio_w < ratio_h:
# It must be fixed by width
resize_width = width
resize_height = round(ratio_w * image_pil.height)
else:
# Fixed by height
resize_width = round(ratio_h * image_pil.width)
resize_height = height
image_resize = image_pil.resize((resize_width, resize_height), Image.ANTIALIAS)
background = Image.new('RGBA', (width, height), (255, 255, 255, 255))
offset = (round((width - resize_width) / 2), round((height - resize_height) / 2))
background.paste(image_resize, offset)
return background.convert('RGB')
The accepted answer is great, I am just happy not to use OpenCV.
As #Nemanja mentioned, if you want to make it work for any aspect ration. Here is the snippet to use. I just twisted the code a bit.
from PIL import Image
def Reformat_Image_With_Ratio(ImageFilePath, desired_aspect_ratio):
image = Image.open(ImageFilePath, 'r')
width = image.width
height = image.height
img_aspect_ratio = width/height
if (img_aspect_ratio != desired_aspect_ratio):
bigside = width if width > height else height
other_side = int(bigside * desired_aspect_ratio)
background = Image.new('RGBA', (other_side, bigside), (255, 0, 0, 255))
offset = (int(round(((bigside - width) / 2), 0)), int(round(((bigside - height) / 2),0)))
background.paste(image, offset)
background.save('out4.png')
print("Image has been resized !")
else:
print("Image is already a valid aspect ratio, it has not been resized !")
Reformat_Image_With_Ratio('test.png', 9/16)
The other answer didn't work for me, I rewrote it and this worked:
def resize_with_pad(im, target_width, target_height):
'''
Resize PIL image keeping ratio and using white background.
'''
target_ratio = target_height / target_width
im_ratio = im.height / im.width
if target_ratio > im_ratio:
# It must be fixed by width
resize_width = target_width
resize_height = round(resize_width * im_ratio)
else:
# Fixed by height
resize_height = target_height
resize_width = round(resize_height / im_ratio)
image_resize = im.resize((resize_width, resize_height), Image.ANTIALIAS)
background = Image.new('RGBA', (target_width, target_height), (255, 255, 255, 255))
offset = (round((target_width - resize_width) / 2), round((target_height - resize_height) / 2))
background.paste(image_resize, offset)
return background.convert('RGB')

how to create a french flag in python?

I'm trying to create a french flag without using most of python's built in functions but for some reason my loop won't work... can anyone tell me why?
from PIL import Image
def french():
flag= Image.new('RGB', (300, 200))
pixels=flag.getdata()
pixs= list(pixels)
r=0
w=100
b=200
while True:
for x in range(r,(r+100)):
pixs[x]= (255,0,0)
flag.putdata(pixs)
r=+300
for x in range(w,(w+100)):
pixs[x]= (255,255,255)
flag.putdata(pixs)
w=+300
for x in range(b,(b+100)):
pixs[x]=(0,0,255)
flag.putdata(pixs)
b=+300
return (r>60000 or w>60000 or b>60000)
flag.save('frenchflag.png')
french()
Instead of
# on first pass through the loop, exit the function and return False
return (r>60000 or w>60000 or b>60000)
I think you mean
# when we are at the end of the pixel buffer, leave the loop
if r >= 60000:
break
Pixel-poking is a horribly inefficient way to create an image; use PIL.ImageDraw instead:
from PIL import Image, ImageDraw
RED = (255, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
def french(fname, width=300, height=200):
flag = Image.new("RGB", (width, height))
p, q = width//3, 2*width//3 # bar edge coordinates
draw = ImageDraw.Draw(flag)
draw.rectangle((0, 0, p, height), RED)
draw.rectangle((p, 0, q, height), WHITE)
draw.rectangle((q, 0, width, height), BLUE)
flag.save(fname)
french("frenchflag.png")
which produces

text being cut in pygame

I seem to have problems with displaying text on the screen
The code draws text on the screen but half 'S' of 'Score' gets cut for reason.
However, if I change screen.blit(text, self.score_rect, self.score_rect) to screen.blit(text, self.score_rect), it works fine. I would like to know why is this happening and how can I fix this.
Thanks.
Here's the code:
class Score(object):
def __init__(self, bg, score=100):
self.score = score
self.score_rect = pygame.Rect((10,0), (200,50))
self.bg = bg
def update(self):
screen = pygame.display.get_surface()
font = pygame.font.Font('data/OpenSans-Light.ttf', 30)
WHITE = (255, 255, 255)
BG = (10, 10, 10)
score = "Score: " + str(self.score)
text = font.render(score, True, WHITE, BG)
text.set_colorkey(BG)
screen.blit(
self.bg,
self.score_rect,
self.score_rect)
screen.blit(text,
self.score_rect,
self.score_rect)
def main():
pygame.init()
#initialize pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption('Score Window')
#initialize background
bg = pygame.Surface((screen.get_size())).convert()
bg.fill((30, 30, 30))
screen.blit(bg, (0, 0))
#initialize scoreboard
score_board = Score(bg)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit(0)
score_board.update()
pygame.display.flip()
Well - it looks like the third parameter on the call do blit, where you repeat the core_rect` parameter is designed exactly to do that: it selects a rectangular area on the
source image (in this case your rendered text) to be pasted in the destination (in this case, the screen).
Text in Pygame is rendered with nice margins, you should not need the source-crop parameter at all - and if you thinbk ou do, you should pass it a suitable set of coordinates, relevant inside the rendered text, not therectangle with the destination coordinates on the screen.
From http://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit:
blit() draw one image onto another blit(source, dest, area=None,
special_flags = 0) -> Rect Draws a source Surface onto this Surface.
The draw can be positioned with the dest argument. Dest can either be
pair of coordinates representing the upper left corner of the source.
A Rect can also be passed as the destination and the topleft corner of
the rectangle will be used as the position for the blit. The size of
the destination rectangle does not effect the blit.
An optional area rectangle can be passed as well. This represents a
smaller portion of the source Surface to draw.
...

Categories

Resources