Show and flip image using python - python

I am trying to encode a String to a QR code. Then show the QR code image on a display using python.
Here is my code:
import pyqrcode
from PIL import Image
import os
import pygame
from time import sleep
qr = pyqrcode.create("This is a string one")
qr.png("QR.png", scale=16)
pygame.init()
WIDTH = 1280
HEIGHT = 1080
scr = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
img = pygame.image.load("QR.png")
scr.blit(img,(0,0))
pygame.display.flip()
sleep(3)
Now I want to display and flip the image in a loop.
I want to do it in a loop as the string ("This is a string one") is not constant. It will be updated (such as, I get string from mysql). When the string updates, I want to display the new QR code image after 3 seconds then flip it, then continue.
But when I put the code in a loop, it crashes and the image does not flip or update.
import pyqrcode
from PIL import Image
import os
import pygame
from time import sleep
while(1):
qr = pyqrcode.create("Nguyen Tran Thanh Lam")
qr.png("QR.png", scale=16)
pygame.init()
WIDTH = 1280
HEIGHT = 1080
scr = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
img = pygame.image.load("QR.png")
scr.blit(img,(0,0))
pygame.display.flip()
sleep(5)
Update:
After 5 second, pygame-windows not flip. I must use Ctrl-C to Interrupt.
Traceback (most recent call last):
File "qr.py", line 18, in <module>
sleep(5)
KeyboardInterrupt
Thank you in advance.

pygame.display.flip doesn't flip the image, it updates the display/screen. To actually flip an image you have to use pygame.transform.flip.
There are various other problems, for example you should do the initialization, call pygame.display.set_mode and load the image before the while loop starts. After loading an image, call the convert or convert_alpha method to improve the blit performance:
img = pygame.image.load("QR.png").convert()
You also need to call pygame.event.pump() or use an event loop for event in pg.event.get():, otherwise the program will freeze because the OS thinks that your program has stopped responding.
To implement a timer you can use pygame.time.get_ticks. time.sleep makes your program unresponsive and should usually not be used in a game.
Here's an example:
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock() # A clock to limit the frame rate.
image = pg.Surface((100, 100))
image.fill((50, 90, 150))
pg.draw.rect(image, (120, 250, 70), (20, 20, 20, 20))
previous_flip_time = pg.time.get_ticks()
done = False
while not done:
for event in pg.event.get():
# Close the window if the users clicks the close button.
if event.type == pg.QUIT:
done = True
current_time = pg.time.get_ticks()
if current_time - previous_flip_time > 1000: # 1000 milliseconds.
# Flip horizontally.
image = pg.transform.flip(image, True, False)
previous_flip_time = current_time
screen.fill((30, 30, 30))
screen.blit(image, (100, 200))
# Refresh the display/screen.
pg.display.flip()
clock.tick(30) # Limit frame rate to 30 fps.
if __name__ == '__main__':
pg.init()
main()
pg.quit()

Related

MoviePy preview showing besides screen blit

I've got a problem.
I'm creating the Game Menu which i want to be:
background as video file (using MoviePy)
buttons on the video
The problem is video is showing, but the button1 not.
Please help...
from moviepy.editor import *
import pygame
from pygame import mixer
import os
import cv2
print(os.getcwd())
# Initialization the PyGame
pygame.init()
# Create the screen
WIN = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
# Menu Background
background = pygame.image.load('Assets/background.png')
button1 = pygame.image.load('Assets/button.png')
# Background SOUND
mixer.music.load('Assets/mainmenumusic.mp3')
mixer.music.set_volume(0.2)
mixer.music.play(-1)
# Game TITLE and ICON
pygame.display.set_caption("Game Launcher") # title of the game
icon = pygame.image.load('Assets/icon.png') # define the icon variable
pygame.display.set_icon(icon) # set the window icon to the icon variable
# FPS Lock
FPS = 60
clip = VideoFileClip('Assets/particles.mp4', audio=False)
# Game loop
clock = pygame.time.Clock()
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
WIN.blit(button1, (960, 540))
clip.preview(fullscreen=True)
pygame.display.update()
The button is not showing because you blit it before the background video so the video covers it. You can just try with:
clip.preview(fullscreen=True)
WIN.blit(button1, (960, 540))

Pygame slideshow delay anormally long

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()

pygame not displaying my image

i started learning pygame and i followed some tutorials to make simple hello world project and it works but when i do it my self trying to display my image on the window nothing happen!
this is my code
__author__ = 'mohammed'
import sys
import pygame
import color
# -----------setup------------------------
pygame.init() # start pygame
screensize = (800, 600) # variable that we will use to declare screen size
screen = pygame.display.set_mode(screensize) # set the screen size
pad = pygame.image.load('2.png')
x = 100
y = 100
# -----------setup------------------------
# -------------------main loop------------
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.blit(pad, (x, y))
screen.fill(red)
pygame.display.update()
i am importing my file that contain colors and their rgb :
red = (255, 0, 0)
It looks like you're filling the screen after the image is drawn, covering the image. Try switching the order of the rows:
screen.blit(pad, (x, y))
and
screen.fill(red)

Rendering vanishing text in pygame

I'm making a small game using pygame. When the game starts I want to display "start" and vanish it after a few seconds. How to do that?
First you need a timer variable (there are other questions about timers, so I won't explain them here). I'm just counting the frames in the following example.
To remove the text abruptly you can just keep blitting it until the time is up.
if timer > 0:
screen.blit(txt_surf, (position))
The slowly disappearing text can be achieved by filling the text surface with white and the current alpha value (which is reduced each frame) and by passing the pg.BLEND_RGBA_MULT special flag. That will affect only the alpha channel of the surface.
txt_surf.fill((255, 255, 255, alpha), special_flags=pg.BLEND_RGBA_MULT)
Also, use a copy of the original text surface, otherwise it would subsequently reduce the alpha of the previously modified surface and the text would disappear too quickly.
import pygame as pg
def main():
pg.init()
clock = pg.time.Clock()
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 64)
orig_surf = font.render('Enter your text', True, pg.Color('royalblue'))
txt_surf = orig_surf.copy()
alpha = 255 # The current alpha value of the surface.
timer = 20 # To get a 20 frame delay.
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
if timer > 0:
timer -= 1
else:
if alpha > 0:
# Reduce alpha each frame, but make sure it doesn't get below 0.
alpha = max(0, alpha-4)
# Create a copy so that the original surface doesn't get modified.
txt_surf = orig_surf.copy()
txt_surf.fill((255, 255, 255, alpha), special_flags=pg.BLEND_RGBA_MULT)
screen.fill((30, 30, 30))
screen.blit(txt_surf, (30, 60))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Please try adding below code:
for i in range(whatevernumberbutnotover1000):
mytext.set_alpha(i)

How to capture pygame screen?

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.

Categories