Pyglet App Running Slowly - python

I have this application that is creating an alternate version of cookie clicker named pizza clicker. It's very basic but it's running really slowly and I can't get why.
import pyglet
window = pyglet.window.Window(fullscreen=True, caption="Click For Pizzas", resizable=True)
win_width = window.width
win_height = window.height
window.set_fullscreen(False)
window.set_size(win_width, win_height)
image_height = round(int(win_width/5)/1.4, 1)
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
def background(self):
background_img = pyglet.resource.image('pizza_clicker.png')
background_img.width = (win_width/5)*4
background_img.height = win_height
background_img.blit(int(win_width/5), 0, 0.5)
def drawSidebar(self):
width = int(win_width/5)
height = int(win_height)
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
sidebar = sidebar_pattern.create_image(width, height)
sidebar.blit(0, 0)
pizza = []
images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(images)):
divideby = 1.4 / (i + 1)
pizza.append(pyglet.resource.image(images[i]))
pizza[i].width = width
pizza[i].height = round(width/1.4, 1)
pizza[i].blit(0, window.height - (round(width/divideby, 1)))
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
#window.event
def on_mouse_press(x, y, buttons, modifiers):
if buttons & pyglet.window.mouse.LEFT and x > win_width/5:
main.addPoint()
elif buttons & pyglet.window.mouse.LEFT and x < win_width/5:
main.getNumber(y)
#window.event
def on_draw():
window.clear()
main.background()
main.label.draw()
main.drawSidebar()
main = Main()
pyglet.app.run()
So the problem is that when I click on the right side of the window, it should add a point (or many) instantly but it lags for a few seconds. Also, just so nobody get confused, the code does work, but just slowly. What should I do to solve it?

On every draw() iteration, you're doing:
background_img = pyglet.resource.image('pizza_clicker.png')
This means you're loading in the same picture, from hard-drive, every render sequence. You're also doing a for loop over different pizza images where you also fetch them from hard drive:
for i in range (0, len(images)):
divideby = 1.4 / (i + 1)
pizza.append(pyglet.resource.image(images[i]))
I strongly suggest you read up on how resources are loaded in, and use a cProfiler analyzer.
A good example of how you could profile your code, is here.
Since this is a external source, I'll include two SO's links as well that's about equally good (but not as potent or self explaining):
https://stackoverflow.com/a/23164271/929999
Using cProfile results with KCacheGrind
Here's a tl-dr version:
python -m cProfile -o profile_data.pyprof your_script.py
pyprof2calltree -i profile_data.pyprof -k
This should render a so called, "call tree", of all the executions your code did, how long they took and how much memory they used up. All the way from start to bottom of your application.
However, I strongly suggest you do 1 rendering sequence and add a exit(1) after the first render. Just so you profile 1 run, not 60 per second.
Keywords to search for to get a hang of why your code is slow: Python, profiling, kcachegrind, cprofile, cprofiling, callstack.
Spoiler alert
To solve the majority of your problems, move all I/O intensive operations (loading images, creating shapes etc) into the __init__ call of your main class.
The end product would look something like this instead:
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
self.background_img = pyglet.resource.image('pizza_clicker.png')
self.background_img.width = (win_width/5)*4
self.background_img.height = win_height
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
self.sidebar = sidebar_pattern.create_image(width, height)
self.pizzas = []
width = int(win_width/5)
height = int(win_height)
self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(pizza_images)):
resource = pyglet.resource.image(pizza_images[i])
resource.width = width
resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
self.pizzas.append(resource)
def background(self):
self.background_img.blit(int(win_width/5), 0, 0.5)
def drawSidebar(self):
width = int(win_width/5)
height = int(win_height) # You're using win_height here, but window.height later. It's strange.
self.sidebar.blit(0, 0)
for i in range (0, len(self.pizza_images)):
divideby = 1.4 / (i + 1)
self.pizzas[i].blit(0, window.height - (round(width/divideby, 1)))
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
But why stop here, there's a lot of heavy use of blit here. Blit is fine for like one or two objects. But it quickly gets hard to track what and where you're "blitting" everything to. You're also doing a whole lof of division, addition and other sorts of calculations in loops and stuff.
Remember, loops are the devil in when it comes to rendering.
If you got a loop some where, you can almost certainly start looking there for performance issues (anyone looking at this comment and going "pff he has no clue what he's saying".. Yea I know, but it's a good beginners tip).
I strongly suggest you put your images into pyglet.sprite.Sprite() objects instead. They keep track of positions, rendering and most importantly, they support batched rendering. That is your holy grail of the mother land! If anything's going to save you performance wise in pyglet.. well.. 3D rendering in general, it's batched rendering.
See, the graphic card was designed with one thing in mind.. Taking a HUGE mathematical equation and just swallow it whole. It's particularly good at taking a big fat stack of information and just shooting it to your screen. It's not as good at multiple commands. Meaning if you're sending many smaller packets back and fourth to the graphics card, it's going to perform no wear near optimum because of overhead and other things.
So, putting your images into sprites, and putting those sprites into batches, and not using any for loops and on-rendering resources loads..
This is what your code would look like:
class Main(object):
def __init__(self):
self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
x=win_width//2, y=win_height - 100,
anchor_x='left', anchor_y='top')
self.points = 0
self.number = 1
self.background_layer = pyglet.graphics.OrderedGroup(0)
self.foreground_layer = pyglet.graphics.OrderedGroup(1)
self.batch = pyglet.graphics.Batch()
self.background_img = pyglet.sprite.Sprite(pyglet.resource.image('pizza_clicker.png'), batch=self.batch, group=self.background_layer)
self.background_img.width = (win_width/5)*4
self.background_img.height = win_height
self.background.x = int(win_width/5)
self.background.y = 0
sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
self.sidebar = pyglet.sprite.Sprite(sidebar_pattern.create_image(width, height), batch=self.batch, group=self.background_layer)
self.sidebar.x = 0
self.sidebar.y = 0
self.pizzas = []
width = int(win_width/5)
height = int(win_height)
self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
for i in range (0, len(pizza_images)):
divideby = 1.4 / (i + 1)
resource = pyglet.sprite.Sprite(pyglet.resource.image(pizza_images[i]), batch=self.batch, group=self.foreground_layer)
resource.width = width
resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
resource.x = 0
resource.y = window.height - (round(width/divideby, 1))
self.pizzas.append(resource)
def draw(self):
# This is instead of doing:
# - self.background.draw()
# - self.sidebar.draw()
# - self.pizzas[i].draw()
self.batch.draw()
self.label.draw() # You could put this in a batch as well :)
def getNumber(self, y):
if y > window.height - int(image_height):
self.number = 1
elif y > window.height - (int(image_height)*2):
self.number = 5
elif y > window.height - (int(image_height)*3):
self.number = 10
elif y > window.height - (int(image_height)*4):
self.number = 20
def addPoint(self):
self.points += self.number
self.label.text = 'Pizzas: %s' %self.points
#window.event
def on_draw():
window.clear()
main.draw()
Now, the code ain't perfect. But it will hopefully give you a sense of the heading you should be going towards. I haven't executed this code either, mainly because I don't have all the pizza images or the time. I might come back here and do so, and tidy up the (most likely) spelling errors I have.

Related

Move multiple tkinter.canvas figures simultaneously

I want to make many circles to move on a given trajectory simultaneously. Everything works fine only with a single figure moving. Whenever I add another figure it starts speeding up until it starts freezing apparently. This happens no matter what I use threads or canvas.move() and canvas.after() methods.
Actually this is also quite weird, because with my full version of code it started to slow down after adding more figures. The one that I sent is simplified in order to show the issue with moving. Maybe that happens, because I used my own method to draw trajectory lines with many little squares, but that is not the point.
What can be done to move figures at the same time with the same speed without that much of a lag? I wanted to try to use procceses instead of threads, but did not really understand how they work and I doubt that would change anything significantly.
Maybe you could also give an advice on how to work with individual pixels in canvas without drawing rectangle with width of one pixel?
EDIT:
Forgot to mention. For some reason even if there is a single figure moving, if I move my mouse figure's moving will slow down until I stop touching the mouse. I suppose the reason is that tkinter starts registering events, but I need it to draw the trajectory, so removing is not really an option. What can be done to get rid of this issue?
import math
import tkinter as tk
from enum import Enum
import threading
class Trajectory:
# This trajectory should do the same trick both for circles, squares and maybe images
# x0, y0 - start point, x1, y1 - end point
def __init__(self, x0, y0, x1, y1, id, diameter):
print('aaaa', id)
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
self.id = id
self.sign = 1
self.diameter = diameter
self.dir = self.get_normalized_dir()
def has_arrived(self, x, y):
return math.sqrt((self.x1 - (x + self.diameter // 2)) * (self.x1 - (x + self.diameter // 2)) +
(self.y1 - (y + self.diameter // 2)) * (self.y1 - (y + self.diameter // 2))) < 1
def get_normalized_dir(self):
L = math.sqrt((self.x1 - self.x0) * (self.x1 - self.x0) + (self.y1 - self.y0) * (self.y1 - self.y0))
return (self.x1 - self.x0) / L, (self.y1 - self.y0) / L
def swap_points(self):
self.x0, self.y0, self.x1, self.y1 = self.x1, self.y1, self.x0, self.y0
def __str__(self):
return f'x0 {self.x0} y0 {self.y0} x1 {self.x1} y1 {self.y1} id {self.id} sign {self.sign}'
class App(tk.Tk):
def __init__(self):
# action_33 was intented as an Easter egg to smth (at least I think so). However,
# I forgot what it meant :(
super().__init__()
self.bind('<Motion>', self.on_mouse)
self.geometry('400x400')
self.resizable(False, False)
self.canvas = tk.Canvas(self, bg='white', width=400, height=400)
self.canvas.pack(fill="both", expand=True)
self.start_point = []
self.end_point = []
self.is_drawing = False
self.OUTLINE = 'black'
self.canvas.bind("<Button-1>", self.callback)
self.title('Object trajetory')
self.bg_line = None
self.figure_color = 'green'
self.figures = [] # will store only trajectory class
self.diameter = 40
def move_figures(self):
# if not self.is_drawing:
for figure in self.figures:
self.canvas.move(figure.id, figure.dir[0] * 0.1 * figure.sign, figure.dir[1] * 0.1 * figure.sign)
if figure.has_arrived(self.canvas.coords(figure.id)[0], self.canvas.coords(figure.id)[1]):
figure.sign = -figure.sign
figure.swap_points()
self.canvas.after(1, self.move_figures)
def move_for_thread(self, figure):
while True:
self.canvas.move(figure.id, figure.dir[0] * 0.1 * figure.sign, figure.dir[1] * 0.1 * figure.sign)
if figure.has_arrived(self.canvas.coords(figure.id)[0], self.canvas.coords(figure.id)[1]):
figure.sign = -figure.sign
figure.swap_points()
def delete_shadow_line(self):
if self.bg_line is not None:
self.canvas.delete(self.bg_line)
def on_mouse(self, event):
if self.is_drawing:
self.delete_shadow_line()
self.bg_line = self.canvas.create_line(self.start_point[0], self.start_point[1], event.x, event.y)
def callback(self, event):
if not self.is_drawing:
self.start_point = [event.x, event.y]
self.is_drawing = True
else:
self.is_drawing = False
self.bg_line = None
self.end_point = [event.x, event.y]
fig = self.canvas.create_oval(self.start_point[0] - self.diameter // 2,
self.start_point[1] - self.diameter // 2,
self.start_point[0] + self.diameter // 2,
self.start_point[1] + self.diameter // 2,
fill=self.figure_color, outline='')
# self.figures.append(
# Trajectory(self.start_point[0], self.start_point[1], self.end_point[0], self.end_point[1], fig,
# self.diameter))
# self.move_figures()
traj = Trajectory(self.start_point[0], self.start_point[1], self.end_point[0], self.end_point[1], fig, self.diameter)
t = threading.Thread(target=self.move_for_thread, args=(traj,))
t.start()
if __name__ == '__main__':
print('a')
app = App()
app.mainloop()
Using threads is adding more complexity than necessary. The code that uses after is easily capable of doing the animation.
However, you have a critical flaw. You're calling move_figures() every time you draw a line, and each time you call it you are starting another animation loop. Since the animation loop moves everything, the first object gets moved 1000 times per second. When you add two elements, each element is moving 1000 times per second twice. Three elements, it's moving 1000 times per second three times and so on.
So, start by removing the threading code. Then, call move_figures() exactly once, and within it, you should not be calling it with after(1, ...) since that's attempting to animate it 1000 times per second. Instead, you can reduce the load by 90% by using after(10, ...) or some other number bigger than 1.
You can call the function exactly once by calling it from App.__init__ rather than in App.callback.

Python Pyglet slows down the animation. Objects are drawn slowly on the screen

I have repeatedly encountered the problem of slowing down animation when drawing various objects in Python. The problem is especially acute when the number of objects being drawn increases. I would not say that there are too many objects: > = 200 (r=10) circles are already a slide show.
I think it's not about the library I use (pyglet). Before that I used the p5py analog p5.js the brakes were even stronger (in truth, the creators of p5py know that it is much slower than its counterparts (Issues from Github)). I also tried Tkinter - I thought that a clean canvas would solve my problems. But that didn't happen. And now I run the code in Pyglet - I get a slideshow.
I also noticed that the computer starts to take off on the jet thrust of coolers when the animation is slowing down. At the same time, the CPU load of the Python process never exceeds 20% (very strange). Computer not quite a calculator: Intel i5-8300H 4 cores (8 virtual) 2.30 GHz.
What could be the problem? An animation of 1000 points should not turn into a slideshow, even if it is Python.
PS. I also think that the problem is not in my code. Exactly the same (not about the code below) code on p5.js gives smooth animation in the browser (but the browser uses a video card). But just in case, I put my last count on Pyglet - this is not the top of optimization, but it should not work so slowly:
sketch.py
import pyglet
import random
from firework import Firework
window = pyglet.window.Window(400, 400)
main_batch = pyglet.graphics.Batch()
fireworks = [Firework(main_batch)]
#window.event
def on_draw():
window.clear()
main_batch.draw()
def update(dt):
if random.random() < 0.03:
fireworks.append(Firework(main_batch))
for f in fireworks:
f.update()
if f.done():
fireworks.remove(f)
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1 / 120.0)
pyglet.app.run()
particle.py
import p5
import pyglet
class Particle(pyglet.shapes.Circle):
def __init__(self, x, y, color, firework, batch=None):
radius = 2
if firework:
radius = 4
super().__init__(x, y, radius, radius * 5, color, batch)
self.color = color
self.firework = firework
self.opacity = 255
self._done = False
self.acc = p5.Vector(0, 0)
if self.firework:
self.vel = p5.Vector(0, p5.random_uniform(8, 12))
else:
self.vel = p5.Vector.random_2D() * p5.random_uniform(-10, -2)
#property
def batch(self):
return self._batch
def update(self):
self.acc += p5.Vector(0, -0.2)
if not self.firework:
self.vel *= 0.9
if self.opacity > 4:
self.opacity -= 4
else:
self._done = True
self.opacity = 0
self.vel += self.acc
pos = p5.Vector(self.x, self.y) + self.vel
self.x = pos.x
self.y = pos.y
self.acc *= 0
def done(self):
return self._done
firework.py
import p5
import random
from particle import Particle
class Firework:
def __init__(self, batch):
self.color = tuple(random.randint(0, 255) for _ in range(3))
self.firework = Particle(p5.random_uniform(400), 0, self.color, True, batch=batch)
self.bach = batch
self._exploded = False
self.particles = []
def done(self):
if self._exploded and len(self.particles) == 0:
return True
else:
return False
def explode(self):
for _ in range(70):
p = Particle(self.firework.x, self.firework.y, self.color, False, batch=self.firework.batch)
self.particles.append(p)
def update(self):
if not self._exploded:
self.firework.update()
if self.firework.vel.y < 0:
self._exploded = True
self.explode()
self.firework.delete()
for p in self.particles:
p.update()
if p.done():
self.particles.remove(p)
p.delete()

How to prevent fast moving objects passing through statics when calculating pi with colliding blocks

I am trying to implement a similar program to the following in Python using Pymunk and Pyglet. My current implementation works well at low velocities however at high speeds the block can pass through the static wall. This because in 1/60s clock cycle the block moves further than the thickness of the wall. I have seen others solve this by having a limiting speed however in my case this would not work as the velocity is important to calculate the value for PI. I would like to know if there is any way of preventing this from happening.
import pyglet
import pymunk
class Block:
"""
The class for a block
Mass: the mass the block
X: Initial x position
Y: Initial y position
PhysSpace: The physics space to add items to
RenderBatch: Batch to add block to
"""
def __init__(self, Mass, X, Y, PhysSpace, RenderBatch):
# Create body with given mass and infinite moment of inertia
self.Body = pymunk.Body(Mass, pymunk.inf)
# Set Body's position
self.Body.position = X, Y
# Create shape for body
BodyShape = pymunk.Poly.create_box(self.Body, size=(50, 50))
# Define shapes elasticity
BodyShape.elasticity = 1
# Add block to the physics space
PhysSpace.add(self.Body, BodyShape)
# Import block image
BlockImg = pyglet.image.load('res/sqr.png')
# Set anchor point of image to be the centre
BlockImg.anchor_x = BlockImg.width // 2
BlockImg.anchor_y = BlockImg.height // 2
# Create sprite for block
self.BlockSprite = pyglet.sprite.Sprite(BlockImg, x=self.Body.position.x, y=self.Body.position.y,
batch=RenderBatch)
def update(self):
# Set the position of the sprite to be equal to the position of the physics body
self.BlockSprite.position = self.Body.position
def give_velocity(self, velocity):
# Set velocity of the body
self.Body.velocity = (velocity, 0)
class Simulation(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set background to be clear
pyglet.gl.glClearColor(1, 1, 1, 1)
# Set clock speed
pyglet.clock.schedule_interval(self.update, 1/60)
# Create batch to draw all the graphics with
self.Batch = pyglet.graphics.Batch()
# Create Title Label
self.TitleLabel = pyglet.text.Label(text='Block Collision Simulator', x=self.width / 2, y=self.height - 20,
batch=self.Batch, anchor_x='center', anchor_y='center', font_size=24,
color=(0, 0, 0, 255))
self.Counter = -2
self.CounterLabel = pyglet.text.Label('Counter = 0'.format(self.Counter), x=self.width / 2, y=self.height - 60, anchor_x='center',
anchor_y='center', font_size=24, color=(0, 0, 0, 255), batch=self.Batch)
# Initiate space for Physics engine
self.Space = pymunk.Space()
self.Handler = self.Space.add_default_collision_handler()
self.Handler.begin = self.coll_begin
# Create the ground in physics engine
Ground = pymunk.Poly.create_box(self.Space.static_body, size=(self.width, 20))
Ground.body.position = self.width / 2, 10
self.Space.add(Ground)
# Create the sprite for the ground
GroundImg = pyglet.image.load('res/ground.png')
self.GroundSprite = pyglet.sprite.Sprite(GroundImg, x=0, y=0, batch=self.Batch)
# Create Wall in physics engine
Wall = pymunk.Poly.create_box(self.Space.static_body, size=(20, self.height))
Wall.body.position = 10, self.height / 2
Wall.elasticity = 1
self.Space.add(Wall)
# Create the sprite for the wall
WallImg = pyglet.image.load('res/wall.png')
self.WallSprite = pyglet.sprite.Sprite(WallImg, x=0, y=0, batch=self.Batch)
self.BlockRight = Block(10000, 2 * (self.width / 3), 45, self.Space, self.Batch)
self.BlockRight.give_velocity(-100)
self.BlockLeft = Block(1, self.width / 3, 45, self.Space, self.Batch)
pyglet.app.run()
def coll_begin(self, arbiter, space, data):
self.Counter += 1
if self.Counter > 0:
self.CounterLabel.text = 'Counter: {}'.format(self.Counter)
return True
def on_draw(self):
self.clear()
self.Batch.draw()
def update(self, dt):
self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()
One way is as you write to limit the velocity. Another way is to call the step function with a smaller dt. (On the same note, you should almost always use a fixed value for the dt, that will help to keep the simulation stable).
One way to use smaller dt is to call the step function multiple times for each call to the update function. So you can try something like this:
def update(self, dt):
for _ in range(10):
self.Space.step(dt/10)
#self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()

Create a Racing Car in Python, Why is my car not racing?

I am working on a homework problem for my Python course that is supposed to display a car (got that) and then the car is supposed to simulate driving across the window until it reaches the end, and then restarting at the beginning. I have a race function that is supposed to be doing this but it's not doing anything. I'm having a hell of a time with classes and tkinter. I know that I need the value of x to be incremented each time it loops and updates so that the car looks like its moving, I'm just not sure how to implement that. Is my boolean not written correctly? I've tried changing things around a few different ways with the function but I can't seem to get it to do anything, so I must be missing something.
UPDATE: I have moved the race method into the class, and called it within the constructor. I put a print statement to get the value of x in the race method and its showing that x is being incremented correctly, but when I run the program my car disappears. So it's updating the value of x as it should, but its not displaying the graphic.
Code updated below
Any suggestions are appreciated!
# Import tkinter
from tkinter import *
# Set the height and Width of the window
width = 800
height = 800
# Create race car class with canvas as the argument
class RacingCar(Canvas):
# Constructor
def __init__(self, master, width, height):
# Constructor
Canvas.__init__(self, master, width = width, height = height)
# Create x and y variables for starting position
self.x = 10
self.y = 40
# Display the car
self.display_car()
self.race()
# Function to display car
def display_car(self):
# Delete original car
self.delete("car")
# Create first wheel
self.create_oval(self.x + 10, self.y - 10, self.x + 20,\
self.y, fill = "black", tags = "car")
# Create the second wheel
self.create_oval(self.x + 30, self.y - 10, self.x + 40,\
self.y, fill = "black", tags = "car")
# Create the body
self.create_rectangle(self.x, self.y - 20, self.x + 50,\
self.y - 10, fill = "green", tags = "car")
# Create the roof
self.create_polygon(self.x + 10, self.y - 20, self.x + 20,\
self.y - 30, self.x + 30, self.y - 30,\
self.x + 40, self.y - 20, fill = "green",\
tags = "car")
def race():
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()
window = Tk()
window.title("Racing Car")
racecar = RacingCar(window, width = 240, height = 50 )
racecar.pack()
window.mainloop()
This might not get it done (so I make this answer a community wiki), but I suggest you move the race method in the class:
class RaceCar(Canvas):
# Your current code
def race(self):
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()

Even faster reading desktop color info

Is there any way to achieve faster color reading than what I wrote?
class MyApi:
def __init__(self):
self.width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
self.height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
self.left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
self.top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
self.hwin = win32gui.GetDesktopWindow()
self.hwindc = win32gui.GetWindowDC(self.hwin)
self.srcdc = win32ui.CreateDCFromHandle(self.hwindc)
self.memdc = self.srcdc.CreateCompatibleDC()
self.bmp = win32ui.CreateBitmap()
self.bmp.CreateCompatibleBitmap(self.srcdc, self.width, self.height)
self.memdc.SelectObject(self.bmp)
def get_xy_color(self, *args):
self.memdc.BitBlt((0, 0), (self.width, self.height), self.srcdc, (self.left, self.top), win32con.SRCCOPY)
bmp = self.bmp.GetBitmapBits(False)
# colors = []
# for pixel in args:
# tmp = (pixel[1] * self.width + pixel[0]) * 4
# colors += [255 + c if c < 0 else c for c in bmp[tmp:tmp+3]][::-1]
# return colors
Uncommented get_xy_color part takes about 0.11 ms - it is far too long for my purposes. Can it be faster?
BitBlt is pretty fast, but GetBitmapBits is taking long time.
Also I can say that I don't need whole screen info, I would be happy if I would get some part of it, for example from point (100, 100) to (150, 150).
The purpose is to get a colors at least 30 times a second for Ambilight project.
Here you go jotto:
self.bmp = win32ui.CreateBitmap()
self.bmp.CreateCompatibleBitmap(self.srcdc, self.width, 80)
self.memdc.SelectObject(self.bmp)
def get_xy_color(self, left, right):
self.memdc.BitBlt((0, -500), (self.width, 580), self.srcdc, (self.left, self.top), win32con.SRCCOPY)
bmp = self.bmp.GetBitmapBits(False)
This will focus on a line between 500 and 500 px on height and full width of screen. I was just not aware that I can use negative values. I have checked how to do this by saving bitmap as a file and seeking for good solution.
For now I do not proccess whole screen and reduced time to between 10 and 20 ms. WHich is perfect result for me.
Purpose of the code was for AmbiLight projects. Colors are being send through Serial Port to Arduino, which proccess this to LED colors.
With this solution I am able to do between 39 and 60 gets&sends to Arduino - good result.

Categories