Least performance intensive way to show images [duplicate] - python

I cant blit images in normal speed if the images in array.
#brife review
In my code i defined 10 images as a variable(x1-x10)
those 100 images relevant for specific class (object.draw_function()), and will be bliting in main loop according specific condtions.
in the object.draw_function() all the images are saved in lst "images_lst" = [img1,img2,img3,,,,img10]
and bliting from that array according rulles.
i noticed that if the len of the array is higher then 4,5 , the loop FPS is slower. and i dont understand why ? the loading images is outside the loop.
Code example:
#loading images
img1 = pygame.image.load(r'images\game_background1\img1.jpg')
img2 = pygame.image.load(r'images\game_background1\img2.jpg')
img3 = pygame.image.load(r'images\game_background1\img3.jpg')
'
'
'
img10= pygame.image.load(r'images\game_background1\img10.png')
#define font and space size
space_width,space_height = pysical_tm_img.get_width(),pysical_tm_img.get_height()
font0_0 = pygame.font.SysFont(pygame.font.get_fonts()[0],12)
font0_02 = pygame.font.SysFont(pygame.font.get_fonts()[0],17)
# define class
class EXAMPLE():
def __init__(self,x,y,text,power,energy,range):
self.rect = pygame.Rect(x,y,100,10)
self.text = text
self.power = power
self.energy = energy
self.range = range
def draw_func(self,surface):
img_lst = [img1,text1,img2,text2,img3,text3......img10,text10]
for i,img in enumerate(img_lst):
if i % 2 == 0 :
img_rect = img.get_rect(center=(self.rect.x +20 + (i *space_width*2),self.rect.top + space_height))
surface.blit(img,img_rect)
else:
img_rect = img.get_rect(center=(self.rect.x +20 + space_width + ((i-1) *space_width*2),self.rect.top + space_height))
surface.blit(img,img_rect)
#main loop
while True:
if somthing:
object1 = EXAMPLE(10,10,"abc",100,50,10)
object1.draw_func(screen)
elif somthing else:
object3 = EXAMPLE(10,10,"abc",100,50,10)
object3.draw_func(screen)
pygame.display.update()
clock.tick(60)
I dont understand whats wrong and why i cant append more images to my images list without reducing runtime.
Another question that not much relevant to this code but rellevant to runtime. if this code without the main loop is in file number 1 and in this file i import pygame and init the font only.
pygame.font.init()
and in file num 2 where the main loop is running i import pygame and init pygame
pygame.init()
is it reduce my proggram runtime?

Ensure that the image Surface has the same format as the display Surface. Use convert() (or convert_alpha()) to create a Surface that has the same pixel format. This improves performance when the image is blit on the display, because the formats are compatible and blit does not need to perform an implicit transformation.
e.g.:
img1 = pygame.image.load(r'images\game_background1\img1.jpg').convert()

Related

Storing multiple image files in a dictionary

I'm relatively inexperienced in python and was writing a shooter game for a school project. I'm trying to de-duplicate some variables which hold images:
wavetransition=pygame.image.load("TransitionScreen.png")
wavetransition=pygame.transform.scale(wavetransition,(WIDTH,HEIGHT))
wavetransition2=pygame.image.load("TransitionScreen1.png")
wavetransition2=pygame.transform.scale(wavetransition2,(WIDTH,HEIGHT))
wavetransition3=pygame.image.load("TransitionScreen2.png")
wavetransition3=pygame.transform.scale(wavetransition3,(WIDTH,HEIGHT))
wavetransition4=pygame.image.load("TransitionScreen3.png")
wavetransition4=pygame.transform.scale(wavetransition4,(WIDTH,HEIGHT))
wavetransition5=pygame.image.load("TransitionScreen4.png")
wavetransition5=pygame.transform.scale(wavetransition5,(WIDTH,HEIGHT))
these images are used as transition screens between levels in the game. They are then called in a random.randrange() variable:
rander=random.randrange(1,4)#Just For These Numbers To Redefine
if rander2==1:#The Random Backgrounds Displayed On The Wave Screen
screen2.blit(wavetransition,(0,0))
if rander2==2:
screen2.blit(wavetransition2,(0,0))
if rander2==3:
screen2.blit(wavetransition3,(0,0))
if rander2==4:
screen2.blit(wavetransition4,(0,0))
if rander2==5:
screen2.blit(wavetransition5,(0,0))
is there any way to make this appear 'shorter'? Thanks
Use lists and iterations:
# image paths
img_paths = ["TransitionScreen.png", "TransitionScreen1.png", ...]
wavetransitions = []
for img_path in img_paths:
# loads, resizes and adds image to wavetransitions list
img = pygame.image.load(img_path)
img = pygame.transform.scale(img, (WIDTH, HEIGHT))
wavetransitions.append(img)
# as rander2 is the index+1 of the image, you can simply do
screen2.blit(wavetransitions[rander2 - 1], (0, 0))
You could use a list for images and a dict for wavetransitions:
import re
import random
images = ["TransitionScreen0.png", # Note, added 0 to the filename
"TransitionScreen1.png",
"TransitionScreen2.png",
"TransitionScreen3.png",
"TransitionScreen4.png"]
wavetransitions = dict()
for image in images:
m = re.search(r'(\d+)$', image.split('.')[0]).group(0)
key = "wavetransition" + m # m is the number that ends the name of image
value = pygame.image.load(image)
value = pygame.transform.scale(value, (WIDTH, HEIGHT))
wavetransitions[key] = value
wavetransitions_keys = wavetransitions.keys()
screen2.blit(wavetransitions[random.choice(wavetransitions_keys)], (0, 0))

How to show an Image with pillow and update it?

I want to show an image recreated from an img-vector, everything fine.
now I edit the Vector and want to show the new image, and that multiple times per second.
My actual code open tons of windows, with the new picture in it.
loop
{
rearr0 = generateNewImageVector()
reimg0 = Image.fromarray(rearr0, 'RGB')
reimg0.show()
}
What can I do to create just one Window and always show just the new image?
Another way of doing this is to take advantage of OpenCV's imshow() function which will display a numpy image in a window and redraw it each time you update the data.
Note that OpenCV is quite a beast of an installation, but maybe you use it already. So, the code is miles simpler than my pygame-based answer, but the installation of OpenCV could take many hours/days...
#!/usr/local/bin/python3
import numpy as np
import cv2
def sin2d(x,y):
"""2-d sine function to plot"""
return np.sin(x) + np.cos(y)
def getFrame():
"""Generate next frame of simulation as numpy array"""
# Create data on first call only
if getFrame.z is None:
xx, yy = np.meshgrid(np.linspace(0,2*np.pi,w), np.linspace(0,2*np.pi,h))
getFrame.z = sin2d(xx, yy)
getFrame.z = cv2.normalize(getFrame.z,None,alpha=0,beta=1,norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)
# Just roll data for subsequent calls
getFrame.z = np.roll(getFrame.z,(1,2),(0,1))
return getFrame.z
# Frame size
w, h = 640, 480
getFrame.z = None
while True:
# Get a numpy array to display from the simulation
npimage=getFrame()
cv2.imshow('image',npimage)
cv2.waitKey(1)
That looks like this:
It is dead smooth and has no "banding" effects in real life, but there is a 2MB limit on StackOverflow, so I had to decrease the quality and frame rate to keep the size down.
You can do that pretty simply and pretty fast with pygame.
You write a function called getFrame() that returns a numpy array containing an image that is calculated by your simulation.
By way of example, I create a 2-d sine wave on the first pass then roll that 1 pixel down and 2 pixels across on subsequent calls to simulate movement.
#!/usr/local/bin/python3
import numpy as np
import pygame
h,w=480,640
border=50
N=0
getFrame.z = None
def sin2d(x,y):
"""2-d sine function to plot"""
return np.sin(x) + np.cos(y)
def getFrame():
"""Generate next frame of simulation as numpy array"""
# Create data on first call only
if getFrame.z is None:
xx, yy = np.meshgrid(np.linspace(0,2*np.pi,h), np.linspace(0,2*np.pi,w))
getFrame.z = sin2d(xx, yy)
getFrame.z = 255*getFrame.z/getFrame.z.max()
# Just roll data for subsequent calls
getFrame.z = np.roll(getFrame.z,(1,2),(0,1))
return getFrame.z
pygame.init()
screen = pygame.display.set_mode((w+(2*border), h+(2*border)))
pygame.display.set_caption("Serious Work - not games")
done = False
clock = pygame.time.Clock()
# Get a font for rendering the frame number
basicfont = pygame.font.SysFont(None, 32)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Clear screen to white before drawing
screen.fill((255, 255, 255))
# Get a numpy array to display from the simulation
npimage=getFrame()
# Convert to a surface and splat onto screen offset by border width and height
surface = pygame.surfarray.make_surface(npimage)
screen.blit(surface, (border, border))
# Display and update frame counter
text = basicfont.render('Frame: ' + str(N), True, (255, 0, 0), (255, 255, 255))
screen.blit(text, (border,h+border))
N = N + 1
pygame.display.flip()
clock.tick(60)
That looks like this. In real life it is very fast and very smooth, but there is a 2MB size limit for videos on StackOverflow, so I have generated a GIF with a low-ish frame rate and small-ish size just to keep it under 2MB.
Obviously you can add in detection of Up and Down arrows to speed up or slow down the animation, and you could detect left/right arrow and Spacebar to go backwards/forwards or pause the animation.
According to Pillow documentation the method Image.show() is mainly intended for debugging purposes. On Windows, it saves the image to a temporary BMP file, and uses the standard BMP display utility to show it.
You should take a look at the matplotlib image library.
You can try:
import matplotlib.pyplot as plt
imgplot = plt.imshow(rearr0) # "plot" the image.
For each iteration, you can call imgplot.clf(), it will clear the plot keeping the axis. I have not tried it, but it should work

how to change the color of a pyglet window

I am creating a program which must change the color of individual pixels in a pyglet window. I am unable to find any way to do this in the docs. Is there a way to do this?
For funsies, I'll add another answer that is more along the lines of what you might need. Because the window itself will be whatever "clear" color buffer you decide via:
window = pyglet.window.Window(width=width, height=height)
pyglet.gl.glClearColor(0.5,0,0,1) # Note that these are values 0.0 - 1.0 and not (0-255).
So changing the background is virtually impossible because it's "nothing".
You can however draw pixels on the background via the .draw() function.
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
#window.event
def on_draw():
window.clear()
for i in range(10):
x = randint(0,width)
y = randint(0,height)
pyglet.graphics.draw(1, pyglet.gl.GL_POINTS,
('v2i', (x, y)),
('c3B', (255, 255, 255))
)
pyglet.app.run()
This will create 10 randomly placed white dots on the background.
To add anything above that simply place your .blit() or .draw() features after the pyglet.graphics.draw() line.
You could use the magic function SolidColorImagePattern and modify the data you need.
R,G,B,A = 255,255,255,255
pyglet.image.SolidColorImagePattern((R,G,B,A).create_image(width,height)
This is a .blit():able image. It's white, and probably not what you want.
So we'll do some more wizardry and swap out all the pixels for random ones (War of the ants):
import pyglet
from random import randint
width, height = 500, 500
window = pyglet.window.Window(width=width, height=height)
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
data = image.get_image_data().get_data('RGB', width*3)
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
image.set_data('RGB', width*3, new_image)
#window.event
def on_draw():
window.clear()
image.blit(0, 0)
pyglet.app.run()
For educational purposes, I'll break it down into easier chunks.
image = pyglet.image.SolidColorImagePattern((255,255,255,255)).create_image(width, height)
Creates a solid white image, as mentioned. It's width and height matches the window-size.
We then grab the image data:
data = image.get_image_data().get_data('RGB', width*3)
This bytes string will contain width*height*<format>, meaning a 20x20 image will be 1200 bytes big because RGB takes up 3 bytes per pixel.
new_image = b''
for i in range(0, len(data), 3):
pixel = bytes([randint(0,255)]) + bytes([randint(0,255)]) + bytes([randint(0,255)])
new_image += pixel
This whole block loops over all the pixels (len(data) is just a convenience thing, you could do range(0, width*height*3, 3) as well, but meh.
The pixel contists of 3 randint(255) bytes objects combined into one string like so:
pixel = b'xffxffxff'
That's also the reason for why we step 3 in our range(0, len(data), 3). Because one pixel is 3 bytes "wide".
Once we've generated all the pixels (for some reason the bytes object image can't be modified.. I could swear I've modified bytes "strings" before.. I'm tired tho so that's probably a utopian dream or something.
Anyhow, once all that sweet image building is done, we give the image object it's new data by doing:
image.set_data('RGB', width*3, new_image)
And that's it. Easy as butter in sunshine on a -45 degree winter day.
Docs:
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/programming_guide/quickstart.html
https://github.com/Torxed/PygletGui/blob/master/gui_classes_generic.py
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#get_image_data
https://pythonhosted.org/pyglet/api/pyglet.image.ImageData-class.html#set_data
You can also opt in to get a region, and just modify a region.. But I'll leave the tinkering up to you :)
You can blit pixels into background 'image'. You can look at this Stack Overflow question.
If you mean background color, I can help. There is one option that I know of, the pyglet.gl.glClearColor function.
for example,:
import pyglet
from pyglet.gl import glClearColor
win = pyglet.window.Window(600, 600, caption = "test")
glClearColor(255, 255, 255, 1.0) # red, green, blue, and alpha(transparency)
def on_draw():
win.clear()
That will create a window with a white background(as opposed to the default, black)

How do I display a tiled map in Pygame using Tiled and pytmx?

I'm aware there is a similar question but the answers were not specific enough to be useful in my case.
I am trying to create a program that can display a tiled map created in Tiled and uploaded into Pygame using pytmx. The only issue I'm having is blitting the images onto the screen in Pygame.
This is the error I keep receiving:
Traceback (most recent call last):
File "C:\Users\b\Desktop\Frozen Map Textures\test.py", line 32, in
screen.blit(images[i],(x*32,y*32))
TypeError: argument 1 must be pygame.Surface, not None
If anybody knows how to fix the issue is, I would be very grateful! Thanks for all your help!
The code is below:
import pygame
from pytmx import load_pygame
import random
white = (255,255,255)
#create window
screenSize = (800,600)
screen = pygame.display.set_mode(screenSize)
pygame.display.set_caption("GameName")
screen.fill(white)
gameMap = load_pygame("Frozen.tmx")
#creates list of single tiles in first layer
images = []
for y in range(50):
for x in range(50):
image = gameMap.get_tile_image(x,y,0)
images.append(image)
#displays tiles in locations
i = 0
for y in range(50):
for x in range(50):
screen.blit(images[i],(x*32,y*32))
i += 1
#main loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
pygame.quit()
I am not very familiar with pytmx, but I think the problem is that variable i runs from 0 to 2600 in the first for-loop (you have 2600 items / images / tiles in the images list). But when you create the list of tile locations (imageLocs), i only runs from 0 to 49, so you have a list 50 different locations.
This means you don´t have "enough" locations for each tile.
In addition to that you don´t need to increment any variables in a for i in rang(value)-loop in python, because i runs throw (i.e. gets assigned to) all values the range() function returns.
You could update your code to avoid this problem:
#Original code
#create a list of 2600 single tiles in first layer
images = []
for y in range(50):
for x in range(50):
image = gameMap.get_tile_image(x,y,0)
images.append(image)
#blit all tiles onto the screen
i = 0 #runs from 0 to 2600
for y in range(50):
for x in range(50):
screen.blit(images[i],(x * 32, y * 32))
i += 1
#Orginal code
I hope this helps :)

Efficiently masking a surface in pygame

I need to draw a circle filled with random gray colors and a black outline using pygame. This is what it should look like:
The radius increases by expansion_speed * dt every frame and the surface is updated 60 times per second, so however this is achieved (if even possible) needs to be fast. I tried masking an stored texture but that was too slow. My next idea was to read the pixels from this stored texture and only replace the difference between the last and current surfaces. I tried this too but was unable to translate the idea to code.
So how can this be done?
See my update to your previous related question. It has some info about performance. You could try to enable hardware acceleration in fullscreen mode, but I never personally tried it, so can't give good advice how to do it properly. Just use two differnt colorkeys for extracting circle from noise and putting the whole surface to the display. Note that if your Noise surface has pixels same as colorkey color then they also become transparent.
This example I think is what you are trying to get, move the circle with mouse and hold CTRL key to change radius.
Images:
import os, pygame
pygame.init()
w = 800
h = 600
DISP = pygame.display.set_mode((w, h), 0, 24)
clock = pygame.time.Clock( )
tile1 = pygame.image.load("2xtile1.png").convert()
tile2 = pygame.image.load("2xtile2.png").convert()
tw = tile1.get_width()
th = tile1.get_height()
Noise = pygame.Surface ((w,h))
Background = pygame.Surface ((w,h))
for py in range(0, h/th + 2) :
for px in range(0, w/tw + 2):
Noise.blit(tile1, (px*(tw-1), py*(th-1) ) )
Background.blit(tile2, (px*(tw-1), py*(th-1) ) )
color_key1 = (0, 0, 0)
color_key2 = (1, 1, 1)
Circle = pygame.Surface ((w,h))
Circle.set_colorkey(color_key1)
Mask = pygame.Surface ((w,h))
Mask.fill(color_key1)
Mask.set_colorkey(color_key2)
strokecolor = (10, 10, 10)
DISP.blit(Background,(0,0))
def put_circle(x0, y0, r, stroke):
pygame.draw.circle(Mask, strokecolor, (x0,y0), r, 0)
pygame.draw.circle(Mask, color_key2, (x0,y0), r - stroke, 0)
Circle.blit(Noise,(0,0))
Circle.blit(Mask,(0,0))
dirtyrect = (x0 - r, y0 - r, 2*r, 2*r)
Mask.fill(color_key1, dirtyrect)
DISP.blit(Circle, (0,0))
X = w/2
Y = h/2
R = 100
stroke = 2
FPS = 25
MainLoop = True
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
Keys = pygame.key.get_pressed()
MR = pygame.mouse.get_rel() # get mouse shift
if Keys [pygame.K_ESCAPE] :
MainLoop = False
if Keys [pygame.K_LCTRL] :
R = R + MR[0]
if R <= stroke : R = stroke
else :
X = X + MR[0]
Y = Y + MR[1]
DISP.blit(Background,(0,0))
put_circle(X, Y, R, stroke)
pygame.display.flip( )
pygame.mouse.set_visible(True)
pygame.event.set_grab(False)
pygame.quit( )
Many years ago we had a font rendering challenge with the Pygame project.
Someone created an animated static text for the contest but it was far too slow.
We put our heads together and made a much quicker version. Step one was to create a smallish image with random noise. Something like 64x64. You may need a bigger image if your final image is large enough to notice the tiling.
Every frame you blit the tiled noise using a random offset. Then you take an image with the mask, in your case an inverted circle, and draw that on top. That should give you a final image containing just the unmasked noise.
The results were good. In our case it was not noticeable that the noise was just jittering around. That may be because the text did not have a large unobstrcted area. I'd be concerned your large circle would make the trick appear obvious. i guess if you really had a large enough tiled image it would still work.
The results and final source code are still online at the Pygame website,
http://www.pygame.org/pcr/static_text/index.php

Categories

Resources