I'm currently using PIL to display images in Tkinter. I'd like to temporarily resize these images so that they can be viewed more easily. How can I go about this?
Snippet:
self.pw.pic = ImageTk.PhotoImage(Image.open(self.pic_file))
self.pw.pic_label = TK.Label(self.pw , image=self.pw.pic,borderwidth=0)
self.pw.pic_label.grid(column=0,row=0)
Here's what I do and it works pretty well...
image = Image.open(Image_Location)
image = image.resize((250, 250), Image.ANTIALIAS) ## The (250, 250) is (height, width)
self.pw.pic = ImageTk.PhotoImage(image)
There you go :)
EDIT:
Here is my import statement:
from Tkinter import *
import tkFont
from PIL import Image
And here is the complete working code I adapted this example from:
im_temp = Image.open(Image_Location)
im_temp = im_temp.resize((250, 250), Image.ANTIALIAS)
im_temp.save("ArtWrk.ppm", "ppm") ## The only reason I included this was to convert
## The image into a format that Tkinter woulden't complain about
self.photo = PhotoImage(file="ArtWrk.ppm") ## Open the image as a tkinter.PhotoImage class()
self.Artwork.destroy() ## Erase the last drawn picture (in the program the picture I used was changing)
self.Artwork = Label(self.frame, image=self.photo) ## Sets the image too the label
self.Artwork.photo = self.photo ## Make the image actually display (If I don't include this it won't display an image)
self.Artwork.pack() ## Repack the image
And here are the PhotoImage class docs: http://www.pythonware.com/library/tkinter/introduction/photoimage.htm
Note...
After checking the pythonware documentation on ImageTK's PhotoImage class (Which is very sparse) I appears that if your snippet works than this should as well as long as you import the PIL "Image" Library an the PIL "ImageTK" Library and that both PIL and tkinter are up-to-date. On another side-note I can't even find the "ImageTK" module life for the life of me. Could you post your imports?
if you don't want save it you can try it:
from Tkinter import *
from PIL import Image, ImageTk
root = Tk()
same = True
#n can't be zero, recommend 0.25-4
n=2
path = "../img/Stalin.jpeg"
image = Image.open(path)
[imageSizeWidth, imageSizeHeight] = image.size
newImageSizeWidth = int(imageSizeWidth*n)
if same:
newImageSizeHeight = int(imageSizeHeight*n)
else:
newImageSizeHeight = int(imageSizeHeight/n)
image = image.resize((newImageSizeWidth, newImageSizeHeight), Image.ANTIALIAS)
img = ImageTk.PhotoImage(image)
Canvas1 = Canvas(root)
Canvas1.create_image(newImageSizeWidth/2,newImageSizeHeight/2,image = img)
Canvas1.config(bg="blue",width = newImageSizeWidth, height = newImageSizeHeight)
Canvas1.pack(side=LEFT,expand=True,fill=BOTH)
root.mainloop()
the easiest might be to create a new image based on the original, then swap out the original with the larger copy. For that, a tk image has a copy method which lets you zoom or subsample the original image when making the copy. Unfortunately it only lets you zoom/subsample in factors of 2.
Related
The code below illustrates a buffer which displays with pygame.image.frombuffer but not with tkinter PhotoImage
import chess, chess.svg, pylunasvg as pl
from tkinter import Tk, PhotoImage
# This is where I create the buffer
board = chess.Board(chess.STARTING_FEN)
svg_text = chess.svg.board(board, size=400)
document = pl.Document.loadFromData(svg_text)
byts = bytes(document.renderToBitmap())
#Now that I have this raster buffer 'byts'
surf = pygame.image.frombuffer(byt, (400, 400), 'RGBA') #works
root = Tk()
PhotoImage(data=byt) #raises an error about the image
I know this is as a result of something I don't know about tkinter. I would really appreciate that you point out what is wrong and what I can do.
Thanks in advance
PhotoImage's argument must be a file path. However, there is a solution using a PLI image. See Tkinter PhotoImage. Make a PLI image form the buffer and use that image to create an ImageTk.PhotoImage object:
from PIL import Image, ImageTk
image = Image.frombytes('RGBA', (400, 400), byt, 'raw')
root = Tk()
photo_image = ImageTk.PhotoImage(image)
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 have been trying to display several images (in png) on tkinter. All of them have a transparent background, but for some reason jailer.png (the handcuffs) has been displaying with a black background (see screenshot), while every other image works as it should. Why ?
Here's my code (if create_a_label_with_an_image is redudant, it's because i had a lot of problem wth garbage disposal, if anyone knows the trick, pls tell me !) :
import tkinter as tk
from tkinter import ttk
from os import walk
from PIL import Image, ImageTk
class Gui:
def __init__(self):
self.frames={"main":tk.Tk()} #every frame will be added in this dictionnary
self.l_widgets={} #all widgets
def create_notebook(self, name, config_args={}, grid_args={}, frame="main"):
self.l_widgets[name] = (ttk.Notebook(self.frames[frame], **config_args),0)
self.l_widgets[name][0].grid(**grid_args)
def create_a_label_with_an_image(self, name, filename, size=(None, None), config_args={}, grid_args={}, frame="main"):
image_temp=Image.open(filename)
if size!=(None,None):
image_temp.thumbnail(size)
image=ImageTk.PhotoImage(image_temp)
self.l_widgets[name] = (ttk.Label(self.frames[frame]),image) #Image is stored at several places to hope it will not get erased
self.l_widgets[name][0].config(**config_args, image=image)
self.l_widgets[name][0].image=image
self.l_widgets[name][0].grid(**grid_args)
def add_to_notebook(self, name, name_frame, config_args={}, grid_args={}):
self.frames[name_frame]=ttk.Frame(self.frames["main"])
self.l_widgets[name][0].add(self.frames[name_frame], **config_args)
self.l_widgets[name][0].grid(**grid_args)
#END ####
g=Gui()
#here, i get all path to icons from this directory
mypath = "./Wolvesville Notebook/icons/characters"
filenames = next(walk(mypath), (None, None, []))[2] # [] if no file
if filenames==[]:
raise Warning("no icons found")
g.create_notebook("roles")
g.add_to_notebook("roles","all", config_args={"text":"all"})
nbr_images=0
for icon in filenames:
g.create_a_label_with_an_image(icon[:-4], mypath+"/"+icon,
size=(50,50),
grid_args={"row":nbr_images//12,
"column":nbr_images%12},
frame=str(pool))
nbr_images+=1
tk.mainloop()
Here's what the final result look like : Notebook, with one onglet named all, and 5 icons, all transparent but handcuffs
Here is the file that doesn't work : jailer.png.
Thank you in advance for the help !
Check the format of PNG file first, convert to "RGBA" if not.
>>> from PIL import Image
>>> im = Image.open('d:/jailer.png')
>>> im.mode
'LA'
>>> im.getpixel((0, 0))
(0, 0)
>>> new_im = im.convert(mode="RGBA")
>>> new_im.save("D:/jailer_new.png", format="PNG")
Simple code to show the differece, left one is your original copy.
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
im1 = Image.open("D:/jailer.png")
im2 = Image.open("D:/jailer_new.png")
pic1 = ImageTk.PhotoImage(im1)
pic2 = ImageTk.PhotoImage(im2)
label_1 = Label(root, image=pic1, bg='blue')
label_2 = Label(root, image=pic2, bg='blue')
label_1.pack(side='left')
label_2.pack(side='left')
root.mainloop()
AKX commented under my question to convert the grayscale + alpha into rgb + alpha, and it works ! I don't know why it was in grayscale, but it doesn't matter. I will close the subject, thanks !
PS : I can't seem to put a comment as the answer, so I wrote another one.
I'm learning the tkinter module and I got a problem. I did a fonction to show an image from my folder but my image won't show up but the resolution of the window is the same as my image. Anybody have a solution ?
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
def my_image(img, row, column) :
pic = ImageTk.PhotoImage(Image.open("C:/Users/Mark/Downloads/test/" + img))
my_img = Label(image=pic)
return my_img.grid(row=row, column=column)
# Create image
image1 = my_image("luigi_icon.png", 0, 1)
root.mainloop()
This problem was many times on Stackoverflow.
There is bug in PhotoImage which removes image from memory when it is assigned to local variable. You have to assign it to global variable or to some object
Common solution is to assign pic to label which displays it.
my_img.pic = pic
See Note at the end of page PhotoImage
(it is archived version on Wayback Machine because original version was removed)
BTW:
You have other problem. grid() gives None. You should return my_img if you want to access label later (ie. to replace image).
Currently original tk.PhotoImage can work with png so you don't need PIL. But if you want to work with some less popular image format then you may need pillow.
import os
#from tkinter import *
import tkinter as tk # PEP8: `import *` is not preferred
from PIL import ImageTk, Image
# --- functions ---
def my_image(img, row, column) :
fullpath = os.path.join("C:/Users/Mark/Downloads/test/", img)
#pic = ImageTk.PhotoImage(Image.open(fullpath))
# or
#pic = ImageTk.PhotoImage(file=fullpath)
# or even without `PIL/pillow` for `png`
pic = tk.PhotoImage(file=fullpath)
my_img = tk.Label(image=pic)
my_img.grid(row=row, column=column)
my_img.pic = pic # solution for bug in `PhotoImage`
return my_img
# --- main ---
root = tk.Tk()
image1 = my_image("luigi_icon.png", 0, 1)
root.mainloop()
I need to resize an image, but I want to avoid PIL, since I cannot make it work under OS X - don't ask me why...
Anyway since I am satisfied with gif/pgm/ppm, the PhotoImage class is ok for me:
photoImg = PhotoImage(file=imgfn)
images.append(photoImg)
text.image_create(INSERT, image=photoImg)
The problem is - how do I resize the image?
The following works only with PIL, which is the non-PIL equivalent?
img = Image.open(imgfn)
img = img.resize((w,h), Image.ANTIALIAS)
photoImg = ImageTk.PhotoImage(img)
images.append(photoImg)
text.image_create(INSERT, image=photoImg)
Thank you!
Because both zoom() and subsample() want integer as parameters, I used both.
I had to resize 320x320 image to 250x250, I ended up with
imgpath = '/path/to/img.png'
img = PhotoImage(file=imgpath)
img = img.zoom(25) #with 250, I ended up running out of memory
img = img.subsample(32) #mechanically, here it is adjusted to 32 instead of 320
panel = Label(root, image = img)
You have to either use the subsample() or the zoom() methods of the PhotoImage class. For the first option you first have to calculate the scale factors, simply explained in the following lines:
scale_w = new_width/old_width
scale_h = new_height/old_height
photoImg.zoom(scale_w, scale_h)
If you don't have PIL installed --> install it
(for Python3+ users --> use 'pip install pillow' in cmd)
from tkinter import *
import tkinter
import tkinter.messagebox
from PIL import Image
from PIL import ImageTk
master = Tk()
def callback():
print("click!")
width = 50
height = 50
img = Image.open("dir.png")
img = img.resize((width,height), Image.ANTIALIAS)
photoImg = ImageTk.PhotoImage(img)
b = Button(master,image=photoImg, command=callback, width=50)
b.pack()
mainloop()
I just had the same problem, and I found that #Memes' answer works rather well. Just make sure to reduce your ratio as much as possible, as subsample() takes a rather long time to run for some reason.
Basically, the image is zoomed out to the least common factor of the two sizes, and then being subsidized by the origonal size. This leaves you with the image of the desired size.
I had a requirement where I wanted to open an image, resize it, keeping the aspect ratio, save it under a new name, & display it in a tkinter window (using Linux Mint). After looking through dozens of forum questions, and dealing with some weird errors (semmingly involving the PIL to Pillow fork in Python 3.x), I was able to develop some code that works, using a predefined new maximum width or new maximum height (scaling up or down as necessary), and a Canvas object, where the image is displayed centered in the frame. Note that I did not include the file dialogs, just a hardcoded Image open & save for one file:
from tkinter import *
from PIL import ImageTk, Image
import shutil,os
from tkinter import filedialog as fd
maxwidth = 600
maxheight = 600
mainwindow = Tk()
picframe = Frame(mainwindow)
picframe.pack()
canvas = Canvas(picframe, width = maxwidth, height = maxheight)
canvas.pack()
img = Image.open("/home/user1/Pictures/abc.jpg")
width, height = img.size # Code to scale up or down as necessary to a given max height or width but keeping aspect ratio
if width > height:
scalingfactor = maxwidth/width
width = maxwidth
height = int(height*scalingfactor)
else:
scalingfactor = maxheight/height
height = maxheight
width = int(width*scalingfactor)
img = img.resize((width,height), Image.ANTIALIAS)
img.save("/home/user1/Pictures/Newabc.jpg")
img = ImageTk.PhotoImage(img) # Has to be after the resize
canvas.create_image(int(maxwidth/2)-int(width/2), int(maxheight/2)-int(height/2), anchor=NW, image=img) # No autocentering in frame, have to manually calculate with a new x, y coordinate based on a NW anchor (upper left)