How to convert ImageTk to Image? - python-3.x

Let's say I have some ImageTk.PhotoImage image stored in the variable imgtk. How can I convert it back to an Image.Image?
The reason is that I want to resize it, but it seems that .resize() only works for Image.Images.

I know it is awfully late, but I just came across the same issue and I just discovered that there is a getimage(imagetk) function in the ImageTk interface.
So, to get your imgtk back as an PIL Image you can do:
img = ImageTk.getimage( imgtk )
I just did a quick test on Windows (Python 3.8.5/Pillow 8.1.2/Tkinter 8.6) and it seems to work fine:
# imgtk is an ImageTk.PhotoImage object
img = ImageTk.getimage( imgtk )
img.show()
img.close()

Ok, that was not easy but I think I have a solution though you need to go into some private methods of label.image. Maybe there is a better way if so I would love to see.
import tkinter as tk
from tkinter import Label
import numpy as np
from PIL import Image, ImageTk
root = tk.Tk()
# create label1 with an image
image = Image.open('pic1.jpg')
image = image.resize((500, 750), Image.ANTIALIAS)
picture = ImageTk.PhotoImage(image=image)
label1 = Label(root, image=picture)
label1.image = picture
# extract rgb from image of label1
width, height = label1.image._PhotoImage__size
rgb = np.empty((height, width, 3))
for j in range(height):
for i in range(width):
rgb[j, i, :] = label1.image._PhotoImage__photo.get(x=i, y=j)
# create new image from rgb, resize and use for label2
new_image = Image.fromarray(rgb.astype('uint8'))
new_image = new_image.resize((250, 300), Image.ANTIALIAS)
picture2 = ImageTk.PhotoImage(image=new_image)
label2 = Label(root, image=picture2)
label2.image = picture2
# grid the two labels
label1.grid(row=0, column=0)
label2.grid(row=0, column=1)
root.mainloop()
Actually you can zoom and reduce the original picture by using the methods zoom to enlarge the picture (zoom(2) doubles the size) and subsample to reduce the size (subsample(2) halves the picture size).
for example
picture2 = label1.image._PhotoImage__photo.subsample(4)
reduces the size of the picture to a quarter and you can skip all the conversion to an Image.
According to label1.image._PhotoImage__photo.subsample.__doc__:
Return a new PhotoImage based on the same image as this widget but use only every Xth or Yth pixel. If y is not given, the default value is the same as x
and label1.image._PhotoImage__photo.zoom.__doc__:
Return a new PhotoImage with the same image as this widget but zoom it with a factor of x in the X direction and y in the Y direction. If y is not given, the default value is the same as x.

Related

How can I resize image without stretching it in Tkinter?

I am currently working on a project which requires displaying Images of different sizes. So I wanted to use tkinter for that purpose.
Here is my Code for displaying image
from tkinter import Tk, Label
from PIL import Image, ImageTk
root = Tk()
root.title("Display Images")
root.geometry('550x550')
root.maxsize(550, 550)
root.iconbitmap('icon.ico')
root.configure(background='#333')
image = Image.open('./image.jpg')
resized_image = image.resize((540, 540), Image.ANTIALIAS)
disp_image = ImageTk.PhotoImage(resized_image)
label = Label(image=disp_image) #.pack(padx=10, pady=10)
label.configure(border=0)
label.pack(padx=10, pady=10)
root.mainloop()
which works perfectly fine but
I wanted to display different sizes of images without stretching them.
Like, If image size exceeds the window size, it should take the size of the window, and If image size is less than the size of the window then there should be no change in image size.
Example:
I have two images of width and height [200x300, 400x500]
and my display size(windows width and height) is 350x350
When I display an image of size 200x300, it should display as it is without change in size and when I display another image it should change its size to 350x350
So, is there a way to pull that off?
Here is a solution:
from tkinter import Tk, Label
from PIL import Image, ImageTk
root = Tk()
root.title("Display Images")
root.geometry('550x550')
root.maxsize(550, 550)
root.configure(background='#333')
image = Image.open('./image.jpg')
# main part -----------------------------------------
if image.width > 540 and image.height > 540:
resized_image = image.resize((540, 540), Image.ANTIALIAS)
elif image.width > 540:
resized_image = image.resize((540, image.height), Image.ANTIALIAS)
elif image.height > 540:
resized_image = image.resize((image.width, 540), Image.ANTIALIAS)
else:
resized_image = image
# end main part ----------------------------------------
disp_image = ImageTk.PhotoImage(resized_image)
label = Label(image=disp_image, border=0)
label.pack(padx=10, pady=10)
root.mainloop()
Simple use of if statements.

Tkinter resizing images in labels arranged in a grid

To start, I'm totally new to Tkinter and am trying to make a Raspberry Pi Media Player of sorts...
I grab the directories of all .mp4 files on a USB drive, and use PIL to put the thumbnails of the videos into a 3x3 grid of labels, with the grid inside of a frame (frame2 in the code).
Right now, with the thumbnails of varying sizes, the labels are also inconsistently sized. Also, only the top-right portion of larger thumbnails are displayed, rather than the entire image.
How can I to scale and fit the thumbnails into consistently sized labels, in grid form?
Here is part of my code (It's quite large so I try to include only the relevant parts):
import tkinter as tk
from subprocess import Popen
from time import sleep
import os
from random import randint
import imageio
from PIL import ImageTk, Image
from pathlib import Path
#putting 100th frame of video with 'path' into the label
def pack_thumbnail(path, label):
#this is probably not a good way to do this
video = imageio.get_reader(path)
for i in range(100):
try:
image = video.get_next_data()
except:
video.close()
break
frame_image = ImageTk.PhotoImage(Image.fromarray(image))
label.config(image=frame_image)
label.image = frame_image
window = tk.Tk()
window.attributes("-fullscreen", True)
frame1 = tk.Frame(master=window, width=200, height=100, bg="white")
frame1.pack(fill=tk.Y, side=tk.LEFT)
#frame2 contains the grid of labels
frame2 = tk.Frame()
for i in range(3):
frame2.columnconfigure(i, weight=1, minsize=75)
frame2.rowconfigure(i, weight=1, minsize=50)
for j in range(0, 3):
frame = tk.Frame(master=frame2, relief=tk.RAISED, borderwidth=1)
frame.grid(row=i, column=j, padx=5, pady=5)
#path to video to get thumbnail (i only have 3 videos so i randomize it)
vid_path=f"/media/pi/{os.listdir('/media/pi/')[0]}/{folder_name}/{videos[randint(0, 2)]}"
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
pack_thumbnail(vid_path, label)
label.pack(padx=5, pady=5)
frame2.pack()
window.bind("<Escape>", lambda x: window.destroy())
window.mainloop()
You can use Image.thumbnail() to resize the image:
# putting 100th frame of video with 'path' into the label
def pack_thumbnail(path, label):
with imageio.get_reader(path) as video:
image = video.get_data(100) # use get_date() instead of get_next_data()
w, h = 200, 200 # thumbnail size
image = Image.fromarray(image)
image.thumbnail((w, h)) # resize the image
frame_image = ImageTk.PhotoImage(image)
label.config(image=frame_image, width=w, height=h)
label.image = frame_image

Is there a function to display an image in tkinter (that is any file type) and display it as a specific x-y position on the canvas

I have an array of image paths and a function that finds the paths connected to buttons that are where I want to display the image
I have already tried adding
file_path = photo.name
img = Image.open(file_path)
photo_image = ImageTk.PhotoImage(img)
tk.Label(window, image=photo_image).pack(side=tk.TOP)
to my function but the photos end up being too big and I want the pictures to be placed right above the buttons so I know the exact x-y coordinates I want the pictures to be I just need a way to get them there and to make them smaller
To resize your image, pass a tuple of size in pixels:
img = Image.open("1.bmp").resize((640, 480))
To place your image right above the buttons, you can use place.
tk.Label(root, image=photo_image).place(x=0,y=0) #change x and y to your desired value
You can also get the current loc of your button widget to calculate the exact location on where you need to place your label:
x,y = widget.winfo_x(), widget.winfo_y()
To sum it up:
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
root.geometry("500x500")
def place_image():
x, y = button1.winfo_x(), button1.winfo_y()
tk.Label(root, image=photo_image).place(x=x,y=y+20)
button1 = tk.Button(root,text="Click me",command=place_image)
button1.pack()
img = Image.open("1.bmp").resize((400, 300))
photo_image = ImageTk.PhotoImage(img)
root.mainloop()
You can place the label at specific pixel coordinates within the canvas using .place(). You can also define the label's width and height in pixels, inside the brackets. This will therefore define the width and height in pixels, of the image.
E.g:
tk.Label(window, image=photo_image).place(x=300, y=400, width=200, height=50)
place() Pixel coordinates work like this in tkinter (for a 600 pixel by 600 pixel canvas):
Increasing a tkinter widget's X coordinate moves it rightwards.
Increasing a tkinter widget's Y coordinate moves it downwards.

How do I get my program to update this tkinter label in my nested loop so that it shows the grayscale process?

So I made a program that converts an image to grayscale. I first used graphics.py from a Zelle's Python Programming An Introduction To Computer Science book. I believe this is based on tkinter. I finished the program using a nested loop to iterate through a .gif picture and grab each individual pixel, replacing it with a grayscale algorithm. I had the change happen in a loop and it gave it an effect that shows the grayscale happening across the window. This was slow but it looked neat, but I realized I wanted to be able to use more file types. I found PIL for Python 3.3 and tkinter, using PIL to open the images, turning them into a tkinter PhotoImage, than displaying them in a tkinter window. Now my program will show the image before being processed and after, I would simply like to see the program update the image in the loop so that it shows the grayscale process. Any help would be great appreciated.
Here is my code:
from PIL import Image, ImageTk
from graphics import GraphWin
import tkinter
window = tkinter.Tk()
window.title('# Grayscale')
def GrayScaleConvertor():
#Opens image, creates window and draws image
picToConvert = 'sea.jpg'
pic = Image.open(picToConvert)
picWidth, picHeight = pic.size
# Treats the image as a 2d array, iterates through changing the
#values of each pixel with the algorithm for gray
tkPic = ImageTk.PhotoImage(pic, master = window)
label1 = tkinter.Label(window, image = tkPic)
rgbList = pic.load() #Get a 2d array of the pixels
for row in range(picWidth):
for column in range(picHeight):
rgb = rgbList[row,column]
r,g,b = rgb # Unpacks the RGB value tuple per pixel
grayAlgorithm1 = (r+g+b) // 3
rgbList[row,column] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
# Converting to a tkinter PhotoImage
tkPic1 = ImageTk.PhotoImage(pic, master = window)
label2 = tkinter.Label(window, image = tkPic1)
# Draws the images to the window
label1.pack() # The image before grayscale
label2.pack() # The image after being grayscaled
window.mainloop()
GrayScaleConvertor()
You could use after(time, function_name) to call function (for example) every 10ms and change one (or more) pixel.
pseudocode:
tkinter.after(10, change_one_pixel)
def change_one_pixel():
change_next_pixel() # if you change more pixels you get faster animation
create_new_image()
update_label()
if any_pixel_left:
tkinter.after(10, change_one_pixel)
EDIT:
Full working code
#import Tkinter as tk # Python 2.7.x
import tkinter as tk # Python 3.x
from PIL import Image, ImageTk
class GrayScaleConvertor():
def __init__(self):
self.window = tk.Tk()
self.window.title('# Grayscale')
#Opens image, creates window and draws image
picToConvert = 'sea.jpg'
#picToConvert = 'background.jpg'
self.pic = Image.open(picToConvert)
# Treats the image as a 2d array, iterates through changing the
#values of each pixel with the algorithm for gray
self.tkPic1 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label1 = tk.Label(self.window, image = self.tkPic1)
# Converting to a tkinter PhotoImage
self.tkPic2 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label2 = tk.Label(self.window, image = self.tkPic2)
# Draws the images to the window
self.label1.pack() # The image before grayscale
self.label2.pack() # The image after being grayscaled
self.column = 0 # start column
self.step = 10 # number columns in one step
self.window.after(1, self.change_pixel) # 1ms
def run(self):
self.window.mainloop()
def change_pixel(self):
rgbList = self.pic.load() #Get a 2d array of the pixels
picWidth, picHeight = self.pic.size
# not to leave image
if self.column + self.step > picWidth:
self.step = picWidth - self.column
# change columns
for column in range(self.column, self.column+self.step):
for row in range(picHeight):
rgb = rgbList[column,row]
r,g,b = rgb # Unpacks the RGB value tuple per pixel
grayAlgorithm1 = (r+g+b) // 3
rgbList[column,row] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
# change image in label
if self.tkPic2:
del self.tkPic2
self.tkPic2 = ImageTk.PhotoImage(self.pic, master = self.window)
self.label2.config(image = self.tkPic2)
# move start column
self.column += self.step
# if still are columns - call again
if self.column < picWidth:
print "change image"
self.window.after(1, self.change_pixel)
else:
print "the end"
GrayScaleConvertor().run()

How to resize an image using tkinter?

Is it possible to resize an image using tkinter only? If so, how can that be done?
You can resize a PhotoImage using the zoom and subsample methods. Both methods return a new PhotoImage object.
from tkinter import *
root = Tk() #you must create an instance of Tk() first
image = PhotoImage(file='path/to/image.gif')
larger_image = image.zoom(2, 2) #create a new image twice as large as the original
smaller_image = image.subsample(2, 2) #create a new image half as large as the original
However, both of these methods can only take integer values as arguments, so the functionality is limited.
It is possible to scale by decimal values but it is slow and loses quality. The below code demonstrates scaling by 1.5x:
new_image = image.zoom(3, 3) #this new image is 3x the original
new_image = new_image.subsample(2, 2) #halve the size, it is now 1.5x the original
from PIL import Image
img = Image.open("flower.png")
img = img.resize((34, 26), Image.ANTIALIAS)
For further information, go to http://effbot.org/imagingbook/image.htm
Here is a way to resize images (PhotoImages) using just tkinter. Here is a simple function that may suit your needs. This is a rudimentary function that reads the image pixel by pixel simply scaling from one image to another. It may be slow, but depending on your needs, it may suit you well. (In this context/discussion when I refer to an image, I am referring to a PhotoImage or instance of PhotoImage.)
Basically, it takes three arguments:
your image (an instance of a PhotoImage)
your desired width in pixels
your desired height in pixels
It returns a new instance of a PhotoImage.
If you want, you can reference your original image to the returned image effectively resizing your image. Or you can just retrieve a new image.
Here is the function and some examples:
from tkinter import *
def resizeImage(img, newWidth, newHeight):
oldWidth = img.width()
oldHeight = img.height()
newPhotoImage = PhotoImage(width=newWidth, height=newHeight)
for x in range(newWidth):
for y in range(newHeight):
xOld = int(x*oldWidth/newWidth)
yOld = int(y*oldHeight/newHeight)
rgb = '#%02x%02x%02x' % img.get(xOld, yOld)
newPhotoImage.put(rgb, (x, y))
return newPhotoImage
That function should resize an image, pixel by pixel and return a new image.
Basically in a nutshell, you create an image of the desired size and must fill it in pixel by pixel with your desired colors using data from the original image. You can think of this process maybe using a line (y=mx+b where b=0) or ratio or scale factor or however you want to think about it. Bottom line is you have to fill in the new pixel data by retrieving data from the original image.
To change the size of your image you could do something like this. Here would be example code:
from tkinter import *
#insert the resize function here
root = Tk()
myCanvas = Canvas(root, width=300, height=300)
myCanvas.pack()
puppyImage = PhotoImage(file="bassethound.png") # a 200px x 200px image
puppyImage = resizeImage(puppyImage, 150, 150) # resized to 150px x 150px
myCanvas.create_image(50, 50, anchor=NW, image=puppyImage)
myCanvas.create_text(0, 0, anchor=NW, text="original 200x200\nnow150x150")
root.mainloop()
And here is the result:
And here is the 200x200 image, shrunk to 100x100 and expanded to 300x300 with this code:
from tkinter import *
#insert the resize function here
root = Tk()
myCanvas = Canvas(root, width=600, height=300)
myCanvas.pack()
puppyImage = PhotoImage(file="bassethound.png") # a 200px x 200px image
puppySmall = resizeImage(puppyImage, 100, 100)
puppyLarge = resizeImage(puppyImage, 300, 300)
myCanvas.create_image(0, 0, anchor=NW, image=puppyImage)
myCanvas.create_text(0, 0, anchor=NW, text="original 200x200")
myCanvas.create_image(200, 0, anchor=NW, image=puppySmall)
myCanvas.create_text(200, 0, anchor=NW, text="small 100x100")
myCanvas.create_image(300, 0, anchor=NW, image=puppyLarge)
myCanvas.create_text(300, 0, anchor=NW, text="larger 300x300")
root.mainloop()
And here is the result of that code:
Here are just some arbitrary numbers, say 273px and 88px:
from tkinter import *
# put resize function here
root = Tk()
myCanvas = Canvas(root, width=400, height=100)
myCanvas.pack()
puppyImage = PhotoImage(file="bassethound.png") # a 200px x 200px image
puppyImage = resizeImage(puppyImage, 273, 88) # resized to 273px x 88px
myCanvas.create_image(0, 0, anchor=NW, image=puppyImage)
root.mainloop()
and the result:
This answer inspired by acw1668 and roninpawn at the following link:
How to rotate an image on a canvas without using PIL?
photo attribution:
n nlhyeyyeusysAnderson Nascimento, CC BY 2.0 https://creativecommons.org/licenses/by/2.0, via Wikimedia Commons
from: https://en.wikipedia.org/wiki/Puppy
Just in case anyone comes across this for future reference, as I was looking for this myself earlier. You can use tkinter's PhotoImage => subsample method
I wouldn't say it really resizes in a certain sense but if you look up the documentation it returns the same image but skips X amount of pixels specified in the method.
ie:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, ....)
canvas_image = tk.PhotoImage(file = path to some image)
#Resizing
canvas_image = canvas_image.subsample(2, 2) #See below for more:
#Shrinks the image by a factor of 2 effectively
canvas.create_image(0, 0, image = canvas_image, anchor = "nw")
self.canvas_image = canvas_image #or however you want to store a refernece so it's not collected as garbage in memory
So say our original image was 400x400 it is now effectively at 200x200. This is what I've been using when I need to compile a game or something I made and don't want to deal with PIL and it's compiling issues.
However, other than the above reason I'd just use PIL.
As far as I know (and it's been a while since I've touched Tkinter), it's a GUI toolkit. The closest it comes to "images" is the PhotoImage class which allows you to load them up and use them in your GUIs. If you want to edit/alter an image, I think you'd be better of using the Python imaging library (PIL).

Resources