I'm trying to take a screenshot of another window specified by a valid hwnd. The procedure works, however the window flickers when the screenshot is taken.
The first function uses PrintScreen, the second BitBlt. Each is called from a routine that takes a screenshot every 10 seconds.
Is there a way to avoid a flicker when using the PrintScreen or BitBlt functions?
Any help would be appreciated.
public Bitmap GetScreenshot(IntPtr ihandle)
{
IntPtr hwnd = ihandle;//handle here
RECT rc;
FocusWindow(ihandle);
GetWindowRect(hwnd, out rc);
Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap;
try
{
hdcBitmap = gfxBmp.GetHdc();
}
catch
{
return null;
}
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);//err here once
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
public Bitmap CaptureWindowImage(IntPtr hWnd) //, System.Drawing.Rectangle wndRect)
{
IntPtr hWndDc = GetDC(hWnd);
IntPtr hMemDc = CreateCompatibleDC(hWndDc);
RECT rc;
GetWindowRect(hWnd, out rc);
IntPtr hBitmap = CreateCompatibleBitmap(hWndDc, rc.Width, rc.Height);
SelectObject(hMemDc, hBitmap);
BitBlt(hMemDc, 0, 0, rc.Width, rc.Height, hWndDc, 0, 0, TernaryRasterOperations.SRCCOPY);
Bitmap bitmap = Bitmap.FromHbitmap(hBitmap);
DeleteObject(hBitmap);
ReleaseDC(hWnd, hWndDc);
ReleaseDC(IntPtr.Zero, hMemDc);
DeleteDC(hMemDc);
return bitmap;
}
Related
I'm building a small video recording of a specific Windows but I just noticied it will not work when the Window is minimized, it take screenshot only the application's window superior bar. Is possible to workaround it?
Here's my current code:
public Bitmap GetScreenshot(IntPtr hwnd)
{
RECT rc;
if (!GetWindowRect(hwnd, out rc))
throw new Win32Exception(Marshal.GetLastWin32Error());
Bitmap bmp = new Bitmap(rc.right - rc.left, rc.bottom - rc.top, PixelFormat.Format32bppArgb);
using (var gfxBmp = Graphics.FromImage(bmp))
{
IntPtr hdcBitmap = gfxBmp.GetHdc();
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
return bmp;
}
}
I am having a problem with capturing the graphics of another proccess, because for some users it is just a pure black screen which I capture. Unfortunately I have no idea why this is happening for some users only. I am using the sub window directly instead of the window handle and I ensured by posting the address of the windowhandle and checking with spy++ that this window handle is actually the right one.
const string className = "BlueStacksApp";
const string windowName = "_ctl.Window";
processMainHWND = process.MainWindowHandle;
clientRectangleHandle = User32.FindWindowEx(processMainHWND, 0, className, windowName);
internal Bitmap MakeSnapshot(IntPtr AppWndHandle, RECT rect)
{
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
IntPtr hdcTo = IntPtr.Zero;
IntPtr hdcFrom = IntPtr.Zero;
IntPtr hBitmap = IntPtr.Zero;
try
{
Bitmap clsRet = null;
// get device context of the window...
hdcFrom = User32.GetWindowDC(AppWndHandle);
// create dc that we can draw to...
hdcTo = GDI32.CreateCompatibleDC(hdcFrom);
hBitmap = GDI32.CreateCompatibleBitmap(hdcFrom, width, height);
// validate
if (hBitmap != IntPtr.Zero)
{
// adjust and copy
IntPtr hLocalBitmap = GDI32.SelectObject(hdcTo, hBitmap);
bool result = GDI32.BitBlt(hdcTo, 0, 0, width, height, hdcFrom, 0, 0, GDI32.SRCCOPY);
GDI32.SelectObject(hdcTo, hLocalBitmap);
// create bitmap for window image...
clsRet = Image.FromHbitmap(hBitmap);
}
return clsRet;
}
finally
{
// release the unmanaged resources
if (hdcFrom != IntPtr.Zero)
User32.ReleaseDC(AppWndHandle, hdcFrom);
if (hdcTo != IntPtr.Zero)
GDI32.DeleteDC(hdcTo);
if (hBitmap != IntPtr.Zero)
GDI32.DeleteObject(hBitmap);
}
}
I grab an image from a external camera. The winforms application works so far. Now I need to transfer it to WPF. Therefore I converted the bitmap-image to WPF like this:
GrabHandler frameCallbackDelegate;
void frameCallBack(IntPtr lpUserData, ref VCECLB_FrameInfoEx frameInfo)
{
if (frameInfo.dma_status != 0) return;
// fill image
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
BitmapData bmdata = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
UInt32 outputBitDepth;
IntPtr stride = new IntPtr(bmdata.Stride);
VCECLB_Error err = NativeFunctions.VCECLB_UnpackRawPixelsEx(ref pRawPixelInfo, frameInfo.lpRawBuffer, bmdata.Scan0, ref stride, VCECLB_Output_Format.VCECLB_EX_FMT_TopDown | VCECLB_Output_Format.VCECLB_EX_FMT_3Channel, out outputBitDepth);
image.UnlockBits(bmdata);
Dispatcher.Invoke(new Action(() =>
{
// Convert bitmap to WPF-Image
var bmp = new Bitmap(image);
var hBitmap = bmp.GetHbitmap();
ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
image_LowLight.Source = wpfBitmap;
DeleteObject(hBitmap);
image.Dispose();
}));
GC.Collect();
}
private void StartGrab(object sender, RoutedEventArgs e)
{
start_button.IsEnabled = true;
stop_button.IsEnabled = false;
frameCallbackDelegate = new GrabHandler(frameCallBack);
m_ImageAquisition.StartGrab(frameCallbackDelegate);
}
When I enter for the first time the frameCallBack from the StartGrab, the image.Width and image.Height have a valid value (not 0) and it seems correct. When the frameCallBack is entered for the second time all image-data (width and height) are 0 and therefore rect gives an error.
Is there something wrong in the way I am using the Dispatcher.Invoke to get a valid bitmap-image?
Thanks!
Avoid using volatile data in an Action() which gets executed some time later by the Dispatcher.
Refactor your code like this:
GrabHandler frameCallbackDelegate;
void frameCallBack(IntPtr lpUserData, ref VCECLB_FrameInfoEx frameInfo)
{
if (frameInfo.dma_status != 0) return;
// fill image
Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
BitmapData bmdata = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat);
UInt32 outputBitDepth;
IntPtr stride = new IntPtr(bmdata.Stride);
VCECLB_Error err = NativeFunctions.VCECLB_UnpackRawPixelsEx(ref pRawPixelInfo, frameInfo.lpRawBuffer, bmdata.Scan0, ref stride, VCECLB_Output_Format.VCECLB_EX_FMT_TopDown | VCECLB_Output_Format.VCECLB_EX_FMT_3Channel, out outputBitDepth);
image.UnlockBits(bmdata);
// Convert bitmap to WPF-Image
var bmp = new Bitmap(image);
var hBitmap = bmp.GetHbitmap();
ImageSource wpfBitmap =
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
DeleteObject(hBitmap);
image.Dispose();
Dispatcher.Invoke(new Action(() =>
{
image_LowLight.Source = wpfBitmap;
}));
GC.Collect();
}
It's a fairly standard screen capture function using BitBlt that's found in the net:
Main Function:
while(true)
{
printscreen = GetDesktopImage(X, Y, secMonitorSize.Width, secMonitorSize.Height);
Thread.Sleep(1000);
}
Capture Desktop function:
public Bitmap GetDesktopImage(int X, int Y, int width, int height)
{
IntPtr hDC = WIN32_API.GetDC(WIN32_API.GetDesktopWindow());
IntPtr hMemDC = WIN32_API.CreateCompatibleDC(hDC);
IntPtr m_HBitmap = WIN32_API.CreateCompatibleBitmap(hDC, width, height);
if (m_HBitmap != IntPtr.Zero)
{
IntPtr hOld = (IntPtr)WIN32_API.SelectObject(hMemDC, m_HBitmap);
WIN32_API.BitBlt(hMemDC, 0, 0, width, height, hDC, X, Y, WIN32_API.SRCCOPY | WIN32_API.CAPTURE_BLT);
WIN32_API.SelectObject(hMemDC, hOld);
WIN32_API.DeleteDC(hMemDC);
WIN32_API.ReleaseDC(WIN32_API.GetDesktopWindow(), hDC);
Bitmap printscreen = System.Drawing.Image.FromHbitmap(m_HBitmap);
WIN32_API.DeleteObject(m_HBitmap);
return printscreen;
}
return null;
}
The problem is that the code runs fine for roughly 20 mins, then CreateCompatibleBitmap will keep returning 0. Using setlasterror=true on CreateCompatibleBitmap, it displays the error code 997 (Overlapped I/O Operation Is In Progress).
There's only symantec running in the background. Anyone have any idea how do I start troubleshooting?
GDI functions do not use GetLastError(), so using setlasterror=true will report errors from earlier API calls.
Try this:
public Bitmap GetDesktopImage(int X, int Y, int width, int height)
{
Bitmap printscreen = null;
IntPtr hWnd = WIN32_API.GetDesktopWindow();
IntPtr hDC = WIN32_API.GetDC(hWnd);
if (hDC != IntPtr.Zero)
{
IntPtr hMemDC = WIN32_API.CreateCompatibleDC(hDC);
if (hMemDC != IntPtr.Zero)
{
IntPtr m_HBitmap = WIN32_API.CreateCompatibleBitmap(hDC, width, height);
if (m_HBitmap != IntPtr.Zero)
{
IntPtr hOld = (IntPtr)WIN32_API.SelectObject(hMemDC, m_HBitmap);
WIN32_API.BitBlt(hMemDC, 0, 0, width, height, hDC, X, Y, WIN32_API.SRCCOPY | WIN32_API.CAPTURE_BLT);
WIN32_API.SelectObject(hMemDC, hOld);
printscreen = System.Drawing.Image.FromHbitmap(m_HBitmap);
WIN32_API.DeleteObject(m_HBitmap);
}
WIN32_API.DeleteDC(hMemDC);
}
WIN32_API.ReleaseDC(hWnd, hDC);
}
return printscreen;
}
I am attempting to generate a BitmapFrame that is based on a UIElement. Here is my function:
private BitmapFrame RenderToBitmap2()
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
VisualBrush aVisualBrush = new VisualBrush(GenerateTestStackPanel());
drawingContext.DrawRectangle(aVisualBrush, new Pen(Brushes.Green, 2), new Rect(new Size(150, 150)));
drawingContext.Close();
renderBitmap.Render(drawingVisual);
return BitmapFrame.Create(renderBitmap);
}
For testing and debugging purposes, I am using an additional function that creates a simple StackFrame that should create a valid visual element that can be represented:
private StackPanel GenerateTestStackPanel()
{
// Create a red Ellipse.
Ellipse myEllipse = new Ellipse();
myEllipse.Fill = Brushes.Green;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.Black;
// Set the width and height of the Ellipse.
myEllipse.Width = 200;
myEllipse.Height = 200;
// Add the Ellipse to the StackPanel.
StackPanel myStackPanel = new StackPanel();
myStackPanel.Children.Add(myEllipse);
return myStackPanel;
}
For some reason, the VisualBrush is not being rendered in the DrawRetangle(...) function. I can see the green border but nothing else. In addition, if I swap out the VisualBrush with a standard brush, it works great:
drawingContext.DrawRectangle(Brushes.Plum, new Pen(Brushes.Green, 2), new Rect(new Size(150, 150)));
Thanks in advance!
Take a look at this for an alternate way to create a BitmapSource from a UIElement:
MSDN Thread
I have also been trying to get the VisualBrush to work without any luck which brought me to this thread.
public static BitmapSource CreateBitmapSourceFromVisual(
Double width,
Double height,
Visual visualToRender,
Boolean undoTransformation)
{
if (visualToRender == null)
{
return null;
}
RenderTargetBitmap bmp = new RenderTargetBitmap((Int32)Math.Ceiling(width),
(Int32)Math.Ceiling(height), 96, 96, PixelFormats.Pbgra32);
if (undoTransformation)
{
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(visualToRender);
dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(width, height)));
}
bmp.Render(dv);
}
else
{
bmp.Render(visualToRender);
}
return bmp;
}
I had the same issue before, I wanted to copy content of a UIElement to an image, I used the same approach in the answer above and it seems to work fine, only problem I has, I wanted it to work real-time, so I had to dig deeper, I found some references about using windows APIs to copy the content of the element into a Bitmap, and then this bitmap has to be converted into a BitmapSource so it's usable in WPF
so far it works fine, but it seems to leak memory (not sure about this). I will try to re-use the UIElement hwnd Handle and the bitmap object for better performance and the memory leak (if it exists)
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest, // handle to destination DC
int nXDest, // x-coord of destination upper-left corner
int nYDest, // y-coord of destination upper-left corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
IntPtr hdcSrc, // handle to source DC
int nXSrc, // x-coordinate of source upper-left corner
int nYSrc, // y-coordinate of source upper-left corner
System.Int32 dwRop // raster operation code
);
[DllImport("User32.dll")]
public extern static System.IntPtr GetDC(System.IntPtr hWnd);
[DllImport("User32.dll")]
public extern static int ReleaseDC(System.IntPtr hWnd, System.IntPtr hDC); //modified to include hWnd
//[DllImport("gdi32.dll")]
//[return: MarshalAs(UnmanagedType.Bool)]
//internal static extern bool DeleteObject(IntPtr hObject);
private static Bitmap GetBitmapFromControl(Window element, int width, int height)
{
HwndSource hWnd = (HwndSource)HwndSource.FromVisual(element);
System.IntPtr srcDC = GetDC(hWnd.Handle);
Bitmap bm = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bm);
System.IntPtr bmDC = g.GetHdc();
BitBlt(bmDC, 0, 0, bm.Width, bm.Height, srcDC, 0, 0, 0x00CC0020 /*SRCCOPY*/);
ReleaseDC(hWnd.Handle, srcDC);
g.ReleaseHdc(bmDC);
g.Dispose();
return bm;
}
public static BitmapSource ToWpfBitmap(this Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Bmp);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
// According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
// Force the bitmap to load right now so we can dispose the stream.
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
//DeleteObject(bitmap.GetHbitmap());
bitmap.Dispose();
return result;
}
}