I'm using Tkinter with Python 3. I want to display something like this, with a particular token showing up behind an overlay:
(This is the overlay, followed by a token, and finally the overlay over the token. The token may not always be centered though behind the overlay.)
The problem is that I need to create these images dynamically. I can't use predefined images .png or .gif images. In particular, the colors of these images are going to change frequently, and I don't know them a priori.
All of the sample code that I've seen to create images on a canvas assume that you're loading up a .gif or .png, something like this:
token_image = tk.PhotoImage(file="token.png")
canvas = tk.Canvas(width=500, height=500, background="black")
canvas.pack()
token = canvas.create_image((50,50), image=token_image)
But is there a way to do this generating the token_image dynamically? How would you create the irregular shape shown in the overlay, with the circular "bite" taken out of it?
Thanks.
as a basic example:
import tkinter as tk
from PIL import Image, ImageDraw, ImageTk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Dynamic Image Test")
tk.Label(self, text="Overlay").grid(column=1, row=1, sticky="nesw")
tk.Label(self, text="Token").grid(column=2, row=1, sticky="nesw")
tk.Label(self, text="Combined").grid(column=3, row=1, sticky="nesw")
self.label_1 = tk.Label(self)
self.label_1.grid(column=1, row=2, sticky="nesw")
self.label_2 = tk.Label(self)
self.label_2.grid(column=2, row=2, sticky="nesw")
self.label_3 = tk.Label(self)
self.label_3.grid(column=3, row=2, sticky="nesw")
self.images = [None, None, None]
self.photos = [None, None, None]
self.show_changes()
def overlay(self):
im = Image.new("RGBA", (100,100), (255,255,0,255))
draw = ImageDraw.Draw(im)
draw.ellipse((10,10,90,90),fill=(0,0,0,0))
return im
def token(self):
im = Image.new("RGBA", (100,100), (0,0,0,0))
draw = ImageDraw.Draw(im)
draw.ellipse((0,0,100,100),fill=(0,0,255,255))
draw.line((15,15,85,85),fill=(255,0,0,255), width=5)
draw.line((15,85,85,15),fill=(255,0,0,255), width=5)
return im
def combine(self, overlay, token):
return Image.alpha_composite(token, overlay)
def show_changes(self):
self.images[0] = self.overlay()
self.photos[0] = ImageTk.PhotoImage(self.images[0])
self.label_1.configure(image=self.photos[0])
self.images[1] = self.token()
self.photos[1] = ImageTk.PhotoImage(self.images[1])
self.label_2.configure(image=self.photos[1])
self.images[2] = self.combine(self.images[0], self.images[1])
self.photos[2] = ImageTk.PhotoImage(self.images[2])
self.label_3.configure(image=self.photos[2])
if __name__ == "__main__":
app = App()
app.mainloop()
i was going to do a more complete example, allowing changing the colours on the fly, but i'll be honest and say i can't be bothered.
the ImageDraw module lets you do basic manipulation of images, drawing lines, rectanges ellipses etc, and also supports working in RGBA format (ie transparency)
so the colour tuples take the form (red, green, blue, alpha) where all values have a range of 0-255, with 255 meaning fully opaque in the alpha channel.
hopefully this shows you where you need to look to do what you want.
Related
I am doing a painting program where one can draw things, change the background color and save it as a file on the computer. Everything works except that changing the background color takes way too much time.
This is the code:
from tkinter import *
import tkinter.filedialog as tk
from tkinter import Menu
from tkinter.colorchooser import askcolor
from tkinter.filedialog import asksaveasfile,askopenfilename
import os
from PIL import Image as im
from PIL import ImageTk,ImageDraw,ImageColor
class P:
x=y=None
image=None
my_image=im.new("RGB",(600,600),(255,255,255))
draw=ImageDraw.Draw(my_image)
def __init__(self,window):
self.window = window
self.upper_frame = Frame(window)
self.upper_frame.grid(row=0,column=0, padx=10, pady=5,sticky="ew")
self.lower_frame = Frame(window)
self.lower_frame.grid(row=2, column=0, padx=10, pady=5,sticky="ew")
self.canvas= Canvas(self.lower_frame,width=700,height=530,bg="white")
self.canvas.grid()
self.bg = Button(self.upper_frame,text="Background",command= self.bgcolor) #change bg color
self.bg.grid(row=2,column=1,padx=(100,10))
self.upper_menu()
def bgcolor(self):
chosen_color = askcolor(color=self.canvas["bg"])[1]
self.canvas.configure(bg=chosen_color)
color_RGB = ImageColor.getcolor(chosen_color, "RGB")
img = self.my_image
for i in range(0,600):#pixels in width
for j in range(0,600): #height = 600 pix
data = img.getpixel((i,j)) #gives color of pixel
if (data[0]==255 and data[1]==255 and data[2]==255): #data represent RGB color
img.putpixel((i,j),color_RGB) #changes pixel color
def save_pic(self,event=None): #save image on comp.
my_file=asksaveasfile(mode="w",defaultextension=".png",filetypes=[("png files","*.png")],
initialdir="/home/b/",parent=window)
if my_file is not None:
path=os.path.abspath(my_file.name)
self.my_image.save(path)
def upper_menu(self):
self.menubar = Menu(window)
self.menu1 = Menu(self.menubar, tearoff=0)
self.menu1.add_command(label="Save pic", command=self.save_pic)
self.menu1.add_separator()
self.menu1.add_command(label="Exit", command=window.destroy)
self.menubar.add_cascade(label="Settings", menu=self.menu1)
self.menu2 = Menu(self.menubar, tearoff=0)
self.window.config(menu=self.menubar)
window = Tk()
window.geometry("720x590")
p = P(window)
window.mainloop()
I use the method bgcolor to change the background. How to make it work faster?
I suspect the problem is with calling putpixel 360,000 times. Instead, try creating the color data in the loop and then call putdata once after the loops have finished.
I'm not overly familiar with PIL, but this makes a huge difference when doing similar things with the tkinter PhotoImage class: doing one pixel at a time is slow, doing an array of pixels is fast.
I am new to Tkinter and python. I am trying to upload two images and then perform some operations on them. The problem is that the Window class is loading all at once or the code in running parallel, so the images uploaded after that have been already assigned to None since they were uploaded later in the ScrollableFrame class and did not have a value earlier.
from tkinter import *
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps
import os
# ************************
# Scrollable Frame Class
# ************************
row=0
column=0
imagePaths = []
#Class to generate a frame to add to the GUI with vertical and horizontal scroll bars
class ScrollableFrame(Frame):
#The Constructor method for the class
def __init__(self, parent , *args, **kw):
Frame.__init__(self, parent, *args, **kw)
#Defining the position of the frame grid
self.grid(row = row , column = column)
self.image = None
self.imageFile = None
#Defining the vertical scroll bar
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.grid(row=row, column=column+1, sticky=N+S)
#Defining the horizontal scroll bar
hscrollbar = Scrollbar(self, orient = 'horizontal')
hscrollbar.grid(row=row+1, column=column, sticky=E+W)
#Defining the canvas to put the scroll bars on
canvas = Canvas(self, bd=0, highlightthickness=0, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
canvas.grid(row=row, column=column, sticky = N+S+E+W)
canvas.config( width=800, height = 800 )
#Defining the scrolling commands (vertically and horizontally )
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)
#Defining the scroll region where the scrolling is active
canvas.config(scrollregion= (0,0,1280,1024))
self.canvas = canvas
def openImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
def showImage(self):
#Getting the path of the image
imageFile = filedialog.askopenfilename(initialdir=os.getcwd(),title="Select BMP File",filetypes=[("BMP Files",("*.bmp",".png",".jpg",".jpeg",".tif",".tiff"))])
#Assigning the image value to this frame object
self.imageFile = imageFile
if not imageFile:
return
#Checking for the extension of the image
filename, file_extension = os.path.splitext(imageFile)
#If it is a .bmp, this means that it is an HD image, where we can directly display it
if file_extension == '.bmp':
imageToDisplay = Image.open(imageFile)
#border = (0, 0, 0, 66) #Decide on the area you want to crop in terms of no. pixels: left, up, right, bottom
#ImageOps.crop(imageToDisplay, border)
img = ImageTk.PhotoImage(imageToDisplay)
self.image = img
#print ("Done conversion")
self.canvas.create_image(row, column, image=self.image, anchor=NW)
class Window(Frame):
def __init__(self, master=None):
global row, column,imagePaths
Frame.__init__(self, master)
self.master = master
self.pos = []
self.master.title("BMP Image GUI")
self.pack(fill=BOTH, expand=1)
self.label = Label(self, text="Instructions: \n 1. Open the HD image. \n 2. Open the EBSD image. \n 3. Open the Color Map image.", anchor=W, justify=LEFT)
self.label.place(x=1640, y=0)
menu = Menu(self.master)
self.master.config(menu=menu)
self.frame1 = ScrollableFrame(self)
row=0
column=1
self.frame2 = ScrollableFrame(self)
# File Bar
file = Menu(menu)
file.add_command(label="Open HD image", command=self.frame1.showImage)
img = Image.open("original.bmp")
HD = self.frame2.imageFile
file.add_command(label="Open EBSD image", command=self.frame2.openImage)
EBSD = self.frame2.imageFile
print (HD)
print (EBSD)
root = tk.Tk()
root.geometry("%dx%d" % (1670, 1024))
root.title("BMP Image GUI")
app = Window(root)
app.pack(fill=tk.BOTH, expand=1)
#print (HD)
root.mainloop()
So printing the HD and EBSD images is giving None. What I am aiming to to make them get the actual value assigned after the upload.
This is a lot of code and it doesn't run. It also has a few problems. When you are coding complex applications it is best to do it one little piece at a time or you'll have problems finding the problems. Here are a few:
Don't use global variables in an object oriented application. The names row, column and imagePaths should belong to either of the two classes.
The menu doesn't work because you have not implemented it correctly:
file = Menu(menu)
menu.add_cascade(label='File', menu=file) # You need this for it to work
file.add_command(label="Open HD image", command=self.frame1.showImage)
# etc...
You are packing app twice, once in it's __init__() function and once after it's been created (in the global scope).
The scrollable frames are packed in front of the Label with instructions so you can't see it.
Try fixing these problems by writing components, and when each component works then combine them. If there is a problem with any of the components, or if everything works but for one thing, come back here and we will be able to give you a better answer.
I can't display images longer (height) than about 30612 pixels high. I've read that there is a maximum height to canvas. I'd like to get the source file and extend that to 90 or 100k pixels in height. Conversely, I've seen suggested that a canvas may be buffered, if this is true, I have no clue how to implement it.. Any help is appreciated!
I am using code I found off Stack that is supposed to deal with large images, it does alright, but ultimately hit's the cavas height limit.
Canvas Limit
from tkinter import *
from PIL import ImageTk
from PIL import *
Image.MAX_IMAGE_PIXELS = None
class ScrolledCanvas(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.master.title("Spectrogram Viewer")
self.pack(expand=YES, fill=BOTH)
canv = Canvas(self, relief=SUNKEN)
canv.config(width=400, height=500)
# canv.config(scrollregion=(0,0,1000, 1000))
# canv.configure(scrollregion=canv.bbox('all'))
canv.config(highlightthickness=0)
sbarV = Scrollbar(self, orient=VERTICAL)
sbarH = Scrollbar(self, orient=HORIZONTAL)
sbarV.config(command=canv.yview)
sbarH.config(command=canv.xview)
canv.config(yscrollcommand=sbarV.set)
canv.config(xscrollcommand=sbarH.set)
sbarV.pack(side=RIGHT, fill=Y)
sbarH.pack(side=BOTTOM, fill=X)
canv.pack(side=LEFT, expand=YES, fill=BOTH)
self.im = Image.open("Test_3.tif")
width, height = self.im.size
canv.config(scrollregion=(0, 0, width, height))
self.im2 = ImageTk.PhotoImage(self.im)
self.imgtag = canv.create_image(0, 0, anchor="nw", image=self.im2)
ScrolledCanvas().mainloop()
I tried to put together a bigger image from displays of a grid of canvases. This looks like it might work, at least if you just want to display a big image. I have just tested with a small image and not paid any attention to memory or speed or anything...
from tkinter import *
from scrframe import VerticalScrolledFrame
root = Tk()
tiles = VerticalScrolledFrame(root) # Scrolled frame
tiles.grid()
tw = 90 # Tile width
th = 110 # Tile height
rows = 4 # Number of tiles/row
cols = 4 # Number of tiles/column
tile_list = [] # List of image tiles
img = PhotoImage(file='pilner.png')
for r in range(rows):
col_list = []
for c in range(cols):
tile = Canvas(tiles.interior, highlightthickness=0, bg='tan1',
width=tw, height=th)
tile.create_image(-c*tw, -r*th, image=img, anchor ='nw')
tile.grid(row=r, column=c)
col_list.append(tile)
tile_list.append(col_list)
root.mainloop()
Now, scrolling a frame seems to raise some problems, but there also seems to be solutions. I tried to use VerticalScrolledFrame as described in Python Tkinter scrollbar for frame and it works fine. As it only provides for a vertical scrollbar you'd have to implement horizontal scrollbar yourself. Maybe a few additional functions as scrolling with the mouse wheel, keyboard shortcuts or other would be useful.
I got the VerticalScrolledFrame from TKinter scrollable frame and modified it for Python 3.
This is the code I've come up with from several sources - Thanks to figbeam for all the help. Also, this is not pretty!!!! The button shows up in the center of the Tkinter window. If you'd like to modify this, please do.
from tkinter import *
from PIL import ImageTk as itk
from PIL import Image
import math
import numpy as np
Image.MAX_IMAGE_PIXELS = None #prevents the "photo bomb" warning from popping up. Have to have this for really large images.
#----------------------------------------------------------------------
# makes a simple window with a button right in the middle that let's you go "down" an image.
class MainWindow():
#----------------
def __init__(self, main):
# canvas for image
_, th, tw, rows, cols = self.getrowsandcols()
self.canvas = Canvas(main, width=tw, height=th)
self.canvas.grid(row=0, column=0)
# images
self.my_images = self.cropimages() # crop the really large image down into several smaller images and append to this list
self.my_image_number = 0 #
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images[self.my_image_number])
# button to change image
self.button = Button(main, text="DOWN", command=self.onDownButton)
self.button.grid(row=0, column=0)
#----------------
def getimage(self):
im = Image.open("Test_3.png") # import the image
im = im.convert("RGBA") # convert the image to color including the alpha channel (which is the transparency best I understand)
width, height = im.size # get the width and height
return width, height, im # return relevent variables/objects
def getrowsandcols(self):
width, height, im = self.getimage()
im = np.asarray(im) # Convert image to Numpy Array
tw = width # Tile width will equal the width of the image
th = int(math.ceil(height / 100)) # Tile height
rows = int(math.ceil(height / th)) # Number of tiles/row
cols = int(math.ceil(width / tw)) # Number of tiles/column
return im, th, tw, rows, cols #return selected variables
def cropimages(self):
self.my_images = [] # initialize list to hold Tkinter "PhotoImage objects"
im, th, tw, rows, cols = self.getrowsandcols() # pull in needed variables to crop the really long image
for r in range(rows): # loop row by row to crop all of the image
crop_im =im[r * th:((r * th) + th), 0:tw] # crop the image for the current row (r). (th) stands for tile height.
crop_im = Image.fromarray(crop_im) # convert the image from an Numpy Array to a PIL image.
crop_im = itk.PhotoImage(crop_im) # convert the PIL image to a Tkinter Photo Object (whatever that is)
self.my_images.append(crop_im) # Append the photo object to the list
crop_im = None
return self.my_images
def onDownButton(self):
# next image
self.my_image_number += 1 #every button pressed will
# return to first image
if self.my_image_number == len(self.my_images):
self.my_image_number = 0
# change image
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images[self.my_image_number]) #attaches the image from the image list to the canvas
#----------------------------------------------------------------------
root = Tk()
MainWindow(root)
root.mainloop()
Here's the issue -
I'm writing a program that will iterate through a series of image manipulations - both effecting the whole image as well as just portions of the image. I need to demonstrate these changes to the user (myself) so I can see what's going wrong with my manipulations as they take place.
I originally tried to use PIL and Tkinter to do this - but I couldn't even load an image into the GUI - here's a bit of code formed from the corners of the web, via many google searches:
from Tkinter import *
import Image, ImageDraw, ImageTk
import time
class Test(Frame):
def __init__(self):
Frame.__init__(self)
self.c = Canvas(self, width=574, height=431, bg="red")
self.c.pack()
Button(self, text="Process", command=self.procImg).pack()
Button(self, text="Quit", command=self.quit).pack()
def procImg(self):
t = time.time()
self.flashImg = Image.open("./in_img/resize.bmp")
#self.flashImg = flashImg.resize((574, 431))
self.flashImg = self.flashImg.convert("L")
#self.photo = ImageTk.BitmapImage(flashImg)
self.c.photo = ImageTk.PhotoImage(self.flashImg)
self.c.create_image(574, 431, anchor=NW, image=self.c.photo)
self.c.create_rectangle(50, 50, 100, 100, fill="blue")
self.update()
print time.time()-t
t = Test()
t.pack()
t.mainloop()
So the above code is pretty bad, I know - but I wanted to post something to prove that I have at least been working at this.
Can anyone suggest to me a new way of approaching this problem using Python? I'd rather not learn a different language - I'm new to the Tkinter library so if something else is better suited for this, I have no issues learning a new library.
Also, FYI, the "resize.bmp" image is a resized .JPG from a digital camera. I tried that one too and it didn't work - I really need to find a way to flash bitmaps from memory to the screen in a GUI so I can adjust parameters as the processing is going on.
Thanks for your help!
The image is probably there. It's just not visible.
Instead of :
self.c.create_image(574, 431, anchor=NW, image=self.c.photo)
try :
self.c.create_image(0, 0, anchor=NW, image=self.c.photo)
Also, if you keep a reference to the canvas image item, you can swap different images in and out.
eg. (self.canvasItem) below :
from Tkinter import *
from PIL import Image, ImageTk, ImageDraw, ImageOps, ImageEnhance
class ImageButcher(Tk):
def __init__(self):
Tk.__init__(self)
#create ui
f = Frame(self, bd=2)
self.colour = StringVar(self)
self.colourMenu = OptionMenu(f, self.colour,
*('red','green','blue','white'))
self.colourMenu.config(width=5)
self.colour.set('red')
self.colourMenu.pack(side='left')
self.rectangleButton = Button(f, text='Rectangle',
command=self.draw_rectangle)
self.rectangleButton.pack(side='left')
self.brightenButton = Button(f, text='Brighten',
command=self.on_brighten)
self.brightenButton.pack(side='left')
self.mirrorButton = Button(f, text='Mirror',
command=self.on_mirror)
self.mirrorButton.pack(side='left')
f.pack(fill='x')
self.c = Canvas(self, bd=0, highlightthickness=0,
width=100, height=100)
self.c.pack(fill='both', expand=1)
#load image
im = Image.open('IMG_1584.JPG')
im.thumbnail((512,512))
self.tkphoto = ImageTk.PhotoImage(im)
self.canvasItem = self.c.create_image(0,0,anchor='nw',image=self.tkphoto)
self.c.config(width=im.size[0], height=im.size[1])
self.img = im
self.temp = im.copy() # 'working' image
def display_image(self, aImage):
self.tkphoto = pic = ImageTk.PhotoImage(aImage)
self.c.itemconfigure(self.canvasItem, image=pic)
def on_mirror(self):
im = ImageOps.mirror(self.temp)
self.display_image(im)
self.temp = im
def on_brighten(self):
brightener = ImageEnhance.Brightness(self.temp)
self.temp = brightener.enhance(1.1) # +10%
self.display_image(self.temp)
def draw_rectangle(self):
bbox = 9, 9, self.temp.size[0] - 11, self.temp.size[1] - 11
draw = ImageDraw.Draw(self.temp)
draw.rectangle(bbox, outline=self.colour.get())
self.display_image(self.temp)
app = ImageButcher()
app.mainloop()
I have a window with a label as my frame. I did this because i wanted an image in the background. But now im having trouble with the other labels i have used. The other labels i have used to actually labeled things dont have a transparent background. Is there a way to make the background of these labels transparent?
import Tkinter as tk
root = tk.Tk()
root.title('background image')
image1 = Tk.PhotoImage(file='image_name.gif')
# get the image size
w = image1.width()
h = image1.height()
# make the root window the size of the image
root.geometry("%dx%d" % (w, h))
# root has no image argument, so use a label as a panel
panel1 = tk.Label(root, image=image1)
panel1.pack(side='top', fill='both', expand='yes')
# put a button/label on the image panel to test it
label1 = tk.Label(panel1, text='here i am')
label1.pack(side=Top)
button2 = tk.Button(panel1, text='button2')
button2.pack(side='top')
# start the event loop
root.mainloop()
i think it can help, all black will be transparent
root.wm_attributes('-transparentcolor','black')
It is not supported with transparent backgrounds in Tk.
If you are working with images and putting text onto them, the most convenient way is - I think - utilizing Canvas widget.
tkinter Canvas widget has methods as .create_image(x, y, image=image, options) and .create_text(x, y, text="Some text", options).
use this :
from tkinter import *
main=Tk()
photo=PhotoImage(file='test.png')
Label(main,image=photo,bg='grey').pack()
#your other label or button or ...
main.wm_attributes("-transparentcolor", 'grey')
main.mainloop()
this work if use bg='grey' you can change it in line 7
good luck :)