-
-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow for capturing obscured windows #180
Comments
So it seems the dll also will allow for returning the window handle as well, so you do not need any further dependencies:
|
This is definitely possible to achieve, just by changing the following line of _get_srcdc within windows.py:
0 sets it to "fullscreen", but you can capture a specific window by passing in it's HWND. In addition, you can change self.user32.GetWindowDC to self.user32.GetDC, which gets purely the client area of the window. See here for the difference. I've hacked together a version that works for my purposes, with minimal modification, however it's probably not suitable for a pull request (I pass in the HWND when initialising mss). This also would solve #158 , however this would be a Windows only solution. |
We could add 2 keyword arguments to the Windows class, something like WDYT? Better names in mind (knowing that they should be OS-agnostic)? |
The part dealing with the Windows handle retrieval should not be part of MSS. I would like to keep it simple and focused on screenshot stuff only. |
They sound suitable to me. I'm not sure of the implementation for Linux/MacOS, however OS-agnostic keywords make sense for future implementation.
Agreed, easy enough to acquire the HWND with win32gui. |
For now, let's focus on Windows only. Future implementations may come later for other OSes. Do you want to work on it @TrInsanity? |
@BoboTiG I've really got no experience with PRs & best practices etc. so wouldn't know where to start. I can give it a go but it'll likely need modification! |
Yeah, go ahead and I will help you :) |
any update on this? :) |
Hi, was there any development on this? I need a solution which can capture hardware accelerated windows ( |
I have the following which is working for some automation I'm doing. The window in question is obscured and also a game (thus hardware accelerated). from PIL import Image
def capture_win_alt(convert: bool = False, window_name: Optional[str] = "MegaMan_BattleNetwork_LegacyCollection_Vol2"):
# Adapted from https://stackoverflow.com/questions/19695214/screenshot-of-inactive-window-printwindow-win32gui
global WIN_HANDLES
from ctypes import windll
import win32gui
import win32ui
if WIN_HANDLES is None:
assert window_name is not None
print("Acquiring window handle")
windll.user32.SetProcessDPIAware()
hwnd = win32gui.FindWindow(None, window_name)
left, top, right, bottom = win32gui.GetClientRect(hwnd)
w = right - left
h = bottom - top
print(f"Client rect: {left}, {top}, {right}, {bottom}")
hwnd_dc = win32gui.GetWindowDC(hwnd)
mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
save_dc = mfc_dc.CreateCompatibleDC()
bitmap = win32ui.CreateBitmap()
bitmap.CreateCompatibleBitmap(mfc_dc, w, h)
WIN_HANDLES = (hwnd, hwnd_dc, mfc_dc, save_dc, bitmap)
(hwnd, hwnd_dc, mfc_dc, save_dc, bitmap) = WIN_HANDLES
save_dc.SelectObject(bitmap)
# If Special K is running, this number is 3. If not, 1
result = windll.user32.PrintWindow(hwnd, save_dc.GetSafeHdc(), 3)
bmpinfo = bitmap.GetInfo()
bmpstr = bitmap.GetBitmapBits(True)
im = Image.frombuffer("RGB", (bmpinfo["bmWidth"], bmpinfo["bmHeight"]), bmpstr, "raw", "BGRX", 0, 1)
if result != 1:
win32gui.DeleteObject(bitmap.GetHandle())
save_dc.DeleteDC()
mfc_dc.DeleteDC()
win32gui.ReleaseDC(hwnd, hwnd_dc)
WIN_HANDLES = None
raise RuntimeError(f"Unable to acquire screenshot! Result: {result}")
open_cv_image = np.array(im)[:, :, ::-1].copy()
return open_cv_image I haven't checked the performance of this however. YMMV. |
@lorcan2440 make sure you don't leak handles. I stored them in a global variable for this reason. |
import cv2
import numpy as np
from ctypes import windll
import win32gui
import win32ui from contextlib import contextmanager
@contextmanager
def gdi_resource_management(hwnd):
# Acquire resources
hwnd_dc = win32gui.GetWindowDC(hwnd)
mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
save_dc = mfc_dc.CreateCompatibleDC()
bitmap = win32ui.CreateBitmap()
try:
yield hwnd_dc, mfc_dc, save_dc, bitmap
finally:
# Ensure resources are released
win32gui.DeleteObject(bitmap.GetHandle())
save_dc.DeleteDC()
mfc_dc.DeleteDC()
win32gui.ReleaseDC(hwnd, hwnd_dc) def capture_win_alt(window_name: str):
windll.user32.SetProcessDPIAware()
hwnd = win32gui.FindWindow(None, window_name)
left, top, right, bottom = win32gui.GetClientRect(hwnd)
w = right - left
h = bottom - top
with gdi_resource_management(hwnd) as (hwnd_dc, mfc_dc, save_dc, bitmap):
bitmap.CreateCompatibleBitmap(mfc_dc, w, h)
save_dc.SelectObject(bitmap)
result = windll.user32.PrintWindow(hwnd, save_dc.GetSafeHdc(), 3)
if not result:
raise RuntimeError(f"Unable to acquire screenshot! Result: {result}")
bmpinfo = bitmap.GetInfo()
bmpstr = bitmap.GetBitmapBits(True)
img = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo["bmHeight"], bmpinfo["bmWidth"], 4))
img = np.ascontiguousarray(img)[..., :-1] # make image C_CONTIGUOUS and drop alpha channel
return img |
dropping a note here: if you need high performance capture (>=60fps) then make sure you aren't opening/closing the handles every time. I figure people who actually need that will know that, but worth reiterating. (It's why I stored my handles in a global) |
General information:
Description of the warning/error
This is not related to an error message. This has to do with capturing obscured windows.
Other details
In order to capture an obscured window, a library I have been using in AHK which also uses dll capture has figured out how to do it. I understand that this is not written in python or c, but this code may be an assistance in understanding the steps he has done in order to utilize the GetDCEx call.
Essentially he does what seems to be two captures, which is not ideal, but it works. I was hoping that it would be possible to collaborate on implementing this feature into your library, or if you would rather use this code as a starting point:
Bind Window function to set and remember the window
Some of the code here is not really necessary. Mostly it is just having a static value saved in the class which can be assigned to a window handle. When performing further screenshots, this handle will be used the get the DC instead of the screen.
Capturing the screenshot, determining if a window handle is present and using that instead
Upvote & Fund
The text was updated successfully, but these errors were encountered: