I am attempting to rotate an label with an image which is on a canvas with tkinter.
I have three images that need rotating (pitch, roll and yaw) and they will eventually rotate according to IMU sensor outputs.
import tkinter as tk
from tkinter import PhotoImage
from PIL import Image
root = tk.Tk()
root.title('Pitch/Roll/Yaw Simulator')
# pick image file
fname = "PRY_Diag_Dials.png"
bg_image = tk.PhotoImage(file=fname)
# get the width and height of the image
w = bg_image.width()
h = bg_image.height()
# size the window so the image will fill it
root.geometry("%dx%d+50+30" % (w, h))
cv = tk.Canvas(root, width=w, height=h)
cv.pack(side='top', fill='both', expand='yes')
cv.create_image(0, 0, image=bg_image, anchor='nw')
#add images
pitch = tk.PhotoImage(file="Pitch2.gif")
pitch_lbl = tk.Label(cv, image=pitch,bg="white")
pitch_lbl.image = pitch
pitch_lbl.place(x=60, y=180)
roll = tk.PhotoImage(file="Roll2.gif")
roll_lbl = tk.Label(cv, image=roll,bg="white")
roll_lbl.image = roll
roll_lbl.place(x=325, y=180)
yaw = tk.PhotoImage(file="Yaw2.gif")
yaw_lbl = tk.Label(cv, image=yaw,bg="white")
yaw_lbl.image = yaw
yaw_lbl.place(x=590, y=180)
root.mainloop()
How can I rotate the image labels through tkinter?
tkinter doesn't have any support for rotating canvas items.
From the official tcl/tk documentation (tkinter is a wrapper around a tcl interpreter):
Individual items may be moved or scaled using widget commands described below, but they may not be rotated.
If you are looking to simple rotate 90, 180 or flip then you can use PIL to transpose.
See this link for the details: Transpose.
Here is the important bits just in case link brakes in the future:
Image.transpose(method)
Transpose image (flip or rotate in 90 degree steps)
Parameters: method – One of:
PIL.Image.FLIP_LEFT_RIGHT
PIL.Image.FLIP_TOP_BOTTOM
PIL.Image.ROTATE_90
PIL.Image.ROTATE_180
PIL.Image.ROTATE_270
PIL.Image.TRANSPOSE
Returns: Returns a flipped or rotated copy of this image.
Related
from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
I want to convert canvas content to a bitmap or other image, and then do other operations, such as rotating or scaling the image, or changing its coordinates.
Bitmaps can improve efficiency to show if I am no longer drawing.
What should I do?
You can either generate a postscript document (to feed into some other tool: ImageMagick, Ghostscript, etc):
from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
cv.update()
cv.postscript(file="file_name.ps", colormode='color')
root.mainloop()
or draw the same image in parallel on PIL and on Tkinter's canvas (see: Saving a Tkinter Canvas Drawing (Python)). For example (inspired by the same article):
from Tkinter import *
import Image, ImageDraw
width = 400
height = 300
center = height//2
white = (255, 255, 255)
green = (0,128,0)
root = Tk()
# Tkinter create a canvas to draw on
cv = Canvas(root, width=width, height=height, bg='white')
cv.pack()
# PIL create an empty image and draw object to draw on
# memory only, not visible
image1 = Image.new("RGB", (width, height), white)
draw = ImageDraw.Draw(image1)
# do the Tkinter canvas drawings (visible)
cv.create_line([0, center, width, center], fill='green')
# do the PIL image/draw (in memory) drawings
draw.line([0, center, width, center], green)
# PIL image can be saved as .png .jpg .gif or .bmp file (among others)
filename = "my_drawing.jpg"
image1.save(filename)
root.mainloop()
I have found a great way of doing this which is really helpful. For it, you need the PIL module. Here is the code:
from PIL import ImageGrab
def getter(widget):
x=root.winfo_rootx()+widget.winfo_x()
y=root.winfo_rooty()+widget.winfo_y()
x1=x+widget.winfo_width()
y1=y+widget.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save("file path here")
What this does is you pass a widget name into the function. The command root.winfo_rootx() and the root.winfo_rooty() get the pixel position of the top left of the overall root window.
Then, the widget.winfo_x() and widget.winfo_y() are added to, basically just get the pixel coordinate of the top left hand pixel of the widget which you want to capture (at pixels (x,y) of your screen).
I then find the (x1,y1) which is the bottom left pixel of the widget. The ImageGrab.grab() makes a printscreen, and I then crop it to only get the bit containing the widget. Although not perfect, and won't make the best possible image, this is a great tool for just getting a image of any widget and saving it.
If you have any questions, post a comment! Hope this helped!
Use Pillow to convert from Postscript to PNG
from PIL import Image
def save_as_png(canvas,fileName):
# save postscipt image
canvas.postscript(file = fileName + '.eps')
# use PIL to convert to PNG
img = Image.open(fileName + '.eps')
img.save(fileName + '.png', 'png')
Maybe you can try to use widget_winfo_id to get the HWND of the canvas.
import win32gui
from PIL import ImageGrab
HWND = canvas.winfo_id() # get the handle of the canvas
rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
im = ImageGrab.grab(rect) # get image of the current location
A better way for #B.Jenkins's answer that doesn't need a reference to the root object:
from PIL import ImageGrab
def save_widget_as_image(widget, file_name):
ImageGrab.grab(bbox=(
widget.winfo_rootx(),
widget.winfo_rooty(),
widget.winfo_rootx() + widget.winfo_width(),
widget.winfo_rooty() + widget.winfo_height()
)).save(file_name)
On my system had serious issues with ghostscript and the ImageGrab in general. Solution draw on PIL Image, save as a file, load file on PhotoImage, which is used to create new TKinter Canvas.
canvas = Canvas(win, width=IMG_W, height=IMG_H)
img = PILImg.new("RGB", (IMG_W, IMG_H), "#000")
draw = ImageDraw.Draw(img)
draw.rectangle([x,y,w,h], fill=color, outline=border)
img.save("stock-chart.png")
copyImg = PhotoImage(file="stock-chart.png")
canvas.create_image(IMG_W_2, IMG_H_2, image=copyImg)
I´m designing a slideshow with no user intervention, where:
a. Every image is generated by the Python script itself
b. There´s no file saving, for performance reasons
c. Every image is shown in fullscreen for a certain time
d. It´s a loop that´s supposed to never end. There´s always going to be an image to show
So far, by adapting code found in a few pages, I have it running. But every image is shown for X time and then the desktop background appears for a second or so.
I´d like to have a smooth switching from one file to next, such as FEH does. As a matter of fact, I´m trying to replace FEH because I need finer control of the display of each file (for instance, changing the time it appears on screen).
Here´s my code:
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageTk
import tkinter
def show_center(pil_image, msDelay):
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
root.after(msDelay, root.destroy)
root.mainloop()
### script body
while True:
# loads common background image
img = Image.open(baseImage)
draw = ImageDraw.Draw(img)
# here: image customization
draw.rectangle(....)
draw.text(....)
img.paste(....)
# shows this file
thisDelay = some Number Calculation
show_center(img, thisDelay)
Any ideas on how to avoid the desktop appearing between images? This will run in a headless Raspberry. I´m using Python3 on Raspbian.
Thanks in advance!
You can use after() instead of the while loop and simply use Label instead of Canvas:
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw, ImageFont
import time
import random
def update_image():
# sample drawing
image = base_image.copy()
draw = ImageDraw.Draw(image)
draw.rectangle((100, 100, 500, 400), outline=random.choice(('red', 'green', 'blue', 'magenta', 'gold', 'orange')))
draw.text((120, 120), f"""{time.strftime("%F %T")}""")
# update image
tkimg = ImageTk.PhotoImage(image)
label.config(image=tkimg)
label.image = tkimg # save a reference to avoid garbage collected
ms_delay = random.randint(1000, 9000) # sample delay calculation
root.after(ms_delay, update_image)
root = tk.Tk()
root.attributes("-fullscreen", 1, "-topmost", 1)
base_image = Image.open("/path/to/base/image")
# label for showing image
label = tk.Label(root, bg="black")
label.pack(fill="both", expand=1)
update_image() # start the slide show
root.mainloop()
Well, it´s working quite well.
The solution required a bit of logic (maybe it makes sense not to destroy the object for every image) and a bit of good old trial & error.
The changes were:
Init the canvas only once, use global vars to make it persistent
For every image, call the display function and keep calling root.update() until the required timeout is reached
So, the prior function gets divided, and it looks like:
global canvas, root
global w, h
def init_image():
global canvas, root
global w, h
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
return
def show_center(pil_image, msDelay):
global canvas, root
global w, h
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
inicio = int(time.time() * 1000)
while 1:
root.update()
if (int(time.time() * 1000) - inicio) > msDelay:
break
return
Function init_image() is called once at beginning, and function show_center() is called for every image as the original post.
I hope this can be useful to anybody trying to accomplish the same.
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()
Im using python 2.7 to build a satellite tracker for a cubesat project at my university. Now I've got the whole thing running as I want it to. I produce 4 images and a load of text based on the ISS's orbit parameters as our cubesat is not yet flying.
The 4 pictures are:
1) Satellite's current position on a globe based on its TLE straight from celestrak.
2) Satellite's current position on a mercator(flat) map.
3) Its current lat/lon position translated to a google earth view to physically interpret where it is.
4) Its current lat/lon position based on current weather maps, for whether or not there are clouds in the region, obscuring the satellites remote sensing imaging capabilities. (Reason why the picture looks ugly and purple, is because this region had no current weather data at the time of retrieving data)
Then I need to implement a text box with information on altitude, velocity, spatial resolution etc.
It's all nice and dandy. But I cant for the love of god figure out how to get this Tkinter GUI to play nice! I'm a geoscientist, not a computer scientist and python programming is very new to me :D
Anyway I did a quick layout in photoshop as of how I want the finished data to be produced in a Tkinter GUI. See the primitive image below:
Now here is my shameful code below:
import Tkinter as tk
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
#The Label widget is a standard Tkinter widget used to display a text or image on the screen.
globeview = tk.Label(window, image = img1)
mercatorview = tk.Label(window, image = img2)
currentweather= tk.Label(window, image = img3)
gearth = tk.Label(window, image = img4)
#The Pack geometry manager packs widgets in rows or columns.
globeview.pack(side = "top", fill = "both", expand = "yes")
mercatorview.pack(side = "top", fill = "both", expand = "yes")
currentweather.pack(side = "bottom", fill = "both", expand = "yes")
gearth.pack(side = "bottom", fill = "both", expand = "yes")
#Start the GUI
window.mainloop()
Which produces this horror show!
My issues are clearly:
1) Images are not aligned in any way I want. Normally in HTML I'd set up table rows, align and fit it with spacers as I want. But I don't know how to define that here and I've spend hours being frustrated of this by now.
2) I need to add a text box. Every time I've tried to add various versions of text boxes. I get weird pyimage'xx' errors and no text box seems to materialize.
3) For the future: A button under the images that will show the full size uncropped picture. But that's not imperative right now!
So I'm hoping one of you have a nice way to do this, or can point me in a direction.. or perhaps even have done something like it where I can see your code and just tweak the numbers of a bit for aligning the pixels.
Thank you in advance.
You are trying to lay everything out in a single frame. That can be done, but it's much easier to make subframes for things that fall in neat rows, columns or grids, and then put the subframes into the final arrangement.
Totally untested guess:
import Tkinter as tk
from tkFont import Font
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Aarhus University Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
header = tk.Label(window, text="Header", font=Font(size=40))
header.pack()
toprow = tk.Frame(window)
globeview = tk.Label(toprow, image = img1)
globeview.pack(side = "left") # the side argument sets this to pack in a row rather than a column
infobox = tk.Text(toprow)
infobox.pack(side = "left")
toprow.pack() # pack the toprow frame into the window
bottomrow = tk.Frame(window)
mercatorview = tk.Label(bottomrow, image = img2)
currentweather= tk.Label(bottomrow, image = img3)
gearth = tk.Label(bottomrow, image = img4)
mercatorview.pack(side = "left")
currentweather.pack(side = "left")
gearth.pack(side = "left")
bottomrow.pack()
#Start the GUI
window.mainloop()
import Tkinter as tk
from PIL import ImageTk, Image
#This creates the main window of an application
window = tk.Tk()
window.title("Aarhus University Satellite Control Center")
window.geometry("1000x800")
window.configure(background='grey')
#Imports the pictures.
pic1 = "Globeview.png"
pic2 = "MercatorView.png"
pic3 = "currentweathercroppedsmall.png"
pic4 = "GECurrentcroppedsmall.png"
#Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
img1 = ImageTk.PhotoImage(Image.open(pic1))
img2 = ImageTk.PhotoImage(Image.open(pic2))
img3 = ImageTk.PhotoImage(Image.open(pic3))
img4 = ImageTk.PhotoImage(Image.open(pic4))
#The Label widget is a standard Tkinter widget used to display a text or image on the screen.
globeview = tk.Label(window, image = img1).grid(row=0,column=0)
mercatorview = tk.Label(window, image = img2).grid(row=1,column=0)
currentweather= tk.Label(window, image = img3).grid(row=1,column=1)
gearth = tk.Label(window, image = img4).grid(row=1,column=2)
#Start the GUI
window.mainloop()
You need to add row and column in the label grid option. After you are creating the Label , put the grid and mention row and column . I have updated the code, just check it . If you face any problem write us back, and remove pack, which I deleted in the code :) Cheers
I'm working on using PIL to average the pixel intensities over a subarea of a video. What I want to do is:
-Use ffmpeg to turn the video into several frames
-Use PIL to choose a window in each frame (this is the step I'd like help with)
-Do some sort of analysis on that window in each frame, and aggregate the data (like, say, average color vs. time)
I'm at quite a loss as to how to do the middle step -- does anyone have suggestions?
Found a solution using Tkinter:
import Tkinter
import Image, ImageDraw, ImageTk
window = Tkinter.Tk()
window.title('Calcium Imaging Software')
mouse_X = 0
mouse_Y = 0
ellipseBox = []
listOfCircles = []
#stuff to set up the image
image = Image.open("test.jpg")
draw = ImageDraw.Draw(image)
canvas = Tkinter.Canvas(window, width=image.size[0], height=image.size[1])
canvas.pack()
image_tk = ImageTk.PhotoImage(image)
canvas.create_image(image.size[0]//2, image.size[1]//2, image=image_tk)
#define a function to be run on the mouseclick
def drawEllipse(event):
global ellipseBox
print "clicked at: ", event.x, event.y
mouse_X = event.x
mouse_Y = event.y
ellipseBox.append((mouse_X,mouse_Y))
print "box corners: ",ellipseBox
#When two corners are selected, draw the ellipse
if len(ellipseBox) == 2:
draw.ellipse(ellipseBox,outline=(255,255,255))
listOfCircles.append(tuple(ellipseBox))
ellipseBox = []
window.update()
#bind mouse click to drawing an ellipse
canvas.bind("<Button-1>", drawEllipse)
Tkinter.mainloop()
And this does almost everything I want! However, I can't get the ellipses to show up on the image -- any suggestions?