How to do a screenshot of a tkinter application? - python

I need to do a screenshot of the content of the tkinter application below. I am on Windows 7 (or 8).
from Tkinter import *
def test(x):
#print "I'm in event:", x
if x == 1: # if event on entry e1
print 'e1 event' # do some thing
elif x == 2: # also if event on entry e2
print 'e2 event' # do some thing else
else:
print 'no event'
def test1(x):
test(1)
def test2(x):
test(2)
root=Tk()
root.minsize(500,500)
e1=Entry(root)
e1.pack()
e2=Entry(root)
e2.pack()
e1.bind( "<FocusOut>", test1)
e2.bind( "<FocusOut>", test2)
button=Button(root, text='print').pack(side=BOTTOM)
root.mainloop()

I made a module that takes screenshot of tkinter window.
You can install with: pip install tkcap
GitHub repository
Usage:
import tkcap
cap = tkcap.CAP(master) # master is an instance of tkinter.Tk
cap.capture(FileName) # Capture and Save the screenshot of the tkiner window

Since you mentioned that you are on Windows. You can use the Win32 API as directed in this answer Fastest way to take a screenshot with python on windows. Hope this helps.
But actually Pyscreenshot should be what you are looking for.
Take the following code for example:
from pyscreenshot import grab
im = grab(bbox=(100, 200, 300, 400))
im.show()
As you can see you can use bbox to take screenshot that is at co-ordinates (100, 200) and has a width of 300 and a height of 400.
Also as regards the printing check out Printing using win32api. I hope these help.
Using PIL you can do a resize:
from PIL import Image
from pyscreenshot import grab
img = grab(bbox=(100, 200, 300, 400))
# to keep the aspect ratio
w = 300
h = 400
maxheight = 600
maxwidth = 800
ratio = min(maxwidth/width, maxheight/height)
# correct image size is not #oldsize * ratio#
# img.resize(...) returns a resized image and does not effect img unless
# you assign the return value
img = img.resize((h * ratio, width * ratio), Image.ANTIALIAS)
I would advise changing your program so that you can resize the image before printing

Related

Get color at point of mouse click in Tkinter

I'm working on a program (in Linux) where I need to know the color at the point where of mouse click. I can't install external modules (PIL is fine though). I did try and see if there are solutions posted in the net (Return RGB Color of Image Pixel under Mouse Tkinter) but all of them seem to use modules which I'll have to install. Is there a way I can do it with these restrictions in mind?
Here is a short script that enables colors to be found and displayed in tkinter.
It uses PIL ImageGrab.grab() to grab the entire screen as pic then you simply press space-bar to find the color under the mouse pointer. It uses pic.getpixel(x, y)
import tkinter as tk
from PIL import ImageGrab
master = tk.Tk()
master.columnconfigure(0, weight = 1)
master.title("Colors")
button = tk.Button(master, text = "Press Spacebar")
button.grid(sticky = tk.NSEW)
canvas = tk.Canvas(master, width = 200, height = 200)
canvas.grid(sticky = tk.NSEW)
pic = ImageGrab.grab()
def color():
x, y = master.winfo_pointerx(), master.winfo_pointery()
r, g, b = pic.getpixel((x, y))
hue = f"#{r:02x}{g:02x}{b:02x}"
button.config( text = f"{x}, {y} = {hue}")
canvas["background"] = hue
button["command"] = color
button.focus_force()
master.mainloop()

Python: auto slideshow with PIL/TkInter with no saving images

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.

How do i properly attach a .PNG file into a TKINTER button?

I am doing a battleship game and a function below that executes to create a new button with an explosion image as it's background. I am using Mac & python 3.7
global redraw_gameboard
global Player
global AI_player
script_dir = os.path.dirname(__file__)
rel_path = "explode.png"
image = ImageTk.PhotoImage(file=os.path.join(script_dir, rel_path))
new_button = Button(redraw_gameboard,
height = 2,
width = 4,
command= already_shot,
image=image)
new_button.grid(row = row, column = column)
This is what is coming out:
I am not sure what you expect, since I don't know what does the "explode.png" image look like. Also, when asking questions on stackoverflow, please always try to post a minimal reproducible example.
However, as I understand, the problem probably comes from the fact that the image is bigger than the button, and it is cropped. Then, only the upper left part of the image is displayed in your buttons.
Suggested solution:
(You will need to install the pillow package if it is not done yet)
import os
from PIL import Image, ImageTk
import tkinter
# Sizes in pixels
BUTTON_HEIGHT = 40
BUTTON_WIDTH = 40
root = tkinter.Tk()
script_dir = os.path.dirname(__file__)
rel_path = "explode.png"
image = Image.open(os.path.join(script_dir, rel_path))
image = image.resize((BUTTON_WIDTH,BUTTON_HEIGHT))
imtk = ImageTk.PhotoImage(image)
# Using a void image for other buttons so that the size is given in pixels too
void_imtk = tkinter.PhotoImage(width=BUTTON_WIDTH, height=BUTTON_HEIGHT)
def create_button(row, column, im):
new_button = tkinter.Button(root,
height = BUTTON_HEIGHT,
width = BUTTON_WIDTH,
image=im)
new_button.grid(row = row, column = column)
create_button(0,0, imtk)
create_button(0,1, void_imtk)
create_button(1,0, void_imtk)
create_button(1,1, imtk)
root.mainloop()
Of course, you will want to make some changes for your program, e.g. using your widget architecture.

Cropping and displaying images using PIL and Tkinter

I am beginning to learn Python after being trapped using VB6 forever. To help myself learn some gui with Tkinter, I'm making a simple slot machine. To animate the wheels, I have a ~300x2000 image that has all of the pictures I intend to use displayed as a strip. About 6 pictures right now. I just show part of the image (wheel) at a time to make it spin. Anyway, I'm having trouble with the part of the code where I come to the end of the image and need to transition from the end of the image back to the beginning (wheel spins down, so the start is the bottom and end is the top).
For some reason, I can't get the images to crop and display correctly, even though I think my script makes sense logically. My code is posted below. You can make an image around the resolution of 300x2000 or so to use with this to see the problem I am having. My code starts printing outside of the area that I want it to show (outside the showsize variable) and I can't figure out why.
Any help with this problem would be much appreciated. It seems that the crops don't cut the images short enough, but all the information that I found about it make me think my script should be working just fine. I've tried to annotate my code to explain what's going on.
from Tkinter import *
from PIL import Image, ImageTk
def main():
root = Tk()
root.title = ("Slot Machine")
canvas = Canvas(root, width=1500, height=800)
canvas.pack()
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 #amount of source image to show at a time - part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 0 #amount of pause between each frame
for spintimes in range(1,numberofspins):
for y in range(wheelh,showsize,-speed): #spin to end of image, from bottom to top
cropped = im.crop((0, y-showsize, wheelw, y)) #crop which part of wheel is seen
tk_im = ImageTk.PhotoImage(cropped)
canvas.create_image(bx1, by, image=tk_im) #display image
canvas.update() # This refreshes the drawing on the canvas.
canvas.after(cycle_period) # This makes execution pause
for y in range (speed,showsize,speed): #add 2nd image to make spin loop
cropped1 = im.crop((0, 0, wheelw, showsize-y)) #img crop 1
cropped2 = im.crop((0, wheelh - y, wheelw, wheelh)) #img crop 2
tk_im1 = tk_im2 = None
tk_im1 = ImageTk.PhotoImage(cropped1)
tk_im2 = ImageTk.PhotoImage(cropped2)
#canvas.delete(ALL)
canvas.create_image(bx1, by, image=tk_im2) ###THIS IS WHERE THE PROBLEM IS..
canvas.create_image(bx1, by + y, image=tk_im1) ###PROBLEM
#For some reason these 2 lines are overdrawing where they should be. as y increases, the cropped img size should decrease, but doesn't
canvas.update() # This refreshes the drawing on the canvas
canvas.after(cycle_period) # This makes execution pause
root.mainloop()
if __name__ == '__main__':
main()
It's simpler, and much faster and smoother if you don't recreate your images on every cycle. Here's my solution using canvas.move(). Notice that I moved the canvas.create_image calls outside of the for loop. I also put the code in a class and added a 'spin' button, and added something to make it exit without errors.
from Tkinter import *
from PIL import Image, ImageTk
import sys
class SlotMachine():
def __init__(self):
root = Tk()
root.title = ("Slot Machine")
self.canvas = Canvas(root, width=1200, height=800)
self.canvas.grid(column=0,row=0)
button = Button(root, text="Spin!", width=20, command = self.spin)
button.grid(column=0,row=1)
self.alive = True
root.protocol("WM_DELETE_WINDOW", self.die)
root.mainloop()
def spin(self):
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 # amount of source image to show at a time -
# part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 3 #amount of pause between each frame
tk_im1 = ImageTk.PhotoImage(im)
tk_im2 = ImageTk.PhotoImage(im)
im1id = self.canvas.create_image(bx1, by + showsize, image=tk_im1)
im2id = self.canvas.create_image(bx1, by + showsize + wheelh,
image=tk_im2)
for spintimes in range(1,numberofspins):
for y in range(wheelh,0,-speed):
if self.alive:
self.canvas.move(im1id, 0, -speed)
self.canvas.move(im2id, 0, -speed)
self.canvas.update()
self.canvas.after(cycle_period)
else:
sys.exit()
self.canvas.move(im1id, 0, wheelh)
self.canvas.move(im2id, 0, wheelh)
def die(self):
self.alive = False
if __name__ == '__main__':
mySlotMachine = SlotMachine()
This will place parts of the wheel outside of its box, but you can just put your slot machine texture on top of that.

Image resize under PhotoImage

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)

Categories

Resources