0

Fixed: Solution at the end.

I am trying to export/save a tkinter canvas by using the ImageGrab.grab() method. However the saved image is always off by a bunch of pixels. I tried many exemples as well as my own tries.

Question: Can you try to run this code and tell me if you get the whole visible canvas saved (make sure to change the uploaded image (line 17). How can I save the whole visible canva?

Here is a code exemple that you can try (note that here I tried the crop method). At the end of it you can see multiple exemples that I have tried.

from tkinter import *
from PIL import ImageTk, Image
import tkinter as tk
import pyscreenshot as ImageGrab   

def exportCanvas(event=1):  #try whatever you want here
    print("exporting....")  #to know that the function has started
    ImageGrab.grab().crop((canvas.winfo_rootx(), canvas.winfo_rooty() ,canvas.winfo_rootx()+canvas.winfo_width(), canvas.winfo_rooty()+canvas.winfo_height())).save("testImageGrab.png")
    print("... export ended.")  #to know that the function has finished

#Create root and canvas
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400, background="bisque")
canvas.pack(fill=BOTH, expand=1)

#Plot an image          !!!!! Change text.png" to an image you have in your repertory !!!!!
imageTemp=Image.open("text.png")
schematicImage=ImageTk.PhotoImage(imageTemp)
imageAffichee=canvas.create_image(0,0,image=schematicImage)

#Bind q key to start the exporting/saving of the canvas
root.bind("<Key-q>",exportCanvas)

root.mainloop()


"""
Things that I have tried but doesn't get the right part of the screen

1: f.leno (https://stackoverflow.com/questions/47653748/performing-imagegrab-on-tkinter-screen)

box = (canvas.winfo_rootx(),canvas.winfo_rooty(),canvas.winfo_rootx()+canvas.winfo_width(),canvas.winfo_rooty() + canvas.winfo_height())
grab = ImageGrab.grab(bbox = box)
grab.save("testImageGrab.png")

2: B.Jenkins (https://stackoverflow.com/questions/9886274/how-can-i-convert-canvas-content-to-an-image)

x=root.winfo_rootx()+canvas.winfo_x()
y=root.winfo_rooty()+canvas.winfo_y()
x1=x+canvas.winfo_width()
y1=y+canvas.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save("testImageGrab.png")

3: Alon (https://stackoverflow.com/questions/9886274/how-can-i-convert-canvas-content-to-an-image)

ImageGrab.grab(bbox=(
canvas.winfo_rootx(),
canvas.winfo_rooty(),
canvas.winfo_rootx() + canvas.winfo_width(),
canvas.winfo_rooty() + canvas.winfo_height()
)).save("testImageGrab.png")

4: Mike - SMT (https://stackoverflow.com/questions/54637592/how-to-screenshot-the-entire-tkinter-canvas-as-png-or-pdf)

x = root.winfo_rootx() + canvas.winfo_x()
y = root.winfo_rooty() + canvas.winfo_y()
xx = x + canvas.winfo_width()
yy = y + canvas.winfo_height()
ImageGrab.grab(bbox=(x, y, xx, yy)).save("testImageGrab.png")
"""

This code is expected to get the whole visible canvas saved, however this is what I get: Result

This is my whole screen:WholeScreen

Additionnaly I made this program that should save the selected area of the canva. It works by saving the whole screen (this is working, I tried it individually) then cropping by selection. The event.x and event.y should return the x and y value of the mouse position on the screen, not on the canva which is waht we are looking for when cropping a screen shoot of the whole screen.

from tkinter import *
from PIL import ImageTk, Image
import tkinter as tk
import pyscreenshot as ImageGrab   

def qPressed(event=1):
    global started
    started = 1
    #change cursor style so that we know we are about to start the area selection
    root.config(cursor="cross")

def B1Motion (event=1):
    global started, initPos, x, y
    if started:
        if (initPos==0):
            #get the initial values (x and y pos of the first mouse 1 click)
            x=event.x
            y=event.y
            initPos=1

def B1Release (event=1):
    global started, initPos, x, y
    if started:
        if initPos:
            #to know that the function has started
            print("exporting....")  #to know that the function has started
            #grab screen, crop based on x and y of first time mouse 1 was pressed after q was pressed and current x and y of mouse
            ImageGrab.grab().crop((x, y, event.x, event.y)).save("testImageGrab.png")   
            #to know that the function has finished
            print("... export ended.")  
            #reset the variables and others
            x=0
            y=0
            initPos=0
            started=0
            root.config(cursor="arrow")

#Create root and canvas
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400, background="bisque")
canvas.pack(fill=BOTH, expand=1)

#Variables
started=0
initPos=0

#Plot an image          !!!!! Change text.png" to an image you have in your repertory !!!!!
imageTemp=Image.open("text.png")
schematicImage=ImageTk.PhotoImage(imageTemp)
imageAffichee=canvas.create_image(0,0,image=schematicImage)

#Binds
root.bind("<Key-q>",qPressed)
root.bind("<B1-Motion>",B1Motion)
root.bind("<ButtonRelease-1>",B1Release)

root.mainloop()

Thank you in advance for your kind answers. <3

As stated by H3coder, my settings had "application zoom" at 150%. I have been able to fix my issue by simply multiplying my values by 1.5 It would be nice to fetch the "application zoom" value directly from the computer, I ll look into that later. So this code works if you don't have a random zoom.

GaetanG
  • 1
  • 2
  • When I tried, the code worked for me. The only way I was able to reproduce the problem was when I scaled the screen setting _during_ the running of the code. If I scaled before the start of run, then the code still works correctly. – H3coder Jun 15 '22 at 16:39
  • Thanks for your feedback. What do you mean by "scale the screen setting"? Do you mean "go in computer settings and change the resolution of the screen" ? – GaetanG Jun 16 '22 at 07:25
  • Windows 10 "Settings" app (the icon just above the power icon) -> *System* -> *Display*. Under *Scale and layout*. From your comment, I am assuming you didn't change this setting during the running of the code. My question would be: what are your 1) resolution setting, 2) scale setting, 3) how many screens do you use? 4) do you use anything that would change the *zoom* of the screen? – H3coder Jun 16 '22 at 09:12
  • Maybe can try printing out all the parameters of the crop function right before calling ImageGrab. Also print out screen size: `print(f"screen w = {root.winfo_screenwidth()} h = {root.winfo_screenheight()}")` – H3coder Jun 16 '22 at 09:27
  • Indeed, my settings had "application zoom" at 150%. I have been able to fix my issue by simply multiplying my values by 1.5 It would be nice to fetch the "application zoom" value directly from the computer, I ll look into that later. – GaetanG Jun 21 '22 at 07:50

0 Answers0