WPF CreateBitmapSourceFromHBitmap memory leaks - c#

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.

Related

C# bitblt bitmap render to control

I work on a small real-time project where a fast bitmap rendering technique is quite necessary. I need to display many(hundreds) small blocks in a picturebox per second,i found the bitblt example from the pinvoke.net website.
I use a while loop(right now it's infinite ),to retrieve a particular bitmap,and then calling the Invalidate() method to trigger the Paint event.
This is my code:
protected override void OnPaint(PaintEventArgs e)
{
IntPtr pTarget = e.Graphics.GetHdc();
IntPtr pSource = CreateCompatibleDC(pTarget);
IntPtr pOrig = SelectObject(pSource, bmp.GetHbitmap());
BitBlt(pTarget, 0, 0, bmp.Width, bmp.Height, pSource, 0, 0, TernaryRasterOperations.SRCCOPY);
DeleteObject(pOrig);
DeleteDC(pSource);
e.Graphics.ReleaseHdc(pTarget);
}
private void Display()
{
while (true)
{
frame = desktopDuplicator.GetLatestFrame();
if (frame != null)
{
bmp = frame.DesktopImage;//retrieve image.
this.Invoke(new Action(() => this.Invalidate()));//trigger the repaint event
}
}
}
It works fine for few seconds,then i'm getting an System.ArgumentException on this line:
BitBlt(pTarget, 0, 0, bmp.Width, bmp.Height, pSource, 0, 0, TernaryRasterOperations.SRCCOPY);
Does anyone have an idea what is wrong here? i keep releasing the used resources(in the paint event)...why i'm getting this error?
Thanks in advance.
Does anyone have an idea what is wrong here? i keep releasing the used resources(in the paint event)...why i'm getting this error?
Actually you are not releasing all the used resources, specifically the bitmap handle returned by the bmp.GetHbitmap() call. The correct sequence is to select back the original default bitmap handle into device context and then delete your bitmap handle, as explained in the SelectObject documentation:
This function returns the previously selected object of the specified type. An application should always replace a new object with the original, default object after it has finished drawing with the new object.
IntPtr targetDC = e.Graphics.GetHdc();
IntPtr sourceDC = CreateCompatibleDC(targetDC);
IntPtr sourceBitmap = bmp.GetHbitmap();
IntPtr originalBitmap = SelectObject(sourceDC, sourceBitmap);
BitBlt(targetDC, 0, 0, bmp.Width, bmp.Height, sourceDC, 0, 0, TernaryRasterOperations.SRCCOPY);
SelectObject(sourceDC, originalBitmap);
DeleteObject(sourceBitmap);
DeleteDC(sourceDC);
e.Graphics.ReleaseHdc(targetDC);
From the documentation for the Bitmap.GetHbitmap method:
You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.
You don't currently appear to be calling that, which will lead to a leak. You should call DeleteObject once you're done with the resource, so maybe something like:
protected override void OnPaint(PaintEventArgs e)
{
IntPtr pTarget = e.Graphics.GetHdc();
IntPtr pSource = CreateCompatibleDC(pTarget);
IntPtr pOrig = SelectObject(pSource, bmp.GetHbitmap());
BitBlt(pTarget, 0, 0, bmp.Width, bmp.Height, pSource, 0, 0, TernaryRasterOperations.SRCCOPY);
DeleteObject(pOrig);
DeleteDC(pSource);
e.Graphics.ReleaseHdc(pTarget);
}

How to redraw only a region of a layered window?

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.

Is it possible to get a reference to the raw image buffer for system.drawing.graphics class?

I'm trying to render an image from an unmanaged control on to a WPF window. So far I'm able to get a working graphics object (because I'm able to overlay image to the unmanaged control). What I'm hoping to do is the opposite, which is to capture image from graphics object and save as a imagesource for another control.
var graphics = Graphics.FromHwnd(hwndPtr);//From image unmanaged source
graphics.(??) // save to bitmap or any image format
If not possible to save an image with graphics object directly, would it be possible to get a raw reference to the image buffer of the graphics object? (For use with code like below)
var bmp = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(
hwntPtr,120,120,format,stride,0);
Thanks in Advance..
This is slightly modified code from an MSDN forum article but it should do the trick as far as getting a System.Drawing.Bitmap from a System.Drawing.Graphics object.
[DllImport("coredll.dll", SetLastError = true)]
internal static extern bool BitBlt(IntPtr hDest, int nDstX, int nDstY, int nWidth, int nHeight, IntPtr hSrc, int nSrcX, int nSrcY, uint BitBltOperation);
Bitmap SaveAsBitmap(Graphics gxSrc, int sizeX, int SizeY)
{
Bitmap bm = new Bitmap(sizeX, sizeY);
Graphics gxDst = Graphics.FromImage(bm);
IntPtr hSrc = gxSrc.GetHdc();
IntPtr hDst = gxDst.GetHdc();
BitBlt(hDst, 0, 0, sizeX, sizeY, hSrc, 0, 0, 0xcc0020); // SourceCopy operation
gxDst.ReleaseHdc(hDst);
gxSrc.ReleaseHdc(hSrc);
gxDst.Dispose();
return bm;
}
Original article

Use native HBitmap in C# while preserving alpha channel/transparency

Let's say I get a HBITMAP object/handle from a native Windows function. I can convert it to a managed bitmap using Bitmap.FromHbitmap(nativeHBitmap), but if the native image has transparency information (alpha channel), it is lost by this conversion.
There are a few questions on Stack Overflow regarding this issue. Using information from the first answer of this question (How to draw ARGB bitmap using GDI+?), I wrote a piece of code that I've tried and it works.
It basically gets the native HBitmap width, height and the pointer to the location of the pixel data using GetObject and the BITMAP structure, and then calls the managed Bitmap constructor:
Bitmap managedBitmap = new Bitmap(bitmapStruct.bmWidth, bitmapStruct.bmHeight,
bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);
As I understand (please correct me if I'm wrong), this does not copy the actual pixel data from the native HBitmap to the managed bitmap, it simply points the managed bitmap to the pixel data from the native HBitmap.
And I don't draw the bitmap here on another Graphics (DC) or on another bitmap, to avoid unnecessary memory copying, especially for large bitmaps.
I can simply assign this bitmap to a PictureBox control or the the Form BackgroundImage property. And it works, the bitmap is displayed correctly, using transparency.
When I no longer use the bitmap, I make sure the BackgroundImage property is no longer pointing to the bitmap, and I dispose both the managed bitmap and the native HBitmap.
The Question: Can you tell me if this reasoning and code seems correct. I hope I will not get some unexpected behaviors or errors. And I hope I'm freeing all the memory and objects correctly.
private void Example()
{
IntPtr nativeHBitmap = IntPtr.Zero;
/* Get the native HBitmap object from a Windows function here */
// Create the BITMAP structure and get info from our nativeHBitmap
NativeMethods.BITMAP bitmapStruct = new NativeMethods.BITMAP();
NativeMethods.GetObjectBitmap(nativeHBitmap, Marshal.SizeOf(bitmapStruct), ref bitmapStruct);
// Create the managed bitmap using the pointer to the pixel data of the native HBitmap
Bitmap managedBitmap = new Bitmap(
bitmapStruct.bmWidth, bitmapStruct.bmHeight, bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);
// Show the bitmap
this.BackgroundImage = managedBitmap;
/* Run the program, use the image */
MessageBox.Show("running...");
// When the image is no longer needed, dispose both the managed Bitmap object and the native HBitmap
this.BackgroundImage = null;
managedBitmap.Dispose();
NativeMethods.DeleteObject(nativeHBitmap);
}
internal static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct BITMAP
{
public int bmType;
public int bmWidth;
public int bmHeight;
public int bmWidthBytes;
public ushort bmPlanes;
public ushort bmBitsPixel;
public IntPtr bmBits;
}
[DllImport("gdi32", CharSet = CharSet.Auto, EntryPoint = "GetObject")]
public static extern int GetObjectBitmap(IntPtr hObject, int nCount, ref BITMAP lpObject);
[DllImport("gdi32.dll")]
internal static extern bool DeleteObject(IntPtr hObject);
}
The following code worked for me even if the HBITMAP is an icon or bmp, it doesn't flip the image when it's an icon, and also works with bitmaps that don't contain Alpha channel:
private static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
BitmapData bmpData;
if (IsAlphaBitmap(bmp, out bmpData))
return GetlAlphaBitmapFromBitmapData(bmpData);
return bmp;
}
private static Bitmap GetlAlphaBitmapFromBitmapData(BitmapData bmpData)
{
return new Bitmap(
bmpData.Width,
bmpData.Height,
bmpData.Stride,
PixelFormat.Format32bppArgb,
bmpData.Scan0);
}
private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
{
Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
for (int y = 0; y <= bmpData.Height - 1; y++)
{
for (int x = 0; x <= bmpData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));
if (pixelColor.A > 0 & pixelColor.A < 255)
{
return true;
}
}
}
}
finally
{
bmp.UnlockBits(bmpData);
}
return false;
}
Right, no copy is made. Which is why the Remarks section of the MSDN Library says:
The caller is responsible for
allocating and freeing the block of
memory specified by the scan0
parameter, however, the memory should
not be released until the related
Bitmap is released.
This wouldn't be a problem if the pixel data was copied. Incidentally, this is normally a difficult problem to deal with. You can't tell when the client code called Dispose(), there's no way to intercept that call. Which makes it impossible to make such a bitmap behave like a replacement for Bitmap. The client code has to be aware that additional work is needed.
After reading the good points made by Hans Passant in his answer, I changed the method to immediately copy the pixel data into the managed bitmap, and free the native bitmap.
I'm creating two managed bitmap objects (but only one allocates memory for the actual pixel data), and use graphics.DrawImage to copy the image. Is there a better way to accomplish this? Or is this good/fast enough?
public static Bitmap CopyHBitmapToBitmap(IntPtr nativeHBitmap)
{
// Get width, height and the address of the pixel data for the native HBitmap
NativeMethods.BITMAP bitmapStruct = new NativeMethods.BITMAP();
NativeMethods.GetObjectBitmap(nativeHBitmap, Marshal.SizeOf(bitmapStruct), ref bitmapStruct);
// Create a managed bitmap that has its pixel data pointing to the pixel data of the native HBitmap
// No memory is allocated for its pixel data
Bitmap managedBitmapPointer = new Bitmap(
bitmapStruct.bmWidth, bitmapStruct.bmHeight, bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);
// Create a managed bitmap and allocate memory for pixel data
Bitmap managedBitmapReal = new Bitmap(bitmapStruct.bmWidth, bitmapStruct.bmHeight, PixelFormat.Format32bppArgb);
// Copy the pixels of the native HBitmap into the canvas of the managed bitmap
Graphics graphics = Graphics.FromImage(managedBitmapReal);
graphics.DrawImage(managedBitmapPointer, 0, 0);
// Delete the native HBitmap object and free memory
NativeMethods.DeleteObject(nativeHBitmap);
// Return the managed bitmap, clone of the native HBitmap, with correct transparency
return managedBitmapReal;
}

Remove alpha from a BitmapSource

I use BitBlt() and CreateBitmapSourceFromHBitmap() to capture a window as a BitmapSource that I can display on an Image element in a WPF application. But for some reason, most of the application that it captures is transparent. Here is a source vs. capture image of what's happening:
(source: umbc.edu)
It's gray because the background of the window it's on is gray. Whatever background I give the window will show through.
How can I get the captured image to more accurately reflect the original?
The problem in your code could be due to the Win32 API you're using (CreateCompatibleDC, SelectObject, CreateBitmap...). I tried with a simpler code, using only GetDC and BitBlt, and it works fine for me. Here's my code:
public static Bitmap Capture(IntPtr hwnd)
{
IntPtr hDC = GetDC(hwnd);
if (hDC != IntPtr.Zero)
{
Rectangle rect = GetWindowRectangle(hwnd);
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
using (Graphics destGraphics = Graphics.FromImage(bmp))
{
BitBlt(
destGraphics.GetHdc(),
0,
0,
rect.Width,
rect.Height,
hDC,
0,
0,
TernaryRasterOperations.SRCCOPY);
}
return bmp;
}
return null;
}
I tried it in Windows Forms and WPF (with Imaging.CreateBitmapSourceFromHBitmap), it works fine in both cases for the same screenshot (SO page in Firefox).
HTH,

Categories

Resources