This question already has answers here:
How can I load an animated GIF and get all of the individual frames in Pygame?
(3 answers)
Closed 2 years ago.
So I have this idea for a game where you dodge projectiles while controlling a helicopter. I am wondering if you can make a sprite appear as a gif, or something along the lines of two images switching every fraction of a second. I know how to make a sprite appear as one image:
self.surf = pygame.image.load("example.png").convert()
But I was wondering if this had an effect:
self.surf = pygame.image.load("example.gif").convert()
Unfortunately, it only displayed the first image in the gif.
Here is the gif:
Edit: Ok so I looked at the answers and tried to implement them in my code, but then it was all too confusing and I had tried to do something a bit more simple. This is what I came up with:
if play == 1:
self.surf = pygame.image.load("Image2.png").convert()
pygame.display.update()
play = 2
time.sleep(.2)
if play == 2:
self.surf = pygame.image.load("Image1.png").convert()
pygame.display.update()
play = 1
time.sleep(.2)
But, all that did was display the player sprite as image 1. Is there anything I can add to make this work?
PyGame respectively the pygame.image module can only handle non-animated GIFs.
But on the PyGame homepage is introduced the GIFImage library:
This library adds GIF animation playback to pygame.
Another option is to use the Pillow library (pip install Pillow).
Write a function, that can convert a PIL image to a pygame.Surface:
(see also PIL and pygame.image)
def pilImageToSurface(pilImage):
mode, size, data = pilImage.mode, pilImage.size, pilImage.tobytes()
return pygame.image.fromstring(data, size, mode).convert_alpha()
Use the PIL library to load a GIF frame by frame:
(see also Extracting The Frames Of An Animated GIF Using Pillow
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
if pilImage.format == 'GIF' and pilImage.is_animated:
for frame in ImageSequence.Iterator(pilImage):
pygameImage = pilImageToSurface(frame.convert('RGBA'))
frames.append(pygameImage)
else:
frames.append(pilImageToSurface(pilImage))
return frames
See also Load animated GIF and a simple animated gif viewer example:
import pygame
from PIL import Image
def pilImageToSurface(pilImage):
mode, size, data = pilImage.mode, pilImage.size, pilImage.tobytes()
return pygame.image.fromstring(data, size, mode).convert_alpha()
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
if pilImage.format == 'GIF' and pilImage.is_animated:
for frame in ImageSequence.Iterator(pilImage):
pygameImage = pilImageToSurface(frame.convert('RGBA'))
frames.append(pygameImage)
else:
frames.append(pilImageToSurface(pilImage))
return frames
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
gifFrameList = loadGIF("my_gif.gif")
currentFrame = 0
run = True
while run:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
rect = gifFrameList[currentFrame].get_rect(center = (250, 250))
window.blit(gifFrameList[currentFrame], rect)
currentFrame = (currentFrame + 1) % len(gifFrameList)
pygame.display.flip()
You can use the list of pygame.Surface objects to generate a Spritesheet.
Pygame can't do gifs, but if you really want to, you could animate it frame by frame one image at a time.
Related
I had opened an image using PIL, as
image = Image.open("SomeImage.png")
Draw some text on it, as
draw = ImageDraw.Draw(image)
draw.text(Some parameters here)
and then saved it as
image.save("SomeOtherName.png")
to open it using pygame.image
this_image = pygame.image.load("SomeOtherName.png")
I just want to do it without saving.. Can that be possible? It is taking a lot of time to save and then load(0.12 sec Yes, that is more as I have multiple images which require this operation). Can that save method be surpassed?
Sadly the accepted answer doesn't work anymore, because Image.tostring() has been removed. It has been replaced by Image.tobytes(). See Pillow - Image Module.
Function to convert a PIL Image to a pygame.Surface object:
def pilImageToSurface(pilImage):
return pygame.image.fromstring(
pilImage.tobytes(), pilImage.size, pilImage.mode).convert()
It is recommended to convert() the Surface to have the same pixel format as the display Surface.
Minimal example:
import pygame
from PIL import Image
def pilImageToSurface(pilImage):
return pygame.image.fromstring(
pilImage.tobytes(), pilImage.size, pilImage.mode).convert()
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
pilImage = Image.open('myimage.png')
pygameSurface = pilImageToSurface(pilImage)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(pygameSurface, pygameSurface.get_rect(center = (250, 250)))
pygame.display.flip()
You could use the fromstring() function from pygame.image. The following should work, according to the documentation:
image = Image.open("SomeImage.png")
draw = ImageDraw.Draw(image)
draw.text(Some parameters here)
mode = image.mode
size = image.size
data = image.tostring()
this_image = pygame.image.fromstring(data, size, mode)
I have a pretty weird situation but i will try hard to explain everything the best i can. So im coding a game using PyGame. I used moviepy to show a video on the surface. Now im doing a combatscreen and i want to animate the attacks. Ill show a quick screenshot
When clicked on "Fireball" i want to show a fireball. The video itself has transparency but it fills it up with white.
The code i used previously to show cutscenes for example is following:
video = VideoFileClip('assets/Attacks/Frantz/fireball.webm')
video.preview()
When it plays the video it looks like this:
My initial file was a gif that i converted to an mp4. i found out that mp4 doesn't support alpha/transparency i tried using the gif by replacing video = VideoFileClip('assets/Attacks/Frantz/fireball.mp4') with video = VideoFileClip('assets/Attacks/Frantz/fireball.gif')
but the same thing happend with the white background (And yes the gif has 100% transparency)
I kinda don't know what to do. Should i try other file formats, if yes how do i remove the transparency but i think i need to change something in the code so i might be able to actually use a gif or something.
Heres the file of the gif btw
I know my issue if very weird but its for a school project and i would greatly appreciate some help
A movie is not what you want. What you want is an animated sprite. An animated sprite is made up of many different sprites that are displayed in consecutive frames. The source of these sprites can be a sprite sheet, an animated GIF, or a list of bitmaps.
There are various questions and answers on this topic. For example:
Animated sprite from few images
How can I load an animated GIF and get all of the individual frames in PyGame?
How do I create animated sprites using Sprite Sheets in Pygame?
Since your GIF is not transparent, you have to set the color key for the transparent color with set_colorkey():
pygameImage.set_colorkey((0, 0, 0))
Example:
import pygame
from PIL import Image, ImageSequence
def pilImageToSurface(pilImage):
return pygame.image.fromstring(
pilImage.tobytes(), pilImage.size, pilImage.mode).convert()
def pilImageToSurface(pilImage):
return pygame.image.fromstring(
pilImage.tobytes(), pilImage.size, pilImage.mode).convert()
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
if pilImage.format == 'GIF' and pilImage.is_animated:
for frame in ImageSequence.Iterator(pilImage):
pygameImage = pilImageToSurface(frame.convert('RGB'))
pygameImage.set_colorkey((0, 0, 0))
frames.append(pygameImage)
else:
frames.append(pilImageToSurface(pilImage))
return frames
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:#
pygame.draw.rect(background, color, rect)
gifFrameList = loadGIF('fire.gif')
currentFrame = 0
run = True
while run:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
rect = gifFrameList[currentFrame].get_rect(center = (250, 250))
window.blit(gifFrameList[currentFrame], rect)
currentFrame = (currentFrame + 1) % len(gifFrameList)
pygame.display.flip()
pygame.quit()
exit()
I'm setting up a Slideshow system mixing images and videos, from a directory.
I'm using a Raspberry Pi B, pygame and vlc.
I didn't install X so everything happens in framebuffer.
My actual code is working but :
The 4 seconds delay is not respected. The image is displayed +- 11 seconds.
One of the images witch has nothing particular, is displayed much longer, +- 1m30. (my real problem)
I tried a bash script with fbi, fim, vlc without suitable result. The closest was with vlc but it takes too long to render an image in framebuffer.
I'm quite new to pygame. Here is the code:
import pygame
import sys
import time
import vlc
import os
filesdir = '/home/pi/SMBmount/'
pygame.init()
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
black = 0, 0, 0
screen = pygame.display.set_mode(size)
while True:
# For every file in filesdir :
for filename in os.listdir(filesdir):
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
fullname = filesdir + filename
img = pygame.image.load(fullname)
img = pygame.transform.scale(img, size)
imgrect = img.get_rect()
screen.fill(black)
screen.blit(img, imgrect)
pygame.mouse.set_visible(False)
pygame.display.flip()
time.sleep(4)
# Elif video:
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
fullname = filesdir + filename
# Create instane of VLC and create reference to movie.
vlcInstance = vlc.Instance("--aout=adummy")
media = vlcInstance.media_new(fullname)
# Create new instance of vlc player
player = vlcInstance.media_player_new()
# Load movie into vlc player instance
player.set_media(media)
# Start movie playback
player.play()
# Do not continue if video not finished
while player.get_state() != vlc.State.Ended:
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
player.stop()
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
I'm open to any alternative able to work with pictures AND videos.
EDIT: It was finally the time it takes to pygame to resize the (next) image with pygame.transform.scale().
Is there any way to optimise that ? Like for example, to print fullscreen without resizing the large images ?
I cannot reproduce the behaviour without the images and the videos, but here a couple of advices which should help in speed up the code when displaying images.
Do not use time.sleep(). It will freeze the game for the given time, so all calculations are done outside this time window, consuming more time. Better to use pygame time Clock. From the docs of its tick() method:
If you pass the optional framerate argument the function will delay to keep the game running slower than the given ticks per second. This can be used to help limit the runtime speed of a game. By calling Clock.tick(40) once per frame, the program will never run at more than 40 frames per second.
The tick() method should be called once per iteration in the main loop, so better to not put it inside an if statement.
Here:
screen.fill(black)
screen.blit(img, imgrect)
The first line screen.fill(black) is completely useless: you are redrawing the whole surface in the second line covering all the black background, since the image is rescaled to the screen size. You can safely blit the image without filling the background with black.
This will save time, because each time you use blit or fill, pygame in background does a lot of operation on the Surface to change the color of the pixels (the more the pixels changed, the longer the time needed).
This of course if any of the images you load has an alpha channel. If you have pictures with alpha channel, you need to paint black the background before. To save time, I suggest to remove the alpha channel from the images using another program.
pygame.transform.scale() requires time, especially if you have very large picture. Try to rescale your image with another program and load in pygame images of size the closer possible to your screen.
When loading the images, add .convert(). This will make blitting faster. Should be: img = pygame.image.load(fullname).convert().
In the end, your code should look like:
imgexts = ['png', 'jpg', 'jpeg']
videxts = ['mp4', 'mkv']
#filtering out non video and non image files in the directory using regex
#remember to import re module
showlist = [filename for filename in os.listdir(filesdir) if re.search('[' + '|'.join(imgexts + videxts) + ']$', filename.lower())]
clock = pygame.time.Clock()
while True:
# For every file in filesdir :
for filename in showlist:
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
#all your stuff but NOT the time.sleep()
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
#unchanged here
clock.tick(0.25) #framerate = 0.25 means 1 frame each 4 seconds
for event in pygame.event.get():
#unchanged here
I figured out what were the issues, with the help of Valentino.
He helped me to optimize the code to improve the loading times of every image, that fixed the first issue.
See his answer.
Additionnally, I added a block of code :
# If image is not same dimensions
if imgrect.size != size:
img = Image.open(fullname)
img = img.resize(size, Image.ANTIALIAS)
img.save(fullname, optimize=True, quality=95)
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
If the picture is not the screen resolution, I use Pillow (PIL) to resize and reduce the color palette to 8-bit (256 colors).
It reduces file sizes significantly (especially for big files) and allow pygame to load the image faster.
It fixed the second issue.
For those interested, the full code is :
import pygame
import sys
import vlc
import os
import re
from PIL import Image
filesdir = '/home/pi/SMBmount/'
imgexts = ['png', 'jpg', 'jpeg']
videxts = ['mp4', 'mkv', 'avi']
time = 5 # Time to display every img
#filtering out non video and non image files in the directory using regex
showlist = [filename for filename in os.listdir(filesdir) if re.search('[' + '|'.join(imgexts + videxts) + ']$', filename.lower())]
pygame.init()
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
while True:
# For every file in filesdir :
for filename in showlist:
filenamelower = filename.lower()
# If image:
if filenamelower.endswith('.png') or filenamelower.endswith('.jpg') or filenamelower.endswith('.jpeg'):
fullname = filesdir + filename
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
# If image is not same dimensions
if imgrect.size != size:
img = Image.open(fullname)
img = img.resize(size, Image.ANTIALIAS)
img.save(fullname, optimize=True, quality=95)
img = pygame.image.load(fullname).convert()
imgrect = img.get_rect()
screen.blit(img, imgrect)
pygame.mouse.set_visible(False)
pygame.display.flip()
# Elif video:
elif filenamelower.endswith('.mp4') or filenamelower.endswith('.mkv') or filenamelower.endswith('.avi'):
fullname = filesdir + filename
# Create instane of VLC and create reference to movie.
vlcInstance = vlc.Instance("--aout=adummy")
media = vlcInstance.media_new(fullname)
# Create new instance of vlc player
player = vlcInstance.media_player_new()
# Load movie into vlc player instance
player.set_media(media)
# Start movie playback
player.play()
# Do not continue if video not finished
while player.get_state() != vlc.State.Ended:
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
player.stop()
clock.tick(1 / time) # framerate = 0.25 means 1 frame each 4 seconds
# Quit if keyboard pressed during video
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
pygame.display.quit()
pygame.quit()
sys.exit()
This question already has an answer here:
How to center an image in the middle of the window in pygame?
(1 answer)
Closed 6 months ago.
I am using python 2.7, I would like to know if there is a way to centre an image in pygame. I have set the screen to fullscreen, is this possible? If it helps i will attach the code, thanks.
import pygame
import os
_image_library = {}
def get_image(path):
global _image_library
image = _image_library.get(path)
if image == None:
canonicalized_path = path.replace('/', os.sep).replace('\\', os.sep)
image = pygame.image.load(canonicalized_path)
_image_library[path] = image
return image
pygame.init()
screen = pygame.display.set_mode((0, 0))
done = False
clock = pygame.time.Clock()
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill((0, 0, 0))
screen.blit(get_image('sw.png'), (0, 0)
pygame.display.flip()
clock.tick(60)
Yeah, it's possible.
You have to create a rect object of the image:
image = "insert image"
rect = image.get_rect ()
And then:
rect.center = ("coordinates of center")
This StackOverflow question might be able to help you.
Pygame Image position
Basically though, you'd get the image rectangle. From there there are a lot of useful functions you can call to position it.
How can I capture and save a sequence of images or a video of a pygame screen?
Basically I want to share my game video on youtube. Also, want to make a tutorial.
The game is rendered mainly in a loop:
def main():
while True:
GetInput()
Move()
Shift()
Draw()
With the Draw() function doing all the blit() and stuff before doing the pygame.display.flip()
Use pygame.image.save on your screen surface:
window = pygame.display.set_mode(...)
...
pygame.image.save(window, "screenshot.jpeg")
Note that this will slow down your program tremendously. If it is time-based, you may wish to fake the framerate when doing a capture.
The accepted answer said you could save the current screen with the pygame.image.save method
This is a good idea, however you might not want to save the images on the hard drive while the game is running, also pointed out by the question. Instead, you should save the screens in the program and then process them after the game has stopped running.
Here is my code with comments, that only shows the general idea of how a screen recording might work. It uses opencv (with numpy) and pygame but you have to install ffmpeg to convert the images to a video (try ffmpg in the terminal to test). Don't let the program run too long, because the saving still takes quite a while and is about proportional to the recorded frames. For more efficiency, you could only record every second frame or so.
from contextlib import contextmanager
import os
import time
import cv2
import numpy as np
import pygame as pg
from pygame import surfarray
def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
cvarray = cvarray.swapaxes(0,1) #rotate
cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
return cvarray
def timer_wrapper(func):
def inner(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
#print("Finished:" ,func.__name__ ,end-start)
return end - start
return inner
#contextmanager
def video_writer(*args,**kwargs):
video = cv2.VideoWriter(*args,**kwargs)
try:
yield video
finally:
video.release()
#timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
size = frames[0].get_size()
codec_dict={
"avi":'DIVX',
"mp4":'MP4V'
}
codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
for frame in frames:
try:
pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
except:
pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
cv_frame = pg_to_cv2(pg_frame) # then convert the np array so it is compatible with opencv
video.write(cv_frame) #write the frame to the video using opencv
def draw_fps(s:pg.Surface,clock:time.Clock):
fps = clock.get_fps()
sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)
# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()
running=True
try:
while running:
dt = clock.tick(60) # aim for ... fps
for event in pg.event.get():
if event.type == pg.QUIT:
running=False
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
running=False
window.fill(BLACK)
draw_fps(window,clock)
window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
frames.append(window_copy) # We save the current frame together with the time passed between the frames
dts.append(dt)
pg.display.flip()
#if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
pg.quit()
# At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
# Only then we will write the video to the hard drive (That is what makes the save so slow).
# General information about the recording
frame_num = len(frames)
dt_sum = sum(dts)
average_dt = dt_sum/frame_num
# This is only an approximation:
# for each frame we have width * height many pixels -> frame_num * width * height
# A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize() #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
print("Total time:" , dt_sum/1000,"s")
print("Average time per frame:" , average_dt,"ms")
print("Number of frames:", frame_num)
print("Memory usage approximation" , memory_usage_approx/1000, "KB")
args = (frames,dts,"avi","screen_recording")
time_for_save = save_frames(*args)
file_name = args[3]+"."+args[2]
video_memory_usage = os.path.getsize(file_name)
print("Video memory usage:" , video_memory_usage/1000, "KB")
with open("test.txt", "a") as f:
print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
print("_"*100,file=f)
Or you just use a lib like Pygame Recorder.
x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1]
I found a cool way;
you can use
x3 = pygame.surfarray.pixels3d(window)
to get all the pixels on any surface or the screen (the window variable)!
You can use this in things like NumPy where you can use the code
array = numpy.uint8(x3)
to get the image of the surface as a NumPy array and then
im = PIL.Image.fromarray(array)
to make it a Pillow image, if you want. Then you can show it with a simple im.show() or just do whatever with it.