How to show an Image with pillow and update it? - python

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

Related

Least performance intensive way to show images [duplicate]

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

Programmatically divide screen into N-rectangles with pygame

I have a stream of input data from multiple "channels" and I want to represent each channel's data stream within a section of the screen using pygame.
The streaming data is captured in a pandas data.frame and summarized every few seconds. Right now I've prototyped this using only the first channel of data and fullscreen mode in pygame (i.e., scr = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)).
The way I'm trying to divide up the screen is a lot like this but instead of having a fixed number of sub-screens to create, I'm hoping to reference the number of columns in the pandas data.frame to divide up the screen programmatically.
Should this be done using Surface and some sort of loop? Just looking for some guidance as this type of programming is new to me. I've seen something vaguely similar with geographic data (like this) but the context of working with displays and pygame is enough to make me a bit uncertain.
I would indeed work with pygame.Surfaces and a loop.
I took a primer on pygame (my go to is: https://realpython.com/pygame-a-primer/) and added how I would implement multiple sub_screens (or surfs in my script below). I hope my code and comments are clear enough to get what's going on, otherwise just ask!
For now I just fill the sub-screens with random colors, but you can blit anything you want on the sub-screens (in or outside the while-loop), as they are just good old pygame.Surfaces.
import pygame
import random
# init pygame
pygame.init()
# set up parameters
n_rows = 5
n_cols = 3
screen_width = 800
screen_height = 500
# Create a dict with the row and column as key,
# and a tuple containing a Surface and its topleft coordinate as value
surf_width = round(screen_width / n_cols)
surf_height = round(screen_height / n_rows)
surfaces_dct = {}
for row in range(n_rows):
for col in range(n_cols):
# create a surface with given size
surf = pygame.Surface(size=(surf_width, surf_height))
# get its top left coordinate
top_left = (col*surf_width, row*surf_height)
# put it in the dict as a tuple
surfaces_dct[(row, col)] = (surf, top_left)
# Here you can blit anything to each surface/sub-screen (defined by it's row and column):
for key, value in surfaces_dct.items():
# unpack the key, value pairs
(row, col) = key
(surf, top_left) = value
# I just fill each surface with a random color for demonstration
(r, g, b) = [random.randint(0, 255) for i in range(3)]
surf.fill((r, g, b))
# Set up the drawing window
screen = pygame.display.set_mode([screen_width, screen_height])
# Run until the user asks to quit
running = True
while running:
# Did the user click the window close button?
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# You can also run this for-loop here, inside the main while-loop
# to dynamically change what's on each surf
# as you see you can also unpack the key and value like this
for (row, col), (surf, top_left) in surfaces_dct.items():
# I'll leave it blank here
pass
# Finally place(/blit) the surfaces on the screen given
screen.blits(list(surfaces_dct.values()))
#### the above is the same as doing:
# for surf, top_left in surfaces_dct.values():
# screen.blit(surf, top_left)
# Update the display
pygame.display.update()

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)

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

Texturing drawn shapes - python pygame

Im currently using the random import to create five random x, y values and taking those values and drawing a polygon with the pygame.draw.polygon () command. If I had a texture square I wanted to apply over top of that shape instead of having just on rgb value what would be the most efficient way to do that? i want to take the generated polygon below and with out hard coding its shape, taking a general texture square and making all that green that new texture as if that shape was cut out of the texture square.
import pygame,random
from pygame import*
height = 480
width = 640
#colors
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
white = (255,255,255)
black = (0,0,0)
pygame.init()
points = [ ]
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption("PlayBox")
r = random
for i in range(0,5):
x = r.randrange(0,640)
y = r.randrange(0,480)
points.append([x,y])
running = True
while running == True:
screen.fill(white)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
pygame.draw.polygon(screen,green,points,0)
pygame.display.update()
pygame.display.update()
One option, of course, would be to re-implement the "bucket fill" algorithm yourself,
and copy pixels inside the polygon. That would be a lot of work, and wouldget slow done in pure Python - still, it would launch you into the basic foundations of image manipulation http://en.wikipedia.org/wiki/Flood_fill
Since Pygame already does the heavy lifting, but provides just solid color fills,
the way to go is to use pygame's results as a clipping mask to your texture. Unfortunatelly that is probably more difficult than it should. I hope my sample here
can be useful for others having the same needs.
Pygame gives us some primitives to manipulate the color planes in the surfaces,
but they are definitely low level. Another thing is that these primitives require
numpy to be installed - I am not certain if Window's pyagames installer include it -
otherwise people running your project have to be told to install numpy themselves.
So, teh way to go is:
Load your desired texture in a surface (for less headache, one of the same size
of the final image), to draw the shape you want to be painted with the texture
in a mask surface, with 8bpp (B&W) - which works as a transparency map to the
texture -
them use pygame's surfarray utilities to blit everything together:
# coding: utf-8
import random
import pygame
SIZE = 800,600
def tile_texture(texture, size):
result = pygame.Surface(size, depth=32)
for x in range(0, size[0], texture.get_width()):
for y in range(0, size[1], texture.get_height()):
result.blit(texture,(x,y))
return result
def apply_alpha(texture, mask):
"""
Image should be a 24 or 32bit image,
mask should be an 8 bit image with the alpha
channel to be applied
"""
texture = texture.convert_alpha()
target = pygame.surfarray.pixels_alpha(texture)
target[:] = pygame.surfarray.array2d(mask)
# surfarray objets usually lock the Surface.
# it is a good idea to dispose of them explicitly
# as soon as the work is done.
del target
return texture
def stamp(image, texture, mask):
image.blit(apply_alpha(texture, mask), (0,0))
def main():
screen = pygame.display.set_mode(SIZE)
screen.fill((255,255,255))
texture = tile_texture(pygame.image.load("texture.png"), SIZE)
mask = pygame.Surface(SIZE, depth=8)
# Create sample mask:
pygame.draw.polygon(mask, 255,
[(random.randrange(SIZE[0]), random.randrange(SIZE[1]) )
for _ in range(5)] , 0)
stamp(screen, texture, mask)
pygame.display.flip()
while not any(pygame.key.get_pressed()):
pygame.event.pump()
pygame.time.delay(30)
if __name__ == "__main__":
pygame.init()
try:
main()
finally:
pygame.quit()

Categories

Resources