2

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?

enter image description here

theozh
  • 22,244
  • 5
  • 28
  • 72
  • I have no answer now, but I opend an issue in [MSS](https://github.com/BoboTiG/python-mss/issues/66) for a new feature. – Tiger-222 Aug 17 '18 at 08:15
  • Probably looking for this? https://stackoverflow.com/questions/19695214/python-screenshot-of-inactive-window-printwindow-win32gui – Tarun Lalwani Jul 10 '19 at 17:58
  • thank you for the link! Why did I overlook this? With this it seems to always get the correct screenshot (although once I had a wrong output). So the grabbing problem seems to be solved. With this I can save it as PNG. Now, I still need find out how to get it on the clipboard and get the dpi changed. I'm not familiar with these modules and hope to find good documentation with helpful examples. – theozh Jul 10 '19 at 19:59

2 Answers2

1

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")
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
0

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
theozh
  • 22,244
  • 5
  • 28
  • 72
  • Thanks again Tarun Lalwani. I would like to split the bounty between you for the link and @hazzey for his answer. I don't know how to do it and whether SO allows this. – theozh Jul 10 '19 at 20:53
  • I don't think splitting is allowed on SO. You can only give it to one person. The only thing you can do is open bounty on that question and award it to him later – Tarun Lalwani Jul 16 '19 at 15:41