Screenshot comes out black - c#

I'm trying to make a screen capture manager & recorder, both screenshot & video, although the latter is not important for this question.
The user can select a process he/she wants to be captured and set a hotkey.
The screenshots work fine for the regular desktop and some games. However, when trying to capture a screenshot from some games (e.g Splinter Cell Blacklist), if these games are full-screen, and depending on Windows 7 Aero, the contents are black, or the desktop is showing with a small bar at the top left of the screen.
Here's my initial code:
public static Bitmap GetScreen()
{
Bitmap bmpScreenCapture = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
using (Graphics g = Graphics.FromImage(bmpScreenCapture))
{
g.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0, 0,
bmpScreenCapture.Size,
CopyPixelOperation.SourceCopy);
}
return bmpScreenCapture;
}
During my search I found a code project article:
http://www.codeproject.com/Articles/274461/Very-fast-screen-capture-using-DirectX-in-Csharp
And slightly modified the code
public Surface CaptureScreen()
{
Surface s = Surface.CreateOffscreenPlain(d, rc.Width, rc.Height, Format.A8R8G8B8, Pool.Scratch);
d.GetFrontBufferData(0, s);
Surface.ToFile(s, #"C:\test\img.jpg", ImageFileFormat.Jpg);
return s;
}
However, also that image comes out black.
Does anyone have any suggestions as to how this is done?

Use this code (which I found here) to disable Aero before taking any screenshots, then re-enable it.
public readonly uint DWM_EC_DISABLECOMPOSITION = 0;
public readonly uint DWM_EC_ENABLECOMPOSITION = 1;
[DllImport("dwmapi.dll", EntryPoint = "DwmEnableComposition")]
protected static extern uint Win32DwmEnableComposition(uint uCompositionAction);
public void ManageAero(bool a)
{
if (a)
Win32DwmEnableComposition(DWM_EC_ENABLECOMPOSITION );
if (!a)
Win32DwmEnableComposition(DWM_EC_DISABLECOMPOSITIO N);
}

Related

How to use CopyFromScreen to create an image of a win32 control in a WPF Window

I want to create an image from a win32 control (ie WindowsFormsHost, Hwndhost , webview2) being displayed in a WPF window.
Am I correct that RenderTargetBitmap will not work for these kinds of controls?
Assuming I am correct, I am looking at the methods used in this GitHub project to make the image. The project wraps the webbrowser control in a ContenControl. It then gets the position of this content control on the screen and uses the Graphics class method CopyFromScreen.
private void CreateScreenshotFromContent()
{
Point upperLeftPoint = _airspaceContent.PointToScreen(new Point(0, 0));
var bounds = new System.Drawing.Rectangle((int)(upperLeftPoint.X * _scalingFactor),
(int)(upperLeftPoint.Y * _scalingFactor),
(int)(_airspaceContent.RenderSize.Width * _scalingFactor),
(int)(_airspaceContent.RenderSize.Height * _scalingFactor));
using (var bitmap = new System.Drawing.Bitmap((int)bounds.Width, (int)bounds.Height))
{
using (var g = System.Drawing.Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new System.Drawing.Point(bounds.Left, bounds.Top),
System.Drawing.Point.Empty,
new System.Drawing.Size((int)bounds.Width, (int)bounds.Height));
}
_airspaceScreenshot.Source = GetImageSourceFromBitmap(bitmap);
}
}
// https://stackoverflow.com/questions/5977445/how-to-get-windows-display-settings
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private void GetScalingFactor()
{
var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
IntPtr desktop = g.GetHdc();
int LogicalScreenHeight = GetDeviceCaps(desktop, 10);
int PhysicalScreenHeight = GetDeviceCaps(desktop, 117);
float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
_scalingFactor = ScreenScalingFactor; // 1.25 = 125%
}
public ImageSource GetImageSourceFromBitmap(System.Drawing.Bitmap bitmap)
{
using (var memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
The GitHub project uses the WebBrowser control. One small complication is I am updating this to the WebView2 control which is newer but is also just a win32 control in WPF.
All works fine if the PC's scaling is 100%. The resulting image almost perfectly matches what the control displays. Unfortunately this does not if scaling is changed.
This is how the window looks with the Webbrowser control with 200% scaling.
The image however is considerable enlarged when the scaling is 200%.
My project now targets 4.7.2 .Net Framework which if I understand correctly means it is now DPI aware thus I understand I do not need to do anything extra with the app config or manifest. Am I correct Manifest changes like below are no longer needed?
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
</windowsSettings>
</application>
Assuming _scalingFactor is problematic, I would recommend to use VisualTreeHelper.GetDpi method. In this case, GetScalingFactor method will be like the following:
public void GetScalingFactor()
{
_scalingFactor = (float)VisualTreeHelper.GetDpi(_airspaceContent).DpiScaleX;
}
I think this class has much room for refactoring though.

How can take an screenshot for an specific section in a windows universal app (doesn't matter the computer resolution)

I'm working in a company where the owner wish take a screenshot of a windows mail app; specifically the section where the e-mail are show; and if the window or the section have an scroll, then must avoid it and take the screenshot of that whole section.
I'm building it on a .net console app, and I have download a lot of examples where shows just how to take a screeshot of an specific or any window.
The closest code (I think) that I found was this:
IntPtr current = IntPtr.Zero;
IntPtr handle = IntPtr.Zero;
List<IntPtr> thumbs = new List<IntPtr>();
if (handle == IntPtr.Zero)
handle = ((System.Windows.Interop.HwndSource)System.Windows.Interop.HwndSource.FromVisual(this)).Handle;
current = DWM.GetWindow(handle, DWM.GetWindowCmd.First);
do
{
int GWL_STYLE = -16;
int TASKSTYLE = 0x10000000 | 0x00800000;
if (TASKSTYLE == (TASKSTYLE & DWM.GetWindowLong(current, GWL_STYLE)))
{
thumbs.Add(current);
}
current = DWM.GetWindow(current, DWM.GetWindowCmd.Next);
if (current == handle)
current = DWM.GetWindow(current, DWM.GetWindowCmd.Next);
}
while (current != IntPtr.Zero);
this.DataContext = thumbs;
What customer expects it's to take an screenshot of a windows mail app but as I said before, the section that shows the e-mail in fact. So, it must looks something like:
Result
I am not sure how to do it from a Console Application, as that is very limited, but you can create a screen capture program from Windows Forms significantly easier.
The course I took from Huw Collingbourne who will teach you how to do it. It is not a small program to type out in this box though.
The program he teaches had a mainform, then would pop up a smaller transparent form that you would maneuver over the area you want to capture. To capture you would press the button which would capture the transparent form with this code:
formRect = new Rectangle(this.Left, this.Top, this.Left + this.Width, this.Top + this.Height);
this.Hide();
mainForm.GrabRect(formRect);
Going back to the main form the program would put the picture on the picturebox for you to view.
public void GrabRect(Rectangle rect)
{
int rectWidth = rect.Width - rect.Left;
int rectHeight = rect.Height - rect.Top;
Bitmap bm = new Bitmap(rectWidth, rectHeight);
Graphics g = Graphics.FromImage(bm);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(rectWidth, rectHeight));
this.pb_screengrab.Image = bm;
Clipboard.SetImage(bm);
g.Dispose();
}
Here are the links to his course
https://bitwisecourses.com/p/program-a-screen-capture-tool-in-c-sharp
and his course sold through udemy
https://www.udemy.com/course/program-a-screen-capture-tool-in-c/learn/lecture/15760316#content
bitwisecourses.com is his direct website I believe. Otherwise Udemy will offer nice discounts like $10-$13 for their various classes. Once you bought a bunch and don’t do anything for a few months they will try to charge you full price, but you only need to email them and they will start discounting you again.
I put everything I did here: https://github.com/johnbnstx/HuwBurn_ScreenGrab
I hope this helps.

Creating Windows 10 Transparency effects in c# form

How do you create the transparency effects that you see in windows 10? Something like this:
I have no clue how to approach this in c#. Logically thinking I would take a snapshot of the desktop every time the form comes into focus. Then blur it and place it at 0, 0(screen to client coordinates). That doesn't seem very effective. Any help? Again. not an experienced C# programmer, so a detailed explanation would be much appreciated
Edit: I saw some the answers referring me to a page for alpha blending. This is not what I am looking for. I wanted to know how to create the blur that you see in the image, the rest I can figure out at my own pace
Though all of the comments and the answers say it is not possible for WinForms, it definitely works also for WinForms (as SetWindowCompositionAttribute can be called on a Win32 window handle):
internal enum AccentState
{
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
}
internal enum WindowCompositionAttribute
{
WCA_ACCENT_POLICY = 19
}
[StructLayout(LayoutKind.Sequential)]
internal struct AccentPolicy
{
public AccentState AccentState;
public int AccentFlags;
public int GradientColor;
public int AnimationId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct WindowCompositionAttributeData
{
public WindowCompositionAttribute Attribute;
public IntPtr Data;
public int SizeOfData;
}
internal static class User32
{
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
}
And then in your Form constructor:
public Form1()
{
InitializeComponent();
BackColor = Color.Black; // looks really bad with the default back color
var accent = new AccentPolicy { AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND };
var accentStructSize = Marshal.SizeOf(accent);
var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false);
var data = new WindowCompositionAttributeData
{
Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY,
SizeOfData = accentStructSize,
Data = accentPtr
}
User32.SetWindowCompositionAttribute(Handle, ref data);
Marshal.FreeHGlobal(accentPtr);
}
Result:
Windows Forms doesn't support AcrylicBrush so far. Only UWP support this.
But you have a Win32 API SetWindowCompositionAttribute to simulate this behavior.
The SetWindowCompositionAttribute API
By calling the Windows internal API SetWindowCompositionAttribute, you can get a lightly blurred transparent Window but this transparency is much less than the AcyclicBrush.
How to implement it
Calling SetWindowCompositionAttribute API is not very easy, so I've written a wrapper class for easier usage. But it's for WPF only.
I've written two posts talking about this:
https://walterlv.github.io/post/win10/2017/10/02/wpf-transparent-blur-in-windows-10.html (not in English)
3 Ways to create a window with blurring background on Windows 10 - walterlv
Other options
It's recommended to use AcrylicBrush using UWP and you can read Microsoft's documents Acrylic material - UWP app developer | Microsoft Docs for more details about it.
You can get that by using some algorithms
I don't know will it work or not. When I see your post I just think and got this concept.
Get entire windows desktop wallpaper image by using copyscreen method
Draw that image into a new bitmap where bitmap width = screen resolution.width and bitmap height = screen resolution.height eg: Bitmap bm = new Bitmap (1920, 1080)
Learn how to blur a bitmap image in c#. There is many of blogs teaching how to blur a bitmap programmatically.
Make blur of captured desktop wallpaper image
Put a picture of inside the form and draw the blurred bitmap into picturebox
You can make FormBorderStyle.None to get rid of old borders
You should keep picturebox picture style = normal in order to get full blurred image

Screenshot of active window outside borders

I recently found a code to make a screenshot of the active window. It's actually working however the image is a little bit too big, it goes a little bit outside the borders of the current window.
This is the screenshot taken with my program:
This is the screenshot taken with alt+printscreen:
This is my class:
public static class Screenshotter
{
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[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
}
public static void MakeScreenshot()
{
var foregroundWindowsHandle = GetForegroundWindow();
var rect = new RECT();
GetWindowRect(foregroundWindowsHandle, out rect);
Rectangle bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
Bitmap bmp = new Bitmap(bounds.Width, bounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
}
bmp.Save("test.png", ImageFormat.Png);
}
}
I just want it screenshots the active window and not a little bit outside the window. I hope somebody can help me :)
I ran into a problem with the symptoms you have described. In my case, it was due to delay between the moments when a window is registered in system as "foreground" and when it actually fully shown on the screen in front of other windows. You probably observe the same. When you do g.CopyFromScreen(...), you get the pixels from a screen's region that may be still in transition from previous foreground window to the current.
In the first saved image, you can see a screen shot of foreground window (command prompt) made in 150 milliseconds after my image capturing program start:
As you can see, it is a mixture of previous foreground window's pixels (Visual Studio) and new one.
It took another 150 ms to fully update the screen:
So, it is not your screenshot has wrong size - it's the new foreground window has not "inflated" yet to its final boundaries.
A simple (and ugly) solution is: insert Thread.Sleep(...) before your call g.CopyFromScreen(...) to give the system enough time to fully replace the pixels on the screen.

How to draw directly on the Windows desktop, C#?

This question has been asked for other languages, and even for those other languages, I have found their answers lacking in how to exactly do it, cleanly (no messed up screen repaints, etc..).
Is it possible to draw onto the Windows Desktop from C#? I am looking for an example if possible.
Try the following:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
class Program {
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
static void Main(string[] args) {
IntPtr desktop = GetDC(IntPtr.Zero);
using (Graphics g = Graphics.FromHdc(desktop)) {
g.FillRectangle(Brushes.Red, 0, 0, 100, 100);
}
ReleaseDC(IntPtr.Zero, desktop);
}
}
You can try:
Graphics.FromHwnd(IntPtr.Zero)
You can see a real-world code example within https://uiautomationverify.codeplex.com/SourceControl/latest#UIAVerify/Tools/visualuiverify/utils/screenrectangle.cs
This draws a rectangle that will appear on the screen until the user chooses to remove it at an arbitrary position (wont be repainted over). It uses a windows form thats hidden/ appears as a popup.
This is the code behind the UIAVerify.exe tool in the current Windows SDK.
If you want to use the above, copy the following files into your project:
utils\screenboundingrectangle.cs
utils\screenrectangle.cs
win32\*
Might need to update namespaces accordingly + add references to System.Drawing + System.Windows.Forms
Then you can draw a rectangle with the following code:
namespace Something
{
public class Highlighter
{
ScreenBoundingRectangle _rectangle = new ScreenBoundingRectangle();
public void DrawRectangle(Rectangle rect)
{
_rectangle.Color = System.Drawing.Color.Red;
_rectangle.Opacity = 0.8;
_rectangle.Location = rect;
this._rectangle.Visible = true;
}
}
}
and
var rect = Rectangle.FromLTRB(100, 100, 100, 100);
var hi = new Highlighter();
hi.DrawRectangle(rect);

Categories