Take screenshot of multiple desktops of all visible applications and forms - c#

I'm working with a system that has 4 outputs (monitors) with e.g. 1280x1024 pixels for each output. I need a screenshot of the whole desktop and all open applications on it.
I tried GetDesktopWindow() (MSDN) but it doesn't work properly. Some forms don't shown on the captured picture.

i tried GetDesktopWindow() function but it doesn't work properly.
Of course not.
The GetDesktopWindow function returns a handle to the desktop window. It doesn't have anything to do with capturing an image of that window.
Besides, the desktop window is not the same thing as "the entire screen". It refers specifically to the desktop window. See this article for more information and what can go wrong when you abuse the handle returned by this function.
i'm working with a system that have 4 outputs (monitors) with 1280x1024(e.g) for each output. i need a screenshot from whole desktop and all open applications on it.
This is relatively simple to do in the .NET Framework using the Graphics.CopyFromScreen method. You don't even need to do any P/Invoke!
The only trick in this case is making sure that you pass the appropriate dimensions. Since you have 4 monitors, passing only the dimensions of the primary screen won't work. You need to pass the dimensions of the entire virtual screen, which contains all of your monitors. Retrieve this by querying the SystemInformation.VirtualScreen property, which returns the bounds of the virtual screen. As the documentation indicates, this is the bounds of the entire desktop on a multiple monitor system.
Sample code:
// Determine the size of the "virtual screen", which includes all monitors.
int screenLeft = SystemInformation.VirtualScreen.Left;
int screenTop = SystemInformation.VirtualScreen.Top;
int screenWidth = SystemInformation.VirtualScreen.Width;
int screenHeight = SystemInformation.VirtualScreen.Height;
// Create a bitmap of the appropriate size to receive the screenshot.
using (Bitmap bmp = new Bitmap(screenWidth, screenHeight))
{
// Draw the screenshot into our bitmap.
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
}
// Do something with the Bitmap here, like save it to a file:
bmp.Save(savePath, ImageFormat.Jpeg);
}
Edit:
please check your solution with a wpf application in a thread that is not your main thread. i tried it. it doesn't work!
Hmm, I didn't see a WPF tag on the question or mentioned anywhere in the body.
No matter, though. The code I posted works just fine in a WPF application, as long as you add the appropriate references and using declarations. You will need System.Windows.Forms and System.Drawing. There might be a more WPF-esque way of doing this that doesn't require a dependency on these WinForms assemblies, but I wouldn't know what it is.
It even works on another thread. There is nothing here that would require the UI thread.
Yes, I tested it. Here is my full test code:
using System.Windows;
using System.Windows.Forms; // also requires a reference to this assembly
using System.Drawing; // also requires a reference to this assembly
using System.Drawing.Imaging;
using System.Threading;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// Create a new thread for demonstration purposes.
Thread thread = new Thread(() =>
{
// Determine the size of the "virtual screen", which includes all monitors.
int screenLeft = SystemInformation.VirtualScreen.Left;
int screenTop = SystemInformation.VirtualScreen.Top;
int screenWidth = SystemInformation.VirtualScreen.Width;
int screenHeight = SystemInformation.VirtualScreen.Height;
// Create a bitmap of the appropriate size to receive the screenshot.
using (Bitmap bmp = new Bitmap(screenWidth, screenHeight))
{
// Draw the screenshot into our bitmap.
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
}
// Do something with the Bitmap here, like save it to a file:
bmp.Save("G:\\TestImage.jpg", ImageFormat.Jpeg);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}

I have created a tiny helper because I needed this case today and tried many different functions. Independently of the number of monitors, you can save it as a file on the disk or store it in a binary field in db with the following code blocks.
ScreenShotHelper.cs
using System.ComponentModel;//This namespace is required for only Win32Exception. You can remove it if you are catching exceptions from another layer.
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Company.Core.Helpers.Win32 {
public static class ScreenShotHelper {
private static Bitmap CopyFromScreen(Rectangle bounds) {
try {
var image = new Bitmap(bounds.Width, bounds.Height);
using var graphics = Graphics.FromImage(image);
graphics.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
return image;
}
catch(Win32Exception) {//When screen saver is active
return null;
}
}
public static Image Take(Rectangle bounds) {
return CopyFromScreen(bounds);
}
public static byte[] TakeAsByteArray(Rectangle bounds) {
using var image = CopyFromScreen(bounds);
using var ms = new MemoryStream();
image.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
public static void TakeAndSave(string path, Rectangle bounds, ImageFormat imageFormat) {
using var image = CopyFromScreen(bounds);
image.Save(path, imageFormat);
}
}
}
Usage - Binary Field
var bounds = new Rectangle();
bounds = Screen.AllScreens.Aggregate(bounds, (current, screen)
=> Rectangle.Union(current, screen.Bounds));
_card.ScreenShot = Convert.ToBase64String(ScreenShotHelper.TakeAsByteArray(bounds));
Usage - Disk file
var bounds = new Rectangle();
bounds = Screen.AllScreens.Aggregate(bounds, (current, screen)
=> Rectangle.Union(current, screen.Bounds));
ScreenShotHelper.TakeAndSave(#"d:\screenshot.png", bounds, ImageFormat.Png);

Related

How to take a screenshot of what the user can see xamarin forms

I've seen many other posts that use Xamarin.Essentials.Screenshot.CaptureAsync(). The problem with that is that it takes the screenshot of the top-most view and not what the user can see.
My question is, is it is possible to take a screenshot of what the user can see and if it is, how?
EDIT :
I haven't been very clear of what I am trying to achieve, so here is it:
I am trying to take a screenshot of the phone default screen(where all the apps are etc.) and also other apps. So for that I use a foreground service. It works and all but the screenshots are still only the view of the app
The following code can get the view of the activity with a bitmap, and then you can save the bitmap as a image file to the device.
private void takeScreenShot(Activity activity)
{
View view = activity.Window.DecorView;
view.DrawingCacheEnabled = true;
view.BuildDrawingCache();
Bitmap bitmap = view.DrawingCache;
Rect rect = new Rect();
activity.Window.DecorView.GetWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.Top;
int width = activity.WindowManager.DefaultDisplay.Width;
int height = activity.WindowManager.DefaultDisplay.Height;
Bitmap screenShotBitmap = Bitmap.CreateBitmap(bitmap, 0, statusBarHeight, width,
height - statusBarHeight);
view.DestroyDrawingCache();
string uri = System.IO.Path.Combine("/sdcard/Download", "screen.jpg");
using (System.IO.FileStream os = new System.IO.FileStream(uri, System.IO.FileMode.Create))
{
screenShotBitmap.Compress(Bitmap.CompressFormat.Png, 100, os);
os.Close();
}
}

Save panel or Form as image with High quality

I am currently using this code to screen capture the panel I created, but whenever I am saving it, the quality is bad. Is there any way to maintain the good quality when saving it?
I tried resizing the panel but the result is still the same.
I tried doing a normal screen shot with the snipping tool and it also has the same result with the codes I use.
Any suggestions? or help?
private void SaveControlImage(Control theControl)
{
snapCount++;
int width = panel1.Size.Width;
int height = panel1.Size.Height;
Bitmap bm = new Bitmap(width, height, PixelFormat.Format64bppPArgb);
panel1.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
//bm.Save(#"D:\TestDrawToBitmap.bmp", ImageFormat.Bmp);
bm.Save(deskTopPath + #"/Offer_" + snapCount.ToString() + "_" + DateTime.Now.ToString("yyyyMMdd") + #".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
Like here, it looks pixelated if you compare it to what you're reading now (like this website). I tried to screen cap the form but it looks like the uploaded picture so its useless
Screenshot:
Bitmap bmp= new Bitmap(controls.Width, controls.Height-50, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics Grap = Graphics.FromImage(bmp);
Grap.CopyFromScreen(PointToScreen(controls.Location).X, PointToScreen(controls.Location).Y, 0, 0, AnhDoThi.Size, CopyPixelOperation.SourceCopy);
SaveFileDialog save = new SaveFileDialog();
save.Filter = "JPEG|*.jpg";
DialogResult tl = save.ShowDialog();
if (tl == DialogResult.OK)
{
bmp.Save(save.FileName);
MessageBox.Show("Completed !");
}
This is what I use to save a screenshot:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
private void SaveControlAsImage(Control control, string path)
{
Bitmap bitmap = new Bitmap(control.Width, control.Height);
control.DrawToBitmap(bitmap, control.Bounds);
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
{
/* using ImageFormat.Png or ImageFormat.Bmp saves the image with better quality */
bitmap.Save(fs, ImageFormat.Png);
}
}
Control screenshot using Windows Snipping Tool
Control screenshot using SaveControlAsImage (ImageFormat.Png)
Control screenshot using SaveControlAsImage (ImageFormat.Jpeg)
It's not the best quality (just a little blurry), but this removes the distortion around the text and keeps the right color.
Sidenote: You're not using the parameter (theControl) passed to your method, so you might as well remove it (not recommended), or change all the calls to panel1 inside the method to theControl and call the method like
this.SaveControlImage(this.panel1);
Also, when you're accessing just a property (i.e. this.panel1.Size.Width) it's better to just call the property instead of assign it to a variable. If you're calling a method and getting a value back (which you'll be using more than once), you must assign it to a variable (i.e. int arrayCount = array.Count()).
Windows Forms is not using vector graphics to draw the user interface. Therefore, all you can get generically is to draw the control to a Bitmap instead of to the screen. This works independently of whether your control is visible on the screen, but there is not more than that. If you want a higher-resolution image from a Windows Forms control, the only way is to resize the control and hope that it supports zooming.

Taking a screenshot using Graphics.CopyFromScreen with 150% scaling

I'm trying to create a screenshot/bitmap of my screen. I wrote this function:
public static Bitmap CreateScreenshot(Rectangle bounds)
{
var bmpScreenshot = new Bitmap(bounds.Width, bounds.Height,
PixelFormat.Format32bppArgb);
var gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(bounds.X, bounds.Y,
0, 0,
new Size(bounds.Size.Width, bounds.Size.Height),
CopyPixelOperation.SourceCopy);
return bmpScreenshot;
}
This function is being called in my overlay form that should draw the bitmap onto itself. I'm currently using GDI+ for the whole process.
private void ScreenshotOverlay_Load(object sender, EventArgs e)
{
foreach (Screen screen in Screen.AllScreens)
Size += screen.Bounds.Size;
Location = Screen.PrimaryScreen.Bounds.Location;
_screenshot = BitmapHelper.CreateScreenshot(new Rectangle(new Point(0, 0), Size));
Invalidate(); // The screenshot/bitmap is drawn here
}
Yep, I dispose the bitmap later, so don't worry. ;)
On my laptop and desktop computer this works fine. I've tested this with different resolutions and the calculations are correct. I can see an image of the screen on the form.
The problem starts with the Surface 3. All elements are being scaled by a factor of 1.5 (150%). This consequently means that the DPI changes. If I try to take a screenshot there, it does only capture like the upper-left part of the screen but not the whole one.
I've made my way through Google and StackOverflow and tried out different things:
Get the DPI, divide it by 96 and multiply the size components (X and Y) of the screen with this factor.
Add an entry to application.manifest to make the application DPI-aware.
The first way did not bring the desired result. The second one did, but the whole application would have to be adjusted then and this is quite complicated in Windows Forms.
Now my question would be: Is there any way to capture a screenshot of the whole screen, even if it is has a scalation factor higher than 1 (higher DPI)?
There must be a way to do this in order to make it working everywhere.
But at this point I had no real search results that could help me.
Thanks in advance.
Try this, which is found within SharpAVI's library. It works well on devices regardless of resolution scale. And I have tested it on Surface 3 at 150%.
System.Windows.Media.Matrix toDevice;
using (var source = new HwndSource(new HwndSourceParameters()))
{
toDevice = source.CompositionTarget.TransformToDevice;
}
screenWidth = (int)Math.Round(SystemParameters.PrimaryScreenWidth * toDevice.M11);
screenHeight = (int)Math.Round(SystemParameters.PrimaryScreenHeight * toDevice.M22);
SharpAVI can be found here: https://github.com/baSSiLL/SharpAvi It is for videos but uses a similar copyFromScreen method when getting each frame:
graphics.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(screenWidth, screenHeight));
Before taking your screen shot, you can make the process DPI aware:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
private static Bitmap Screenshot()
{
SetProcessDPIAware();
var screen = System.Windows.Forms.Screen.PrimaryScreen;
var rect = screen.Bounds;
var size = rect.Size;
Bitmap bmpScreenshot = new Bitmap(size.Width, size.Height);
Graphics g = Graphics.FromImage(bmpScreenshot);
g.CopyFromScreen(0, 0, 0, 0, size);
return bmpScreenshot;
}

GDI+ Nested Graphics objects

With System.Drawing.Graphics, how can I have "nested graphics objects".
The main purpose is to have several clipped regions.
This is somehow the thing I want to do:
The whole screen is a Graphics object
Red area is another Graphics inside it and clipped
Green area is another one, clipped
Inside Graphics objects can be anything NOT only DrawString
The code should look like this:
using (var bmp = new System.Drawing.Bitmap(200, 200))
using (var mainG = System.Drawing.Graphics.FromImage(bmp))
using (var redG = ???)
using (var greenG = ???)
{
redG.SetClip(new RectangleF(...));
greenG.SetClip(new RectangleF(...));
// fill redG and greenG
}
NOTE: the result should go to a meta file and be vector graphic, so creating bitmaps and placing them around the mainG is NOT an option.
Assuming it's okay for the two vector contexts to be seperate while they are drawn into, you can use System.Drawing.Imaging.Metafile to catch the vector operations and then combine them into the bigger context.. Something like this:
using (Graphics gRef = this.CreateGraphics())
{
IntPtr hdc = gRef.GetHdc();
using (System.Drawing.Imaging.Metafile mf =
new System.Drawing.Imaging.Metafile(hdc,
System.Drawing.Imaging.EmfType.EmfPlusDual))
{
gRef.ReleaseHdc();
using (Graphics redG = Graphics.FromImage(mf))
{
redG.SetClip(new RectangleF(...));
// .. draw on redG
}
// repeat for greenG
// .. save and or combine as desired
}
}
}
An alternative approach would be to study the Enhanced Metafile format (http://msdn.microsoft.com/en-us/library/cc230724.aspx) and try to reproduce clipping masks manually.

Need to delete a file, in use by my own app

I'm fiddling with a desktop gadget (a clock). It has a reflection effect underneath it that needs to be transparent, and I'm using CopyFromScreen to get the background, then just setting the form background to this.
Like so (part of a "dock/undock" button):
Rectangle bounds = this.Bounds;
using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height))
using (Graphics g = Graphics.FromImage(ss))
{
this.Opacity = 0;
g.CopyFromScreen(this.Location, Point.Empty, bounds.Size);
ss.Save(#"C:\clock1s\bg", ImageFormat.Png);
this.Opacity = 100;
this.BackgroundImage = new Bitmap(#"C:\clock1s\bg");
g.Dispose();
}
Now, whenever I want to use this again (e.g move the clock) I'm not able to delete that file or resave it because it is currently in use. I've already tried setting the form BG to something else, but that didn't work.
How do I go about?
EDIT: Sorted, thanks (Lance McNearney).
Now, what if I had to save it to file?
Also, bonus question:
the goddamn batwatch http://upload.snelhest.org/images/100219batwatch.jpg
Selections and icons ending up underneath the form is a minor annoyance, and if possible I'd like them to either end up on top, or below (keeping the smooth anti-aliasing). I'm assuming that's way out of my leauge to fix though.
Do you really need to save the image to the file system? Can't you store it in memory just for your application (and you won't have the problem anymore)?
A MemoryStream should work as a drop-in replacement for the file path in your code (although I obviously can't compile to test it):
Rectangle bounds = this.Bounds;
using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height))
using (Graphics g = Graphics.FromImage(ss))
{
this.Opacity = 0;
g.CopyFromScreen(this.Location, Point.Empty, bounds.Size);
using(MemoryStream ms = new MemoryStream()) {
ss.Save(ms, ImageFormat.Png);
this.Opacity = 100;
this.BackgroundImage = new Bitmap(ms);
}
g.Dispose();
}

Categories

Resources