Capture Screenshot of Active Window

Capture screenshot of active window?

ScreenCapture sc = new ScreenCapture();
// capture entire screen, and save it to a file
Image img = sc.CaptureScreen();
// display image in a Picture control named imageDisplay
this.imageDisplay.Image = img;
// capture this window, and save it
sc.CaptureWindowToFile(this.Handle,"C:\\temp2.gif",ImageFormat.Gif);

http://www.developerfusion.com/code/4630/capture-a-screen-shot/

How do I take a screenshot of a specfic window in Qt (Python, Linux), even if the windows are overlapped?

As pointed out by @eyllanesc, it appears that it is not possible to do it in Qt, at least not with QScreen::grabWindow, because grabWindow() doesn't actually grab the window itself, but merely the area occupied by the window. The documentation contains the following warning.

The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too. The mouse cursor is generally not grabbed.

The conclusion is that it's impossible do to it in pure Qt. It's only possible to implement such a functionality by writing a low-level X program. Since the question asks for a solution "in Qt", any answer that potentially involves deeper, low-level X solutions are out-of-scope. This question can be marked as resolved.

The lesson to learn here: Always check the documentation before using a function or method.


Update: I managed to solve the problem by reading the window directly from X via Xlib. Somewhat ironically, my solution uses GTK to grab the window and sends its result to Qt... Anyway, you can write the same program with Xlib directly if you don't want to use GTK, but I used GTK since the Xlib-related functions in GDK is pretty convenient to demonstrate the basic concept.

To get a screenshot, we first convert our window ID to an GdkWindow suitable for use within GDK, and we call Gdk.pixbuf_get_from_window() to grab the window and store it in a gdk_pixbuf. Finally, we call save_to_bufferv() to convert the raw pixbuf to a suitable image format and store it in a buffer. At this point, the image in the buffer is suitable to use in any program, including Qt.

The documentation contains the following warning:

If the window is off the screen, then there is no image data in the obscured/offscreen regions to be placed in the pixbuf. The contents of portions of the pixbuf corresponding to the offscreen region are undefined.

If the window you’re obtaining data from is partially obscured by other windows, then the contents of the pixbuf areas corresponding to the obscured regions are undefined.

If the window is not mapped (typically because it’s iconified/minimized or not on the current workspace), then NULL will be returned.

If memory can’t be allocated for the return value, NULL will be returned instead.

It also has some remarks about compositing,

gdk_display_supports_composite has been deprecated since version 3.16 and should not be used in newly-written code.

Compositing is an outdated technology that only ever worked on X11.

So basically, it's only possible to grab a partially obscured window under X11 (not possible in Wayland!), with a compositing window manager. I tested it without compositing, and found the window is blacked-out when compositing is disabled. But when composition is enabled, it seems to work without problem. It may or may not work for your application. But I think if you are using compositing under X11, it probably will work.

from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess


class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)

self.label = QtWidgets.QLabel(self)
self.screen = QtWidgets.QApplication.primaryScreen()

self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()

@staticmethod
def grab_screenshot():
from gi.repository import Gdk, GdkX11

window_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))

display = GdkX11.X11Display.get_default()
window = GdkX11.X11Window.foreign_new_for_display(display, window_id)

x, y, width, height = window.get_geometry()
pb = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)

if pb:
buf = pb.save_to_bufferv("bmp", (), ())
return buf[1]
else:
return

@QtCore.pyqtSlot()
def timer_handler(self):
screenshot = self.grab_screenshot()
self.pixmap = QtGui.QPixmap()
if not self.pixmap:
return

self.pixmap.loadFromData(screenshot)
self.label.setPixmap(self.pixmap)
self.label.setFixedSize(self.pixmap.size())


if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()

Now it captures an active window perfectly, even if there are overlapping windows on top of it.

Now it captures an active window perfectly, even if there are overlapping windows on top of it.

Delphi Active Window Screenshot

I have tested and got the same result.

Sample Image

original with border

Sample Image

But if you set

sSkinProvider1.AllowExtBorders:=False;

you get a screenshot without the transparent roundet border.

Sample Image

then set back

sSkinProvider1.AllowExtBorders:=True;

No need to do after that a second

Form1.Repaint;

You will see only a short switch.

procedure TForm1.BitBtn1Click(Sender: TObject);
var
path:string;
b:TBitmap;
begin
sSkinProvider1.AllowExtBorders:=False;
Form1.Repaint;
path:= ExtractFilePath(Application.ExeName) + 'Screenshot\';
b := TBitmap.Create;
try
ScreenShot(TRUE, b) ;
b.SaveToFile(path + 'Screenshot_1.png');
finally
b.FreeImage;
FreeAndNil(b) ;
sSkinProvider1.AllowExtBorders:=True;
[...]

btw. do not set the path like

path:= ExtractFilePath(Application.ExeName) + '/Screenshot/';

use windows style backslash and only one

path:= ExtractFilePath(Application.ExeName) + 'Screenshot\'; 

Tested with Delphi5

Screenshot non-active external application

The API you're after is PrintWindow:

void Example()
{
IntPtr hwnd = FindWindow(null, "Example.txt - Notepad2");
CaptureWindow(hwnd);
}

[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr handle, ref Rectangle rect);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

public void CaptureWindow(IntPtr handle)
{
// Get the size of the window to capture
Rectangle rect = new Rectangle();
GetWindowRect(handle, ref rect);

// GetWindowRect returns Top/Left and Bottom/Right, so fix it
rect.Width = rect.Width - rect.X;
rect.Height = rect.Height - rect.Y;

// Create a bitmap to draw the capture into
using (Bitmap bitmap = new Bitmap(rect.Width, rect.Height))
{
// Use PrintWindow to draw the window into our bitmap
using (Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdc = g.GetHdc();
if (!PrintWindow(handle, hdc, 0))
{
int error = Marshal.GetLastWin32Error();
var exception = new System.ComponentModel.Win32Exception(error);
Debug.WriteLine("ERROR: " + error + ": " + exception.Message);
// TODO: Throw the exception?
}
g.ReleaseHdc(hdc);
}

// Save it as a .png just to demo this
bitmap.Save("Example.png");
}
}


Related Topics



Leave a reply



Submit