Capture Screenshot Including Semitransparent Windows in .Net

Capture screenshot Including Semitransparent windows in .NET

Forms that have the TransparencyKey or Opacity property set are so-called layered windows. They are shown using the "overlay" feature of the video adapter. Which make them being able to have their transparency effects.

Capturing them requires turning on the CopyPixelOperation.CaptureBlt option in the CopyFromScreen overload that accepts the CopyPixelOperation argument.

Unfortunately, this overload has a critical bug that prevents this from working. It doesn't validate the value properly. Still not fixed in .NET 4.0. There is no other good fix but fall back to using P/Invoke to make the screen shot. Here's an example:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
Size sz = Screen.PrimaryScreen.Bounds.Size;
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(@"c:\temp\test.png");
bmp.Dispose();
}

// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
}
}

Fwiw, a later Windows version provided a workaround for this bug. Not exactly sure which, I think it was Win7 SP1. The BitBlt() function will now do what you want if you pass only the CopyPixelOperation.CaptureBlt option. But of course that workaround wasn't applied retro-actively to earlier Windows versions so you can't really depend on it.

Capture screenshot that includes BaloonTip

As Hans Passant mentions in a commet to the question, using CopyPixelOperation.CaptureBlt is the key to the solution.

As it did not work with the solutions I already tried I found a similar question Capture screenshot Including Semitransparent windows in .NET, that deals with semi-transparent windows.

All-in-all the solutions that enables me to take a screenshot that includes the balloon tip from a notification icon looks like this:

class ScreenCapture
{
public void CaptureScreenToFile(string fileName)
{
Size sz = Screen.PrimaryScreen.Bounds.Size;
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(fileName);
bmp.Dispose();
}

// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
}

Taking a PNG screenshot of a window, but keeping the aero transparency

I think you meant this link. It describes how to revert alpha blending to get the "true" alpha values back.
Basically you have to put a white rectangle below the window, take a screenshot and do the same with a black rectangle. Then you can regain the alpha channel of the window, as described in the blog post linked above.

EDIT: Sorry, now i posted the corrected link.

Is CopyFromScreen a right way to get screenshots?


  1. Yes, it is right way
  2. There are a few possibilities. I recommend sending it via email or FTP (as it's simple in c#).
  3. No, personally I don't think you need additional compression. Your screenshots are already saved as JPEG, so they are already compressed.

Code snippet for sending email with attachment:

MailMessage mail = new MailMessage();
SmtpClient SmtpServer = new SmtpClient(YOUR SMTP SERVER ADDRESS);
mail.From = new MailAddress(SENDER ADDRESS);
mail.To.Add(RECEIVER ADDRESS);
mail.Subject = "Test Mail - 1";
mail.Body = "mail with attachment";
System.Net.Mail.Attachment attachment;
attachment = new System.Net.Mail.Attachment("YOURFILE");
mail.Attachments.Add(attachment);
SmtpServer.Port = 587;
SmtpServer.Credentials = new System.Net.NetworkCredential(YOUR_SMTP_USER_NAME, YOUR_SMTP_PASSWORD);
SmtpServer.EnableSsl = true;
SmtpServer.Send(mail);

(based on this blog)

Code snippet for FTP:

    FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://www.contoso.com/test.htm");
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential ("anonymous","janeDoe@contoso.com");
StreamReader sourceStream = new StreamReader("testfile.txt");
byte [] fileContents = Encoding.UTF8.GetBytes(sourceStream.ReadToEnd());
sourceStream.Close();
request.ContentLength = fileContents.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(fileContents, 0, fileContents.Length);
requestStream.Close();
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Console.WriteLine("Upload File Complete, status {0}", response.StatusDescription);
response.Close();

(based on MSDN)



Related Topics



Leave a reply



Submit