How to customize the contains-function of tkinter canvas elements/images? - python

I added a png image with transparent regions (alpha=0) to a tkinter canvas (see minimal example below). A mouse-click is recognized when clicking somewhere in the rectangle that contains the tkinter image.
How do I adjust the functionality such that any mouse binding (clicking, moving, etc.) is only called when the mouse is located on pixels with non-zero alpha values of the image?
Tux (linux mascot) example image :
try:
# Tkinter for Python 2.xx
import Tkinter as tk
except ImportError:
# Tkinter for Python 3.xx
import tkinter as tk
image_path = "./tux.png"
class Application(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.canvas = tk.Canvas(self, width=600, height=600)
self.canvas.pack(fill="both", expand=True)
# create an example image
self.tk_image = tk.PhotoImage(file=image_path)
self.image_obj= self.canvas.create_image(200, 200, anchor = 'center',
image=self.tk_image)
self.canvas.tag_bind(self.image_obj, '<Button-1>', self.clicked)
def clicked(self, event):
print("I am tux.")
def main():
app_win = tk.Tk()
app = Application(app_win).pack(fill='both', expand=True)
app_win.mainloop()
if __name__ == '__main__':
main()

How do I adjust the functionality such that any mouse binding (clicking, moving, etc.) is only called when the mouse is located on pixels with non-zero alpha values of the image?
You can't. That's simply not something that tkinter directly supports. Unfortunately, tkinter's support of transparency is a bit lacking.
What you might be able to do, however, is have the bound function get the image object, compute which pixel was clicked on, and then query the image instance to get the color of that pixel.

Related

Why does Tkinter Canvas widget show images at a wrong scale?

I'm trying to open image files and display them in python 3.8 using Tkinter and Pillow, but something is scaling the images wrong on my screen.
import tkinter as tk
from PIL import Image, ImageTk
class ViewingWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.image = None
self.canvas = tk.Canvas(self, width=500, height=500)
self.canvas.pack()
def setImage(self, img):
self.image = img
print(img.width())
print(img.height())
print(self.canvas["width"])
print(self.canvas["height"])
self.canvas.create_image(0, 0, anchor=tk.NW, image=img)
window = tk.Tk()
canvas = ViewingWindow(window)
canvas.pack()
img = Image.open("500x500.jpg")
img = ImageTk.PhotoImage(img)
canvas.setImage(img)
window.mainloop()
This is the result, shown for reference is Windows image viewer at "show actual size", and Gimp at scaling=100%:
The 4 print statements all show "500", every part of the system seems to agree that the image is shown at 500x500, except the actual pixels on the screen. For whatever reason it's scaled to something close to 750x750, what in the world is scaling my image? This is consistent for all images I've tried to open in Tkinter, and regardless on window size and widget sizes.
Tested on Windows 10 with screen resolution 1920x1080.
it's scaled to something close to 750x750
750/500 = 150%.It seems that your system zoom ratio is 150%.
To show the right image size.Just like furas said,you need to use DPI awareness.Read the problem in MSDN official doc
About DPI awareness.
To solve the problem,you can set the DPI awareness in your code.(Or you can change the zoom ratio to 100%)
In my PC,the are not the same size without DPI awareness.Now after set the DPI awareness:
Add this in your code:
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2) # this could only be used when your version of windows >= 8.1

How to display multiple images inside tkinter window?

I want to display 7 different images and plot them inside tkinter window. I only know to display it using OpenCV which actually displays the image outside the Tkinter GUI window. How to plot the images inside the GUI window?
Tkinter GUI:
I advise you read the documentation of tkinter as it has tons of example on how to achieve a soluction to your problem. I have made a sample GUI which updates image on the GUI with a randomized pixel picture. Note that there are a lot of different approaches how to do this. In my case I have made a class in which I display a frame and a button. The button calls a function to randomize the image (or changes to the next one in your case) and calls the function to update the frame with a new image. Hope it gives you a jump start. Cheers!
P.S.: for multiple images use more labels and functions;)
Code:
import tkinter as tk
from tkinter import *
import cv2
import numpy as np
from PIL import Image, ImageTk
class DisplayImage:
def __init__(self, master):
self.master = master
master.title("GUI")
self.image_frame = Frame(master, borderwidth=0, highlightthickness=0, height=20, width=30, bg='white')
self.image_frame.pack()
self.image_label = Label(self.image_frame, highlightthickness=0, borderwidth=0)
self.image_label.pack()
self.Next_image = Button(master, command=self.read_image, text="Next image", width=17, default=ACTIVE, borderwidth=0)
self.Next_image.pack()
def display_image(self, event=None):
self.cv2image = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGBA)
self.from_array = Image.fromarray(self.cv2image)
self.imgt = ImageTk.PhotoImage(image=self.from_array)
self.image_label.configure(image=self.imgt)
def read_image(self, event=None):
self.img = np.random.randint(255, size=(250,250,3),dtype=np.uint8)
self.master.after(10, self.display_image)
def main():
root = tk.Tk()
GUI = DisplayImage(root)
GUI.read_image()
root.mainloop()
if __name__ == '__main__':
main()
Output:
If you are OK with using a wrapper with tkinter, then PySimpleGUI is a good choice.
There was a new Demo application posted this week that demonstrates how to show a webcam in a GUI window. There's another Demo posted that plays back a video file using OpenCV, again in a GUI window.
The code that generated that screen shot can be found here:
Open and Play Video Using PySimpleGU + OpenCV
You can start with the Demo and expand it by adding more buttons.

Scrollbar not working on canvas

I'm creating a json editor in python using tkinter.
I've added a scrollbar by creating a Canvas, and putting a Frame inside it.
Then I set the Scrollbar command to canvas.yview.
Theres two things that are messing up, and I have no idea why.
When I press the scroll buttons (up and down arrows) the canvas is not scrolling
I am packing the scrollbar onto the window (root) right now instead of the frame, because whenever i pack it onto the frame, the tkinter application does not open, and my computer fan starts turning on... Anyone know what is going on here? (Therefore the scrollbar is tiny if you try to run the code)
Here is my code:
EDIT> Code shortened
import Tkinter as tk
import webbrowser
import os
import bjson as bj
class App:
def __init__(self, master):
self.window = master
self.window.geometry("800x450")
self.canvas = tk.Canvas(self.window, width=800, height=400)
self.master = tk.Frame(self.canvas, width=800, height=400)
self.canvas.pack()
self.master.place(x=0, y=0)
scrollbar = tk.Scrollbar(self.window)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
scrollbar.config(command=self.canvas.yview)
def init(self):
master = self.master
self.frames = {
"Home": HomeFrame(master)
}
self.openFrame = None
self.loadFrame("Home")
def loadFrame(self, frame):
self.openFrame = self.frames[frame]
self.openFrame.display()
def setTitle(self, t):
self.window.title(t)
class Frame:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(master)
self.frame.grid(row=0, column=0, sticky='news')
self.init()
self.frame_create()
def display(self):
self.frame.tkraise() #raises frame to top
self.frame_load() #initializes the frame
def clear(self):
for widget in self.frame.winfo_children():
widget.destroy()
def init(self): pass
def frame_load(self): pass
def frame_create(self): pass
class HomeFrame(Frame):
def frame_create(self):
p = self.frame
for i in range(20):
tk.Label(p, text="This is content... " + str(i)).pack()
for j in range(2):
LineBreak(p)
def LineBreak(p):
tk.Label(p, text="").pack()
root = tk.Tk()
glob = {}
app = App(root)
app.init()
root.mainloop()
It is a bit long, and a bit messy, but you should see how I'm adding the scrollbar in the __init__ of App
Anyone have any idea what's going on, and how to fix it?
Thanks in advance!
There are many things wrong with your code. However, the problem with the scrollbar not working properly has to do with two things you are neglecting to do:
First, scrollbars and widgets require two way communication. The canvas needs to be told about the scrollbar, and the scrollbar needs to be told about the canvas. You are doing one but not the other:
self.canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.configure(command=self.canvas.yview)
Second, you need to configure the scrollregion attribute of the canvas. This tells tkinter what part of the larger virtual canvas you want to be viewable. Typically this is done in a binding on the <Configure> method of the canvas, and usually you will want to set it to the bounding box of everything in the canvas. For the latter you can pass the string "all" to the bbox method:
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
If you know the exact size of the area you want to be scrollable, you can simply set it to that value (eg: scrollregion=(0,0,1000,1000) to scroll around in a region that is 1000x1000 pixels).
The reason for point #2 is that you can't use both pack and grid for widgets that share the same parent. When you do, you'll get the behavior you describe. That is because grid will try to layout all of the widgets. This may result in some widgets changing size. pack will notice the change in the size of one or more widgets and try to re-layout all of the widgets. This may result in some widgets changing size. grid will notice the change in the size of one or more widgets and try to re-layout all of the widgets. And so on.

Ttk background image

Is there any way to import an image into python using PIL and set it as the background for a ttk mainframe that spans the entirety of a Tkinter root window? As of now I have only seen ways to do this is a Tkinter root. Also is there any way to make ttk self adjust the size of the image so that even if it's small it covers the entire screen?
So to sum up I want an image to cover the entire ttk mainframe box without messing with me putting anything else in the ttk frame.
for example
if the pic covered the entire window, a command,
ttk.Button(root, text="Hello").grid(column=0, row=0, sticky=(N,S,W,E))
would still insert a button in the mainframe. Thanks :)
You can't set an image background to a ttk frame, they don't accept image options. So you could make a ttk frame and put a label or something inside it and then have it span the frame adapting the below example.
Here's a small example demonstrating what you want. We load an image with pil, notice the image linked will be smaller (I hope) than your screensize.
So, we set the geometry of the root window to be the entire screen the image is less than this so we resize it to cover the entire width. You can override the max and min height and then set it according to this. Just a sample value. We then place the bg label and grid widgets on top of it. The label has a lower stacking order than the other widgets you will place with grid so they appear on top. Alternatively you could use a canvas, or another widget. With a canvas you'll have to use create_window to place widgets in the canvas.
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
root = tk.Tk()
width, height = root.winfo_screenwidth(), root.winfo_screenheight()
#print(root.winfo_screenheight(), root.winfo_screenwidth())
root.geometry("%dx%d" % (width, height))
#URL FOR BACKGROUND
#https://www.google.com/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&ved=0ahUKEwiVroCiyZHMAhXKeT4KHQHpDVAQjBwIBA&url=http%3A%2F%2Fwallpaperswide.com%2Fdownload%2Fblack_background_metal_hole_very_small-wallpaper-800x480.jpg&psig=AFQjCNEjZ7GDbjG9sFie-yXW3fP85_p0VQ&ust=1460840934258935
image = Image.open("background.jpg")
if image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS)
#print("DONE RESIZING")
# image.save("background.jpg")
#print(image.size)
image = ImageTk.PhotoImage(image)
bg_label = tk.Label(root, image = image)
bg_label.place(x=0, y=0, relwidth=1, relheight=1)
bg_label.image = image
your_button = ttk.Button(root, text='This is a button')
your_button.grid()
root.mainloop()

Creating Blank Images in Python (allowing pixel by pixel manipulation)

I have this code here that creates a Tkinter Canvas widget, then embeds an image within it.
import Tkinter
from PIL import ImageTk, Image
class image_manip(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.configure(bg='red')
self.ImbImage = Tkinter.Canvas(self, highlightthickness=0, bd=0, bg='blue')
self.ImbImage.pack()
self.i = ImageTk.PhotoImage(Image.open(r'test.png'))
self.ImbImage.create_image(150, 100, image=self.i)
def run():
image_manip().mainloop()
if __name__ == "__main__":
run()
I'd like to be able to create a blank image within the Canvas widget, so that I could do pixel by pixel manipulation within the widget. How would one go about this?
To create a new blank image (rather than opening one), you can use the Image.new(...) method in place of your Image.open(...). It is described in The Image Module.
Then call self.i.put(...) to do pixel-by-pixel manipulation. (i is the PhotoImage object as in your example.)
Here's some general information on The Tkinter PhotoImage Class.

Categories