How to update an image on a Canvas? - python

This is the essence of the code I'm having trouble with:
camelot = Canvas(main, width = 400, height = 300)
camelot.grid(row = 0, column = 0, rowspan = 11, columnspan = 3)
MyImage = PhotoImage(file = "sample1.gif")
camelot.create_image(0, 0, anchor = NW, image = MyImage)
This is run at the beginning. What I want to do on later in another function is replace "sample1.gif" by "sample2.gif", and, possibly later on, replace that in turn by "sample3.gif". I'm stuck and nothing I've been trying has worked so far.

Adding image to canvas:
self.image_on_canvas = self.canvas.create_image(0, 0, image = ...)
Changing image on canvas:
self.canvas.itemconfig(self.image_on_canvas, image = ...)
Full example:
from Tkinter import *
#----------------------------------------------------------------------
class MainWindow():
#----------------
def __init__(self, main):
# canvas for image
self.canvas = Canvas(main, width=60, height=60)
self.canvas.grid(row=0, column=0)
# images
self.my_images = []
self.my_images.append(PhotoImage(file="ball1.gif"))
self.my_images.append(PhotoImage(file="ball2.gif"))
self.my_images.append(PhotoImage(file="ball3.gif"))
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="Change", command=self.onButton)
self.button.grid(row=1, column=0)
#----------------
def onButton(self):
# next image
self.my_image_number += 1
# 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])
#----------------------------------------------------------------------
root = Tk()
MainWindow(root)
root.mainloop()
Images used in example:
ball1.gif ball2.gif ball3.gif
Result:

MyImage = PhotoImage(file = "sample1.gif")
labelorbuttontodisplayit.image = MyImage
labelorbuttontodisplayit.configure(image=MyImage)
:P that should do it. I only tried to use that code on label or buttons, never as a Canvas, but i guess you can adapt that piece of code a bit.

Related

How to make a image viewer with left right functionality in tkinter?

I have bunch of links and labels in list to view those I want to make a image viewer with next, previous functionality. My links and labels for respective images are in list. Up to now I had tried this :
urls=[url,url1,url2,url3]
labels=["label 1","label 2","label 3","label 4"]
images=[]
for ur in urls:
raw_data = urllib.request.urlopen(ur).read()
im = Image.open(io.BytesIO(raw_data))
image = ImageTk.PhotoImage(im)
images.append(image)
Now I have images ready in images and now I want to display it in image viewer but only last image is visible in image viewer.
Label(root).grid(row=1,column=1)
for i in range(len(images)):
image_label=Label(root,image=images[i])
image_label.grid(row=1,column=2)
name=Label(root,text=labels[i])
name.grid(row=2,column=2)
def left():
image_label=Label(root,image=images[i-1])
image_label.grid(row=1,column=2)
def right():
image_label=Label(root,image=images[i+1])
image_label.grid(row=1,column=2)
left_button=Button(root,text="Left",command=left)
left_button.grid(row=2,column=1)
right_button=Button(root,text="Right",command=right)
right_button.grid(row=2,column=3)
Right button is not working and left button is working but for one time only. When I click left button second time then nothing is working.
Error while clicking in right button :
line 45, in right
image_label=Label(root,image=images[i+1])
IndexError: list index out of range
The code is pretty close to original posted example.
There is no lambda in Button, just straight forward command that gives access to left and right functions.
The name label is unnecessary as label objects already have a text attribute so I've integrated image and text into single label and increased the font size.
label compound is used to control where the image is display relative to the text, I've chosen top. That is, the image will be place above the text.
The use of global in functions keeps track of count
from tkinter import Tk, Label, Frame, Button
from PIL import ImageTk, Image
import io
import requests
import urllib
root = Tk()
urls = [url, url1, url2, url3]
labels = ["label 1","label 2","label 3","label 4"]
count = -1
images = []
for ur in urls:
raw_data = urllib.request.urlopen( ur ).read()
im = Image.open(io.BytesIO( raw_data ))
image = ImageTk.PhotoImage(im)
images.append(image)
def left( ):
global count
count = (count - 1) % len(images)
image_label.config(image = images[count], text = labels[count])
def right( ):
global count
count = (count + 1) % len(images)
image_label.config(image = images[count], text = labels[count])
image_label = Label(
root, image = images[count], font = "Helvetica 20 normal",
text = labels[count], compound = "top")
image_label.grid(row = 0, column = 0, columnspan = 2, sticky = "nsew")
Button(
root, text = "<< LEFT", command = left).grid( row = 1, column = 0, sticky = "ew")
Button(
root, text = "RIGHT >>", command = right).grid(row = 1, column = 1, sticky = "ew")
right( ) # display first image
root.mainloop()
Here's a way to do it by showing and hiding pairs of Labels with the image and it name on them. For development and testing purposes, it loads the images from disk instead of downloading them, but you can easily replace that with what you have in your question.
from pathlib import Path
from PIL import Image, ImageTk
from tkinter import Button, Tk, Label
root = Tk()
root.title('Image Viewer')
# Create thumbnails of all the images.
image_folder = '/path/to/image/folder'
images = []
for filepath in Path(image_folder).iterdir():
img = Image.open(filepath)
w, h = img.size
if w > h:
f = 100 / w
nw, nh = 100, int(f*h)
else:
f = 100 / h
nw, nh = int(f*w), 100
# Offsets to center thumbnail within (100, 100).
woff, hoff = (100-nw) // 2, (100-nh) // 2
img = img.resize((nw, nh), Image.BICUBIC) # Shrink so largest dimen is 100.
thumbnail = Image.new('RGBA', (100, 100), (255, 255, 255, 0)) # Blank.
thumbnail.paste(img, (woff, hoff)) # Offset keep it centered.
images.append(ImageTk.PhotoImage(thumbnail))
names = ['Name 1', 'Name 2', 'Name 3', 'Name 4']
def get_images():
"""Create all image and name Label pairs but initially hidden."""
global image_labels, name_labels
image_labels, name_labels = [], []
for i, (img, name) in enumerate(zip(images, names)):
image_label = Label(root, image=img)
image_label.grid(row=1, column=2)
image_labels.append(image_label)
name_label = Label(root, text=name)
name_label.grid(row=2, column=2)
name_labels.append(name_label)
hide(i)
def hide(i):
"""Hide specified image and name pair."""
image_labels[i].grid_remove()
name_labels[i].grid_remove()
def show(i):
"""Unhide specified image and name pair."""
image_labels[i].grid()
name_labels[i].grid()
def shift(direction):
"""Display the image and pair before or after the current one that is
`direction` steps away (usually +/-1).
"""
global current
hide(current)
current = (current + direction) % len(images)
show(current)
left_button = Button(root, text="Left", command=lambda: shift(-1))
left_button.grid(row=2, column=1)
right_button = Button(root, text="Right", command=lambda: shift(+1))
right_button.grid(row=2, column=3)
get_images()
current = 0 # Currently displayed image.
show(current)
root.mainloop()
Screenshot of it running:
Should just update a image label when shift left or right, not create a new label.
Using modulus % to find which one next turn.
To reduce code and executable, I use the image data in PySimpleGUI and simple name for image here.
import tkinter as tk
import PySimpleGUI as sg
def shift(num):
global index
index = (index + num) % size
label1.configure(image=images[index])
label2.configure(text =labels[index])
root = tk.Tk()
images = [tk.PhotoImage(data=image) for image in sg.EMOJI_BASE64_HAPPY_LIST]
size = len(images)
labels = [f'Image {i:0>2d}' for i in range(size)]
index = 0
label1 = tk.Label(root, image=images[index])
label1.grid(row=1, column=2)
label2 = tk.Label(root, text=labels[index])
label2.grid(row=2, column=2)
button1 = tk.Button(root, text="<LEFT", width=6, anchor='center', command=lambda num=-1:shift(num))
button1.grid(row=1, column=1)
button2 = tk.Button(root, text="RIGHT>", width=6, anchor='center', command=lambda num=+1:shift(num))
button2.grid(row=1, column=3)
root.mainloop()
You can try this :
from tkinter import Tk,Label,Button
from PIL import ImageTk,Image
import io
import requests
import urllib
root=Tk()
count=0 # To know the current image and label
urls=[url,url1,url2,url3]
labels=["label 1","label 2","label 3","label 4"]
images=[]
for ur in urls:
raw_data = urllib.request.urlopen(ur).read()
im = Image.open(io.BytesIO(raw_data))
image = ImageTk.PhotoImage(im)
images.append(image)
def change(direction): # Function to change image
global count
if direction=="left":
if count<=0:
count=len(urls)-1
else:
count-=1
else:
if count>=len(urls)-1:
count=0
else:
count+=1
name.config(text=labels[count])
image_label.config(image=images[count])
Label(root).grid(row=1,column=1)
image_label=Label(root,image=images[count])
image_label.grid(row=1,column=2)
left_button=Button(root,text="Left",command=lambda : change("left"))
left_button.grid(row=2,column=1)
name=Label(root,text=labels[count])
name.grid(row=2,column=2)
right_button=Button(root,text="Right",command=lambda : change("right"))
right_button.grid(row=2,column=3)
root.mainloop()

A _tkinter.TclError occured while trying to make a scrollable frame in python

So I have been trying to make a scrollable frame. I've had been searching and these 3 had the most impact:
https://stackoverflow.com/questions/16188420/tkinter-scrollbar-for-frame\
https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter\
How could I get a Frame with a scrollbar in Tkinter?
And I have come up with the following code:
from tkinter import *
class Test_tk_stuff():
def __init__(self):
self.screen = Tk()
self.screen.geometry("500x500")
self.structure()
self.screen.mainloop()
def scroller(self, canvas):
canvas.configure(scrollregion = canvas.bbox("all"))
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame, width = canvas_width)
def structure(self):
parent = Frame(self.screen)
parent.pack(expand = True, fill = BOTH)
self.canvas = Canvas(parent, bg = "green", highlightthickness = 0, relief = RAISED)
self.frame = Frame(self.canvas, bg = "blue")
myscrollbar = Scrollbar(parent, orient = "vertical", command = self.canvas.yview)
self.canvas.configure(yscrollcommand = myscrollbar.set)
myscrollbar.pack(side = RIGHT, fill = Y)
self.canvas.pack(side = LEFT, fill = BOTH, expand = True)
self.canvas.create_window((0, 0), window = self.frame)
# can't do: self.frame.pack(expand = True, fill = BOTH) because it will become unscrollable
# Event Bind
self.frame.bind("<Configure>", lambda event, canvas = self.canvas: self.scroller(self.canvas))
self.canvas.bind("<Configure>", self.frame_expander)
# initialize number of minimum columns
for num_columns in range(3):
self.frame.columnconfigure(num_columns, weight = 1)
a = "Button Text!"
# fill it to - 1.) test scrollbar 2.) actually using the frame inside
for place2 in range(10):
Button(self.frame, text = a, bg = "black", fg = "white").grid(row = place2, column = 1, sticky = "NSEW", ipady = 15)
if __name__ == "__main__":
Test_tk_stuff()
But somehow when I run it, it shows a _tkinter.TclError. I tried searching what that is and how to fix it, but, as you can see, I wasn't able to fix it.
Is there something wrong with my implementation?
Thanks in advance.
canvas.itemconfig() is used on canvas item returned by canvas.create_xxxxx() functions, not on tkinter widget (self.frame).
Save the canvas item ID for the self.frame and use it in canvas.itemconfig():
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame_item, width=canvas_width)
def structure(self):
...
self.frame_item = self.canvas.create_window((0, 0), window = self.frame)
...
use self.frame.config(width=canvas_width) instead of canvas.itemconfig()

Can you rescale a PhotoImage in Python Tkinter?

In my program, I have two buttons. button1 calls a function (bt1) which displays an image in the window and then deletes both buttons. button2 calls a function(bt2) I would like this button to display the same image as button1 but by half the scale factor. I am thinking something like this:
scale_half_img = PhotoImage(file = 'Image.png', scale = 0.5)
Of course, this doesn't work but this is the sort of thing I am looking for.
Full code:
from tkinter import *
window = Tk()
def bt1():
img = PhotoImage(file = 'Image.png')
imglbl = Label(window, image = img, anchor = 'nw')
imglbl.place(x = 0, y = 0, width = 865, height = 800)
button1.destroy()
button2.destroy()
def bt2():
# display scaled down image
button1.destroy()
button2.destroy()
button1 = Button(window, text = 'Display Image', command = bt1)
button1.place(x = 10, y = 10, width = 200, height = 30)
button2 = Button (window, text = 'Display Rescaled Image', command = bt2)
button2.place(x = 10, y = 50, width = 200, height = 30)
window.mainloop()
Use PIL.Image to resize an image in tkinter. You can do it as follows:
from PIL import Image, ImageTk
img = Image.open('<Path to Image>')
img = img.resize((img.size[0]/2, img.size[1]/2), Image.ANTIALIAS)
resized_image = ImageTk.Photoimage(img) # use this

Python 2: Replace word in string with icon image.

I am trying to create a weather application. I have a section of code that retrieved weather information from DarkSkyAPI for the next 7 days. The String that is print out looks like Sat clear-day 10. What I am trying to do is display the text I'm a label but replace the clear-sky section of the string for the clear-sky icon I have.
Format I am looking for is Sat {icon} 10
from Tkinter import *
from datetime import timedelta, datetime
from PIL import Image, ImageTk
import requests
import json
## Location of Icons used
icon_lookup = {
'clear-day' : "Icons/WeatherIcons/WeatherIcon.png",
}
class Weather(Frame):
def __init__(self, parent, *args, **kwargs):
Frame.__init__(self, parent, bg = 'black')
self.iconLbl = Label(self, bg="black")
self.iconLbl.grid(row = 0, column = 1, sticky = W)
self.icon = ''
self.get_weather()
def get_weather(self):
try:
## Get Week's Weather Values
todayPlusOne = 'Sat clear-day 10'
## Get Icon
icon_id = 'clear-day'
icon2 = None
if icon_id in icon_lookup:
icon2 = icon_lookup[icon_id]
if icon2 is not None:
if self.icon != icon2:
self.icon = icon2
image = Image.open(icon2)
image = image.resize((100, 100), Image.ANTIALIAS)
image = image.convert('RGB')
photo = ImageTk.PhotoImage(image)
self.iconLbl.config(image=photo)
self.iconLbl.image = photo
else:
self.iconLbl.config(image='')
except Exception as e:
print "Error: %s. Cannot get weather." % e
self.after(900000, self.get_weather)
class FullscreenWindow:
def __init__(self):
self.tk = Tk()
self.tk.configure(background='black')
self.topFrame = Frame(self.tk, background = 'black', height = 240)
self.middleFrame = Frame(self.tk, background = 'black', height = 240)
self.bottomFrame = Frame(self.tk, background = 'black', height = 240)
self.topFrame.pack(side = TOP, fill = BOTH)
self.middleFrame.pack(side = TOP, fill = BOTH)
self.bottomFrame.pack(side = TOP, fill = BOTH)
self.state = False
self.tk.bind('<Return>', self.toggle_fullscreen)
#weather
self.weather = Weather(self.topFrame)
self.weather.pack(side = LEFT, anchor = NW, padx = 25, pady = 25)
def toggle_fullscreen(self, event=None):
self.state = not self.state
self.tk.attributes('-fullscreen', self.state)
return 'break'
if __name__ == '__main__':
w = FullscreenWindow()
w.tk.resizable(width=False, height=False)
w.tk.geometry('720x480')
w.tk.mainloop()
Is this possible to do?
You cannot put an image in the middle of text in a label; you can only put an image on one side (top, bottom, left, right) or centered. To have both text and an image you need to use the compound attribute of the label.
To have an image in the middle of text, there are at least three options:
create a frame with a label for the text, a label for the icon, and another label for more text
use a canvas, and create text and image items on the canvas
use a one-line text widget, which supports embedded images.

PIL/tkinter - updating label image with button images

I'm trying to implement my simple image viewer, where I have one main label and menubutton with three choices. Each of them creates different numbers of buttons with pictures inside them. I would like create one main label and then update its image with images on the buttons. So when I click on the first button with image, that image will appear on the label, then click second button and that second image will appear and so on. I've tried to create two functions one for creating label and updating its image and the second for getting current image on the button, but I wasn't able to do the second one correctly. Appreciate your help. Here's the code:
try:
import Tkinter as tk
except ImportError:
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class Halabala():
def __init__(self):
self.master = tk.Tk()
self.master.geometry("1100x700")
self.pictures = ["pavuk1.gif", "pavuk2.gif", "pavuk3.gif"]
self.pictures2 = ["cukor1.gif", "cukor2.gif", "cukor3.gif", "cukor4.gif", "cukor5.gif"]
self.lists_images = []
self.lists_images2 = []
self.init_image_list()
self.lists_labels = []
#self.main_label = tk.Label(self.master, image = None).grid(row = 0, column = 0)
#image with original size
self.rbutton = tk.Menubutton(self.master, text = "Choose picture")
self.picks2 = tk.Menu(self.rbutton)
self.rbutton.config(menu=self.picks2)
self.picks2.add_command(label = "Spider", command = partial(self.create_labels,3))
self.picks2.add_command(label = "Sugar", command = partial(self.create_labels,5))
self.rbutton.config(width = 30, bg = "white", bd = 5, relief = tk.RAISED)
self.rbutton.place(x = 900, y = 30)
self.master.mainloop()
def init_image_list(self):
for i in self.pictures:
picture = Image.open(i)
picture.thumbnail((130, 130))
self.lists_images.append(ImageTk.PhotoImage(picture))
for i in self.pictures2:
picture = Image.open(i)
picture.thumbnail((130, 130))
self.lists_images2.append(ImageTk.PhotoImage(picture))
def create_labels(self, num):
for label in self.lists_labels:
label.destroy()
self.lists_labels=[]
for i in range(num):
if num == 3:
but = tk.Button(self.master, image = self.lists_images[i]) #command = self.get_command
but.grid(row = 2, column = i + 1) #label will be in the first row
self.lists_labels.append(but)
else:
but = tk.Button(self.master, image = self.lists_images2[i])
but.grid(row = 2, column = i + 1)
self.lists_labels.append(but)
#def get_command(self):
#self.main_label = tk.Label(self.master, image = self.get_image).grid(row = 0, column = 0)
#def get_image(self):
# don't know how to get current image from button
myapp = Halabala()

Categories