This question already has answers here:
How to make a surface with a transparent background in pygame
(3 answers)
Closed 2 years ago.
I am trying to blit an image with transparencies on top of a Surface with the rest of the map. ( This is the second layer. ) When I blit it, it shows with the transparency as black. Is there a way to fix this. I included the code related to it.
lily_tex = spritesheet.get_sprite(1, 4).convert_alpha()
This gets the image from the spritesheet.
img = pygame.Surface((self.tilesize, self.tilesize))
img.blit(self.img, (0, 0), (x, y, self.tilesize, self.tilesize))
return img.convert()
And this is what pulls it from the sprite sheet.
Below is what blits it to a surface to be blitted to the screen buffer.
def create_map(self):
for map_data in self.map_data:
for row in range(len(map_data)):
for column in range(len(map_data[row])):
if map_data[row][column] == 0:
continue
texture = self.key.get(map_data[row][column])
self.map_img.blit(texture, (column * self.tilesize, row * self.tilesize))
Thank you
When you call convert() the pixel format of the image is changed. If the image format has per alpha pixel, this information will be lost.
Use convert_alpha() instead of convert():
return img.convert()
return img.convert_alpha()
If the image has no per pixel alpha format you must set the transparent color key with set_colorkey():
img = pygame.Surface((self.tilesize, self.tilesize))
img.blit(self.img, (0, 0), (x, y, self.tilesize, self.tilesize))
img.set_colorkey((0, 0, 0))
Alternatively you can create a surface with per pixel alpha format with the SRCALPHA flag:
img = pygame.Surface((self.tilesize, self.tilesize), pygame.SRCALPHA)
img.blit(self.img, (0, 0), (x, y, self.tilesize, self.tilesize))
Related
I am using Pygame and have an image. I can clip a rectangle from it:
image = pygame.transform.scale(pygame.image.load('example.png'), (32, 32))
handle_surface = image.copy()
handle_surface.set_clip(pygame.Rect(0, 0, 32, 16))
clipped_image = surface.subsurface(handle_surface.get_clip())
I have tried to use subsurface by passing a Surface:
handle_surface = image.copy()
hole = pygame.Surface((32, 32))
pygame.draw.circle(hole, (255, 255, 255), (0, 0), 32)
handle_surface.set_clip(hole)
image = surface.subsurface(handle_surface.get_clip())
surf = image.copy()
But I get the error:
ValueError: invalid rectstyle object
This error is because despite its name, subsurface expects a Rect, not a Surface. Is there a way to clip another shape from this image and have collidepoint work correctly?
You cannot use pygame.Surface.subsurface because a Surface is always rectangular and cannot have a circular shape. pygame.Rect.collidepoint detects if a point is inside a rectangular area and therefore cannot help you either.
Collision detection between a circle and a point can be calculated using the distance between the pointer and the center of the circle. Calculate the square of the Euclidean distance (dx*dx + dy*dy) from the point to the center of the circle. Check that the square of the distance is less than the square of the radius. In the following code the coordinates of the point are (px, py) and the circle is defined by its center (cx, cy) and its radius (radius).
dx = px - cx
dy = py - cy
if dx*dx + dy*dy <= radius*radius:
print('hit')
An alternative solution could be PyGame collision with masks. Also pygame.sprite.collide_circle could help, but then you would have to create a pygame.sprite.Sprite object for the point with radius 1, which seems to overcomplicate the problem.
If you want to clip a circular area from a pygame.Surface, see:
how to make circular surface in PyGame
How do I focus light or how do I only draw certain circular parts of the window in pygame?
How do I display a large black rectangle with a moveable transparent circle in pygame?
Can I use an image on a moving object within Pygame as opposed to to a color?
Short instruction:
Create a rectangular partial area from the image (only if the circle does not fill the whole image). However, the image must have an alpha channel. If it does not have one, this can be achieved with convert_alpha.
Create a transparent (pygame.SRCALPHA) mask with the size of the image
Draw a white opaque circle on the mask (I use pygame.draw.ellipse here, because it is easier to set the dimension)
Blend the circular mask with the image using the blend mode pygame.BLEND_RGBA_MIN. (see pygame.Surface.blit)
sub_image = image.subsurface(pygame.Rect(x, y, w, h)).convert_alpha()
mask_image = pygame.Surface(sub_image.get_size(), pygame.SRCALPHA)
pygame.draw.ellipse(mask_image, (255, 255, 255, 255), sub_image.get_rect())
sub_image.blit(mask_image, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
I'm having troubles to correctly load and blit PNG image with Pygame.
Code is actually working, but it display some strange and black "things" around my sprite :
black things around my sprite
To load tileset, I do:
def TilesRessourceFile(self, filename=None, tileSize=None, tiles=None):
logging.info("Loading ressources from %s", filename)
self._tileSize = tileSize
res = pygame.image.load(filename)
res.convert_alpha()
for tile in tiles:
name, (x, y), alpha = tile.values()
surface = pygame.Surface((tileSize, tileSize))
surface.blit(res, (0, 0), area=(x * tileSize, y * tileSize, (x + 1) * tileSize, (y + 1) * tileSize))
surface.convert_alpha() # set_colorkey(alpha)
self._tiles.update({name: surface})
Then I blit the sprite like this
def _implGameObjectRender(self, screen):
# logging.info("Render map %s", self._mapping)
for i in range(len(self._mapping)):
for j in range(len(self._mapping[i])):
screen.blit(self._mapping[i][j], (j * 128, i * 128))
It's probably not much, but I don't find the solution by myself.
I already tried to check :
how to load PNG with pygame
transparency with pygame (convert and convert_alpha)
I'm using this tileset : https://www.gamedevmarket.net/asset/2d-hand-painted-town-tileset-6626/
The tileset provide a json file to load with Tiled. Also tried this, and it perfectly works so I guess the problem is on my side
Thanks for helping me !
Python 3.9.1
Pygame 2.0.1 (SDL 2.0.14)
convert_alpha() does not convert the format of the surface itself, but creates a new surface with a format that provides a per pixel alpha format.
either
surface = pygame.Surface((tileSize, tileSize))
surface = surface.convert_alpha()
or
surface = pygame.Surface((tileSize, tileSize)).convert_alpha()
There are 3 ways to create a transparent Surface:
Set a transparent color key with set_colorkey()
The color key specifies the color that is treated as transparent. For example, if you have an image with a black background that should be transparent, set a black color key:
surface.set_colorkey((0, 0, 0))
You can enable additional functions when creating a new surface. Set the SRCALPHA flag to create a surface with an image format that includes a per-pixel alpha. The initial value of the pixels is (0, 0, 0, 0):
surface = pygame.Surface((tileSize, tileSize), pygame.SRCALPHA)
Use convert_alpha() to create a copy of the Surface with an image format that provides alpha per pixel.
However, if you create a new surface and use convert_alpha(), the alpha channels are initially set to maximum. The initial value of the pixels is (0, 0, 0, 255). You need to fill the entire surface with a transparent color before you can draw anything on it:
surface = pygame.Surface((tileSize, tileSize))
surface = surface.convert_alpha()
surface.fill((0, 0, 0, 0))
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()
I was writing a simple program that gradually fills (using floodfill) an image (black background with random white rectangles) to become totally white by clicking different areas of the image. Completed without a hitch.
So I thought of making it more interesting by toggling between filling with white color and black color. As in, if the pixel I click on is part of a white region, fill it to become black. Otherwise if it is part of a black region, fill it to become white.
However, after I change some boxes to become white, it refuses to change to black after subsequent clicking (unable to toggle the color back). Furthermore, because my rectangles are drawn using 3 or 4 pixels thick lines, after changing all the lines to black, it seems to still 'remember' that those lines exists, such that when I click on certain dark regions, occasionally the region (bounded by those invisible 'previous' lines) would become white color.
I have tried printing the pixel color to confirm that the color picked up is indeed white or black, but yet the floodfill is not filling it with the correct alternate color (written by my if/else loops)
import numpy as np
import cv2 as cv
import random
width = 800
height = 500
img = np.zeros((height, width), np.uint8)
mask = np.zeros((height+2, width+2), np.uint8)
def click_event(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDOWN:
font = cv.FONT_HERSHEY_PLAIN
strxy = "X: {0} Y: {1}".format(x,y)
print(strxy)
fillmeup(x, y)
cv.imshow("test", img)
def fillmeup(x, y):
print(img[y,x])
if img[y,x] == 0:
cv.floodFill(img, mask, (x, y), 255)
elif img[y,x] == 255:
cv.floodFill(img, mask, (x, y), 0)
def drawboxes(qty):
global img
for _ in range(qty):
w = int(random.random()*width)
h = int(random.random()*height)
x = random.randrange(0, width-w)
y = random.randrange(0, height-h)
img = cv.rectangle(img, (x, y), (x+w, y+h), 255, 2)
drawboxes(7)
cv.imshow("test", img)
cv.setMouseCallback("test", click_event)
cv.waitKey(0)
cv.destroyAllWindows()
Well, I would expect that every subsequent click on a black region would produce white, and vice versa, but it is not happening.
And when it does switch back to white, it seems to be bounded by invisible lines that have been turned black already.
Below is an attached sample outcome.
01_start
02_selecting 2 boxes
03_selecting one of the thin white lines changes them to black: correct outcome
04_selecting some random random black space, but yet bounded white rectangles appear. the boundaries were the original white lines. weird outcome
floodFill() updates not only the image but also the mask.
On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the a value specified in flags as described below. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap.
def fillmeup(x, y):
print(img[y,x])
mask = np.zeros((height+2, width+2), np.uint8)
if img[y,x] == 0:
cv.floodFill(img, mask, (x, y), 255)
else:
cv.floodFill(img, mask, (x, y), 0)
This works for me as you describe. If you do not need mask at all, you possibly can write
cv.floodFill(img, None, (x, y), ...)
This works for me too, but I didn't find any evidence that None mask argument is legal for floodFill(). Please notify my to update the answer if you find if it is legal or not in any authoritative source.
I'm looking for method that allow me to draw single pixel on display screen. For example when I click mouse, I want the position of clicked pixel to change color. I know how to read mouse pos, but I could not find simple pixel draw ( there is screen.fill method but it's not working as I want).
You can do this with surface.set_at():
surface.set_at((x, y), color)
You can also use pygame.gfxdraw.pixel():
from pygame import gfxdraw
gfxdraw.pixel(surface, x, y, color)
Do note, however, the warning:
EXPERIMENTAL!: meaning this api may change, or dissapear in later
pygame releases. If you use this, your code will break with the next
pygame release.
You could use surface.fill() to do the job too:
def pixel(surface, color, pos):
surface.fill(color, (pos, (1, 1)))
You can also simply draw a line with the start and end points as the same:
def pixel(surface, color, pos):
pygame.draw.line(surface, color, pos, pos)
The usual method of drawing a point on a Surface or the display is to use [`pygame.Surface.set_at']:
window_surface.set_at((x, y), my_color)
However, this function is very slow and leads to a massive lack of performance if more than 1 point is to be drawn.
Minimal example where each pixel is set separately: repl.it/#Rabbid76/PyGame-DrawPixel-1
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
for y in range(rect.height):
window.set_at((rect.left + x, rect.top + y), color)
pygame.display.flip()
pygame.quit()
exit()
Another option is to use a "pygame.PixelArray" object. This object enables direct pixel access to Surface objects. A PixelArray pixel item can be assigned directly. The pixel can be accessed by subscription. The PixelArray locks the Surface, You have to close() it when you have changed the pixel:
pixel_array = pygame.PixelArray(window_surface)
pixel_array[x, y] = my_color
pixel_array[start_x:end_x, start_y:end_y] = my_color
pixel_array.close()
Minimal example that set one line of pixels at once: repl.it/#Rabbid76/PyGame-DrawPixel-2
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = pygame.Rect(window.get_rect().center, (0, 0)).inflate(*([min(window.get_size())//2]*2))
pixel_array = pygame.PixelArray(window)
for x in range(rect.width):
u = x / (rect.width - 1)
color = (round(u*255), 0, round((1-u)*255))
pixel_array[rect.left + x, rect.top:rect.bottom] = color
pixel_array.close()
pygame.display.flip()
pygame.quit()
exit()
For those who are interested in a more modern answer to the question you can use pygame.draw.circle() to draw a single pixel at a given position (or center).
pygame.draw.circle(surface, color, center, 0)
The documentation specifically says:
radius (int or float) -- radius of the circle, measured from the center parameter, a radius of 0 will only draw the center pixel
One way of doing that is to draw a line staring and ending at the same point.
pygame.draw.line(surface, (255,255,255), (x,y), (x,y))
draw a single coloured pixel
def drawPoint(x,y,color):
s = pygame.Surface((1,1)) # the object surface 1 x 1 pixel (a point!)
s.fill(color) # color as (r,g,b); e.g. (100,20,30)
# now get an object 'rectangle' from the object surface and place it at position x,y
r,r.x,r.y = s.get_rect(),x,y
screen.blit(s,r) # link the object rectangle to the object surface
of course you have to call: pygame.display.update() once you have
drawn all the points you need, don't call update at every single point.
# with this function, you can draw points and change the yer size
def point(surface, color, x, y, size):
'''the surface need the information of the pygame window'''
for i in range(0, size):
pygame.draw.line(surface, color, (x, y-1), (x, y+2), abs(size))