I have code that takes a screenshot...
Size ssSize;
int ssX, ssY, ssWidth, ssHeight;
Bitmap thisScreenshot;
Graphics gfxScreenshot;
public Image Screenshot()
{
ssX = Screen.PrimaryScreen.Bounds.X;
ssY = Screen.PrimaryScreen.Bounds.Y;
ssWidth = Screen.PrimaryScreen.Bounds.Width;
ssHeight = Screen.PrimaryScreen.Bounds.Height;
ssSize = Screen.PrimaryScreen.Bounds.Size;
thisScreenshot = new Bitmap(ssWidth,ssHeight);
gfxScreenshot = Graphics.FromImage(thisScreenshot);
return((Image)gfxScreenshot.CopyFromScreen(ssX, ssY, 0, 0, ssSize));
}
On W7, the resulting image includes the pixels of the calling window;
but on XP it does not. I would like the image to always include the pixels of the
calling process/window. Any clue how I can force this?
UPDATE1:
I've done more experimentation with this, and as a result I'm more confused...
I took the above code and created a totally separate application so that there is no relationship between this and the application that I was originally launching it from.
Strangely enough, I am STILL not seeing the window of that application in the screenshot.
So now I have no relationship between the process doing the screenshot and the window that I want included in the screenshot; yet, that window is still not included.
I did try the PRNT-SCRN button and that does include the window.
Note that this is only a problem on XP.
Set your form's Opacity property to 100 and right-click the TransparencyKey property and select Reset. That ensures that your window is no longer a layered window and won't be missing from the screenshot.
If you want to keep these properties then you'll have to work around in a bug in Graphics.CopyFromScreen(). The overload that uses CopyPixelOperation with the CaptureBlt operation is required to capture layered windows. But won't work due to a bug in the argument validation code. The workaround isn't pretty but functional:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
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);
}
}
Related
I've made a program to take a screenshot of a specific window over and over again, and while performing stress tests everything works fine until the 4997th iteration.
I'm guessing there's a leak somewhere within the screenshot portion but I've tried everything to prevent it from happening to no avail.
Here's Screenshot.cs
[DllImport("user32.dll")]
private static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rectangle rect);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc,int nWidth,int nHeight);
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc,IntPtr hgdiobj);
[DllImport("gdi32.dll")]
private static extern int DeleteDC(IntPtr hdc);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hwnd,IntPtr hdcBlt,UInt32 nFlags);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
public static Bitmap MakeScreenshotOfWindow(IntPtr hWnd)
{
IntPtr hscrdc = GetWindowDC(hWnd);
Rectangle windowRect = new Rectangle();
GetWindowRect(hWnd, ref windowRect);
int width = Math.Abs(windowRect.X - windowRect.Width);
int height = Math.Abs(windowRect.Y - windowRect.Height);
IntPtr hbitmap = CreateCompatibleBitmap(hscrdc, width, height);
IntPtr hmemdc = CreateCompatibleDC(hscrdc);
SelectObject(hmemdc, hbitmap);
PrintWindow(hWnd, hmemdc, 0);
Bitmap bmp = Image.FromHbitmap(hbitmap);
DeleteDC(hscrdc);
DeleteDC(hmemdc);
return bmp;
}
Here's a snippet of where I call the MakeScreenshotOfWindow function:
using (Bitmap screenShot = Screenshot.MakeScreenshotOfWindow(WindowHandle))
{
if (screenShot == null)
{
thumbnailError = "screenShot was null";
return false;
}
else
{
screenShot.Save($"{Program.thumbnailStorage}\\{assetId}-{assetType}-{t}.png", ImageFormat.Png);
screenShot.Dispose();
}
}
Here's the full exception that occurs
System.Runtime.InteropServices.ExternalException (0x80004005): A generic error occurred in GDI+.
at System.Drawing.Image.FromHbitmap(IntPtr hbitmap, IntPtr hpalette)
at System.Drawing.Image.FromHbitmap(IntPtr hbitmap)
at ThumbnailServer.Screenshot.PrintWindow(IntPtr hWnd) in C:\Users\darkg\source\repos\ThumbnailServer\ThumbnailServer\Screenshot.cs:line 69
at ThumbnailServer.ThumbnailGenerator.Click(Int32 t, Int32 assetId, Int32 assetType, String& thumbnailError, Int32 w, Int32 h, Boolean hideSky) in C:\Users\darkg\source\repos\ThumbnailServer\ThumbnailServer\ThumbnailGenerator.cs:line 72
I fixed it by adding this:
[DllImport("gdi32.dll")]
private static extern IntPtr DeleteObject(IntPtr hwnd);
and then this:
DeleteObject(hbitmap);
I wanted to automate the creation of screenshots taken with my balloon tip from a notification icon, so that could easily validate the look of the different languages my application supports. Problem is that the balloon tip is absent from the screenshot although it is shown on the screen on Windows 7.
I have tried with the solutions from Capture screenshot of active window?, e.g.
// From http://www.developerfusion.com/code/4630/capture-a-screen-shot/
var sc = new ScreenCapture();
trayIcon.ShowBalloonTip(10000, "My Title", "My message", ToolTipIcon.Info);
Thread.Sleep(2000); // Just to make sure that the balloon tip is shown
sc.CaptureScreenToFile("MyScreenshot.png", ImageFormat.Png);
and
Rectangle bounds = Screen.GetBounds(Point.Empty);
using(Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using(Graphics g = Graphics.FromImage(bitmap))
{
trayIcon.ShowBalloonTip(10000, "My Title", "My message", ToolTipIcon.Info);
Thread.Sleep(2000); // Just to make sure that the balloon tip is shown
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
bitmap.Save("MyScreenshot.png", ImageFormat.Png);
}
But both take screenshots without showing the balloon tip. So, is there a way to programmatically take a screenshot that includes balloon tips?
Bonus information: On Windows 10 the balloon tip is forced into the normal notification system and taking screenshots of this works as expected.
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);
}
I am using the code below to capture a screenshot of the currently active window. This code comes from Capture screenshot Including Semitransparent windows in .NET, with a few small additions, i.e. it uses GetForegroundWindow and also a timer so that I can select the desired window.
On Windows 10 (x64) this works fine for Firefox browser, but it does not work with Chrome or Edge.
I find it strange that Screenshot captured using BitBlt in C# results a black image on Windows 10 [duplicate] is marked as a duplicate, because the answer from above (first link) does not solve this problem.
Any ideas why it does not work for Chrome or Edge?
Code:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace App1
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void DoCapture()
{
label1.Text = "Capturing...";
try
{
IntPtr hDesk = GetForegroundWindow();
//IntPtr hDesk = GetDesktopWindow();
var windowRect = new RECT();
GetWindowRect(hDesk, out windowRect);
int width = (int)(windowRect.Right - windowRect.Left);
int height = (int)(windowRect.Bottom - windowRect.Top);
Size sz = new Size(width, height);
sz.Width = (int)(sz.Width * 1.25); // this is just an adjustment for the Windows zoom factor of 125%
sz.Height = (int)(sz.Height * 1.25);
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();
label1.Text = "Done";
}
catch (Exception e){
label1.Text = "Exception Occurred";
textBox1.Text = e.ToString();
}
}
void Button1Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
// 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 GetForegroundWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr 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
}
void Timer1Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
DoCapture();
}
}
}
Running this in SharpDevelop 5.1
Likely it doesn't work for these 2 browsers because they use a hardware-accelerated graphics context (OpenGL- or DirectX-based) to render their content, and this is incompatible with GDI-based calls.
I can suggest a simple workaround:
First use GetForegroundWindow to get the active window rect. Then call GetDesktopWindow and use that handle with the call to BitBlt:
Just add a single line to above code:
IntPtr hDesk = GetForegroundWindow();
... // get dimensions of active window
hDesk = GetDesktopWindow(); // add this line
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
Works fine.
I trying to run tessnet on a bitmap returned from a screenshot created with getwindow function but the result is bad. I tried to run on a bmp file saved in paint. This image is same as image created with getwindow and for this the tessnet work. This is the image Any idea?
public const int SRCCOPY = 13369376;
public const int WM_CLICK = 0x00F5;
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "GetDC")]
internal extern static IntPtr GetDC(IntPtr hWnd);
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
internal extern static IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
internal extern static IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
internal extern static IntPtr DeleteDC(IntPtr hDc);
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
internal extern static IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll", EntryPoint = "BitBlt")]
internal extern static bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp);
[DllImport("gdi32.dll", EntryPoint = "SelectObject")]
internal extern static IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
internal extern static IntPtr DeleteObject(IntPtr hDc);
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
public static Bitmap createBitmapFromWindow(string windowClass,string windowTitle,Point sarok1,Point sarok2)
{
IntPtr hWnd = FindWindow(windowClass, windowTitle);
Bitmap bmp = null;
IntPtr hdcFrom = GetDC(hWnd);
IntPtr hdcTo = CreateCompatibleDC(hdcFrom);
RECT windowSize;
GetWindowRect(hWnd, out windowSize);
int height = windowSize.Bottom;
int width = windowSize.Right;
IntPtr hBitmap = CreateCompatibleBitmap(hdcFrom, width, height);
if (hBitmap != IntPtr.Zero)
{
// adjust and copy
IntPtr hLocalBitmap = SelectObject(hdcTo, hBitmap);
int posx, posy;
if (sarok1.X > sarok2.X)
{
posx = sarok2.X;
}
else
{
posx = sarok1.X;
}
if (sarok1.Y > sarok2.Y)
{
posy = sarok2.Y;
}
else
{
posy = sarok1.Y;
}
BitBlt(hdcTo, 0, 0, Math.Abs(sarok1.X-sarok2.X), Math.Abs(sarok1.Y-sarok2.Y),
hdcFrom, posx, posy, SRCCOPY);
SelectObject(hdcTo, hLocalBitmap);
//We delete the memory device context.
DeleteDC(hdcTo);
//We release the screen device context.
ReleaseDC(hWnd, hdcFrom);
//Image is created by Image bitmap handle and assigned to Bitmap variable.
bmp = System.Drawing.Image.FromHbitmap(hBitmap);
DeleteObject(hBitmap);
}
return bmp;
}
public static void main()
{
Bitmap b1 = new Bitmap(createBitmapFromWindow(null, "window title", new Point(557, 460), new Point(670, 500)));
Bitmap b = b1.Clone(new Rectangle(new Point(0, 0), new Size(110, 29)),PixelFormat.Format24bppRgb);
var ocr = new Tesseract();
ocr.Init(#"path", "eng", false);
b.SetResolution(300, 300);
List<Word> l = ocr.DoOCR(b, Rectangle.Empty);
}
Tesseract expects black font on white background. An invert, a histogram equalization and a conversion to grayscale will help.
Looks like your images will consists of numbers only. You can hint tesseract to look for a numbers (and the point only). But I don't know how to do that with Tessnet.
Tesseract doesn't like different sized fonts. If the result is still not good it might be necessary to split the image in two different images and feed them separately.
I'm trying to capture browser screenshot and one of my Win32 api method is GetWindowRect. This is returning same left & right value. This is only happen when I'm running my application in a remote machine having Win7 as a OS.
Also my PrintWindow method failing in this machine. If anyone have faced this issue before please let me know.
Those above two methods works fine with Vista and XP as OS in remote machine.
Adding few of the methods of my application.
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
private Image Capture(IntPtr hwnd)
{
Rectangle windowSize = this.GetWindowPosition(hwnd);
Bitmap bm = new Bitmap(windowSize.Width, windowSize.Height);
using (Graphics g = Graphics.FromImage(bm))
{
IntPtr hdc = g.GetHdc();
if (PrintWindow(hwnd, hdc, 0) == false)
{
throw new Exception("PrintWindow call failed");
}
g.ReleaseHdc(hdc);
g.Flush();
}
return bm;
}
private Rectangle GetWindowPosition(IntPtr hwnd)
{
Rect r = new Rect();
GetWindowRect(hwnd, ref r);
return new Rectangle(r.Left, r.Top, r.Width, r.Height);
}
You aren't checking your Win32 return codes. My guess is that GetWindowRect fails for some reason and so doesn't assign any values to the rect. Thus its values remain uninitialised.
Check the return value and if the call fails use Marshal.GetLastWin32Error() to find out why. You'll need to update your P/Invokes too:
[DllImport("user32.dll", SetLastError=true)]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll", SetLastError=true)]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
...
if (!GetWindowRect(hwnd, ref r))
int ErrorCode = Marshal.GetLastWin32Error();