How to redraw only a region of a layered window? - c#

I have a layered window which is normally drawn this way:
private void SelectBitmap(Bitmap bitmap)
{
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);
POINT sourceLocation = new POINT(0, 0);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
SIZE newSize = new SIZE(bitmap.Width, bitmap.Height);
POINT newLocation = new POINT(Location.X, Location.Y);
UpdateLayeredWindow(Handle, screenDc,
ref newLocation, ref newSize,
memDc,
ref sourceLocation, 0,
ref blend,
ULW_ALPHA);
}
finally
{
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
However, this obviously redraw the whole window every time it's called. It's quite a performance drain on large window. (even on my top of the line PC, which make me wonder how people could handle that in Win2K)
If I read the Microsoft paper on the layered window, it says: UpdateLayeredWindow always updates the entire window. To update part of a window, use the traditional WM_PAINT and set the blend value using SetLayeredWindowAttributes.
I just can't understand the above. How is WM_PAINT supposed to access the layered window bitmap and redraw only part of it on the window? From what I understood, layered windows simply disable the WM_PAINT message and expect the user to draw the window by himself. There's obviously no way to bind the WM_PAINT to the custom drawing done.
Am I missing something very obvious?

After long profiling, I found out it wasn't really the layered window update that was bottleneck. Refreshing the whole screen, the SelectBitmap method above, on a 1920*1200 was taking about 6-8ms. Sure, not very amazing, but plenty enough to refresh at 30 FPS+.
In my case, the performance drains was coming from some thread asking for refresh almost a hundred time per redraw, making everything sluggish. The solution was to break down the refresh/redraw and separate them. One would update (union) a region and the other, when not drawing, would take that region, draw it and then empty it.

Related

Screenshot method generates black images

After failing to use the control.drawtobitmap in c#, my second option was to take screenshots of the desktop and crop out the desired sections.
My hiccup shows up once i switch user accounts, although the program does not crash, once the user is switched the program generates pure black images only.
I used this code as a reference:
WebBrowser.DrawToBitmap() or other methods?
I guess logically this makes sense as this would help windows save resources.
What options/ solutions do i have in my situation?
Edit 1
made a modification to the code for testing:
int c = 0;
while (true)
{
try
{
c++;
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
bmp.Save("picture" + c.ToString() + ".jpg");
Thread.Sleep(5000);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
this works perfectly while on the user account, but as soon as i switch users, it returns the exception: The handle is invalid.
Any ideas?
Edit 2:
The bug in DrawToBitmap is not exactly random...
if i used the code you supplied:
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
bmp.Save(".\\picture.jpg");
it works perfectly, example: http://oi61.tinypic.com/1z23ynp.jpg
However, the moment i right-click on the web-browser control, DrawToBitmap will return a blank image.
example: http://oi60.tinypic.com/9ay0yc.jpg
So i can easily overcome this bug by adding
((Control)webbrowser1).Enabled = false;
this makes any clicking impossible on the web-browser, but unfortunately to deactivate it would render my project useless as its main function is to emulate mouse clicks on a web-browser control.
although this might also be a problem if the window is hidden.
currently im looking at this post, where code is supplied to give you a window handle.
Simulate click into a hidden window
it seems it might be of some value... do have a look.
What were the problems you had with DrawToBitmap?
It works fine here, (W8.1, VS2013) even with a Webcontrol and also after switching users. (But see the edit at the end for the conditions!)
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
// Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
Here is code to take a screenshot of your window:
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height );
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
//Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
I can switch users like I want, the program keeps working.
BTW, the link you posted is really old, many things may have improved.
Edit:
With the updated question things are a lot clearer.
So you want to continuously get a screenshot of your program even when the user has changed, right?
and you want to display a WebControl, right?
A user can have three types of desktop: the logon/logoff screen, the screensaver screen and one or more normal desktop screen(s). But while the user is logged off he has no desktop screen at all.
Therefore the screenshot method will not work if the user has no active desktop, neither as g.CopyFromScreen, which will cause a GDI-error nor using a window handle like in the various solutions on the web, including the ones your link leads to. All these will, at best, show a blank or black screen.
So the DrawToBitmap method is the only one that works.
You wrote that it has random errors. That's not what I see.
The problems come in predictably when the user interacts with the WebBrowser in any way. This includes scrolling or clicking with or without navigation. After these interactions the WebBrowser will draw itself as an empty box until its URL is reloaded - not only refreshed - but really reloaded by webBrowser1.Uri = new Uri(uriPath). This can be done, see my other post
The WebBrowser also has another issue when doing a DrawToBitmap: It will fail (with the said empty box) for any pages that include an <input type="text" element. I'm not sure what's the best way to workaround this, let alone why it happends in the first place.. A screenshot method doesn't have that specific problem.
Edit 2:
The code the OP has dug up code which, using a call to PrintWindow, seems to solve all problems we had: It works while being logged off, works with Refeshing even after clicking in the WebBrowser and scrapes all pages, including those with textual input fields. Hoorah!
After cutting down the slack here is a version that can create a copy of the Form or just the WebBroser (or any other Control) with or without borders:
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public Bitmap CaptureWindow(Control ctl)
{
//Bitmap bmp = new Bitmap(ctl.Width, ctl.Height); // includes borders
Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height); // content only
using (Graphics graphics = Graphics.FromImage(bmp))
{
IntPtr hDC = graphics.GetHdc();
try { PrintWindow(ctl.Handle, hDC, (uint)0); }
finally { graphics.ReleaseHdc(hDC); }
}
return bmp;
}
Finally this code seems to work even when i have switched users.
Code to take screen shot of any unsaved Notepad process ("Untitled - Notepad")
private void Form1_Load(object sender, EventArgs e)
{
//loop for debugging
int c = 0;
while(true)
{
c++;
System.Drawing.Bitmap image = CaptureWindow(FindWindow(null, "Untitled - Notepad"));
image.Save(".\\picture"+c.ToString()+".jpg");
Thread.Sleep(5000);
}
}
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public System.Drawing.Bitmap CaptureWindow(IntPtr hWnd)
{
System.Drawing.Rectangle rctForm = System.Drawing.Rectangle.Empty;
using (System.Drawing.Graphics grfx = System.Drawing.Graphics.FromHdc(GetWindowDC(hWnd)))
{
rctForm = System.Drawing.Rectangle.Round(grfx.VisibleClipBounds);
}
System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(rctForm.Width, rctForm.Height);
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(pImage);
IntPtr hDC = graphics.GetHdc();
try
{
PrintWindow(hWnd, hDC, (uint)0);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}
Note that the window may be hidden but it must still be maximized on the user account to get a complete screen shot.
Digital Rights Management may be stopping this, because Windows adds protection for digital media.
If, for example, you are attempting to create a screen capture of something in Media Player or Media Center using Microsoft's rendering of Graphics - yes, Microsoft is going to "protect you from any potential lawsuit."
Try this: Click "Print Screen" on your keyboard and then go into Microsoft Paint and try pasting your screen capture into it. Is anything there?
I faced with the same problem and I couldn't use CopyFromScreen method because my form with WebBrowser could be hidded. PrintWindow method also didn't work well generating images with black areas, especially when my MDI form partially covered by MDI parent form.
Finally I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. Basically, it just overrides standard control reaction to WM_PRINT message. This allows to do normal Control.DrawToBitmap not only for the WebBrowser, but for a form with WebBrowser in it as well. This also works if the form is hidden (covered by another form, including MDI parent form) and should work when user has locked session with Win+L (I haven't tested it).
public class WebBrowserEx : WebBrowser
{
private const uint DVASPECT_CONTENT = 1;
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwAspect,
IntPtr hdcDraw,
[In] ref System.Drawing.Rectangle lprcBounds
);
protected override void WndProc(ref Message m)
{
const int WM_PRINT = 0x0317;
switch (m.Msg)
{
case WM_PRINT:
Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);
// Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
// results in badly scaled (stretched) image of the browser.
// So, drawing to an intermediate bitmap first.
using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
{
using (var graphics = Graphics.FromImage(browserBitmap))
{
var hdc = graphics.GetHdc();
OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
graphics.ReleaseHdc(hdc);
}
using (var graphics = Graphics.FromHdc(m.WParam))
{
graphics.DrawImage(browserBitmap, Point.Empty);
}
}
// ignore default WndProc
return;
}
base.WndProc(ref m);
}
}

C# Transparent Form using UpdateLayeredWindow draw controls

I'm developing an exe where I need to have a transparent background. I have made the image in Photoshop and it has all the neat stuff (shadows/opacity, reflection etc).
I have been struggling to get it working using TransparentColor+BackColor+Background Image, but I always end up with some pixel not being transparent. So I switched to UpdateLayeredWindow which works fine, but no control is being drawn now.
Here is some of my code
private void Form1_Load(object sender, EventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
protected override void OnPaint(PaintEventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
public void UpdateFormDisplay(Image backgroundImage)
{
IntPtr screenDc = API.GetDC(IntPtr.Zero);
IntPtr memDc = API.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
//Display-image
Bitmap bmp = new Bitmap(backgroundImage);
hBitmap = bmp.GetHbitmap(Color.FromArgb(0)); //Set the fact that background is transparent
oldBitmap = API.SelectObject(memDc, hBitmap);
//Display-rectangle
Size size = bmp.Size;
Point pointSource = new Point(0, 0);
Point topPos = new Point(this.Left, this.Top);
//Set up blending options
API.BLENDFUNCTION blend = new API.BLENDFUNCTION();
blend.BlendOp = API.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = API.AC_SRC_ALPHA;
API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, API.ULW_ALPHA);
//Clean-up
bmp.Dispose();
API.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
API.SelectObject(memDc, oldBitmap);
API.DeleteObject(hBitmap);
}
API.DeleteDC(memDc);
}
catch (Exception)
{
}
}
Here are some images to explain better
If you want to use regular control inside layered window that uses UpdateLayeredWindow API, you need to override control's OnPaint method to redirect drawing to off-screen bitmap which you later use with UpdateLayeredWindow method to update window look.
If you don't want to dig into controls code, or don't have much custom made controls, WM_PRINT message could be used to force controls to paint themselves into provided device context. Classic window subclassing (SetWindowLong/GetWindowLong) for catching moments when controls invalidate themselves is useful, but may be slightly dangerous - you have to watch for callback chain.
Finally, you can use lightweight (windowless) controls. They use form message queue to receive events and draw themselves, so only form drawing code modifications are necessary. Some of standard winforms controls support this mode.

WPF CreateBitmapSourceFromHBitmap memory leaks

so ive been having a bit of a memory leak problem with my C# code, I have searched fairly thoroughly and do realize there is another question here WPF CreateBitmapSourceFromHBitmap() memory leak (and many others) that sort of cover the same topic but doesnt meet my criteria or solve my problem.
So here is my code, I got this from another project a while back
Im fairly positive that the problem has to do with // Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
Here is the full method code, this is being called like this and
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
And is also being called many times per second.(70-100ms per call)
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
//IntPtr hBitmap = bitmap.GetHbitmap();
// IntPtr hBitmap;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
// Gdi32.DeleteObject(hBitmap);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
I really cant understand how I can apply the fix mentioned in the above thread to my problem, because my BitmapSource doesnt seem to be created from a bitmap, I cant see how deleting the bitmap object can help.
Thanks a lot for your help.

how to capture screen picture with normal aspect ratio in c#

i have wide screen monitor and when i capture screen picture via win API in c# the captured picture will be wide. but i want to take normal aspect ratio 1:1 picture.
i don't want take picture first, then resize it because items on image will be malformed.
how i can do that? is this possible?
i am using this code :
public static Bitmap GetDesktopImage()
{
//In size variable we shall keep the size of the screen.
SIZE size;
IntPtr hDC = PlatformInvokeUSER32.GetDC(PlatformInvokeUSER32.GetDesktopWindow());
IntPtr hMemDC = PlatformInvokeGDI32.CreateCompatibleDC(hDC);
do{
size.cx = PlatformInvokeUSER32.GetSystemMetrics(PlatformInvokeUSER32.SM_CXSCREEN);
size.cy = PlatformInvokeUSER32.GetSystemMetrics(PlatformInvokeUSER32.SM_CYSCREEN);
m_HBitmap = PlatformInvokeGDI32.CreateCompatibleBitmap(hDC, size.cx, size.cy);
} while (m_HBitmap == IntPtr.Zero);
if (m_HBitmap!=IntPtr.Zero)
{
IntPtr hOld = (IntPtr) PlatformInvokeGDI32.SelectObject(hMemDC, m_HBitmap);
PlatformInvokeGDI32.BitBlt(hMemDC,0, 0,size.cx,size.cy, hDC, 0, 0, PlatformInvokeGDI32.SRCCOPY);
PlatformInvokeGDI32.SelectObject(hMemDC, hOld);
PlatformInvokeGDI32.DeleteDC(hMemDC);
PlatformInvokeUSER32.ReleaseDC(PlatformInvokeUSER32.GetDesktopWindow(), hDC);
Bitmap res=System.Drawing.Image.FromHbitmap(m_HBitmap);
PlatformInvokeGDI32.DeleteObject(m_HBitmap);
return res;
}
return null;
}
you would need to crop part of the screen. you can't convert 16:9 to 4:3 without cropping.
-OR-
Change the screens resolution to 4:3 resolution,
Take the screen shot,
Change it back.
Call GetWindowRect on the desktop window handle to get the size. I don't think the GetSystemMetric call you use takes the menu bar and such into account.

Problem with BitBtl and DWM (Windows Aero)

I'm developing an application that has to take screenshots of a fullscreen game. The problem is that I've tried many methods but any of them are able to take the screenshot when the game is in fullscreen mode, it will just take a screenshot of what is under the game and a black rectangle on the top left corner.
Is there any alternative to achieve this goal that doesn't involve hooking? As I'm using C#, which is kind of a bad language to make this kind of things.
This is my code right now:
Bitmap bmp = null;
SIZE size;
size.cx = Screen.PrimaryScreen.Bounds.Width;
size.cy = Screen.PrimaryScreen.Bounds.Height;
IntPtr Src = PlatformInvokeUSER32.GetDC(IntPtr.Zero);
IntPtr Dest = PlatformInvokeGDI32.CreateCompatibleDC(Src);
IntPtr HBitmap = (IntPtr)null;
IntPtr hOld = IntPtr.Zero;
HBitmap = PlatformInvokeGDI32.CreateCompatibleBitmap(Src, size.cx, size.cy);
hOld = (IntPtr)PlatformInvokeGDI32.SelectObject(Dest, HBitmap);
PlatformInvokeGDI32.BitBlt(Dest, 0, 0, size.cx, size.cy, Src, 0, 0, (PlatformInvokeGDI32.SRCCOPY|PlatformInvokeGDI32.CAPTUREBLT));
bmp = System.Drawing.Image.FromHbitmap(HBitmap);
PlatformInvokeGDI32.DeleteDC(Dest);
PlatformInvokeUSER32.ReleaseDC(IntPtr.Zero.GetDesktopWindow(), Src);
PlatformInvokeGDI32.DeleteObject(HBitmap);
This works properly on XP, but when it comes to Vista / 7, I need to disable Aero for this code to work, either it will just take a screenshot of what it's under the game as I said before.

Categories

Resources