Troubles with displaying background on Python. Canvas - python

from tkinter import *
WIDTH = 800
HEIGHT = 950
RAD = 20
SPEED = 30
class mainHero():
def __init__(self):
self.image = PhotoImage(file="darth_vader.png")
self.imageSize = [112,180]
self.right_side = WIDTH/2+self.imageSize[0]/2
self.left_side = WIDTH/2-self.imageSize[0]/2
self.obj = canv.create_image((WIDTH/2,HEIGHT-self.imageSize[0]/2), image = self.image)
def move(self, event):
if event.keysym == "Right":
if self.right_side+SPEED<WIDTH:
canv.move(self.obj, SPEED, 0)
self.right_side += SPEED
self.left_side += SPEED
if event.keysym == "Left":
if self.left_side-SPEED>0:
canv.move(self.obj, -SPEED, 0)
self.right_side -= SPEED
self.left_side -= SPEED
print(self.left_side, self.right_side)
root = Tk()
root.title("YeGame")
root.minsize(width = WIDTH, height = HEIGHT)
root.maxsize(width = WIDTH, height = HEIGHT)
canv = Canvas(root,width =WIDTH,height = HEIGHT, bg="green")
canv.create_rectangle(0, 0, WIDTH%SPEED-1, HEIGHT, fill = "yellow")
canv.create_rectangle(WIDTH-WIDTH%SPEED+1, 0, WIDTH, HEIGHT, fill = "yellow")
m = canv.create_image((100,100), image = PhotoImage(file="smallnight.gif"))
canv.pack()
canv.focus_set()
me = mainHero()
canv.bind("<KeyPress>", me.move)
root.mainloop()
This code should create game. But I don't understand why I can see Darth Vader on Canvas but can't see background of Canvas. It should be image of Space. Code which add Darth Vader works, but code which add background doesn't work. Show me please how I can do it in right way.
Photo

The image should be declared before calling create_image like you did above for mister Vader:
root = Tk()
root.title("YeGame")
root.minsize(width = WIDTH, height = HEIGHT)
root.maxsize(width = WIDTH, height = HEIGHT)
canv = Canvas(root,width =WIDTH,height = HEIGHT, bg="green")
canv.create_rectangle(0, 0, WIDTH%SPEED-1, HEIGHT, fill = "yellow")
canv.create_rectangle(WIDTH-WIDTH%SPEED+1, 0, WIDTH, HEIGHT, fill = "yellow")
background = PhotoImage(file="smallnight.gif")
m = canv.create_image((100,100), image = background)
canv.pack()
canv.focus_set()
me = mainHero()
canv.bind("<KeyPress>", me.move)
root.mainloop()

Related

Bouncing dvd logo in python tkinter

I am trying to make a bouncing DVD logo in tkinter, but I don't really know how to make it, it works with a ball, the logo doesn't move. The logo
# 1) create main window
from tkinter import *
from PIL import ImageTk, Image
fen = Tk()
fen.title('AllTech - Bouncing ball')
fen.resizable(False, False)
# 2) create canvas and ball
WIDTH, HEIGHT = 400, 300
canvas = Canvas(fen, width=WIDTH, height=HEIGHT)
canvas.pack()
img = ImageTk.PhotoImage(Image.open("dvd.gif"))
# ball = canvas.create_oval(10, 10, 50, 50, fill='black')
# 3) move the ball
xspeed = yspeed = 3
frame = Frame(fen, width=600, height=400)
frame.pack()
frame.place(anchor='center', relx=0.5, rely=0.5)
label = Label(frame, image = img)
label.pack()
def moveBall():
global xspeed, yspeed
canvas.move(canvas, xspeed, yspeed)
(leftPos, topPos, rightPos, bottomPos) = canvas.coords(img)
if leftPos <= 0 or rightPos >= WIDTH:
xspeed = -xspeed
if topPos <= 0 or bottomPos >= HEIGHT:
yspeed = -yspeed
img.after(30, moveBall)
canvas.after(30, moveBall)
fen.mainloop()
I tried with a ball ad it's work, but I don't know why, it doesn't with the logo.
You need to put the image using canvas.create_image() and then you can move the image using canvas.move().
from tkinter import *
from PIL import ImageTk, Image
fen = Tk()
fen.title('AllTech - Bouncing ball')
fen.resizable(False, False)
WIDTH, HEIGHT = 400, 300
canvas = Canvas(fen, width=WIDTH, height=HEIGHT, bg="white")
canvas.pack()
img = ImageTk.PhotoImage(Image.open("dvd.gif"))
# put the image into canvas
logo = canvas.create_image(0, 0, image=img, anchor="nw")
xspeed = yspeed = 3
def moveLogo():
global xspeed, yspeed
# move the image
canvas.move(logo, xspeed, yspeed)
# get bounding box of the image
(leftPos, topPos, rightPos, bottomPos) = canvas.bbox(logo)
if leftPos <= 0 or rightPos >= WIDTH:
xspeed = -xspeed
if topPos <= 0 or bottomPos >= HEIGHT:
yspeed = -yspeed
canvas.after(30, moveLogo)
canvas.after(30, moveLogo)
fen.mainloop()

Python Tkinter - Draw Shape to Screen Without Creating New Instance

I'm tying to create a basic program for drawing to the screen by creating ovals every frame when the mouse is clicked. However, as the program runs for a bit, it starts becoming very choppy and the circles stop forming cohesive lines, due to the code not running fast enough to process precise mouse movement.
Here is my code -
import tkinter as tk
DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40
def draw_palette(canvas):
canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill = 'light grey', width= 0)
canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill = 'dark grey', width = 1)
canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill = 'dark grey',width = 1)
canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text = 'clear screen') #non-functional
class Brush():
def __init__(self,stroke_size,stroke_color):
self.size = stroke_size
self.color = stroke_color
self.mode = 'draw'
self.pos = (0,0)
self.clicked = False
def render(self,canvas):
if self.clicked:
canvas.create_oval( self.pos.x-self.size/2, self.pos.y-self.size/2,
self.pos.x+self.size/2, self.pos.y+self.size/2,
width = 0, fill = self.color )
def mouse_moved(self,event):
self.pos = event
def mouse_clicked(self,throwaway):
self.clicked = True
def mouse_released(self,throwaway):
self.clicked = False
#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH,DRAW_HEIGHT+PALETTE_HEIGHT))
c = tk.Canvas(root, width = DRAW_WIDTH, height = DRAW_HEIGHT + PALETTE_HEIGHT, bg = 'white')
c.pack()
b = Brush(40,'black')
#bind actions to functions
c.bind("<Button-1>",b.mouse_clicked)
c.bind("<ButtonRelease-1>",b.mouse_released)
c.bind("<Motion>",b.mouse_moved)
#main loop
while 1:
b.render(c)
draw_palette(c)
root.update()
I suppose I'm just asking if there's any way I can speed this up, but specifically I'm wondering if I can draw the shapes to the screen without using create_shape() every time.
For example,
oval = c.create_oval()
while 1:
canvas.draw(oval)
I know you can do something similar with canvas.move(), but I couldn't find anything that fit my situation.
I don't understand why you created loop while 1 and run render() and draw_palette() hundreds of times even if you don't need it.
I draw new circle in mouse_moved() and use root.mainloop() and it runs much better and create smoother line. Probably if I would draw line from previous place to current place or many ovals with some step then I would get even better line
EDIT: I changed little to draw first oval in mouse_click() - so I can see first oval even if I only click and don't move.
import tkinter as tk
# --- constanst ---
DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40
# --- classes ---
class Brush():
def __init__(self,stroke_size,stroke_color):
self.size = stroke_size
self.color = stroke_color
self.mode = 'draw'
self.pos = (0,0)
self.clicked = False
def draw(self):
s = self.size/2
c.create_oval(
self.pos.x-s, self.pos.y-s,
self.pos.x+s, self.pos.y+s,
width=0, fill=self.color
)
def mouse_moved(self, event):
if self.clicked:
self.pos = event
self.draw()
def mouse_clicked(self, event):
self.clicked = True
self.pos = event
self.draw()
def mouse_released(self, event):
self.clicked = False
# --- functions ---
def draw_palette(canvas):
canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill='light grey', width=0)
canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text='clear screen') #non-functional
# --- main ---
#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH, DRAW_HEIGHT+PALETTE_HEIGHT))
c = tk.Canvas(root, width=DRAW_WIDTH, height=DRAW_HEIGHT+PALETTE_HEIGHT, bg='white')
c.pack()
b = Brush(40, 'black')
#bind actions to functions
c.bind("<Button-1>", b.mouse_clicked)
c.bind("<ButtonRelease-1>", b.mouse_released)
c.bind("<Motion>", b.mouse_moved)
draw_palette(c)
root.mainloop()
EDIT:
I added function which adds ovals if distance between previous and current position is too big and there is gap. Now line is smooth even if mouse moves fast.
import tkinter as tk
# --- constanst ---
DRAW_HEIGHT = 560
DRAW_WIDTH = 560
PALETTE_HEIGHT = 40
# --- classes ---
class Brush():
def __init__(self,stroke_size,stroke_color):
self.size = stroke_size
self.color = stroke_color
self.mode = 'draw'
self.pos = None
self.prev = None
self.clicked = False
def draw_oval(self, x, y):
r = self.size/2 # radius
c.create_oval(x-r, y-r, x+r, y+r, width=0, fill=self.color)
def draw(self):
if self.pos:
self.draw_oval(self.pos.x, self.pos.y)
if self.prev:
# calculate distance between ovals
dx = self.pos.x - self.prev.x
dy = self.pos.y - self.prev.y
max_diff = max(abs(dx), abs(dy))
# add ovals if distance bigger then some size of oval (tested with //4, //8, //6, //5)
if max_diff > (self.size//6):
# how many ovals to add
parts = max_diff//(self.size//6)
# distance between ovals
step_x = dx/parts
step_y = dy/parts
# add ovals except first which is already on canvas
for i in range(1, parts):
x = self.pos.x - i*step_x
y = self.pos.y - i*step_y
self.draw_oval(x, y)
def mouse_moved(self, event):
if self.clicked:
self.prev = self.pos
self.pos = event
self.draw()
def mouse_clicked(self, event):
self.clicked = True
self.prev = None
self.pos = event
self.draw()
def mouse_released(self, event):
self.clicked = False
self.prev = None
self.pos = None
# --- functions ---
def draw_palette(canvas):
canvas.create_rectangle(0, 0, DRAW_WIDTH, PALETTE_HEIGHT, fill='light grey', width=0)
canvas.create_rectangle(DRAW_WIDTH/8, PALETTE_HEIGHT/5, 3*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
canvas.create_rectangle(5*DRAW_WIDTH/8, PALETTE_HEIGHT/5, 7*DRAW_WIDTH/8, 4*PALETTE_HEIGHT/5, fill='dark grey', width=1)
canvas.create_text(DRAW_WIDTH/4, PALETTE_HEIGHT/2, text='clear screen') #non-functional
# --- main ---
#set up root window and canvas
root = tk.Tk()
root.geometry('{}x{}'.format(DRAW_WIDTH, DRAW_HEIGHT+PALETTE_HEIGHT))
c = tk.Canvas(root, width=DRAW_WIDTH, height=DRAW_HEIGHT+PALETTE_HEIGHT, bg='white')
c.pack()
b = Brush(40, 'black')
#bind actions to functions
c.bind("<Button-1>", b.mouse_clicked)
c.bind("<ButtonRelease-1>", b.mouse_released)
c.bind("<Motion>", b.mouse_moved)
draw_palette(c)
root.mainloop()

Updatable tkinter canvas with mainloop

I want to be able to make my self a graphical api in Tkinter for a project
using Python 2.7
import Tkinter as tk
# import tkinter as tk # if you are using python 3
class Graphics(tk.Tk):
def __init__(self, width=60, height=60, pixel_width=10):
#super().__init__()
tk.Tk.__init__(self)
self.width, self.height = width, height
self.pixel_width = pixel_width
# self.geometry(f'{self.width*self.pixel_width}x{self.height*self.pixel_width}')
self.geometry('600x600')
self.my_canvas = tk.Canvas(
self,
width = self.width * self.pixel_width,
height = self.height * self.pixel_width,
)
self.pixels = [[None for x in range(self.width)] for y in range(self.height)]
self.fill_area((0, 0), (self.width, self.height), 'white')
self.fill_point((30, 30),'red')
self.fill_area((10, 10), (15, 20), 'yellow')
self.my_canvas.pack()
self.run()
def fill_point(self, point, color):
pixel = self.pixels[point[0]][point[1]]
if pixel is None:
cx0 = self.pixel_width * point[0]
cy0 = self.pixel_width * point[1]
cx1 = self.pixel_width * (point[0] + 1)
cy1 = self.pixel_width * (point[1] + 1)
self.pixels[point[0]][point[1]] = self.my_canvas.create_rectangle(cx0, cy0, cx1, cy1, fill=color) # outline=''
else:
self.my_canvas.itemconfig(pixel, fill=color)
def fill_area(self, pointA, pointB, color):
for x in range(pointA[0], pointB[0]):
for y in range(pointA[1], pointB[1]):
self.fill_point((x, y), color)
def run(self):
self.mainloop()
g = Graphics()
g.fill_point((9,9),'blue')
As I've understood the mainloop method is blocking any further actions on the canvas and I need a solution for an updatable screen.
I tried using threads with mainloop and update methods but it would just immediately exit the window.
Thank you for helping and have a nice day
You probably need to read a little bit about the tkinter canvas, and its immense capabilities: unlike pygame, or other graphics canvasses, it does not need to refresh at a given frequency; canvas items can be addressed individually, and their attributes set precisely.
I reworked your code to display a canvas made of 'scaled pixels': here 60 x 60 pixels wide, with each pixels scaled up by a factor 10.
The 2D list self.pixels contains canvas items; the updates directly change the attributes of these items.
You can remove the black lines around each pixel by setting their attribute outline to the empty string (see comment in the code).
import Tkinter as tk
# import tkinter as tk # if you are using python 3
class Graphics(tk.Tk):
def __init__(self, width=60, height=60, pixel_width=10):
super().__init__(self)
self.width, self.height = width, height
self.pixel_width = pixel_width
# self.geometry(f'{self.width*self.pixel_width}x{self.height*self.pixel_width}')
self.geometry('600x600')
self.my_canvas = tk.Canvas(
self,
width = self.width * self.pixel_width,
height = self.height * self.pixel_width,
)
self.pixels = [[None for x in range(self.width)] for y in range(self.height)]
self.fill_area((0, 0), (self.width, self.height), 'white')
self.fill_point((30, 30),'red')
self.fill_area((10, 10), (15, 20), 'yellow')
self.my_canvas.pack()
self.run()
def fill_point(self, point, color):
pixel = self.pixels[point[0]][point[1]]
if pixel is None:
cx0 = self.pixel_width * point[0]
cy0 = self.pixel_width * point[1]
cx1 = self.pixel_width * (point[0] + 1)
cy1 = self.pixel_width * (point[1] + 1)
self.pixels[point[0]][point[1]] = self.my_canvas.create_rectangle(cx0, cy0, cx1, cy1, fill=color) # outline=''
else:
self.my_canvas.itemconfig(pixel, fill=color)
def fill_area(self, pointA, pointB, color):
for x in range(pointA[0], pointB[0]):
for y in range(pointA[1], pointB[1]):
self.fill_point((x, y), color)
def run(self):
self.mainloop()
g = Graphics()

scrolling a background image in pyglet

I am new to pyglet.I am trying to a have a seamlessly scrolling background.
I have tried everything i could find on stack and google. I turned vsync off because it improved the performance in my computer. I have even used batch to draw on my screen. Used the preload function to load my image and in my Game class convert it to sprite.
What else can i do to make my background scroll smoothly?
Thanks in Advance.
This is my code:
import pyglet
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE)
pyglet.clock.set_fps_limit(60)
WIDTH = 800
HEIGHT = 600
TITLE = 'Bunny'
def preload_image(img):
pyglet.resource.path = ['../res']
pyglet.resource.reindex()
image = pyglet.resource.image(img)
return image
class main(pyglet.window.Window):
def __init__(self):
super(main, self).__init__(800, 600, fullscreen=False, vsync=False)
self.x, self.y = 0, 0
self.sprites = {}
self.batches = {}
self.subgroups = {}
self._handles = {}
self.batches['main'] = pyglet.graphics.Batch()
self.subgroups['base'] = pyglet.graphics.OrderedGroup(0)
self.background = preload_image('mountains.png')
self.background.width = WIDTH
self.background.height = HEIGHT
self.background_sprite = pyglet.sprite.Sprite(self.background, 0, 0, batch=self.batches['main'])
self.background_sprite1 = pyglet.sprite.Sprite(self.background, WIDTH, 0, batch=self.batches['main'])
self.speed = 30
self.frame_rate = 60.0
pyglet.clock.schedule_interval(self.run, 1.0 / self.frame_rate)
pyglet.clock.set_fps_limit(self.frame_rate)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def render(self):
self.clear()
for batch_name, batch in self.batches.items():
batch.draw()
for sprite_name, sprite in self.sprites.items():
sprite._draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
dt = 1/pyglet.clock.get_fps_limit()
print(1/dt)
self.background_sprite.x -= self.speed * dt
self.background_sprite1.x -= self.speed * dt
if self.background_sprite1.x < -WIDTH: self.background_sprite1.x = WIDTH
if self.background_sprite.x < -WIDTH: self.background_sprite.x = WIDTH
event = self.dispatch_events()
x = main()
x.run()
As you can see in the image, there is a small black line in between the two background scrolling images

How should I implement a keybinded space function in python TKinter?

I am trying to make a GUI using python's Tkinter Module that will make a picture of a spaceship land on a platform. I want to be able to keybind the spacebar so that it is an event that will increase the velocity of y(vy) by 3 meters/second. But for some reason, when I try to implement it , it doesn't work. This is my code, can anyone help me?
from Tkinter import Tk, Canvas
from PIL import Image, ImageTk
w,h= 800,600
g = -1.6
dt = 0.05
y = 100.0
vy = 0.0
t = 0.0
def tick():
global y, vy, t
y+= vy*dt
vy += g*dt
t+= dt
print t, y, vy
yp = 0.7*h-0.7*h*y/100.0
canvas.coords(ship, w/4, yp)
canvas.itemconfigure(txt,text='%0.2f'%vy)
if y >= 0.0:
canvas.after(1,tick)
root = Tk()
canvas = Canvas(root, width = w, height = h, bg = 'black')
canvas.pack()
img1 = Image.open('earthrise.jpg').resize((w,h))
pmg1 = ImageTk.PhotoImage(img1)
ship = canvas.create_image(w/2,h/2, image = pmg1)
img2 = Image.open('eagle.jpg').resize((200,200))
pmg2 = ImageTk.PhotoImage(img2)
ship = canvas.create_image(w/4,0, image = pmg2)
canvas.create_rectangle(w/4-150, int(0.5+0.7*h)+100,w/4 + 150,
int(0.5+0.7*h)+125, outline = 'green', fill = 'green')
f=('Times',36,'bold')
txt=canvas.create_text(w-100,50,text='0.0',font=f,fill='white')
def space(evt):
vy+=3
root.bind('<space>',space)
print t,y,vy
canvas.after(1000, tick)
root.mainloop()

Categories

Resources