Take screenshot in process window - c#

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;
}
}

Related

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

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.

BitBlt screen capture not working on Windows 10

I'm using this code to capture a process window in the background:
IntPtr = Process.GetProcessByName("memu")[0].MainWindowHandle;
RECT rc;
GetClientRect(hwnd, out rc);
IntPtr hdcFrom = GetDC(hwnd);
IntPtr hdcTo = CreateCompatibleDC(hdcFrom);
int Width = rc.right;
int Height = rc.bottom;
Bitmap bmp = null;
IntPtr hBitmap = CreateCompatibleBitmap(hdcFrom, Width, Height);
if (hBitmap != IntPtr.Zero) {
IntPtr hLocalBitmap = SelectObject(hdcTo, hBitmap);
BitBlt(hdcTo, 0, 0, Width, Height, hdcFrom, 0, 0, CopyPixelOperation.SourceCopy);
SelectObject(hdcTo, hLocalBitmap);
DeleteDC(hdcTo);
ReleaseDC(hwnd, hdcFrom);
bmp = Image.FromHbitmap(hBitmap);
DeleteObject(hBitmap);
return bmp;
}
This code is capture an Android emulator called MEmu, it is using DirectX to render the content. But this code stopped to work after Windows 10 updated to version 16299 (it was working normally before), it still working on Windows 7 with Aero mode enabled.
When I use this method in the Windows 10 Pro v16299.X it simply return a white image or it returns the emulator "loading screen", not the running content. On Windows 7, if I remove the Aero mode it will act the same, capturing the "loading screen", so looks like somehow the way the transparency works in the new windows 10 pro update changed.
I've tried everything, tried install some modules to force Aero Mode to work on Windows 10, tried PrintWindow to capture the screen in the background, but still the same.
Any ideas what could be happening? Or a possible solution? Or what changed in this last Windows 10 Pro version that could break that code?
Thank you!
For me this is working on Windows 10 11.02.2021:
static void hflipAndSwapRandB(unsigned char* data, int w, int h, int dim)
{
int y = 0;
int x = 0;
int d;
unsigned char swap = 0;
int hh = h / 2;
for (y = 0; y < hh; y++)
{
for (x = 0; x < w; x++)
{
for (d = 0; d < dim; d++)
{
swap = data[y * dim * w + dim * x + d];
data[y * dim * w + dim * x + d] = data[(h - 1 - y) * dim * w + dim * x + dim - 1 - d];
data[(h - 1 - y) * dim * w + dim * x + dim - 1 - d] = swap;
}
}
}
}
static void copyScreen(unsigned char* pixels_out, int x, int y, int width, int height)
{
HDC hScreenDC = GetDC(GetDesktopWindow());
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
if (width == -1 || height == -1)
{
width = GetDeviceCaps(hScreenDC, HORZRES);
height = GetDeviceCaps(hScreenDC, VERTRES);
}
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, x, y, width, height, hScreenDC, x, y, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width-x;
bi.biHeight = height-y;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
GetDIBits(hMemoryDC, hBitmap, 0, height-y, pixels_out, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
hflipAndSwapRandB(pixels_out, width, height, 3);
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
DeleteObject(hBitmap);
}
Hopefully this will solve the problem. There is a method for capturing the screen that is built in to the .net framework that may work. Not sure if it will capture DirectX content, but it may be worth a try.
Please note that this solution captures the current screen, but you will probably be able to modify it to capture only the area you are interested in.
I found this solution here: https://www.c-sharpcorner.com/UploadFile/2d2d83/how-to-capture-a-screen-using-C-Sharp/
private void CaptureMyScreen()
{
try
{
//Creating a new Bitmap object
Bitmap captureBitmap = new Bitmap(1024, 768, PixelFormat.Format32bppArgb);
//Bitmap captureBitmap = new Bitmap(int width, int height, PixelFormat);
//Creating a Rectangle object which will
//capture our Current Screen
Rectangle captureRectangle = Screen.AllScreens[0].Bounds;
//Creating a New Graphics Object
Graphics captureGraphics = Graphics.FromImage(captureBitmap);
//Copying Image from The Screen
captureGraphics.CopyFromScreen(captureRectangle.Left,captureRectangle.Top,0,0,captureRectangle.Size);
//Saving the Image File (I am here Saving it in My E drive).
captureBitmap.Save(#"E:\Capture.jpg",ImageFormat.Jpeg);
//Displaying the Successfull Result
MessageBox.Show("Screen Captured");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
I've had the same white window problem when capturing another application's window. I could capture the whole desktop though. One way around this was to capture the desktop portion occupied by the app, another solution was to elevate my program to run as administrator. Running as administrator allowed me to capture a specific app window (in my case a .Net 4.8 framework app the only change needed was requireAdministrator in app manifest requestedExecutionLevel).
public static System.Drawing.Image CaptureWindow(IntPtr handle, bool capturedesktop = false, bool capturemouse = true)
{
if (IsWindowVisible(handle))
{
IntPtr hdcSrc = GetWindowDC(handle);
RECT windowRect;
GetWindowRect(handle, out windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
if (capturedesktop)
hdcSrc = GetWindowDC(GetDesktopWindow());
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
if (capturedesktop)
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, GDI32.TernaryRasterOperations.SRCCOPY);
else
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.TernaryRasterOperations.SRCCOPY);
if (capturemouse)
{
try
{
CURSORINFO cursorInfo = new CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
IntPtr hicon;
if (GetCursorInfo(ref cursorInfo))
{
if (cursorInfo.flags == CURSOR_SHOWING)
{
if ((hicon = CopyIcon(cursorInfo.hCursor)) != IntPtr.Zero)
{
ICONINFO iconInfo;
if (GetIconInfo(hicon, out iconInfo))
{
using (Bitmap maskBitmap = System.Drawing.Image.FromHbitmap(iconInfo.hbmMask))
{
DrawIconEx(hdcDest, cursorInfo.ptScreenPos.X - iconInfo.xHotspot - windowRect.left, cursorInfo.ptScreenPos.Y - iconInfo.yHotspot - windowRect.top, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003);
GDI32.DeleteObject(iconInfo.hbmMask);
DestroyIcon(hicon);
}
}
else
{
DestroyIcon(hicon);
}
}
}
}
} catch
{
}
}
GDI32.SelectObject(hdcDest, hOld);
GDI32.DeleteDC(hdcDest);
ReleaseDC(handle, hdcSrc);
System.Drawing.Image img = System.Drawing.Image.FromHbitmap(hBitmap);
GDI32.DeleteObject(hBitmap);
return img;
} else
{
return null;
}
}

Getting transparent shell-icons for files and folders in C#

I'm currently working on a small library that enables you to get icons from files and folders. Now, I don't care if it only works on win8+ (cause that's the place I'm going to use it), however, I've run in to a tiny problem with regards to transparency. If you take a look at the following image:
The one I generate (from my library) is to the left, windows explorer is to the right.
Now, as you might see, first off there is 2 black lines in the upper right of the one I generate, second, there is a difference in the background color. So what I'm wondering is this; is there no way to get the exact same image used by windows explorer, or am I simply doing it wrong?
My code (with exception to structs/externs etc. for shortness) bellow, entire code here.
public static class Icon
{
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// zero out the RGB values for all pixels with A == 0
// (AlphaBlend expects them to all be zero)
for (int i = 0; i < dibsection.dsBmih.biWidth * dibsection.dsBmih.biHeight; i++)
{
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved == 0)
{
rgbquad.rgbBlue = 0;
rgbquad.rgbGreen = 0;
rgbquad.rgbRed = 0;
}
else
{
;
}
Marshal.StructureToPtr(rgbquad, ptr, false);
}
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// get the HDCs and select the HBITMAP
Graphics graphics = Graphics.FromImage(bitmap);
IntPtr hdcDest = graphics.GetHdc();
IntPtr hdcSrc = Gdi32.CreateCompatibleDC(hdcDest);
IntPtr hobjOriginal = Gdi32.SelectObject(hdcSrc, hbitmap);
// render the bitmap using AlphaBlend
BLENDFUNCTION blendfunction = new BLENDFUNCTION(BLENDFUNCTION.AC_SRC_OVER, 0, 0xFF, BLENDFUNCTION.AC_SRC_ALPHA);
Gdi32.AlphaBlend(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, width, height, blendfunction);
// clean up
Gdi32.SelectObject(hdcSrc, hobjOriginal);
Gdi32.DeleteDC(hdcSrc);
graphics.ReleaseHdc(hdcDest);
graphics.Dispose();
Gdi32.DeleteObject(hbitmap);
return bitmap;
}
}
It seems copying pixel by pixel was the solution. The following seems to be pixel-perfect equal to the explorer one.
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
{
for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
{
int i = y * dibsection.dsBmih.biWidth + x;
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved != 0)
bitmap.SetPixel(x, y, Color.FromArgb(rgbquad.rgbReserved, rgbquad.rgbRed, rgbquad.rgbGreen, rgbquad.rgbBlue));
}
}
Gdi32.DeleteObject(hbitmap);
return bitmap;
}

How to capture the screen and mouse pointer using Windows APIs?

I'm using the below code to capture the screen in a bitmap. The screen is captured, but I'm unable to get the mouse pointer on the screen. Could you suggest some alternative approach so that the mouse is captured as well?
private Bitmap CaptureScreen()
{
// Size size is how big an area to capture
// pointOrigin is the upper left corner of the area to capture
int width = Screen.PrimaryScreen.Bounds.X + Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Y + Screen.PrimaryScreen.Bounds.Height;
Size size = new Size(width, height);
Point pointOfOrigin = new Point(0, 0);
Bitmap bitmap = new Bitmap(size.Width, size.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(pointOfOrigin, new Point(0, 0), size);
}
return bitmap;
}
[StructLayout(LayoutKind.Sequential)]
struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}
[StructLayout(LayoutKind.Sequential)]
struct POINTAPI
{
public int x;
public int y;
}
[DllImport("user32.dll")]
static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
const Int32 CURSOR_SHOWING = 0x00000001;
public static Bitmap CaptureScreen(bool CaptureMouse)
{
Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);
try
{
using (Graphics g = Graphics.FromImage(result))
{
g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
if (CaptureMouse)
{
CURSORINFO pci;
pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
g.ReleaseHdc();
}
}
}
}
}
catch
{
result = null;
}
return result;
}
If you're NOT looking for the EXACT replica of the cursor you're currently using, you could use the following code, all you have to do is add one line in your original code!
private Bitmap CaptureScreen()
{
// Size size is how big an area to capture
// pointOrigin is the upper left corner of the area to capture
int width = Screen.PrimaryScreen.Bounds.X + Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Y + Screen.PrimaryScreen.Bounds.Height;
Size size = new Size(width, height);
Point pointOfOrigin = new Point(0, 0);
Bitmap bitmap = new Bitmap(size.Width, size.Height);
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(pointOfOrigin, new Point(0, 0), size);
//Following code is all you needed!
graphics.DrawIcon(new Icon("Sample.ico"),Cursor.Position.X-50,Cursor.Position.Y-50);
//The reason I minus 50 in the position is because you need to "offset" the position. Please go check out the post WholsRich commented.
}
return bitmap;
}
}
You could go online and download all kind of icons.
Or use ICO Convert to make your own.
Good luck!

Create a semi-transparent cursor from an image

Is it possible to create a cursor from an image and have it be semi-transparent?
I'm currently taking a custom image and overylaying the mouse cursor image. It would be great if I could make this semi-transparent, but not necessary. The sales guys love shiny.
Currently doing something like this:
Image cursorImage = customImage.GetThumbnailImage(300, 100, null, IntPtr.Zero);
cursorImage.SetResolution(96.0F, 96.0F);
int midPointX = cursorImage.Width / 2;
int midPointY = cursorImage.Height / 2;
Bitmap cursorMouse = GetCursorImage(cursorOverlay);
Graphics cursorGfx = Graphics.FromImage(cursorImageCopy);
cursorGfx.DrawImageUnscaled(cursorMouse, midPointX, midPointY);
Cursor tmp = new Cursor(cursorImage.GetHicon());
alt text http://members.cox.net/dustinbrooks/drag.jpg
I've tried following example, and it was working fine...
public struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
[DllImport("user32.dll")]
public static extern IntPtr CreateIconIndirect(ref IconInfo icon);
public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
{
IntPtr ptr = bmp.GetHicon();
IconInfo tmp = new IconInfo();
GetIconInfo(ptr, ref tmp);
tmp.xHotspot = xHotSpot;
tmp.yHotspot = yHotSpot;
tmp.fIcon = false;
ptr = CreateIconIndirect(ref tmp);
return new Cursor(ptr);
}
And i've put this on button click event (you can call from where you like):
Bitmap b = new Bitmap("D:/Up.png");
this.Cursor = CreateCursor(b, 5, 5);
And the Up.png image is saved with 75% opacity in AdobePhotoshop.
On the top of my head (I would try that first):
create new bitmap with same size as original, but with ARGB structure
drawimage: existing bitmap to the new bitmap
access raw bitmap data, and replace A bytes with 128
You should have nice semitransparent bitmap there.
If performance allows, you can scan for fully transparent pixels and set A to zero for them!
If you want to set transparency of a custom mouse cursor bitmap 'on the fly' you may find this function helpful. It uses a color matrix to set the amount of transparency to any given bitmap and will return the modified one. To have just a touch of transparency the TranspFactor should be between 225 and 245, just try it out. (You need to import System.Drawing and System.Drawing.Imaging)
public static Bitmap GetBMPTransparent(Bitmap bmp, int TranspFactor)
{
Bitmap transpBmp = new Bitmap(bmp.Width, bmp.Height);
using (ImageAttributes attr = new ImageAttributes()) {
ColorMatrix matrix = new ColorMatrix { Matrix33 = Convert.ToSingle(TranspFactor / 255) };
attr.SetColorMatrix(matrix);
using (Graphics g = Graphics.FromImage(transpBmp)) {
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attr);
}
}
return transpBmp;
}
that is very easy, I don't use API.
the code is
Bitmap img = new Bitmap(new Bitmap(#"image.png"), 30, 30); //this is the size of cursor
Icon icono = Icon.FromHandle(img.GetHicon()); //create the Icon object
Cursor = new Cursor(icono.Handle); //the icon Object has the stream to create a Cursor.
I hope that is your solution

Categories