Pixel information from bitmap or wxImage using wxPython - colors

I have a question concerning getting an individual pixel’s color information from a bitmap. I’ve searched these forums, along with demos and tutorials, and while I believe I understand what I need to do in theory, I’m not able to actually do it.
Here’s an example of my code (I’ve shortened it, but this is a working example):
import os, sys
import wx
import wx.lib.plot as plot
import Image
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(500, 500))
HolderPanel = wx.Panel(self, wx.ID_ANY)
panel2 = MyPanel_2(HolderPanel, wx.ID_ANY)
framesizer = wx.BoxSizer(wx.HORIZONTAL)
framesizer.Add(panel2, 1, wx.EXPAND | wx.BOTTOM | wx.TOP | wx.RIGHT, 2)
HolderSizer = wx.BoxSizer(wx.VERTICAL)
HolderSizer.Add(framesizer, 1, wx.EXPAND)
HolderPanel.SetSizer(HolderSizer)
self.Show()
class MyPanel_2(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id, style=wx.SIMPLE_BORDER)
self.SetBackgroundColour('grey')
# Create main image holder
self.img_1 = wx.EmptyImage(300,300)
self.imageCtrl_1 = wx.StaticBitmap(self, wx.ID_ANY, wx.BitmapFromImage(self.img_1)) # Starting with an EmptyBitmap, the real one will get put there by the call onView
self.PhotoMaxSize_1 = (300)
self.imageCtrl_1.Bind(wx.EVT_LEFT_DOWN, self.onMouseClick_img1)
# Create the browse button
brwsBtn = wx.Button(self, wx.ID_ANY, 'Browse', (10, 10))
brwsBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
# Set up the text box
self.photoTxt = wx.TextCtrl(self, size=(200,-1))
# Create the sizers
vsizer1 = wx.BoxSizer(wx.VERTICAL)
hsizer3 = wx.BoxSizer(wx.HORIZONTAL)
vsizer_MAIN = wx.BoxSizer(wx.VERTICAL)
# -----------------------------------------------------------------------------------------------------------
vsizer1.Add(self.imageCtrl_1, proportion = 0, flag= wx.ALIGN_CENTRE | wx.ALL, border=10)
# -----------------------------------------------------------------------------------------------------------
hsizer3.Add(brwsBtn, proportion = 0, flag = wx.ALIGN_CENTRE_VERTICAL | wx.LEFT | wx.RIGHT, border=10)
hsizer3.Add(self.photoTxt, proportion = 1, flag = wx.ALIGN_CENTRE_VERTICAL | wx.LEFT | wx.RIGHT, border = 10)
# -----------------------------------------------------------------------------------------------------------
vsizer_MAIN.Add(vsizer1, proportion = 1, flag = wx.EXPAND)
vsizer_MAIN.Add(hsizer3, proportion = 1, flag = wx.EXPAND)
# -----------------------------------------------------------------------------------------------------------
self.SetSizer(vsizer_MAIN)
def onBrowse(self, event):
"""
Browse for file
"""
wildcard = "pictures (*.jpg, *.jpeg, *.png)|*.jpg;*.jpeg;*.png"
dialog = wx.FileDialog(None, "Choose a file", wildcard=wildcard, style=wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.photoTxt.SetValue(dialog.GetPath())
dialog.Destroy()
self.onView()
def onView(self):
self.filepath = self.photoTxt.GetValue()
self.img_1 = wx.Image(self.filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = self.img_1.GetWidth()
H = self.img_1.GetHeight()
if W > H:
NewW = self.PhotoMaxSize_1
NewH = self.PhotoMaxSize_1 * H / W
else:
NewH = self.PhotoMaxSize_1
NewW = self.PhotoMaxSize_1 * W / H
self.img_1 = self.img_1.Scale(NewW,NewH)
self.imageCtrl_1.SetBitmap(wx.BitmapFromImage(self.img_1)) # Converts the scaled image to a wx.Bitmap and put it on the wx.StaticBitmap
self.Refresh()
def onMouseClick_img1(self, event):
im = Image.open(self.filepath)
pos = event.GetPosition()
pix = im.load()
print pix[pos.x, pos.y]
app = wx.App()
MyFrame(None, -1, 'Current Build')
app.MainLoop()
What this allows me to do is browse for and import an image, resize the image, and then select individual pixels to access their color information. However, there is a problem with this code: the color information does not match with the actual image (and in some cases, there’s an error relating to being out of the image’s range). I went back to check this and realized that
im = Image.open(self.filepath)
is referencing the actual path to the image, and that’s what the event.GetPosition() is reading (since the resized image isn’t the same size, my code isn’t reading what’s presented). I realized that I can get the pixel's color information because I'm looking at a wxImage and not the converted bitmap from the original image. By adjusting the onMouseClick_img1 event to just:
def onMouseClick_img1(self, event):
pos = event.GetPosition()
print pos
I can read the position of any point on my empty StaticBitMap OR on my loaded, resized image that I converted to a bitmap. However, I cannot pull the color information of the selected pixel. After searching, I found this page
and tried both methods, but ended up with errors for both. Since I’d like to use the wx.Image method, I tried this:
def onMouseClick_img1(self, event):
img = wx.ImageFromBitmap(self.img_1)
pos = event.GetPosition()
print (img.GetRed(pos), img.GetGreen(pos), img.GetBlue(pos))
but I get this error message:
Traceback (most recent call last): File "/Users/Documents/[user
name]/Eclipse_workspace/builder/src/GUI/Maybe.py", line 254, in
onMouseClick_img1
img = wx.ImageFromBitmap(self.img_1) File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/wx/_core.py",
line 3750, in ImageFromBitmap
val = core.new_ImageFromBitmap(*args, **kwargs) TypeError: in method 'new_ImageFromBitmap', expected argument 1 of type 'wxBitmap
const &'
I thought that all I needed to do was convert the bitmap back to an image once I had resized it and then pull the pixel information from that, but I'm obviously doing something incorrectly somewhere. Any help would be greatly appreciated. This is my first attempt at a real GUI, and I'm new to wxPython, so if you see something more general that's wrong, please feel free to let me know.

So, I found a way to make this work. Perhaps I wasn't fully understanding what I needed to do in the first place (and maybe I'm still missing key ideas here), but what I've done works. It might be the case that the way that I got this working is ugly or inefficient.. but it works. Did I mention that I think this works?
Instead of trying to pull any information from the displayed bitmap, or convert the bitmap to another format and display that, I display the resized image in a StaticBitmap and use another method to pull the pixel information:
def onView(self):
"""
This is pulling in the image and resizing it, maintaining its original ratio; if the image is square, this is fine as long as the image boxes are square; the ratios
are getting messed up depending on the size of the picture; I would imagine that the images we're going to be importing are going to be pretty uniform.
"""
# Code imports and resizes the image
self.filepath = self.photoTxt.GetValue()
self.img_1 = wx.Image(self.filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = self.img_1.GetWidth()
H = self.img_1.GetHeight()
if W > H:
NewW = self.PhotoMaxSize_1
NewH = self.PhotoMaxSize_1 * H / W
else:
NewH = self.PhotoMaxSize_1
NewW = self.PhotoMaxSize_1 * W / H
self.img_1 = self.img_1.Scale(NewW,NewH)
self.imageCtrl_1.SetBitmap(wx.BitmapFromImage(self.img_1)) # Converts the scaled image to a wx.Bitmap and put it on the wx.StaticBitmap
self.Refresh()
# Code creates an image using wxImage to pull pixel information from
self.img_1_IMAGE = Image.open(self.filepath)
self.img_1_IMAGE_r = self.img_1_IMAGE.resize((NewW, NewH))
def onMouseClick_img1(self, event):
im = self.img_1_IMAGE_r
pos = event.GetPosition()
pix = im.load()
print pix[pos.x, pos.y]
The key difference seems to be (at least to me) that I didn't convert this image into a bitmap and assign it to my StaticBitmap. This allows me to access the pixel information of the resized image when onMouseClick_img1 is called. Since I use the same resizing parameters for each image, the dimensions should match up (and they seem to do so), allowing me to see the image in the StaticBitmap that's displayed while pulling pixel information from the resized image.
If anyone has any input or concerns about this, please feel free to let me know. I just wanted to post what I found that works in case someone else was stuck with a similar issue.

Related

How to convert ImageTk to Image?

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.

How to enhance window size selection on a tkinter project including button-image as label?

I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)

In wxPython, how to get the mouse position related to image in ScrolledPanel?

I am implementing a simple map editor using wxPython. A StaticBitmap image control is hosted in ScrolledPanel. The image size is larger than the panel size, so the scroll bars appear. In mouse moving event handler, function event.GetPosition() is able to get the mouse position related to the top-left of the panel. However, I want to get the mouse position related to the top-left of the image. How could I achieve that? Thanks.
For example, in the first screenshot (scrolled to the top), if mouse is in the top-left of the panel, the position related to image should be (0, 0). In the second screenshot (scrolled to the bottom), if mouse is in the top-left of the panel, the position related to the image is (0, image_height-panel_height).
#!/usr/bin/python
import wx
import wx.lib.scrolledpanel
class SimpleFrame(wx.Frame):
def __init__(self, parent):
super(SimpleFrame, self).__init__(parent)
# add a panel so it looks the correct on all platforms
self.frame_panel = wx.Panel(self)
frame_panel = self.frame_panel
# image panel
self.image_panel = wx.lib.scrolledpanel.ScrolledPanel(frame_panel, style=wx.SIMPLE_BORDER)
image_panel = self.image_panel
image_panel.SetAutoLayout(True)
image_panel.SetupScrolling()
# image panel - image control
self.image_ctrl = wx.StaticBitmap(image_panel)
self.image_ctrl.Bind(wx.EVT_MOTION, self.ImageCtrl_OnMouseMove)
img = wx.Image("image.jpg", wx.BITMAP_TYPE_ANY)
self.image_ctrl.SetBitmap(wx.BitmapFromImage(img))
image_panel.Layout()
image_sizer = wx.BoxSizer(wx.VERTICAL)
image_sizer.Add(self.image_ctrl)
image_panel.SetSizer(image_sizer)
# frame sizer
frame_sizer = wx.BoxSizer(wx.HORIZONTAL)
frame_sizer.Add(image_panel, proportion=1, flag=wx.EXPAND | wx.ALL)
frame_panel.SetSizer(frame_sizer)
return
def ImageCtrl_OnMouseMove(self, event):
# position in control
ctrl_pos = event.GetPosition()
print("ctrl_pos: " + str(ctrl_pos.x) + ", " + str(ctrl_pos.y))
# position in image
#image_pos = ??? convert control position to image position
#print("image_pos: " + str(image_pos.x) + ", " + str(image_pos.y))
app = wx.PySimpleApp()
frame = SimpleFrame(None)
frame.Show()
app.MainLoop()
Scrolling to top:
Scrolling to bottom:
You need to use GetScreenPosition() to get the coordinates of where the window currently is and ScreenToClient() to get the client coordinates of the image. Add them together and you will get your relative position on the image.
The beauty of doing it this way is that the user can re-position the window or change its size and you get a consistent position.
Here's your code amended:
#!/usr/bin/python
import wx
import wx.lib.scrolledpanel
class SimpleFrame(wx.Frame):
def __init__(self, parent):
super(SimpleFrame, self).__init__(parent)
# add a panel so it looks the correct on all platforms
self.frame_panel = wx.Panel(self)
frame_panel = self.frame_panel
# image panel
self.image_panel = wx.lib.scrolledpanel.ScrolledPanel(frame_panel, style=wx.SIMPLE_BORDER)
image_panel = self.image_panel
image_panel.SetAutoLayout(True)
image_panel.SetupScrolling()
# image panel - image control
self.image_ctrl = wx.StaticBitmap(image_panel)
self.image_ctrl.Bind(wx.EVT_MOTION, self.ImageCtrl_OnMouseMove)
self.img = wx.Image("image.jpg", wx.BITMAP_TYPE_ANY)
self.image_ctrl.SetBitmap(wx.BitmapFromImage(self.img))
image_panel.Layout()
image_sizer = wx.BoxSizer(wx.VERTICAL)
image_sizer.Add(self.image_ctrl)
image_panel.SetSizer(image_sizer)
# frame sizer
frame_sizer = wx.BoxSizer(wx.HORIZONTAL)
frame_sizer.Add(image_panel, proportion=1, flag=wx.EXPAND | wx.ALL)
frame_panel.SetSizer(frame_sizer)
return
def ImageCtrl_OnMouseMove(self, event):
# position in control
ctrl_pos = event.GetPosition()
print("ctrl_pos: " + str(ctrl_pos.x) + ", " + str(ctrl_pos.y))
pos = self.image_ctrl.ScreenToClient(ctrl_pos)
print "pos relative to screen top left = ", pos
screen_pos = self.frame_panel.GetScreenPosition()
relative_pos_x = pos[0] + screen_pos[0]
relative_pos_y = pos[1] + screen_pos[1]
print "pos relative to image top left = ", relative_pos_x, relative_pos_y
app = wx.PySimpleApp()
frame = SimpleFrame(None)
frame.Show()
app.MainLoop()
I know that's an old thread, but for all people landing here (like me), there's another way:
def OnMouseMove(self, event):
dc = wx.ClientDC(self)
self.DoPrepareDC(dc)
pos = event.GetLogicalPosition(dc)

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