Why does this C# method not produce the right screenshot? - c#

I want to save the snapshot of a window with the title ending in - Scrivener in a PNG file. To do this, I wrote following method (based on this answer):
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
private void button1_Click(object sender, EventArgs e)
{
Process[] processes = Process.GetProcesses();
Process scrivenerProcess = null;
foreach (Process curProcess in processes)
{
Console.WriteLine("Name: " + curProcess.ProcessName + ", title: " + curProcess.MainWindowTitle);
if (curProcess.MainWindowTitle.EndsWith("- Scrivener"))
{
scrivenerProcess = curProcess;
break;
}
}
if (scrivenerProcess == null)
{
Console.WriteLine("Scrivener not found");
return;
}
var rect = new RECT();
GetWindowRect(new HandleRef(this, scrivenerProcess.MainWindowHandle), out rect);
int width = rect.Right - rect.Left;
int height = rect.Bottom - rect.Top;
var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bmp);
graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, new System.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);
bmp.Save("C:\\usr\\dp\\ref\\marcomm\\2020_04_22_wordCounter\\2020-04-24-TestScreenshot.png", ImageFormat.Png);
Console.WriteLine("Heyo!");
}
There are several problems with this code:
First, if the application I want to capture (Scrivener) is not in the foreground while I'm calling that code, the resulting screenshot is empty.
Second, if the Scrivener window is in the foreground, I get the screenshot of the parent window (see below).
How do I need to change my code in order for it to
a. work even when the window is not in foreground and
b. only capture the word count window (not its parent)?
Here is the code.

Here's your problem:
scrivenerProcess.MainWindowHandle
From the documentation:
The main window is the window opened by the process that currently has the focus
In your screenshot, the window you're after does not have Focus (it has a white background with grey text, indicating it's inactive).
Unfortunately, to enumerate a process' other windows you need to use P/Invoke as they aren't exposed via the Process class. Use EnumWindows or EnumChildWindows.

Related

Take screenshot in process window

I am trying to make a screenshot to the window of a process in Windows 7 64 bit, the problem is that I always get error in the following line:
var bmp = new Bitmap (width, height, PixelFormat.Format32bppArgb);
Saying "invalid parameters", I made a throw to see the errors and width and height are always 0.
Before in 32 bits it worked well, but now in 64 bits it does not work anymore.
The code :
public void CaptureApplication()
{
string procName = "firefox";
var proc = Process.GetProcessesByName(procName)[0];
var rect = new User32.Rect();
User32.GetWindowRect(proc.MainWindowHandle, ref rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bmp);
graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
bmp.Save("c:\\tmp\\test.png", ImageFormat.Png);
}
private class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
}
How do I fix this error?
Getting the process of firefox returns an array of processes and you are looking for the one with an rectangle with realistic sizes which is the main process.
Process[] procs = Process.GetProcessesByName(procName);
var rect = new User32.Rect();
int width = 0;
int height = 0:
foreach (Process proc in procs)
{
User32.GetWindowRect(proc.MainWindowHandle, ref rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
// break foreach if an realistic rectangle found => main process found
if (width != 0 && height != 0)
{
break;
}
}

How can I capture the screen and ignore a specific window in the image

I want to write a program that will magnify the middle of the screen, so I used some methods from user32.dll. I managed to make my program capture the screen and refresh the image on the picturebox with 60FPS rate, but when I want to display in real time the bigger image, the program also captures my forms which displays the bigger image, thus leading to an infinite image inside an image loop. I want to capture the screen and ignore my form which displays the bigger image.
I thought about something like I pass it the handle of the window I want to ignore and it will capture anything but the window i specified. This is my code for now:
Bitmap CaptureWindow(IntPtr handle)
{
IntPtr hdcSrc = User32.GetWindowDC(handle);
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
GDI32.SelectObject(hdcDest, hOld);
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
Bitmap img = null;
try
{
img = new Bitmap(Image.FromHbitmap(hBitmap), 1920 * 2, 1080 * 2);
using (Graphics g = Graphics.FromImage(img))
{
g.FillRectangle(Brushes.White, new Rectangle(0, 0, 200, 1080));//img.Width - (img.Height/2), img.Height));
// g.FillRectangle(Brushes.White, new Rectangle(img.Width - (img.Height / 2) + img.Height, 0, img.Width - img.Height / 2, img.Height));
}
GDI32.DeleteObject(hBitmap);
}
catch (Exception e)
{ }
return img;
}
The handle I pass it is from this method:
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
I already tried this solution: How can i capture the screen without the Form?
but it didn't work well because the picturebox refreshes at 60Hz rate which causes it to stutter.
To be clear, my idea is to magnify the middle of the screen and then display the magnified image in a square in the middle of the screen (the areas outside the square will be left with their original size).
When you capture the initial image, blank out the area that corresponds to inside your form by drawing a rectangle over the same area. You can get the window position and dimensions via the Form and draw the rectangle over that portion of the captured image.

Form Location and Size behaviour

I want to position Form on top left corner of the screen.
I have tried this.Location = new Point(0,0) but window is positioned at (7,0) - top of the window is on top of the screen but left side is 7 pixels from the screen edge.
I created new WinForms app for testing and added only this code:
private void Form1_Load(object sender, EventArgs e)
{
Point p = new Point(0, 0);
WindowState = FormWindowState.Maximized;
Debug.WriteLine("\nMaximized");
Debug.WriteLine("Location: " + Location);
Debug.WriteLine("Size: " + Size);
Debug.WriteLine("PointToScreen(0,0): " + PointToScreen(p));
WindowState = FormWindowState.Normal;
Location = p;
Debug.WriteLine("\nNormal");
Debug.WriteLine("Location: " + Location);
Debug.WriteLine("Size: " + Size);
Debug.WriteLine("PointToScreen(0,0): " + PointToScreen(p));
Debug.Write("\nScreen.PrimaryScreen.WorkingArea: ");
Debug.WriteLine(Screen.PrimaryScreen.WorkingArea);
}
The output is:
Maximized
Location: {X=-8,Y=-8}
Size: {Width=1936, Height=1056}
PointToScreen(0,0): {X=0,Y=23}
Normal
Location: {X=0,Y=0}
Size: {Width=300, Height=300}
PointToScreen(0,0): {X=8,Y=31}
Screen.PrimaryScreen.WorkingArea: {X=0,Y=0,Width=1920,Height=1040}
Why Location = new Point(0,0) doesn't position form on (0,0)?
Is this due to something on my system? I have Win10 and VS2015. Taskbar is on the bottom, there is nothing on the left side of my desktop.
In order to position it on (0,0) I actually have to position it on (-7,0). Also, reported width of the maximized form is 16 pixels larger than screen width. I understand that because of window edges, title bar etc there is a difference between client area size and form size, but this is not it. When the form is maximized there are no left and right edges (client area width = desktop width) but form width is +16px. There is +8px on each of the 4 sides of the form but Y position is OK. Why is Y-position OK and X is not?
Thanks to Uwe Keim and his own answer to his question, I've created MoveForm function that calculates offset and sets location of the form correctly, regardless of Windows version (ie border size):
void MoveForm(Point p) // Move form to point 'p'
{
this.WindowState = FormWindowState.Normal;
this.Location = new Point(0, 0);
Rectangle rec = WindowHelper.GetWindowRectangle(this.Handle);
p.Offset(-rec.Location.X, -rec.Location.Y);
this.Location = p;
}
MoveForm function uses WindowHelper class from Uwe Keim's post:
public static class WindowHelper
{
// https://code.google.com/p/zscreen/source/browse/trunk/ZScreenLib/Global/GraphicsCore.cs?r=1349
/// <summary>
/// Get real window size, no matter whether Win XP, Win Vista, 7 or 8.
/// </summary>
public static Rectangle GetWindowRectangle(IntPtr handle)
{
if (Environment.OSVersion.Version.Major < 6)
{
return GetWindowRect(handle);
}
else
{
Rectangle rectangle;
return DWMWA_EXTENDED_FRAME_BOUNDS(handle, out rectangle) ? rectangle : GetWindowRect(handle);
}
}
[DllImport(#"dwmapi.dll")]
private static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);
private enum Dwmwindowattribute
{
DwmwaExtendedFrameBounds = 9
}
[Serializable, StructLayout(LayoutKind.Sequential)]
private struct Rect
{
// ReSharper disable MemberCanBePrivate.Local
// ReSharper disable FieldCanBeMadeReadOnly.Local
public int Left;
public int Top;
public int Right;
public int Bottom;
// ReSharper restore FieldCanBeMadeReadOnly.Local
// ReSharper restore MemberCanBePrivate.Local
public Rectangle ToRectangle()
{
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
}
private static bool DWMWA_EXTENDED_FRAME_BOUNDS(IntPtr handle, out Rectangle rectangle)
{
Rect rect;
var result = DwmGetWindowAttribute(handle, (int)Dwmwindowattribute.DwmwaExtendedFrameBounds,
out rect, Marshal.SizeOf(typeof(Rect)));
rectangle = rect.ToRectangle();
return result >= 0;
}
[DllImport(#"user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);
private static Rectangle GetWindowRect(IntPtr handle)
{
Rect rect;
GetWindowRect(handle, out rect);
return rect.ToRectangle();
}
}
I figured out the problem.
Just set
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
and test the result. for me it is this:
Maximized
Location: {X=0,Y=0}
Size: {Width=1280, Height=800}
PointToScreen(0,0): {X=0,Y=0}
Normal
Location: {X=0,Y=0}
Size: {Width=477, Height=321}
PointToScreen(0,0): {X=0,Y=0}
It is all about the border.

GetWindowRect seems to be cropping my windows on Win 8.1?

I am using the following code to get the WindowRect for a process on my machine (been testing with the Windows 8.1 calculator).
RECT rc;
GetWindowRect(hWnd, out rc);
var bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
var gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
PrintWindow(hWnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();
But on Windows 8.1, the bitmap produced is cropped (in the case of the calculator, by about 100 pixels width and 150 height).
RECT is defined as:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
I've been pulling my hair out with this, can anyone spot what I'm doing wrong?
Cheers
Rich

Bitmap.FromHbitmap erroring when called over RDP

Our application does some cursor manipulation to enable "relatively" nice drag drop animation on WinForms (at the time WPF wasn't an option). However when using the application over a RDP session it throws a generic GDI+ exception.
The method which throws this is this:
[DllImport("user32")]
private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo);
[DllImport("user32.dll")]
private static extern IntPtr LoadCursorFromFile(string lpFileName);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject);
public static Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO iInfo;
GetIconInfo(cur.Handle, out iInfo);
Bitmap bmp = Bitmap.FromHbitmap(iInfo.hbmColor);
DeleteObject(iInfo.hbmColor);
DeleteObject(iInfo.hbmMask);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
Specifically the line:
Bitmap bmp = Bitmap.FromHbitmap(iInfo.hbmColor);
When debugging hbmColor is 0, which means when running over RDP the call to GetIconInfo doesn't return the required information.
I can check for 0 and handle the special case, but is there anything I can do to make this work over RDP as it would do normally?
Edit
Here's the ICONINFO structure:
[StructLayout(LayoutKind.Sequential)]
struct ICONINFO
{
public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies
// an icon; FALSE specifies a cursor.
public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot
// spot is always in the center of the icon, and this member is ignored.
public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot
// spot is always in the center of the icon, and this member is ignored.
public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon,
// this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is
// the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If
// this structure defines a color icon, this mask only defines the AND bitmask of the icon.
public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this
// structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND
// flag to the destination; subsequently, the color bitmap is applied (using XOR) to the
// destination by using the SRCINVERT flag.
}
From HABJAN's answer below I've added the comments from p/Invoke to the structure above. It looks like hbmMask contains the bitmap reference I'm after, but I'm afraid my bit manipulation skills are rather rusty. When p/Invoke says upper half / lower half - what is it inferring to?
Is it possible to get the black and white bitmap from this?
I think that this is due your RDP color depth. If your cursor is black and white only (via RDP), you will not get hbmColor value as this parameter is optional.
MSDN says:
hbmColor
Type: HBITMAP
Description: A handle to the icon color bitmap. This member can be optional if this structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND flag to the destination; subsequently, the color bitmap is applied (using XOR) to the destination by using the SRCINVERT flag.
EDIT:
public static Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO iInfo;
GetIconInfo(cur.Handle, out iInfo);
Bitmap bmpColor = null;
if (iInfo.hbmColor != IntPtr.Zero) {
bmpColor = Bitmap.FromHbitmap(iInfo.hbmColor);
}
else {
bmpColor = new Bitmap(w,h);
// fill bmpColor with white colour
}
Bitmap bmpMask = Bitmap.FromHbitmap(iInfo.hbmMask);
DeleteObject(iInfo.hbmColor);
DeleteObject(iInfo.hbmMask);
// apply mask bitmap to color bitmap:
// http://stackoverflow.com/questions/3654220/alpha-masking-in-c-sharp-system-drawing
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
... i did not test this code, it's just to give you a brief info what to do...
With the help of HABJAN I was able to come up with a method to do the job. The reason I'm writing the answer here is because the bitmap mask you get from the handle contains two masks, so you have to select which version you want (as per the documentation).
public static Bitmap GetBitmapFromMask(IntPtr maskH)
{
using (var bothMasks = Bitmap.FromHbitmap(maskH))
{
int midY = bothMasks.Height / 2;
using (var mask = bothMasks.Clone(new Rectangle(0, midY, bothMasks.Width, midY), bothMasks.PixelFormat))
{
using (var input = new Bitmap(mask.Width, mask.Height))
{
using (var g = Graphics.FromImage(input))
{
using (var b = new SolidBrush(Color.FromArgb(255, 255, 255, 255)))
g.FillRectangle(b, 0, 0, input.Width, input.Height);
}
var output = new Bitmap(mask.Width, mask.Height, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, input.Width, input.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
for (int y = 0; y < input.Height; y++)
{
byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < input.Width; x++)
{
ptrOutput[4 * x] = ptrInput[4 * x]; // blue
ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red
ptrOutput[4 * x + 3] = ptrMask[4 * x]; // alpha
}
}
}
mask.UnlockBits(bitsMask);
input.UnlockBits(bitsInput);
output.UnlockBits(bitsOutput);
return output;
}
}
}
}
This is a basic copy of the answer linked by HABJAN - it doesn't seem to do either a logical AND or a logical XOR on the resulting bytes - none the less seems to do the required job.

Categories