Tkinter: mini canvas windows as a slider - python

I am new to Tkinter. I want to control the movement of the image with a cursor and a small window:
enter image description here
I tried with this code, but the result is not really what I'm looking for.
import tkinter as tk
from io import BytesIO
import requests
from PIL import Image , ImageTk
def full_dimensions(imag_fs):
top = tk.Toplevel(root)
img = tk.Label(top, image=imag_fs)
img.pack()
def get_image():
_url = 'https://i.imgur.com/4m7AHVu.gif'
_img = requests.get(_url)
if _img.status_code == 200:
_content = BytesIO(_img.content)
else:
_content = 'error.gif'
print('image loaded')
return _content
root = tk.Tk()
_content = get_image()
_x = Image.open(_content)
imag_fs = ImageTk.PhotoImage(_x)
_x.thumbnail((100, 100), Image.ANTIALIAS)
imag = ImageTk.PhotoImage(_x)
img = tk.Button(root, image=imag, command=lambda: full_dimensions(imag_fs))
img.grid(column=3, row=1)
root.mainloop()
I tested a window, but when I import an image I can't control
import tkinter as tk
main_window = tk.Tk()
def check_hand_enter():
canvas.config(cursor="hand1")
def check_hand_leave():
canvas.config(cursor="")
canvas = tk.Canvas(width=200, height=200)
tag_name = "polygon"
canvas.create_polygon((25, 25), (25, 100), (125, 100), (125, 25), outline='black', fill="", tag=tag_name)
canvas.tag_bind(tag_name, "<Enter>", lambda event: check_hand_enter())
canvas.tag_bind(tag_name, "<Leave>", lambda event: check_hand_leave())
canvas.pack()
main_window.mainloop()

So here is how I would do this (a simple example):
# import all necessary modules and classes
from tkinter import Tk, Canvas, Frame
from PIL import Image, ImageTk
import requests
# checking if the file exists, if it doesn't exist download it, if can't download it, exit the program
try:
open('space.jpg')
except FileNotFoundError:
url = 'https://images5.alphacoders.com/866/866360.jpg'
img = requests.get(url)
if img.status_code == 200:
with open('space.jpg', 'wb') as file:
file.write(img.content)
print('File not found. Downloaded the necessary file.')
else:
print('File not found. Could not download the necessary file.')
exit()
class MovableImage(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
# dictionary for storing information about movement
self.start_coords = {'x': 0, 'y': 0, 'move': False}
# dictionary for storing information about movement
self.start_coords_main = {'x': 0, 'y': 0, 'move': False}
# loads the image
self.image = Image.open('space.jpg')
# sets the images to their corresponding variables so that they can be referenced later
# resizes the smaller image to fit the navigation window
self.main_image = ImageTk.PhotoImage(self.image)
self.nav_image = ImageTk.PhotoImage(self.image.resize((200, 100), Image.ANTIALIAS))
# creates the canvas to store the bigger image on
self.main_canvas = Canvas(self, width=700, height=500, highlightthickness=0)
self.main_canvas.pack()
# puts image on canvas
self.main_image_id = self.main_canvas.create_image((0, 0), image=self.main_image, anchor='nw', tags='main_image')
# creates the smaller canvas that will be used for navigation
self.nav_canvas = Canvas(self.main_canvas, width=200, height=100, highlightthickness=0)
# adds the smaller canvas as a window to the main_canvas
self.main_canvas.create_window((500, 400), window=self.nav_canvas, anchor='nw', tags='nav_canvas')
# adds the resized image to nav_canvas
self.nav_canvas.create_image((0, 0), image=self.nav_image, anchor='nw')
# creates a rectangle to indicate the current view of the image
self.nav_box = self.nav_canvas.create_rectangle((0, 0, 70, 50), outline='white')
# binds functions
self.main_canvas.bind('<Button-1>', self.set_start_coords_main)
self.main_canvas.bind('<B1-Motion>', self.move_coords_main)
# binds functions
self.nav_canvas.bind('<Button-1>', self.set_start_coords)
self.nav_canvas.bind('<B1-Motion>', self.move_coords)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords(self, event):
x1, y1, x2, y2 = self.nav_canvas.coords(self.nav_box)
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords['x'] = event.x - x1
self.start_coords['y'] = event.y - y1
self.start_coords['move'] = True
else:
self.start_coords['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords(self, event):
if not self.start_coords['move']:
return
dx = self.start_coords['x']
dy = self.start_coords['y']
x = event.x - dx
y = event.y - dy
if x < 0:
x = 0
elif x + 70 > 200:
x = 130
if y < 0:
y = 0
elif y + 50 > 100:
y = 50
self.nav_canvas.coords(self.nav_box, x, y, x + 70, y + 50)
self.main_canvas.coords(self.main_image_id, -x * 10, -y * 10)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords_main(self, event):
x1, y1, x2, y2 = self.main_canvas.bbox('main_image')
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords_main['x'] = event.x - x1
self.start_coords_main['y'] = event.y - y1
self.start_coords_main['move'] = True
else:
self.start_coords_main['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords_main(self, event):
if not self.start_coords_main['move']:
return
dx = self.start_coords_main['x']
dy = self.start_coords_main['y']
x = event.x - dx
y = event.y - dy
if x < -1300:
x = -1300
elif x > 0:
x = 0
if y < -500:
y = -500
elif y > 0:
y = 0
self.nav_canvas.coords(self.nav_box, -x / 10, -y / 10, -x / 10 + 70, -y / 10 + 50)
self.main_canvas.coords(self.main_image_id, x, y)
# basic Tk() instance and afterwards the root.mainloop()
root = Tk()
MovableImage(root).pack()
# mainloop
root.mainloop()
Few things to mention: this is a pretty hardcoded sample that pretty much works well with only images whose resolution is 2000x1000 pixels, other images may not be shown correctly or resizing won't look as good. With this issue You will have to deal Yourself or ask another question about what issues You encountered while trying to make this adjustable. So it would be great if any image could be put up there and it would work.
About the code, it is pretty simple:
Import modules
check for file (there could be a better way but this works too), then if the file does not exist just download it, if that can't be done, well just exit the program.
Then setting up some references (a dictionary so that global doesn't have to be used and also I would say that a dictionary is better for other reasons too like all the necessary variables are in one place and for example x and y variable names are not taken globally).
Then setting the first function which will register the mouse click. This is done to get the position of mouse relative to the moveable square (basically where is the mouse on the square if the square was an independent window or sth) and it is made possible to move, but before that the function checks if the mouse is inside the square at all. If not moving is disabled so that for example if You were to click outside of the square and start moving it does not do that.
Then the moving function is defined. There the relative x and y coords are retrieved but first it checks if it can move and if it cannot, it stops the execution of that function. Then some more calculations and checking for border (again this is pretty hardcoded and should be changed to a more dynamic function so that it can detect this based on picture). Then comes the moving part which just move both the navigation box and the actual picture to their according spots (this is somewhat hardcoded but basically if You were to keep the scale to 1/10 of the smaller box, then this specific part would work with different images).
Then comes the basic Tk() initiation and later the .mainloop().
In the middle just open the image and set it to 2 variables and resize the one that will go to the navigation box.
Then create the main canvas where the main image will be shown.
Then add that image to canvas and keep the id reference so that the image can be later moved.
Create the smaller canvas and don't pack it or anything but add the instance as a window to the main canvas so that it is on top of it (as in the photo in Your question). Again it is placed in a hardcoded manner.
Then add the navigation image to the navigation canvas and add the small box that will be moved too.
Then bind functions to mouse activity in nav_canvas.
If You have questions ask. Note that the try/except is not necessary if You are using Your own images and it will always come with the program. It is there because You may not have this exact picture with those exact dimensions with that exact name so it is kinda temporary for testing purposes.
EDIT: put code in a class that inherits from Frame so that this can be placed as a widget.

Related

Roll up image implementation using canvas - Python

I want an image to scroll up inside a canvas. But I am unable to do it.
import tkinter
def scroll_image(event):
global roll
roll = roll - 10
canvas1.itemconfig(canvas1.create_image(20, roll, image=i))
roll = 10
windows = tkinter.Tk()
windows.title("My Application")
# Adding canvas to show image there
canvas1 = tkinter.Canvas(windows, width=200, height=100)
i = tkinter.PhotoImage(file="Capture.gif")
canvas1.create_image(20, 20, image=i)
# trying to implement roll-up image
canvas1.itemconfig(canvas1.create_image(20, 20, image=i))
canvas1.bind("<Configure>", scroll_image)
canvas1.grid(column=0, row=2)
windows.mainloop()
I tried using loops but I noticed that loops are running as expected but unfortunately the canvas update is taking place only once. So I removed the loop. But I need to find a way out to implement a roll up image.
Image is moving up and down.
This example use
module PIL/pillow to load jpg/png instead of gif
after(time_in_millisecond, function_name) to repeat function which moves image
img_id to use only one image (instead of creating many images with create_image)
canvas.move(ID, offset_x, offset_y) to move image (or other object)
canvas.coords(ID) to get current positon of image (or other object)
canvas.pack(fill='both', expand=True) to use full window. Canvas will use full window even when you resize window.
Code:
import tkinter as tk
from PIL import Image, ImageTk
def scroll_image():
global offset_y # inform function that you want to assign value to external variable instead of local one.
# move image
canvas.move(img_id, offset_x, offset_y)
# get current position
x, y = canvas.coords(img_id)
print(x, y)
# set position (if you don't use canvas.move)
#canvas.coords(img_id, x+offset_x, y+offset_y)
# x += offset_x
# y += offset_y
# change direction
if y <= -100 or y >= 0:
offset_y = -offset_y
# repeat after 20ms
root.after(20, scroll_image)
offset_x = 0
offset_y = -3
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack(fill='both', expand=True) # use full window
#photo = tk.PhotoImage(file="Capture.gif")
image = Image.open("image.jpg")
photo = ImageTk.PhotoImage(image)
img_id = canvas.create_image(0, 0, image=photo)
# start scrolling
scroll_image()
root.mainloop()
You can use also canvas.coords(ID, x, y) to set new position.
More examples on GitHub for: Tkinter and other Python's modules

How to add transparent buttons on an already existing background in Python?

I can't find answer for my question anywhere so i decided to ask here.
I star a Python project and i want to add some Buttons on my app's background.
I try Tkinter but this adds a "sub-window" in my app's window, not a button.
I want transparent buttons because in my background there are already graphical buttons. Each button will do a command.
Since you say your background already has the look of a button, you don't need to add an additional button. Instead, you should determine the coordinates of the bounding rectangle for each 'pseudo-button' and then create event bindings that will perform actions if the user clicks within those bounds.
class Rectangle:
def __init__(self, x1, y1, x2, y2):
# (x1, y1) is the upper left point of the rectangle
# (x2, y2) is the lower right point of the rectangle
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
return
def contains(self, newx, newy):
# Will return true if the point (newx, newy) lies within the rectangle.
# False otherwise.
return (self.x1 <= newx <= self.x2) and (self.y1 <= newy <= self.y2)
try:
import tkinter as tk
except:
import Tkinter as tk
from PIL import ImageTK, Image
def button_click(event):
# The location of the click is stored in event sent by tkinter
# and can be accessed like:
print("You clicked ({}, {})".format(event.x, event.y))
## Now that you're in here, you can determine if the user clicked
## inside one of your buttons. If they did, perform the action that
## you want. For example, I've created two rectangle objects which
## simulate the location of some.
button1_rect = Rectangle( 100, 100, 200, 200 )
button2_rect = Rectangle( 300, 300, 400, 400 )
# Change the rectangles to reflect your own button locations.
if button1_rect.contains(event.x, event.y):
print("You clicked button number 1!")
if button2_rect.contains(event.x, event.y):
print("You clicked button number 2!")
return
root = tk.Tk()
canvas = tk.Canvas(master = root, height = 500, width = 500)
canvas.bind("<Button-1>", button_click)
# The above command means whenever <Button-1> (left mouse button) is clicked
# within your canvas, the button_click function will be called.
canvas.pack()
your_background = ImageTK.PhotoImage(Image.open("info_screen.png")) # <-- Or whatever image you want...
canvas.create_image(20, 20, image = your_background)
# 20, 20 represents the top left corner for where your image will be placed
root.mainloop()
Run that and click around while watching your console
Read more about binding to tkinter events here: https://effbot.org/tkinterbook/tkinter-events-and-bindings.html

Closing an Image After User Clicks (Python and Pillow)

I am working on a Choose-Your-Own-Adventure game in Python. Every so often, I want the user to be given a scene, and be allowed to find objects in that scene. Whatever they find can be put in their inventory, and whenever they are ready to stop looking for items, they can hit an "Exit" button in the bottom left. However, I am running into some errors with actually getting the button click coordinates to actually do something.
So far, I have this:
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
imageIQ1.create_image(0, 0, image=img, anchor="nw")
def getcoors(eventcoors):
global x0,y0
x0 = evencoors.x
y0 = evencoors.y
print(x0,y0)
After the user clicks a certain set of coordinates (or somewhere near them), I want the program to clear the picture from the screen and continue with the program. For insance, clicking anywhere from (300, 400) to (500, 500) would close the picture and continue with the rest of the program. I know this will use some form of loop like
while (x not in range) and (y not in range):
But I am unsure of what I would actually do to clear the image. I read about using something like .kill() and .terminate(), but they don't work in this situation.
Any ideas?
You need to have a reference for the image in order to later be able to delete it as in:
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
and then when you call:
imageIQ1.delete(canvImg)
it will get deleted.
Based on this you can put that in an event method like:
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
Your final code should look like:
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
root.mainloop()

Tkinter - Coordinates

I'm making a little project from a Pi2Go robot, where it will get data from the ultrasonic sensor, then place an X if it see's a object and O where it currently is, I've got two questions:
How do I set a coordinate position on tkinter? For example I wanted to insert text at 0,0, or 120,120.
Secondly:
How do I get tkinter to constantly update the map I'm building
Cheers!
I just cobbled some code together to give you a short introduction in how to use the place geometry manager. For further explanation please see comments in code:
#!/usr/bin/env python3
# coding: utf-8
from tkinter import *
# init our application's root window
root = Tk()
root.geometry('480x480')
# let's provide same sample coordinates with the desired text as tuples in a list
coords = [(30,30,'X'), (90,90,'X'), (120,120,'X'), (240,240,'X'), (270,270,'X'), (360,360,'O')]
# interate through the coords list and read the coordinates and the text of each tuple
for c in coords:
l = Label(root, text=c[2])
l.place(x=c[0], y=c[1])
# start a loop over the application
root.mainloop()
I am using Python 3. If you are using Python 2, you need to change tkinter to Tkinter. That should do the trick if you need to port my code to Python 2.
use the .place function.
like in the following
label = Label(root, text = 'i am placed')
#places the label in the following x and y coordinates
label.place(20,20)
from tkinter import *
from PIL import Image, ImageTk
class buildWorld:
def __init__(self, root):
self.canvas = Canvas(root, width=1000, height=800)
self.canvas.pack()
self.tmp = Image.new('RGBA', (1000,800), color=(0,0,0) )
self.img = ImageTk.PhotoImage(image=self.tmp)
self.Land = self.canvas.create_image(0, 0, anchor='nw', image=self.img)
self.tmp = Image.new('RGBA', (50, 50), color=(255, 0, 0))
self.img1 = ImageTk.PhotoImage(image=self.tmp)
self.mob1 = self.canvas.create_image(125, 125, anchor='nw', image=self.img1)
self.tmp = Image.new('RGBA', (50, 50), color=(0, 255, 0))
self.img2 = ImageTk.PhotoImage(image=self.tmp)
self.mob2 = self.canvas.create_image(300, 300, anchor='nw', image=self.img2)
root = Tk()
world = buildWorld(root)
mainloop()
The best thing you can do is to use place method whit arguments of your X and Y coordinates.
label = Label(root, text = 'Placed On X and Y')
label.place(x= X,y = Y)
or another way is to use pack method which will take arguments like:
label.pack(padx = W,pady = H)
where W and H are the distances from center points to your coordinates X and Y.

Cropping and displaying images using PIL and Tkinter

I am beginning to learn Python after being trapped using VB6 forever. To help myself learn some gui with Tkinter, I'm making a simple slot machine. To animate the wheels, I have a ~300x2000 image that has all of the pictures I intend to use displayed as a strip. About 6 pictures right now. I just show part of the image (wheel) at a time to make it spin. Anyway, I'm having trouble with the part of the code where I come to the end of the image and need to transition from the end of the image back to the beginning (wheel spins down, so the start is the bottom and end is the top).
For some reason, I can't get the images to crop and display correctly, even though I think my script makes sense logically. My code is posted below. You can make an image around the resolution of 300x2000 or so to use with this to see the problem I am having. My code starts printing outside of the area that I want it to show (outside the showsize variable) and I can't figure out why.
Any help with this problem would be much appreciated. It seems that the crops don't cut the images short enough, but all the information that I found about it make me think my script should be working just fine. I've tried to annotate my code to explain what's going on.
from Tkinter import *
from PIL import Image, ImageTk
def main():
root = Tk()
root.title = ("Slot Machine")
canvas = Canvas(root, width=1500, height=800)
canvas.pack()
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 #amount of source image to show at a time - part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 0 #amount of pause between each frame
for spintimes in range(1,numberofspins):
for y in range(wheelh,showsize,-speed): #spin to end of image, from bottom to top
cropped = im.crop((0, y-showsize, wheelw, y)) #crop which part of wheel is seen
tk_im = ImageTk.PhotoImage(cropped)
canvas.create_image(bx1, by, image=tk_im) #display image
canvas.update() # This refreshes the drawing on the canvas.
canvas.after(cycle_period) # This makes execution pause
for y in range (speed,showsize,speed): #add 2nd image to make spin loop
cropped1 = im.crop((0, 0, wheelw, showsize-y)) #img crop 1
cropped2 = im.crop((0, wheelh - y, wheelw, wheelh)) #img crop 2
tk_im1 = tk_im2 = None
tk_im1 = ImageTk.PhotoImage(cropped1)
tk_im2 = ImageTk.PhotoImage(cropped2)
#canvas.delete(ALL)
canvas.create_image(bx1, by, image=tk_im2) ###THIS IS WHERE THE PROBLEM IS..
canvas.create_image(bx1, by + y, image=tk_im1) ###PROBLEM
#For some reason these 2 lines are overdrawing where they should be. as y increases, the cropped img size should decrease, but doesn't
canvas.update() # This refreshes the drawing on the canvas
canvas.after(cycle_period) # This makes execution pause
root.mainloop()
if __name__ == '__main__':
main()
It's simpler, and much faster and smoother if you don't recreate your images on every cycle. Here's my solution using canvas.move(). Notice that I moved the canvas.create_image calls outside of the for loop. I also put the code in a class and added a 'spin' button, and added something to make it exit without errors.
from Tkinter import *
from PIL import Image, ImageTk
import sys
class SlotMachine():
def __init__(self):
root = Tk()
root.title = ("Slot Machine")
self.canvas = Canvas(root, width=1200, height=800)
self.canvas.grid(column=0,row=0)
button = Button(root, text="Spin!", width=20, command = self.spin)
button.grid(column=0,row=1)
self.alive = True
root.protocol("WM_DELETE_WINDOW", self.die)
root.mainloop()
def spin(self):
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 # amount of source image to show at a time -
# part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 3 #amount of pause between each frame
tk_im1 = ImageTk.PhotoImage(im)
tk_im2 = ImageTk.PhotoImage(im)
im1id = self.canvas.create_image(bx1, by + showsize, image=tk_im1)
im2id = self.canvas.create_image(bx1, by + showsize + wheelh,
image=tk_im2)
for spintimes in range(1,numberofspins):
for y in range(wheelh,0,-speed):
if self.alive:
self.canvas.move(im1id, 0, -speed)
self.canvas.move(im2id, 0, -speed)
self.canvas.update()
self.canvas.after(cycle_period)
else:
sys.exit()
self.canvas.move(im1id, 0, wheelh)
self.canvas.move(im2id, 0, wheelh)
def die(self):
self.alive = False
if __name__ == '__main__':
mySlotMachine = SlotMachine()
This will place parts of the wheel outside of its box, but you can just put your slot machine texture on top of that.

Categories

Resources