I have the below code which currently outputs an image on a blank Pyglet window, however there is only one image outputted. I really need there to be a new image added every two second, with the previous images remaining intact also. For example, one image is added, two seconds later another image is added and two seconds after this another image is added. I have added the random library so an image at random can be added.
The code I have for this is below, it only displays the one image - I feel that this is getting stuck somewhere around the draw section.
import pyglet
import time
import random
window = pyglet.window.Window()
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
choice = images[random.randint(0,2)]
rawImage = pyglet.resource.image(choice)
sprite = pyglet.sprite.Sprite(rawImage)
#window.event
def on_draw():
window.clear()
sprite.draw()
time.sleep(2)
pyglet.app.run()
Any help or advice you can offer with this would be really appreciated.
A few issues/suggestions with your code.
First of all, the following code is redundant:
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
....
pyglet.app.run()
Because pyglet.app.run() is a blocking call, meaning, the loop will never loop - because pyglet.app.run() is in itself, a loop (more on this later).
Except if your application crashes, but you don't handle those exceptions so not even in that case will the code be re-run/looped.
Secondly, you should never define arrays/lists or anything really inside a loop. Loops are usually meant for logical operations, not creating things. Most contently some times it's useful to create things inside a loop, but those times they are more often than not accompanied by a if statement.
Resources cost a lot for the computer, both to setup and for the memory/hard drives etc. So trying to create your lists as early as possible and outside of any loops are suggested. for instance:
window = pyglet.window.Window()
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
while True:
choice = images[random.randint(0,2)]
Would have been a better option, if - again - the loop actually looped. In this case it's just a matter of tidying things up.
Also, this block of code:
#window.event
def on_draw():
window.clear()
sprite.draw()
Should not be created in a loop either, it's meant to replace your window variables on_draw function. So that should be moved out and put as early as possible in your logic IMO. At least kept separated by all other logic so it's not in between a "adding random image" and inside a "loop".
Now, the main reason your code fails, is that you thought this would loop, it doesn't. Again, pyglet.app.run() will lock your code execution on that row, because it's, well a never ending loop inside that function call.
You could expand your code and copy paste the code from pyglet.py's source code and it would look something like this (just to give you an idea of what's happening):
window = pyglet.window.Window()
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
choice = images[random.randint(0,2)]
rawImage = pyglet.resource.image(choice)
sprite = pyglet.sprite.Sprite(rawImage)
#window.event
def on_draw():
window.clear()
sprite.draw()
time.sleep(2)
def run(self):
while True:
event = self.dispatch_events()
if event:
self.on_draw()
note how pyglet.app.run() expands into another while True loop, that never really breaks. It's a bit oversimplified, but that's essentially what happens.
So your sprite = pyglet.sprite.Sprite(rawImage) will never be re-triggered.
Then, to your second biggest issue why this code would never work:
You're doing:
def on_draw():
sprite.draw()
But each loop you would have replaced the old sprite object, with a new one by doing sprite = pyglet.sprite.Sprite(rawImage). So what you would want to do, is keep a list/dictionary outside of the loop with all your visible image, and add to it and only render the images added.
Much like this:
import pyglet
import time
import random
width, height = 800, 600
window = pyglet.window.Window(width, height)
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Keep a timer of when we last added a image
last_add = time.time()
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
choice = image_options[random.randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice))
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## If two seconds have passed, and the ammount of images added are less/equal
## to how many images we have in our "database", aka `image_options`, then we'll
## add another image somewhere randomly in the window.
if time.time() - last_add > 2 and len(images) < len(image_options):
last_add = time.time()
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Then here, is where we loop over all our added images,
## and render them one by one.
for _id_ in images:
images[_id_].draw()
## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## And then enter the never ending render loop.
pyglet.app.run()
Now, this only works when you press a key or press the mouse inside the window. This is because that's the only time a event will be dispatched. And Pyglet will only render things if there's a event that is being triggered. There's two ways you can get around this, the hard core OOP way which I'll skip for now.
The second is to use what's called a Pyglet Clock, where you schedule something to happen at a interval. I'm not really good at this part, since I tend to use my own scheduler etc.
But here's the gist of it:
def add_image():
images[len(images)] = get_random_image()
pyglet.clock.schedule_interval(add_image, 2) # Every two seconds
This is a lot cleaner than doing the if time.time() - last_add > 2.
The result should look something like this:
import pyglet
import time
import random
width, height = 800, 600
window = pyglet.window.Window(width, height)
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
choice = image_options[random.randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice))
def add_image(actual_time_passed_since_last_clock_tick):
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## Then here, is where we loop over all our added ima ges,
## and render them one by one.
for _id_ in images:
images[_id_].draw()
## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image
## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)
## And then enter the never ending render loop.
pyglet.app.run()
This way, you won't need to press any keys or the mouse to trigger a event in Pyglet, it will handle that for you and do what you scheduled it to do.
Next up, is a small optimization from my part. It's a bonus, and will speed things up. It's called batched rendering, when you're rendering a lot of images and sprites, you're currently sending one image at a time to the graphics card. This is very CPU intensive. What you want to do is put the labor on the GPU. Because after all, you're working with graphics, right?
So, batched rendering is pretty easy in this case. Every time you call pyglet.sprite.Sprite, it has a parameter called batch=None (default). if you add a batch to the sprite object, you can render the entire batch by calling batch.draw() instead of each individual sprite.draw().
The solution would look something like this:
import pyglet
import time
from random import randint
width, height = 800, 600
window = pyglet.window.Window(width, height)
main_batch = pyglet.graphics.Batch()
## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}
## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image(x=0, y=0):
choice = image_options[randint(0, len(image_options)-1)]
return pyglet.sprite.Sprite(pyglet.image.load(choice), x=x, y=y, batch=main_batch)
def add_image(actual_time_passed_since_last_clock_tick=0):
image = get_random_image(x=randint(0, width), y=randint(0, height))
images[len(images)] = image
## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
#window.event
def on_draw():
window.clear()
## Instead of looping over each image in `images`,
## just do:
main_batch.draw()
## Ok, lets start off by adding one image.
## Instead of doing it manually, use the function add_image.
add_image()
## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)
## And then enter the never ending render loop.
pyglet.app.run()
I also made some alterations to add_image and get_random_image, mainly so that you can tell what position the image should be in inside the function, because pyglet.sprite.Sprite also takes two other parameters, x and y. So it makes no sense to change x and y after you've created the sprite, unless you want to move them afterwards (for instance, in a pyglet.clock.schedule_interval call).
Related
I am coding a prototype platformer in pygame. I'm using a .png as a tilesheet, I load it and then get a list tileset.tiles of all the different tile textures in it. I then use three layers of .csv tilemaps to associate every tile in the grid with its own corresponding texture. I bake all of the tile layers onto a map surface once, and then blit this surface at every frame.
The problem is that the outcome is not as expected, apparently not all of the tiles are properly blit onto the surface. The problem seems to arise when the same subsurface has to be blit a second time by the load method in the Room class. It's not clear to me what exactly causes this. I have tried playing around with the .csv files, and it seems that different arrangements of tiles, even across layers, have an influence on what is actually rendered on screen. I've added screenshots to illustrate this better. For reference, the id number 8 corresponds to a blue square texture, which should be the sky. The other numbers correspond to several different textures.
bottom layer csv
middle layer csv
top layer csv
outcome
By changing the first tile of the middle layer no difference is shown (tile 0 corresponds to the flower texture):
alternate middle layer
outcome
Or, if I try and change the first few tiles of the bottom layer:
alternate bottom layer
outcome
Generally, if I change some tile number in a csv file, weird things happen, and other seemingly random tiles get blit. Also, I am able to manually place tiles at any position on the screen without any problems (bypassing the load method and directly blitting subsurfaces from the tileset class) so I think that the tileset class is working properly.
Here's the full code:
import pygame as pg
class Tileset:
def __init__(self,img:pg.Surface):
self.tiles = []
self.img = img
self.loadtiles()
def loadtiles(self):
for i in range(16):
for n in range(16):
currentimg = self.img.subsurface(32*n,32*i,32,32)
self.tiles.append(currentimg.copy())
class Tile(pg.sprite.Sprite):
def __init__(self,image:pg.Surface,position:tuple):
self.img = image
self.pos = position
def draw (self,surface:pg.Surface):
surface.blit(self.img,self.pos)
class Room:
def __init__(self,id,size:tuple):
self.id = id
self.layers = [[],[],[]]
self.size = (size[0]*32,size[1]*32)
# Call when a new room must be loaded: reads room csv, stores tile info, overwrites drawn map with new map
def load(self,map):
map = pg.Surface(self.size)
for layer in self.layers:
with open ('levels/final/room'+str(self.id)+'_'+str(self.layers.index(layer))+'.csv') as file:
data = file.readlines()
for rrrow in data: # unprocessed row
rrow = rrrow.strip('\n').split(',') # semi processed row
row = [] # processed row
for rtile in rrow: # unprocessed tile in row
if rtile != -1:
tileimg = tileset.tiles[int(rtile)]
tile = Tile(tileimg,(rrow.index(rtile)*32, data.index(rrrow)*32)) # process tile
tile.draw(map) # draw tile on current tilemap
row.append(tile) # store tile in row
layer.append(row) # store row in layer (to use later for collisions)
return map
### pygame loop setup (incomplete, shouldn't matter ###
res = (32*30,32*20)
scr = pg.display.set_mode(res)
tileset = Tileset(pg.image.load('graphics\stock.png'))
room0 = Room(0,(30,20))
map = pg.Surface((0,0))
map = room0.load(map)
running = True
while running:
scr.fill((0,0,0))
scr.blit(map,(0,0))
pg.display.flip()
I have just solved it. The problem was in the use of the '.index' method to determine what the position of that tile would be. .index would just return the first occurrence of that tile in the list, and not the actual one, so any time a tile would have to appear for a second time in the same row, or every time two rows looked the same, that tile/row would just be positioned on top of its previous copy. Solved by using i and n as counters for the tile and row positions instead of the .index method.
So, couple hours ago I found Pyglet best for me for rendering gif animations, so I', new in this. My problem is that the animated gif renders at its original size in fullscreen window, I need to make it match, but I can't figure out how I should do this, any help? My code:
import sys
import pyglet
from pyglet.window import Platform
if len(sys.argv) > 1:
animation = pyglet.image.load_animation(sys.argv[1])
bin = pyglet.image.atlas.TextureBin()
animation.add_to_texture_bin(bin)
else:
animation = pyglet.resource.animation('gaben.gif')
sprite = pyglet.sprite.Sprite(animation)
screen = Platform().get_default_display().get_default_screen()
window = pyglet.window.Window(width=screen.width, height=screen.height)
window.set_fullscreen(True)
pyglet.gl.glClearColor(1, 1, 1, 1)
#window.event
def on_draw():
window.clear()
sprite.draw()
pyglet.app.run()
The result what I get
The easiest way is to use the sprite object's .scale.
It has the ability to scale the image in proportion to it's original dimensions and you don't need to worry about mapping data or filling in pixel-gaps if you resize the image yourself some how.
To help you get going, this is a simple example of a implementation:
(And it looks like this: https://youtu.be/Ly61VvTZnCU)
import pyglet
from pyglet.window import Platform
monitor = Platform().get_default_display().get_default_screen()
sprite = pyglet.sprite.Sprite(pyglet.resource.animation('anim.gif'))
H_ratio = max(sprite.height, monitor.height) / min(sprite.height, monitor.height)
W_ratio = max(sprite.width, monitor.width) / min(sprite.width, monitor.width)
sprite.scale = min(H_ratio, W_ratio) # sprite.scale = 2 would double the size.
# We'll upscale to the lowest of width/height
# to not go out of bounds. Whichever
# value hits the screen edges first essentially.
window = pyglet.window.Window(width=monitor.width, height=monitor.height, fullscreen=True)
pyglet.gl.glClearColor(1, 1, 1, 1)
#window.event
def on_draw():
window.clear()
sprite.draw()
pyglet.app.run()
I removed some of your code for demo/testing purposes.
The code is by no means perfect, but it will hopefully give you an insight as to how this works).
I have a list with 12 images. All of them have different locations (that is, they do not overlap at all). I want to draw them all at once. In other words, I want to see all 12 pictures at the same time on the screen. So far, I only got this:
lines = [line1,line2,line3,line4,line5,line6
line7,line8,line9,line10,line11,line12]
for i in range(12):
lines[i].draw()
But, of course, this code only draws one pic at a time, after I press any key. Is there then a way to draw the 12 pics at the same time?
Thanks in advance!
Your original code only drew one image at a time because of how the loop was set up -- it was (more or less) saying "For each element in circles, draw several things and flip the front and back buffers". Each time the buffers flip, unless you tell it otherwise via win.flip(clearBuffer = False), the previous things on the screen are removed. To draw the images at the same time, you could just loop through the image list and call the draw() method on each element, e.g.:
for i in imglist:
i.draw()
win.flip()
If you are willing to cede control over properties of individual images, one way would be to use BufferImageStim. This takes longer to initialize, but may be faster than drawing individual images (I haven't timed it properly). Both methods are demonstrated below.
from psychopy import visual, event, core
import urllib
import random
win = visual.Window([400, 400], fullscr = False)
# picture of a cat, save to file
urllib.urlretrieve('https://s-media-cache-ak0.pinimg.com/736x/' +
'07/c3/45/07c345d0eca11d0bc97c894751ba1b46.jpg', 'tmp.jpg')
# create five images with (probably) unique positions
imglist = [visual.ImageStim(win = win, image = 'tmp.jpg',
size = (.2, .2),
pos = ((random.random() - 0.5) * 2,
(random.random() - 0.5) * 2))
for i in xrange(5)]
# draw individual images
for i in imglist:
i.draw()
win.flip()
# wait for key press, then clear window
event.waitKeys()
win.flip()
core.wait(0.5)
# create aggregate stimulus (should look identical)
buffs = visual.BufferImageStim(win, stim = imglist)
buffs.draw()
win.flip()
event.waitKeys()
core.quit()
I'm using Pyglet and I have a little that includes an object moving over a background. Both of them are represented by images (png and jpg).
I've created a non-fullscreen window with size 800x600 and it works fine, but when I toggle to fullscreen... background and object have the same size as before and the rest of the screen is filled with black (empty color).
What I want to do is to "scale" the images or change the resolution when I toggle fullscreen mode.
I've read the documentation, but I can't find the answer to this.
I know that with Pygame, this problem solves itself automatically (if you change the window size, everything rescales automatically)... but how do you do this with pyglet?
This is my relevant code:
import pyglet
WIDTH = 800
HEIGHT = 600
working_dir = '/where/i/have/my/images/'
window = pyglet.window.Window(WIDTH, HEIGHT)
background = pyglet.image.load(working_dir + 'background.jpg')
flying_thing = pyglet.image.load(working_dir + 'flying_thing.png')
#window.event
def on_draw():
window.clear()
background.blit(0, 0)
flying_thing.blit(WIDTH // 2, HEIGHT // 2)
#window.event
def on_key_press(symbol, modifiers):
if symbol == pyglet.window.key.SPACE:
window.set_fullscreen(not window.fullscreen)
pyglet.app.run()
You can try this code changing working_dir, background.jpg and flying_thing.png to a working directory of yours and two images in it.
I didn't tried, but from pyglet docs, blit supports width and height. Its signature is
blit(self, x, y, z=0, width=None, height=None)
Have you tried using
background.blit(width=window.width, height=windows.height)
instead? (I'm not sure the window.width changes on full_screen, let's see...).
This answer can also be relevant to your question: https://stackoverflow.com/a/11183462/931303.
I am writing a simple motion detection program but i want it to be cross platform so im using python and the pyglet library since it provides a simple way to load videos in different formats (specially wmv and mpeg). So far i have the code given below which loads the movie and plays it in a window. Now i need to:
1) grab frame at time t and t-1
2) do a subtraction to see which pixels are active for motion detection.
any ideas on how to grab frames and to skip over frames and is it possible to put the pixel values into a matrix in numpy or something directly from pyglet? or should look into using something other than pyglet?
thanks
kuaywai
import pyglet
import sys
window = pyglet.window.Window(resizable=True)
window.set_minimum_size(320,200)
window.set_caption('Motion detect 1.0')
video_intro = pyglet.resource.media('movie1.wmv')
player = pyglet.media.Player()
player.queue(video_intro)
print 'calculating movie size...'
if not player.source or not player.source.video_format:
sys.exit
myWidth = player.source.video_format.width
myHeight = player.source.video_format.height
if player.source.video_format.sample_aspect > 1:
myWidth *= player.source.video_format.sample_aspect
elif player.source.video_format.sample_aspect < 1:
myHeight /= player.source.video_format.sample_aspect
print 'its size is %d,%d' % (myWidth,myHeight)
player.play()
#window.event
def on_draw():
window.clear()
(w,h) = window.get_size()
player.get_texture().blit(0, h-myHeight,
width=myWidth,
height=myHeight)
pyglet.app.run()
To me it seems like you have to skip the play function and manually step through the video/animation, maybe using source.get_animation().frames which is a list of frames where each frame is a simple image. I'm guessing this isn't really going to be pracitical with large videos but that is generally not something you should be handling in Python anyway.