How to get screenshot and change DPI on the clipboard? - python-3.x

Under Win7 I would like to get the content of a window on the clipboard and set/adjust the DPI setting on the clipboard and copy it to a final application.
The MCVE below is not yet working as desired.
There is an issue:
sometimes it can happen that apparently the window is not yet set to foreground and the ImageGrab.grab(bbox) gets the wrong content. Waiting for some time (2-5 sec) helps, but is not very practical. How to avoid or workaround this?
Here is the code:
from io import BytesIO
from PIL import Image,ImageGrab
import win32gui, win32clipboard
import time
def get_screenshot(window_name, dpi):
hwnd = win32gui.FindWindow(None, window_name)
if hwnd != 0:
win32gui.SetForegroundWindow(hwnd)
time.sleep(2) ### sometimes window is not yet in foreground. delay/timing problem???
bbox = win32gui.GetWindowRect(hwnd)
screenshot = ImageGrab.grab(bbox)
width, height = screenshot.size
lmargin = 9
tmargin = 70
rmargin = 9
bmargin = 36
screenshot = screenshot.crop(box = (lmargin,tmargin,width-rmargin,height-bmargin))
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
output = BytesIO()
screenshot.convert("RGB").save(output, "BMP", dpi=(dpi,dpi))
data = output.getvalue()[14:]
output.close()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
print("Screenshot taken...")
else:
print("No window found named:", window_name)
window_name = "Gnuplot (window id : 0)"
get_screenshot(window_name,200)
Edit:
also this attempt to improve still gets sometimes the wrong content. Maybe somebody can explain why?
win32gui.SetForegroundWindow(hwnd)
for i in range(1000):
print(i)
time.sleep(0.01)
if win32gui.GetForegroundWindow() == hwnd:
break
bbox = win32gui.GetWindowRect(hwnd)
Addition:
That's what I (typically) get when I remove the line with the delay time time.sleep(2).
Left: desired content, right: received content. How can I get a reliable capture of content the desired window? What's wrong with the code? The larger I set the delay time the higher the probability that I get the desired content. But I don't want to wait several seconds to be sure. How can I check whether the system is ready for a screenshot?

As discussed you can use the approach as discussed in below
Python Screenshot of inactive window PrintWindow + win32gui
import win32gui
import win32ui
from ctypes import windll
import Image
hwnd = win32gui.FindWindow(None, 'Calculator')
# Change the line below depending on whether you want the whole window
# or just the client area.
#left, top, right, bot = win32gui.GetClientRect(hwnd)
left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
#result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
print result
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
#PrintWindow Succeeded
im.save("test.png")

Thanks to #Tarun Lalwani pointing to
this answer, I finally have a code which is working for me for the time being. However, it seems to me quite lengthy with a lot of different modules. Maybe it still can be simplified. Suggestions are welcome.
Code:
### get the content of a window and crop it
import win32gui, win32ui, win32clipboard
from io import BytesIO
from ctypes import windll
from PIL import Image
# user input
window_name = 'Gnuplot (window id : 0)'
margins = [8,63,8,31] # left, top, right, bottom
dpi = 96
hwnd = win32gui.FindWindow(None, window_name)
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width = right - left
height = bottom - top
crop_box = (margins[0],margins[1],width-margins[2],height-margins[3])
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
saveDC.SelectObject(saveBitMap)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer( 'RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1).crop(crop_box)
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
output = BytesIO()
im.convert("RGB").save(output, "BMP", dpi=(dpi,dpi))
data = output.getvalue()[14:]
output.close()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
print('"'+window_name+'"', "is now on the clipboard with", dpi, "dpi.")
### end of code

Related

_tkinter.TclError: couldn't open "ImmaginiDiProva/ImmagineProva1360x768.png": no such file or directory

I'm back here with a new error that I can't solve... it's a strange error, idk, I put the images in the same directory of the program, in the directory specified in the code...
Ah, I don't know, but this is all the code:
from tkinter import *
def toggle_fs():
"""
Function that enables fullscreen for the window I create
"""
state = False if root.attributes('-fullscreen') else True
root.attributes('-fullscreen', state)
if not state:
root.geometry('300x300+100+100')
print("Inserisci il nome della foto: ") # Enter the name of the image -> "ImmagineProva"
nomeImg = input()
def getImg(nomeImg):
"""
Function that obtaines the right image, because I have different images that are all the same picture but all are different in resolution (1360x768, 1920x1080...)
"""
# detect the resolution of the monitor
WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight()
# Takes the right image thanks to its name and screen resolution took before
img = PhotoImage(file = "ImmaginiDiProva/" + nomeImg + str(WIDTH) + "x" + str(HEIGHT) + ".png")
# All the images have the same name "ImmagineProva" with the relative resolution
return img
root = Tk(screenName = "Prova")
root.attributes("-fullscreen", True)
canvas = Canvas(root, bg = "white")
canvas.pack(fill = BOTH, expand = True)
img = getImg(nomeImg)
canvas.create_image(0, 0, anchor = NW, image = img)
root.bind("<Escape>", toggle_fs())
mainloop()
I hope this is enough for you... I'm frustrated, I put the images everywhere and it doesn't recognize them, wtf O.o
I read the comments, so I post an image so you'll be able to see everything about the program, images...
An image of the folder of the program
Ah, I did what said the utent in the comment (I don't remember the nick and the page doesn't make me scroll up lol) -> import os; print(etc...) and raised the error FileNotFoundError: [WinError 3] Impossibile trovare il percorso specificato: 'ImmaginiDiProva'
Why this? there is also the folder in the image I posted... I don't understand ahahah
Thank you guys for your help, by the way <3

tkinter doesn't open image via function

Tkinter doesn't open the image. We can ask the opening incorrectly, we need help. I need it to open the image through the menu. be sure to use pil, as the image can be anything. There are no errors in the syntax. Thank you = )
from tkinter import Tk, Frame, Menu, Canvas, PhotoImage
import easygui
from PIL import Image, ImageFilter, ImageTk
def input_file():
a = easygui.fileopenbox(filetypes=["*.jpg"])
original = Image.open(a)
original = original.resize((799, 799), Image.ANTIALIAS)
photoimg = ImageTk.PhotoImage(original)
canvas = Canvas(root, width=799, height=799)
imagesprite = canvas.create_image(10, 10,anchor='nw', image=photoimg)
canvas.pack()
return (imagesprite)
root = Tk()
root.title("Sputnikeca")
#root.iconbitmap('путь к иконке')
root.geometry("800x800+0+0")
my_menu = Menu(root)
root.config(menu=my_menu)
# Create a menu item
file_menu = Menu(my_menu)
my_menu.add_cascade(label = "Файл", menu=file_menu)
file_menu.add_command(label = "Импорт...", command=input_file())
file_menu.add_separator()
file_menu.add_command(label = "Выход", command=root.quit)
root.mainloop()
Here is what you have to do to solve the issue:
def input_file():
global photoimg #keeping a reference
a = easygui.fileopenbox(filetypes=["*.jpg"])
original = Image.open(a).resize((799, 799), Image.ANTIALIAS) #calling it all in one line
photoimg = ImageTk.PhotoImage(original)
canvas = Canvas(root, width=799, height=799)
imagesprite = canvas.create_image(10, 10,anchor='nw', image=photoimg)
canvas.pack()
return imagesprite
and then later remove the () around your function:
file_menu.add_command(label = "Импорт...", command=input_file)
What is being done?
In the first set of code im keeping a reference to the image so the image is not garbage collected by python. You can do so either by saying imagesprite.image = photoimg or global photoimg on top of the function. I also resized the image in the same line that I opened the image, to reduce codes.
And in the second set of codes, im just removing () so that the function is not called(invoked) before choosing the menu item.
And also tkinter itself has a filedialogbox that works like your easygui.fileopenbox(filetypes=["*.jpg"]), read some docs here
from tkinter import filedialog
a = filedialog.askopenfilename(title='Choose a file',initialdir='C:/',filetypes=(('All Files','*.*'),("JPEG
Files",'*.jpeg')))
Hope this helped you solve the error, do let me know if any doubts.
Cheers
If I am not mistaken, your menu opens as soon as you run the application, not when you click the import button.
It's because you need to pass the callback to the add_command, but you're calling the method instead
file_menu.add_command(label = "Import...", command=input_file())
Remove the () from input_file(). just pass input_file. it will not call the method directly anymore.
file_menu.add_command(label = "Import...", command=input_file)

Python 3: Script exits with exit code -1073740771 (0xC000041D), while trying to get a screenshot of window using PyWin32

My script, using Tkinter to create a window, gets a bitmap image with PyWin32, convert the bitmap into a Pillow Image. Then save it to test.png.
The code:
import os
from tkinter import Tk, Frame, Button
import win32gui
import win32ui
from ctypes import windll
from PIL import Image
root = Tk()
frame = Frame(root, bg="#ff0000")
button = Button(frame, bg="#7c7c7c")
button.pack(pady=10, padx=10)
frame.pack(fill="both", expand=True)
root.update()
hwnd = win32gui.GetParent(root.winfo_id())
# Change the line below depending on whether you want the whole window
# or just the client area.
left, top, right, bot = win32gui.GetClientRect(hwnd)
# left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
# result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
print(result)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
#PrintWindow Succeeded
im.save("test.png")
# root.wm_protocol("WM_DELETE_WINDOW", lambda: os.kill(os.getpid(), 0))
root.mainloop()
Also my idea is to have to get an image of the window, even another window overlaps it.
Even when the window is minimized.
This is the PyCharm output:
1
Process finished with exit code -1073740771 (0xC000041D)
It crashes when closing the window.
Does anyone know what's going wrong?
By the way, it seems it's happening with this bit of code: root.update(). #interflexo said that import win32ui is doing it. I think there's a conflict with these two.
Just adding this import at top of my code
import win32ui
triggers this behavior.

I need to make pytesseract.image_to_string faster

i'm capturing the screen and then reading text from it using tesseract to transform it to a string the problem is that it's to slow for what i need i'm doing about 5.6fps and I needed more like 10-20.(i didn't put the imports i used because u can just see them in the code)
i tried everithing i know and nothing helped
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
time.sleep(7)
def getDesiredWindow():
"""Returns the top-left and bottom-right of the desired window."""
print('Click the top left of the desired region.')
pt1 = detectClick()
print('First position set!')
time.sleep(1)
print('Click the bottom right of the desired region.')
pt2 = detectClick()
print('Got the window!')
return pt1,pt2
def detectClick():
"""Detects and returns the click position"""
state_left = win32api.GetKeyState(0x01)
print("Waiting for click...")
while True:
a = win32api.GetKeyState(0x01)
if a != state_left: #button state changed
state_left = a
if a < 0:
print('Detected left click')
return win32gui.GetCursorPos()
def gettext(pt1,pt2):
# From the two input points, define the desired box
box = (pt1[0],pt1[1],pt2[0],pt2[1])
image = ImageGrab.grab(box)
return pytesseract.image_to_string(image)
"""this is the part where i need it to be faster"""
Hi my solution was to make the image smaller.
Yes it might affect the image_to_string result and make it inaccurate but in my case since my images were 1500 width I managed to get 3x speed with this.
Try to change basewidth and try again:
from PIL import Image
basewidth = 600
img = Image.open('yourimage.png')
wpercent = (basewidth/float(img.size[0]))
hsize = int((float(img.size[1])*float(wpercent)))
img = img.resize((basewidth, hsize), Image.ANTIALIAS)
img.save('yourimage.png')

Use ipython widgets to manipulate plots, that are constantly updated from within an infinite loop

Within an ipython notebook, using Jupyter I try to do a calculation, thats running for an extended period of time in a while loop. I use pyplot to show the current status of my calculation and I would like to be able to communicate with the calculation and the plot via ipywidgets. For example this could be a stop button for the calculation, or a slider that adjusts the update rate of the plot.
Below I show a minimal example of a sin function, that is constantly plotted with different phase shifts. The start button starts the calculation, the stop button stops it and the slider should adjust the speed at which the whole thing is updated. The textbox shows the current phase shift.
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import *
import IPython.display as display
import _thread
import time
%matplotlib nbagg
# set up widgets
buttons = widgets.ToggleButtons(
options=['Stop', 'Start'],
description='',
disabled=False,
)
text = widgets.Text(
value='0',
placeholder='',
description='Shift [rad]',
)
speed_slider = widgets.IntSlider(
description = "Speed",
value = 100,
min = 10,
max = 500,
step = 10,
)
container = widgets.HBox(children = [buttons, speed_slider, text])
display.display(container)
# functions
def mySin(x, x0):
return np.sin(x-x0)
def run_app(x, dx0):
x0 = np.remainder(np.float(text.value), 2*np.pi)
while buttons.value == buttons.options[1]:
x0 = np.remainder(x0 + dx0, 2*np.pi)
line.set_ydata(mySin(x, x0))
text.value = str(x0)
time.sleep(speed_slider.value/1000)
fig.canvas.draw()
# setup plot
N = 1000
x = np.linspace(0, 10*np.pi, N)
dx0 = 0.05*2*np.pi
fig, ax = plt.subplots(ncols = 1, nrows = 1, figsize = (8,3))
line = ax.plot(x, mySin(x, np.float(text.value)))[0]
# act on button change
def buttons_on_changed(val):
if buttons.value == buttons.options[1]:
_thread.start_new_thread(run_app, (x, dx0))
buttons.observe(buttons_on_changed)
I try to run the function "run_app" in a new thread in order to make the interaction possible. I'm aware that _thread is deprecated, but I would like to know first, wether this is the right way to go. The example above works more or less, but the execution stops after a couple of seconds or when I do something else in the notebook (scrolling, clicking, ...). So my questions are the following:
Is the Thread closing or just losing priority and why is it doing that?
Is that a good approach at all, or can that be achieved in an easier way. Unfortunately, I haven't found anything browsing the web.
Thanks a lot,
Frank

Resources