The objects in my window sometimes disappear and reappear. This mainly happens when resizing the window. I suppose because my two methods are conflicting each other with the 'glutMainLoopEvent()' function.
I create the window like this:
def create_display(self, window_name):
glutInit()
glutInitDisplayMode(GLUT_RGBA) # initialize colors
glutInitWindowSize(self.get_width(), self.get_height()) # set windows size
glutInitWindowPosition(0, 0) # set window position
glutCreateWindow(f"{window_name}") # create window (with a name) and set window attribute
glutSetWindow(self.get_window_id())
glutDisplayFunc(self.update_display)
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS) # prevent program from stopping
And as the display function this method gets called:
#staticmethod
def update_display():
glClearColor(1, 0, 0, 1) # set backdrop color to red
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white)
glLoadIdentity() # Reset all graphic/shape's position
glutSwapBuffers() # Important for double buffering
(Also, without the glutSwapBuffers() function the window doesn't get updated when I maximize it so black borders are created.)
But in my main loop I always call this method from another script before displaying anything:
#staticmethod
def prepare():
glClearColor(1, 0, 0, 1) # set backdrop color to red
glClear(GL_COLOR_BUFFER_BIT) # clear everything
Also calling the display.update_display() method in the main function results in no object being rendered at all.
Only calling the renderer.prepare() method results in no updates being made for the window.
I figured it out. The glutSwapBuffers() function needs to be called after you finished drawing to the screen (kind of updates the screen with the changes I guess?).
So now I'm calling the display.update_display() method from the main loop and at the end I call glutSwapBuffers() right before the glutMainLoopEvent().
Related
Is there a way to clear the graph in PySimpleGUI before redraw a new image? i notice the function window["-GRAPH-"].draw_image() is causing a serious memory leak when the program run for sometime, as it is trying to stack the picture on top of all drawn images.
Background:
My app is trying to show a live feed from a webcam, meanwhile will also do some drawing (depending on mouse click) on top of the camera feed. In order to detect the mouse click event from the camera feed, im using sg.Graph to capture mouse position.
sample_app_display: user label the box of object in a live camera feed
Code Snippet:
sg.Graph(853, (0, 480), (853, 0), key="-GRAPH-", change_submits=True, drag_submits=False)
...
camera = my_opencv_library(device=0)
while True:
event, values = window.read(timeout=20)
if event == "-GRAPH-":
camera.update_coordinate(values["-GRAPH-"])
# obtain live feed with runtime drawing (based on mouse click)
frame = camera.get_frame()
imgbytes = cv2.imencode(".png", frame)[1].tobytes()
window["-GRAPH-"].draw_image(data=imgbytes, location=(0,0))
Erase the Graph - Removes all figures previously "drawn" using the Graph methods
Erase all figures on sg.Graph by
window['-GRAPH-'].erase()
Remove from the Graph the figure represented by id.
Erase specified figure on sg.Graph by ids
window['-GRAPH-'].delete_figure(ids)
The ids is given to you anytime you call a drawing primitive, like
ids = window["-GRAPH-"].draw_image(data=imgbytes, location=(0,0))
Update code
sg.Graph(853, (0, 480), (853, 0), key="-GRAPH-", change_submits=True, drag_submits=False)
...
camera = my_opencv_library(device=0)
ids = None
while True:
event, values = window.read(timeout=20)
if event == "-GRAPH-":
camera.update_coordinate(values["-GRAPH-"])
# obtain live feed with runtime drawing (based on mouse click)
frame = camera.get_frame()
imgbytes = cv2.imencode(".png", frame)[1].tobytes()
if ids is not None:
window["-GRAPH-"].delete_figure(ids)
ids = window["-GRAPH-"].draw_image(data=imgbytes, location=(0,0))
I am new to dirty rect animation and I am currently trying to store a snapshot of the main display surface window, however I would only like to store the area where my item is going to be blit so the next frame I can call this stored snapshot instead of re blitting the whole background.
I looked at the documentation for Surface.copy() but it doesn't take arguments and I couldn't find anything similar other than pygame.pixelcopy() which from what I understand is not what I am looking for. If Surface.copy() isn't what I am looking for, please let me know of alternatives.
import pygame, time
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen.fill((128, 128, 128))
pygame.display.update()
#immagine a complex pattern being blit to the screen here
pygame.draw.rect(screen, (128, 0, 0), (0, 0, 50, 50))
pygame.draw.rect(screen, (0, 128, 0), (50, 0, 50, 50))
pygame.draw.rect(screen, (0, 0, 128), (200, 0, 50, 50))
#my complex background area that i want to save ()
area_to_save = pygame.Rect(0, 0, 100, 50)
rest_of_background = pygame.Rect(200, 0, 50, 50)
#updating for demo purposes
dirty_rects = [area_to_save, rest_of_background]
for rect in dirty_rects:
pygame.display.update(rect)
temp_screen = screen.copy()
time.sleep(3)
#after some events happen and I draw the item thats being animated onto the background
item_to_animate = pygame.Rect(35, 10, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
pygame.display.update(item_to_animate)
time.sleep(3)
item_to_animate = pygame.Rect(50, 60, 30, 30)
pygame.draw.rect(screen, (0, 0, 0), item_to_animate)
#now that the item has moved, draw back old frame, which draws over the whole surface
screen.blit(temp_screen, (0, 0))
pygame.display.update()
#I understand swapping the drawing of the new item location to after temp_surface blit
#will provide me the desired outcome in this scenario but this is a compressed version of my problem
#so for simplicity sake, is there a way of not saving the whole surface, only those rects defined?
I expect the output of this code to be displaying my background for 3 seconds, then the black square overlaying the patter and then after another 3 seconds, the black square appearing below my pattern.
P.S.:I am new to this site, let me know if I did something wrong please!
Edit: For anyone wondering if this solution (of saving the background before blitting an item over it and then redrawing the saved background before the new item location is blit over) is more efficient than redrawing the whole background and then blitting the item, using a simple square animation over a chequered pattern with redrawing the whole background each time reduced my overall fps by around 50% from 1000 (before redrawing the background) to 500 average. While using dirty rects and this method above I get around 900 fps.
What you want to do can be achieved by pygame.Surface.blit().
Create a surface with the decided size and blit an area of the screen to this surface. Note, the 3rd argument to bilt is an optional parameter which selects a rectangular area of the source surface:
# create a surface with size 'area_to_save.size'
temp_screen = pygame.Surface(area_to_save.size)
# blit the rectangular area 'area_to_save' from 'screen' to 'temp_screen' at (0, 0)
temp_screen.blit(screen, (0, 0), area_to_save)
You could use subsurface to specify the area you want to copy, and then call copy on the returned Surface.
But note that it may happend that this will not increase the performance of your game at all, since copying a lot of Surfaces around does not come free. Just try and check yourself if it actually better then drawing a background surface every frame.
Also note that you should never use time.sleep in your game. While sleep is blocking your game's process, your game can't process events, so you e.g. can't quit the game in this time. Also, it may happen that your game's window will just not be redrawn if you don't process events by calling pygame.event.get; and once the event queue fills up because you never call pygame.event.get, your game will freeze.
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).
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'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.