Task: I got 2 monitors. And I need to show on #1 what is going on #2. In another words, first monitor is nothing but a reflector of second.
Current solution: Just making screenshot every ~100ms and re-render.
Following method is responsible for capturing screenshots:
private BitmapSource MakeScreenshot(Screen screen)
{
using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);
return
Imaging.CreateBitmapSourceFromHBitmap(
screenBmp.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
}
After that I uset Start(...) method to run my "reflection" from second screen to first:
public void Start(int delay, int period)
{
if (_timer != null) throw new InvalidOperationException();
_timer = new System.Threading.Timer(
_ =>
{
_placeholder
.Dispatcher
.Invoke(() =>
{
_placeholder.Source = MakeScreenshot(_targetScreen); // re-render new screenshot
});
},
null,
delay,
period);
}
Problem: After around 30-40 second of pretty nice run it fails with OutOfMemoryException. I've investigated some of posts here, but found nothing regarding my problem.
That is because you leak memory here:
Imaging.CreateBitmapSourceFromHBitmap(
screenBmp.GetHbitmap(), // < here
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
You need to free memory used by GDI bitmap after you call screenBmp.GetHbitmap(). Change that like this:
private BitmapSource MakeScreenshot(Screen screen)
{
using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);
var handle = screenBmp.GetHbitmap();
try {
return
Imaging.CreateBitmapSourceFromHBitmap(
handle,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally {
DeleteObject(handle);
}
}
}
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
And it should not leak any more.
Related
I have a WPF application, and am streaming a camera frame from an opencv DLL into an Image control inside a UserControl.
This works for a while, and then crashes, giving me:
The calling thread cannot access this object because a different thread owns it.
My code is as follows:
The class that calls the image: (running in a thread)
private void imageShow()
{
while (true)
{
if (status == 1)
{
IntPtr ptr = getFrame(); // The DLL function that returns the image.
imgHalfSize = new Bitmap(640, 360, 3 * 640, PixelFormat.Format24bppRgb, ptr);
CameraFrame = ToBitmapSource(imgHalfSize);
CameraFrame.Freeze();
Thread.Sleep(20);
}
}
//conversion from Bitmap to BitmapSource
public BitmapSource CameraFrame;
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(Bitmap source)
{
IntPtr ptr = source.GetHbitmap(); //obtain the Hbitmap
BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
ptr,
IntPtr.Zero,
Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
DeleteObject(ptr); //release the HBitmap
return bs;
}
//The userControl that displays the image:
//xaml
<Image Name="ImageCameraFrame"/>
cs:
public FormCameraViewFull(DllFunctions _Dense)
{
Dense = _Dense; // The class as above
InitializeComponent();
Task.Factory.StartNew(() =>
{
InvokeMethodExample();
});
}
//thread function:
private void InvokeMethodExample()
{
while (true)
{
ImageCameraFrame.Dispatcher.Invoke((Action)(() => ImageCameraFrame.Source = Dense.CameraFrame));
}
}
As above, this works great for some time, then crashes. I was under the impression that the line CameraFrame.Freeze(); should stop this behavior, but I am clearly doing something wrong. Do I need a mutex, or similar lock here?
Thank you.
Use a DispatcherTimer with (an optionally async) Tick handler:
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(50)
};
timer.Tick += async (s, e) =>
{
ImageCameraFrame.Source = await Task.Run(() =>
{
var data = Dense.getFrame();
var format = PixelFormats.Rgb24;
var width = 640;
var height = 360;
var stride = (width * format.BitsPerPixel + 7) / 8;
var bitmap = BitmapSource.Create(width, height, 96, 96,
format, null, data, stride * height, stride);
bitmap.Freeze();
return bitmap;
});
};
timer.Start();
I have next function (makes screenshot)
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
private Screen SavedScreen { get; } = Screen.PrimaryScreen;
private BitmapSource CopyScreen()
{
try
{
BitmapSource result;
using (
var screenBmp = new Bitmap(SavedScreen.Bounds.Width, SavedScreen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
CopyPixelOperation.SourceCopy);
IntPtr hBitmap = screenBmp.GetHbitmap();
//********** Next line do memory leak
result = Imaging.CreateBitmapSourceFromHBitmap( hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DeleteObject(hBitmap);
}
}
return result;
}
catch (Exception ex)
{
//ErrorReporting ($"Error in CopyScreen(): {ex}");
Debugger.Break();
return null;
}
}
And cannot avoid memory leak which is a result of calling Imaging.CreateBitmapSourceFromHBitmap. As I call this function in a cycle this memory leak is very important for me.
Called in WPF application (Windows, c#)
As you already know, you have to Dispose() screenBmp.
You are actually calling it by an using statement, so that should be fine, but I suspect the try/catch could interfere.
Do you have a chance to move the try/catch so that only the CopyFromScreen and CreateBitmapSourceFromHBitmap are surrounded?
From comments
Since only after that closing brace of the using statement you are sure that the screenBmp can be disposed, I'm forcing a GC collect there
GC.Collect();
return result;
and it doesn't seem leaking.
Here is my demo
class Program
{
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
private static Screen SavedScreen { get; } = Screen.PrimaryScreen;
private static BitmapSource CopyScreen()
{
//try
//{
BitmapSource result;
using (
var screenBmp = new Bitmap(200, 100))
{
using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
CopyPixelOperation.SourceCopy);
IntPtr hBitmap = screenBmp.GetHbitmap();
bmpGraphics.Dispose();
//********** Next line do memory leak
result = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DeleteObject(hBitmap);
//result = null;
}
}
GC.Collect();
return result;
//}
//catch (Exception ex)
//{
// //ErrorReporting ($"Error in CopyScreen(): {ex}");
// Console.WriteLine(ex.Message);
// Debugger.Break();
// return null;
//}
}
static void Main(string[] args)
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(100);
var test = CopyScreen();
}
}
}
As you are working with bitmaps (screen size) it means expected data size is bigger than 85000 bytes. The objects of such sizes are treated differently by GC.
It is called LOH. See https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/, it was improved in 4.5 https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/
But the problem is still here. Accounting huge objects with high frequency leads to significant increase of memory usage of your application. There're 2 problem leads to it: 1) GC does not work immediatly, it takes time before it started freeing memory; 2) fragmentation of LOH (see the first article), this is why it is not freed and this is why you can see the memory usage is increased.
Possible solutions:
1) Use server GC and concurent GC; force GC manually. Most likely it does not help greatly.
2) Re-use existing object(allocated memory) instead of creating new Bitmap and Graphics all the time in a loop.
3) Switch to use Windows API directly and handle allocations manually.
The following code takes a snapshot of part of the screen (at the mouse coordinates) and should display it in an Image control.
public partial class MainWindow : Window
{
Timer timer = new Timer(100);
public MainWindow()
{
InitializeComponent();
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
viewerImage.Source = GetSnapAtMouseCoords((int)((Grid)viewerImage.Parent).ActualWidth, (int)((Grid)viewerImage.Parent).ActualHeight, System.Windows.Forms.Cursor.Position);
}
private BitmapSource GetSnapAtMouseCoords(int width, int height, System.Drawing.Point mousePosition)
{
IntPtr handle = IntPtr.Zero;
try
{
using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(mousePosition.X, mousePosition.Y, 0, 0, screenBmp.Size);
handle = screenBmp.GetHbitmap();
var bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
handle,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
return bs;
}
}
}
finally
{
DeleteObject(handle);
}
}
}
Everything works up to the point where I set the image source to the BitmapSource. Unfortunately, the image is never rendered on screen.
I think that maybe this is because I am creating the BitmapSource on the GUI thread... But I am not so sure.
Any suggestions or ideas welcome.
Actually it's because you are accessing your GUI on a different Thread. You can either wrap the initial call like this:
Dispatcher.BeginInvoke(new Action(() =>
{
viewerImage.Source = GetSnapAtMouseCoords(
(int)((Grid)viewerImage.Parent).ActualWidth,
(int)((Grid)viewerImage.Parent).ActualHeight,
System.Windows.Forms.Cursor.Position);
}));
Or do all the processing in a background thread an just return a Frozen (Thread safe) BitmapSource. You would hover need to pass (int)((Grid)viewerImage.Parent).ActualWidth differently, since that is owned by the UI thread too.
bs.Freeze();
Dispatcher.BeginInvoke(new Action(() =>
{
viewerImage.Source = bs;
}));
I get this exception on that code.
How to fix it?
Excepton:
The calling thread cannot access this
object because a different thread owns
it.
Code:
void CamProc_NewTargetPosition(IntPoint Center, System.Drawing.Bitmap image)
{
IntPtr hBitMap = image.GetHbitmap();
BitmapSource bmaps = Imaging.CreateBitmapSourceFromHBitmap(hBitMap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
Dispatcher.BeginInvoke((Action)(() =>
{
labelX.Content = String.Format("X: {0}", Center.X); //OK Working
labelY.Content = String.Format("Y: {0}", Center.Y); //OK Working
pictureBoxMain.Source = bmaps; // THERE IS EXCEPTON
}), DispatcherPriority.Render, null);
}
pictureBoxMain is System.Windows.Controls.Image.
You can freeze the BitmapSource so that it can be accessed from any thread:
void CamProc_NewTargetPosition(IntPoint Center, System.Drawing.Bitmap image)
{
IntPtr hBitMap = image.GetHbitmap();
BitmapSource bmaps = Imaging.CreateBitmapSourceFromHBitmap(hBitMap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
bmaps.Freeze();
Dispatcher.BeginInvoke((Action)(() =>
{
labelX.Content = String.Format("X: {0}", Center.X);
labelY.Content = String.Format("Y: {0}", Center.Y);
pictureBoxMain.Source = bmaps;
}), DispatcherPriority.Render, null);
}
You could Freeze the image, as suggested in another thread, which gets rid of the threading restriction but makes the image immutable.
WPF/BackgroundWorker and BitmapSource problem
I need to draw an image pixel by pixel and display it inside a WPF. I am attempting to do this by using a System.Drawing.Bitmap then using CreateBitmapSourceFromHBitmap() to create a BitmapSource for a WPF Image control. I have a memory leak somewhere because when the CreateBitmapSourceFromBitmap() is called repeatedly the memory usage goes up and does not drop off until the application is ended. If I don't call CreateBitmapSourceFromBitmap() there is no noticeable change in memory usage.
for (int i = 0; i < 100; i++)
{
var bmp = new System.Drawing.Bitmap(1000, 1000);
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
source = null;
bmp.Dispose();
bmp = null;
}
What can I do to free the BitmapSource memory?
MSDN for Bitmap.GetHbitmap() states:
Remarks
You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.
So use the following code:
// at class level
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
// your code
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1000, 1000))
{
IntPtr hBitmap = bmp.GetHbitmap();
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
I also replaced your Dispose() call by an using statement.
Whenever dealing with unmanaged handles it can be a good idea to use the "safe handle" wrappers:
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return GdiNative.DeleteObject(handle) > 0;
}
}
Construct one like so as soon as you surface a handle (ideally your APIs would never expose IntPtr, they would always return safe handles):
IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);
And use it like so:
using (handle)
{
... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
The SafeHandle base gives you an automatic disposable/finalizer pattern, all you need to do is override the ReleaseHandle method.
I had the same requirement and issue (memory leak). I implemented the same solution as marked as answer. But although the solution works, it caused an unacceptable hit to performance. Running on a i7, my test app saw a steady 30-40% CPU, 200-400MB RAM increases and the garbage collector was running almost every millisecond.
Since I'm doing video processing, I'm in need of much better performance. I came up with the following, so thought I would share.
Reusable Global Objects
//set up your Bitmap and WritableBitmap as you see fit
Bitmap colorBitmap = new Bitmap(..);
WriteableBitmap colorWB = new WriteableBitmap(..);
//choose appropriate bytes as per your pixel format, I'll cheat here an just pick 4
int bytesPerPixel = 4;
//rectangles will be used to identify what bits change
Rectangle colorBitmapRectangle = new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height);
Int32Rect colorBitmapInt32Rect = new Int32Rect(0, 0, colorWB.PixelWidth, colorWB.PixelHeight);
Conversion Code
private void ConvertBitmapToWritableBitmap()
{
BitmapData data = colorBitmap.LockBits(colorBitmapRectangle, ImageLockMode.WriteOnly, colorBitmap.PixelFormat);
colorWB.WritePixels(colorBitmapInt32Rect, data.Scan0, data.Width * data.Height * bytesPerPixel, data.Stride);
colorBitmap.UnlockBits(data);
}
Implementation Example
//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;
The result is a steady 10-13% CPU, 70-150MB RAM, and the garbage collector only ran twice in a 6 minute run.
This is a great(!!) post, although with all the comments and suggestions, it took me an hour to figure out the pieces. So here is a call to get the BitMapSource with SafeHandles, and then an example usage of it to create a .PNG image file. At the very bottom are the 'usings' and some of the references. Of course, none of the credit is mine, I am just the scribe.
private static BitmapSource CopyScreen()
{
var left = Screen.AllScreens.Min(screen => screen.Bounds.X);
var top = Screen.AllScreens.Min(screen => screen.Bounds.Y);
var right = Screen.AllScreens.Max(screen => screen.Bounds.X + screen.Bounds.Width);
var bottom = Screen.AllScreens.Max(screen => screen.Bounds.Y + screen.Bounds.Height);
var width = right - left;
var height = bottom - top;
using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
BitmapSource bms = null;
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
IntPtr hBitmap = new IntPtr();
var handleBitmap = new SafeHBitmapHandle(hBitmap, true);
try
{
bmpGraphics.CopyFromScreen(left, top, 0, 0, new System.Drawing.Size(width, height));
hBitmap = screenBmp.GetHbitmap();
using (handleBitmap)
{
bms = Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
} // using
return bms;
}
catch (Exception ex)
{
throw new ApplicationException($"Cannot CopyFromScreen. Err={ex}");
}
} // using bmpGraphics
} // using screen bitmap
} // method CopyScreen
Here is the usage, and also the "Safe Handle" class:
private void buttonTestScreenCapture_Click(object sender, EventArgs e)
{
try
{
BitmapSource bms = CopyScreen();
BitmapFrame bmf = BitmapFrame.Create(bms);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bmf);
string filepath = #"e:\(test)\test.png";
using (Stream stm = File.Create(filepath))
{
encoder.Save(stm);
}
}
catch (Exception ex)
{
MessageBox.Show($"Err={ex}");
}
}
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteObject(IntPtr hObject);
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return DeleteObject(handle) > 0;
}
}
And finally a look at my 'usings':
using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Security;
The DLLs referenced included:
* PresentationCore
* System.Core
* System.Deployment
* System.Drawing
* WindowsBase
In my case it did not work directly with this method. I had to add a clean garbage collector in addition
using (PaintMap p = new PaintMap())
{
System.Drawing.Image i = p.AddLineToMap("1");
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(i, 8419, 5953);
IntPtr hBitmap = bmp.GetHbitmap();
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
Image2.ImageSource = bitmapSource;
DeleteObject(hBitmap);
System.GC.Collect();
}
I have a solution for someone who want to load image from memory or other class
public static InteropBitmap Bitmap2BitmapImage(Bitmap bitmap)
{
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
return (InteropBitmap)source;
}
catch (Exception e)
{
MessageBox.Show("Convertion exception: " + e.Message + "\n" +e.StackTrace);
return null;
}
}
And then I use it the set the source of an image
CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);
Image is the following definition
<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
Width="{Binding Width}"
Height="{Binding Height}">
</Image>