Here's the issue -
I'm writing a program that will iterate through a series of image manipulations - both effecting the whole image as well as just portions of the image. I need to demonstrate these changes to the user (myself) so I can see what's going wrong with my manipulations as they take place.
I originally tried to use PIL and Tkinter to do this - but I couldn't even load an image into the GUI - here's a bit of code formed from the corners of the web, via many google searches:
from Tkinter import *
import Image, ImageDraw, ImageTk
import time
class Test(Frame):
def __init__(self):
Frame.__init__(self)
self.c = Canvas(self, width=574, height=431, bg="red")
self.c.pack()
Button(self, text="Process", command=self.procImg).pack()
Button(self, text="Quit", command=self.quit).pack()
def procImg(self):
t = time.time()
self.flashImg = Image.open("./in_img/resize.bmp")
#self.flashImg = flashImg.resize((574, 431))
self.flashImg = self.flashImg.convert("L")
#self.photo = ImageTk.BitmapImage(flashImg)
self.c.photo = ImageTk.PhotoImage(self.flashImg)
self.c.create_image(574, 431, anchor=NW, image=self.c.photo)
self.c.create_rectangle(50, 50, 100, 100, fill="blue")
self.update()
print time.time()-t
t = Test()
t.pack()
t.mainloop()
So the above code is pretty bad, I know - but I wanted to post something to prove that I have at least been working at this.
Can anyone suggest to me a new way of approaching this problem using Python? I'd rather not learn a different language - I'm new to the Tkinter library so if something else is better suited for this, I have no issues learning a new library.
Also, FYI, the "resize.bmp" image is a resized .JPG from a digital camera. I tried that one too and it didn't work - I really need to find a way to flash bitmaps from memory to the screen in a GUI so I can adjust parameters as the processing is going on.
Thanks for your help!
The image is probably there. It's just not visible.
Instead of :
self.c.create_image(574, 431, anchor=NW, image=self.c.photo)
try :
self.c.create_image(0, 0, anchor=NW, image=self.c.photo)
Also, if you keep a reference to the canvas image item, you can swap different images in and out.
eg. (self.canvasItem) below :
from Tkinter import *
from PIL import Image, ImageTk, ImageDraw, ImageOps, ImageEnhance
class ImageButcher(Tk):
def __init__(self):
Tk.__init__(self)
#create ui
f = Frame(self, bd=2)
self.colour = StringVar(self)
self.colourMenu = OptionMenu(f, self.colour,
*('red','green','blue','white'))
self.colourMenu.config(width=5)
self.colour.set('red')
self.colourMenu.pack(side='left')
self.rectangleButton = Button(f, text='Rectangle',
command=self.draw_rectangle)
self.rectangleButton.pack(side='left')
self.brightenButton = Button(f, text='Brighten',
command=self.on_brighten)
self.brightenButton.pack(side='left')
self.mirrorButton = Button(f, text='Mirror',
command=self.on_mirror)
self.mirrorButton.pack(side='left')
f.pack(fill='x')
self.c = Canvas(self, bd=0, highlightthickness=0,
width=100, height=100)
self.c.pack(fill='both', expand=1)
#load image
im = Image.open('IMG_1584.JPG')
im.thumbnail((512,512))
self.tkphoto = ImageTk.PhotoImage(im)
self.canvasItem = self.c.create_image(0,0,anchor='nw',image=self.tkphoto)
self.c.config(width=im.size[0], height=im.size[1])
self.img = im
self.temp = im.copy() # 'working' image
def display_image(self, aImage):
self.tkphoto = pic = ImageTk.PhotoImage(aImage)
self.c.itemconfigure(self.canvasItem, image=pic)
def on_mirror(self):
im = ImageOps.mirror(self.temp)
self.display_image(im)
self.temp = im
def on_brighten(self):
brightener = ImageEnhance.Brightness(self.temp)
self.temp = brightener.enhance(1.1) # +10%
self.display_image(self.temp)
def draw_rectangle(self):
bbox = 9, 9, self.temp.size[0] - 11, self.temp.size[1] - 11
draw = ImageDraw.Draw(self.temp)
draw.rectangle(bbox, outline=self.colour.get())
self.display_image(self.temp)
app = ImageButcher()
app.mainloop()
Related
I am doing a painting program where one can draw things, change the background color and save it as a file on the computer. Everything works except that changing the background color takes way too much time.
This is the code:
from tkinter import *
import tkinter.filedialog as tk
from tkinter import Menu
from tkinter.colorchooser import askcolor
from tkinter.filedialog import asksaveasfile,askopenfilename
import os
from PIL import Image as im
from PIL import ImageTk,ImageDraw,ImageColor
class P:
x=y=None
image=None
my_image=im.new("RGB",(600,600),(255,255,255))
draw=ImageDraw.Draw(my_image)
def __init__(self,window):
self.window = window
self.upper_frame = Frame(window)
self.upper_frame.grid(row=0,column=0, padx=10, pady=5,sticky="ew")
self.lower_frame = Frame(window)
self.lower_frame.grid(row=2, column=0, padx=10, pady=5,sticky="ew")
self.canvas= Canvas(self.lower_frame,width=700,height=530,bg="white")
self.canvas.grid()
self.bg = Button(self.upper_frame,text="Background",command= self.bgcolor) #change bg color
self.bg.grid(row=2,column=1,padx=(100,10))
self.upper_menu()
def bgcolor(self):
chosen_color = askcolor(color=self.canvas["bg"])[1]
self.canvas.configure(bg=chosen_color)
color_RGB = ImageColor.getcolor(chosen_color, "RGB")
img = self.my_image
for i in range(0,600):#pixels in width
for j in range(0,600): #height = 600 pix
data = img.getpixel((i,j)) #gives color of pixel
if (data[0]==255 and data[1]==255 and data[2]==255): #data represent RGB color
img.putpixel((i,j),color_RGB) #changes pixel color
def save_pic(self,event=None): #save image on comp.
my_file=asksaveasfile(mode="w",defaultextension=".png",filetypes=[("png files","*.png")],
initialdir="/home/b/",parent=window)
if my_file is not None:
path=os.path.abspath(my_file.name)
self.my_image.save(path)
def upper_menu(self):
self.menubar = Menu(window)
self.menu1 = Menu(self.menubar, tearoff=0)
self.menu1.add_command(label="Save pic", command=self.save_pic)
self.menu1.add_separator()
self.menu1.add_command(label="Exit", command=window.destroy)
self.menubar.add_cascade(label="Settings", menu=self.menu1)
self.menu2 = Menu(self.menubar, tearoff=0)
self.window.config(menu=self.menubar)
window = Tk()
window.geometry("720x590")
p = P(window)
window.mainloop()
I use the method bgcolor to change the background. How to make it work faster?
I suspect the problem is with calling putpixel 360,000 times. Instead, try creating the color data in the loop and then call putdata once after the loops have finished.
I'm not overly familiar with PIL, but this makes a huge difference when doing similar things with the tkinter PhotoImage class: doing one pixel at a time is slow, doing an array of pixels is fast.
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'm using Tkinter with Python 3. I want to display something like this, with a particular token showing up behind an overlay:
(This is the overlay, followed by a token, and finally the overlay over the token. The token may not always be centered though behind the overlay.)
The problem is that I need to create these images dynamically. I can't use predefined images .png or .gif images. In particular, the colors of these images are going to change frequently, and I don't know them a priori.
All of the sample code that I've seen to create images on a canvas assume that you're loading up a .gif or .png, something like this:
token_image = tk.PhotoImage(file="token.png")
canvas = tk.Canvas(width=500, height=500, background="black")
canvas.pack()
token = canvas.create_image((50,50), image=token_image)
But is there a way to do this generating the token_image dynamically? How would you create the irregular shape shown in the overlay, with the circular "bite" taken out of it?
Thanks.
as a basic example:
import tkinter as tk
from PIL import Image, ImageDraw, ImageTk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Dynamic Image Test")
tk.Label(self, text="Overlay").grid(column=1, row=1, sticky="nesw")
tk.Label(self, text="Token").grid(column=2, row=1, sticky="nesw")
tk.Label(self, text="Combined").grid(column=3, row=1, sticky="nesw")
self.label_1 = tk.Label(self)
self.label_1.grid(column=1, row=2, sticky="nesw")
self.label_2 = tk.Label(self)
self.label_2.grid(column=2, row=2, sticky="nesw")
self.label_3 = tk.Label(self)
self.label_3.grid(column=3, row=2, sticky="nesw")
self.images = [None, None, None]
self.photos = [None, None, None]
self.show_changes()
def overlay(self):
im = Image.new("RGBA", (100,100), (255,255,0,255))
draw = ImageDraw.Draw(im)
draw.ellipse((10,10,90,90),fill=(0,0,0,0))
return im
def token(self):
im = Image.new("RGBA", (100,100), (0,0,0,0))
draw = ImageDraw.Draw(im)
draw.ellipse((0,0,100,100),fill=(0,0,255,255))
draw.line((15,15,85,85),fill=(255,0,0,255), width=5)
draw.line((15,85,85,15),fill=(255,0,0,255), width=5)
return im
def combine(self, overlay, token):
return Image.alpha_composite(token, overlay)
def show_changes(self):
self.images[0] = self.overlay()
self.photos[0] = ImageTk.PhotoImage(self.images[0])
self.label_1.configure(image=self.photos[0])
self.images[1] = self.token()
self.photos[1] = ImageTk.PhotoImage(self.images[1])
self.label_2.configure(image=self.photos[1])
self.images[2] = self.combine(self.images[0], self.images[1])
self.photos[2] = ImageTk.PhotoImage(self.images[2])
self.label_3.configure(image=self.photos[2])
if __name__ == "__main__":
app = App()
app.mainloop()
i was going to do a more complete example, allowing changing the colours on the fly, but i'll be honest and say i can't be bothered.
the ImageDraw module lets you do basic manipulation of images, drawing lines, rectanges ellipses etc, and also supports working in RGBA format (ie transparency)
so the colour tuples take the form (red, green, blue, alpha) where all values have a range of 0-255, with 255 meaning fully opaque in the alpha channel.
hopefully this shows you where you need to look to do what you want.
I am new to python and am trying to create an application that displays different information like a clock, current news, notice board etc.
I got the clock to work however I am encountering a few bugs. One is that a smaller window launches when the application does. I thought this was something to do with the self.root = tk.Tk() in the initialisation however doing anything to this line produces errors.
The other bug is that while the background image (0.png) used to fill up the entire screen as it is the same size as my monitor, when I added the clock to the application, the image is shifted to the bottom right of the screen, leaving a small white line to the top and left of the screen. I have tried to fix this by messing with the panel.pack changing it to grid and place however both of this did nothing to the lines. I feel like something is overwriting this line.
None of these bugs are showing up in the console and I don't know what to do. Here is the code I am running:
from tkinter import *
from PIL import ImageTk, Image
import os
import time
import tkinter as tk
class App(Tk):
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="",font=('comic',50,'bold'),bg = '#464545',fg = '#1681BE')
self.label.place(height = 206,width = 487, x = 1384, y = 824)
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
root = Tk()
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(root, image = img)
panel.pack()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
app = App()
root.geometry("%dx%d+0+0" % (w, h))
root.mainloop()
I hope someone can find what's wrong with it because I certainly can't!
Since your App class inherit from Tk, you don't need to create another root window. So I gathered all your code inside the App class. When I use an image the side of my screen, I don't see any line at the top or at the left of the screen, so I hope it will work for you as well.
from PIL import ImageTk, Image
import os
import time
import tkinter as tk
class App(tk.Tk):
def __init__(self):
# call the __init__ method of Tk class to create the main window
tk.Tk.__init__(self)
# background image
img = ImageTk.PhotoImage(Image.open("0.png"))
panel = Label(self, image=img)
panel.pack()
# clock
self.label = tk.Label(self, text="", font=('comic',50,'bold'),
bg='#464545', fg='#1681BE')
self.label.place(height=206, width=487, x=1384, y=824)
self.update_clock()
# window geometry
w, h = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry("%dx%d+0+0" % (w, h))
self.overrideredirect(True)
self.mainloop()
def update_clock(self):
now = time.strftime('%H:%M:%S')
self.label.configure(text=now)
self.after(1000, self.update_clock)
app = App()
My Problem:
As soon as I convert the input GIF Image into a RGBA to process it, I'm enjoying quality loss on the image. (You can see the both pictures below the code)
The code shown below "works" just I don't got a clue, why i loose quality.
Any suggestions??
My Code:
from PIL import ImageTk, Image as Im
from Tkinter import *
class App:
def __init__(self):
self.root = Tk()
self.root.geometry('1171x900')
self.maskPng = Im.open("Images/mask.gif")
self.maskPng = self.maskPng.convert("RGBA")
self.mask = ImageTk.PhotoImage(self.maskPng)
self.show = Canvas(self.root, width = 1170, height = 780)
self.show.pack()
self.show.create_image(0, 0, image = self.mask, anchor = NW)
self.speedBTN = Button(self.root, text = "hello", command = self.speed).pack(side = "right")
self.changed = False
def speed(self):
self.speedImg = Im.open("Images/speed/3_1.gif")
self.speedImg = self.speedImg.convert("RGBA")
self.maskPng.paste(self.speedImg, (0,0), self.speedImg)
self.maskPng.save("Images/speed/test.gif", "GIF")
self.render()
def render(self):
self.mask = ImageTk.PhotoImage(Im.open('Images/speed/test.gif'))
self.show.create_image(0,0, image = self.mask, anchor = NW)
self.show.image = self.mask
def draw(self):
self.root.mainloop()
main = App()
main.root.mainloop()
Images:
This is my mask.gif:
This is my 3_1.gif:
(What it white on stackoverflow is transparent in the gif)
This is the grainy, harrowing result:
(I marked the most "grainy" areas with red rectangles)
Thanks for your help! :)
Facing the same issue but I might have solution for your use case.
It doesn't seem like you actually do anything with the image so why don't you use the base64 encoded image and read it directly like below:
import Tkinter as tk # Python 2
import tkinter as tk # Python 3
# base64 encoded image data
image = ''
root = tk.Tk()
# Pass the base64 encoded data directly to your PhotoImage()
root.image = tk.PhotoImage(data=image)
# Display image in any way you need.
label = tk.Label(root, image=root.image, bg='white')
root.overrideredirect(True)
root.geometry("+250+250")
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
label.pack()
label.mainloop()
The question is old so it's unlikely that you are still working on this but I hope it will help someone else.
The issue seems to be only with RGBA, RGB works just fine but it obviously doesn't have Alpha channel. The code above will render image in its original form.
My problem is slightly different but relates to this issue. I'm trying to manipulate pixels so I have to load it as RGBA, process it and save again.
Final Edit:
After filling the issue report on github I got reply from the author.
https://github.com/python-pillow/Pillow/issues/3059
The relevant part is :
It's quantization issue.
This gives the same result:
(Image.open('./37943912.gif')
.convert('RGBA')
.quantize()
.save('_out.png'))
This is now verified and should be considered a final answer.