Properly disposing of Bitmap object - c#

I am drawing images in a C# Winforms panel with:
private void DrawPanel_Paint(object sender, PaintEventArgs e)
{
DrawOnPanel(e.Graphics);
}
The called method takes an existing image from my resources (myImage), gives it to another method which resizes the image and returns the resized image so it can be drawn.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = ResizeImage(Resources.myImage);
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
The resize image function is:
public Bitmap ResizeImage(Bitmap image)
{
int scale = 3;
var destImage= new Bitmap(image.Width * scale, image.Height * scale);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.None;
using var wrapMode = new ImageAttributes();
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
return destImage;
}
The program keeps calling DrawPanel.Invalidate() in its loop.
I am detecting a memory leak each time DrawPanel.Invalidate() is called. The memory consumption is rising steadily until the GC takes care of it. While this isn't a game breaking problem, I'm still wondering where and how should I dispose of my objects in the above code.
I tried using using var _resizedImage = ResizeImage(Resources.myImage); in the above DrawOnPanel method but the program returns an error System.ArgumentException: 'Parameter is not valid.'. If I remove using there is no error.

Every time that ResizeImage is called, you create a new Bitmap. This Bitmap should be Disposed as soon as you know that it is not needed anymore. It seems that you don't need the resized image after you've drawn it on the Graphics. Therefore I suggest the following change:
public static void DrawOnPanel(Graphics g)
{
using (Image_resizedImage = ResizeImage(Resources.myImage))
{
g.DrawImage(_resizedImage, destX, destY);
}
// resizedImage is Disposed
// ... several other images resized & manipulated then drawn
}
Room for improvement
Your current design will create a new Resized Bitmap every time DrawOnPanel is called. It seems to me that most of time that Resources.myImage is resized the resized Bitmap will be the same. Why don't you just remember it until the parameters that influence the resized Bitmap are changed?
If I look at the code it seems that you always create the same resized image from an original image. So you could even put all your resized images into one Dictionary for fast lookup:
// get all Image resources that you will resize and put them in a Dictionary
// Key original Bitmap, Value: resized Bitmap
private Dictionary<Bitmap, BitMap> images = this.FetchImages()
.ToDictionary(
// Parameter keySelector: key is original image
image => image,
// Parameter valueSelector: value is the resized image
imgage => ResizeImage(original));
Displaying the resized images will now be much faster: only a Lookup.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = this.Images[Resources.myImage];
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
Don't forget to Dispose the bitmaps when your Form is Closed, or at its latest when your Form is Disposed:
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var resizedImage in images.Values)
resizedImage.Dispose();
}
}

Related

C# GDI+ ScaleTransform ok on picturebox but image saved is original

Hi I have the issue that when I use ScaleTransform(zoomFactor,zoomFactor) the image saved on disk is the original version always, while on screen in the picturebox the image is distorted in proportion to the zoomFactor.
Why this could be happening ? Shouldn't I have the final result as applied from e.Graphics on disk written image ?
My code is the following which is a version with matrix. but the instead of matrix I have used the ScaleTransform as well. Result is always the same:
g=e.Graphics;//inside picturebox_paint()
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
e.Graphics.DrawImage((Bitmap)bmp, 0, 0);
int seed = Convert.ToInt32(Regex.Match(Guid.NewGuid().ToString(), #"\d+").Value);
String destinationFile = #"C:\tmp\photoid\" + new Random(seed).Next() + "_conv.jpg";
//Here I get always the original image back!!!!
bmp.Save(destinationFile);
I have used as well the following idiom but with same results:
//Matrix matrix = new Matrix();
//matrix.Scale(zoomFac, zoomFac);
//e.Graphics.Transform = matrix;
You need to make the PictureBox draw the things it shows on screen into a new Bitmap, which you then can save!
As it is the Image will be saved in the original form and nothing you did in the Paint event, which actually painst onto the surface of the PictureBox will be saved.
So to save everything, i.e. The Image, possibly a BackgroundImage and all you draw in the Paint event you would call DrawToBitmap somehwere.
Somewhere means somewhere else, not in the Paint event, as it will call the Paint event to create the new Bitmap, causing an endless loop..
To call it you would do something like this:
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
pictureBox1.DrawToBitmap(bmpSave, pictureBox1.ClientRectangle);
But maybe this is not really what you want? Maybe you actually want to modify the Image? In that case do not use the Paint event at all!
Instead do something like this:
Bitmap bmpSave = new Bitmap(yourNewWidth, yourNewHeight);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
g.DrawImage((Bitmap)pictureBox1.Image, 0, 0); //
pictureBox1.Image = bmpSave;
bmpSave.Save(...);
}
You could call this from somewhere where the scaling is being triggered from.
Note that doing the scaling repeatedly and each time from the previoulsy scaled version will degrade the quality rather fast. For this always scale from a saved version of the original!!
Btw: Using a Matrix for scaling doesn't really make a difference over ScaleTransform.
But if you want to do a direct scaling why not use the DrawImage overload which takes two Rectangles? This is the most common solution if all you want to to scale and maybe draw other stuff additionally..:
int newWidth = 100; int newHeight = 100; string yourFileName = "D:\\xyz123.jpg";
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
Rectangle newRectangle = new Rectangle(0, 0, newWidth, newHeight);
Rectangle oldRectangle = new Rectangle(Point.Empty, pictureBox1.Image.Size);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.DrawImage((Bitmap)pictureBox1.Image, newRectangle, oldRectangle, GraphicsUnit.Pixel);
bmpSave.Save(yourFileName, ImageFormat.Jpeg);
}
And there there is the scaling Bitmap constructor:
Bitmap bmp = new Bitmap(pictureBox1.Image, newWidth, newHeight);
Which I would recommend if all you want is to scale the Image. As the other solutions it will not change the Image displayed until you assign it back into the PictureBox..:
pictureBox1.Image = bmp ;
Don't forget to dispose of the old Image..
Been a while since I messed with GDI but I think you need to copy back to the Bitmap here.
g.DrawImage(bmp, scaledwidth, scaledheight);
Try something like that before bmp.Save
Edit
Apologies for not seeing that you were copying back to the bitmap. Perhaps the overload which specifies the output rectangle is what you need. Try a DrawImage overload which has the destination Rect. https://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspx

Draw directly to PictureBox

I'm developing a screen-sharing app which runs a loop constantly and receives small frames from a socket. The next step is to draw them into the PictureBox.
Of course, I use thread because I don't want to freeze the ui.
This is my code:
Bitmap frame = byteArrayToImage(buff) as Bitmap;//a praticular bitmap im getting from a socket.
Bitmap current = (Bitmap)pictureBox1.Image;
var graphics = Graphics.FromImage(current);
graphics.DrawImage(frame, left, top);//left and top are two int variables of course.
pictureBox1.Image = current;
But now I'm getting an error:
Object is already in use elsewhere.
in this line var graphics = Graphics.FromImage(current);
Tried to Clone it, Create a New Bitmap(current). still no success.
Invalidate() your PictureBox so it redraws itself:
Bitmap frame = byteArrayToImage(buff) as Bitmap;
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawImage(frame, left, top);
}
pictureBox1.Invalidate();
If you need it to be thread safe, then:
pictureBox1.Invoke((MethodInvoker)delegate {
Bitmap frame = byteArrayToImage(buff) as Bitmap;
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawImage(frame, left, top);
}
pictureBox1.Invalidate();
});

C# Graphics.CopyFromScreen "parameter is not valid"

I had made an app in C# that will perform screen capture continuously and display it in a PictureBox using timer. After running for a few seconds, there was an ArgumentException.
Below is the code and the line that has the ArgumentException
private void timer1_Tick(object sender, EventArgs e)
{
Rectangle bounds = Screen.GetBounds(Point.Empty);
Graphics graphics;
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width , bounds.Height )); // ArgumentException
pictureBox1.Image = bitmap;
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
Besides that, I had notices that an alert saying low memory from Windows after running the app for a few seconds.
Any tips on resolving this problem?
You keep setting a new bitmap to the picturebox, and the previous bitmap is never disposed. After a while, the system runs short of GDI handles and/or memory (running your code, I consumed one gig of memory in under 15 seconds).
You can simply reuse your existing bitmap:
Rectangle bounds = Screen.GetBounds(Point.Empty);
Image bitmap = pictureBox1.Image ?? new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));
if (pictureBox1.Image == null)
{
pictureBox1.Image = bitmap;
}
else
{
pictureBox1.Refresh();
}
}
You also don't have to reset pictureBox1.SizeMode on each iteration.
Alternatively, you can dispose the previous bitmap manually:
Rectangle bounds = Screen.GetBounds(Point.Empty);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));
using (Image prev_bitmap = pictureBox1.Image)
{
pictureBox1.Image = bitmap;
}
}
pictureBox1.Image = bitmap;
Yes, your program won't last long when you frequently update the picture box. The Bitmap class is the singular .NET class where IDisposable can't easily be ignored. It is like an iceberg, bitmaps can use massive amounts of unmanaged memory but very little managed memory. You must dispose bitmaps when you no longer use them to prevent them from consuming all available unmanaged memory for their pixel data. The garbage collector tends to hide that problem but it can't do so when it doesn't run frequently enough. And the managed portion of Bitmap is too small to trigger a collection often enough. Fix:
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = bitmap;
The following KB can help understanding this issue:
Bitmap and Image constructor dependencies
GDI+, and therefore the System.Drawing namespace, may defer the
decoding of raw image bits until the bits are required by the image.
Additionally, even after the image has been decoded, GDI+ may
determine that it is more efficient to discard the memory for a large
Bitmap and to re-decode later. Therefore, GDI+ must have access to the
source bits for the image for the life of the Bitmap or the Image
object.
To retain access to the source bits, GDI+ locks any source file, and
forces the application to maintain the life of any source stream, for
the life of the Bitmap or the Image object.
One has to figure out when is the object ready for disposal.

Png image processing in .NET

I have the following task. Take a base image and overlay on it another one. The base image is 8b png as well as overlay.
Here are the base (left) and overlay (right) images.
Here is a result and how it must look.
The picture in the left is a screenshot when one picture is on top of another (html and positioning) and the second is the result of programmatic merging.
As you can see in the screenshot the borders of the text is darker. Also here are the sizes of the images
Base image 14.9 KB
Overlay image 6.87 KB
Result image 34.8 KB
The size of the resulting image is also huge
Here is my code that I use to merge those pictures
/*...*/
public Stream Concatinate(Stream baseStream, params Stream[] overlayStreams) {
var #base = Image.FromStream(baseStream);
var canvas = new Bitmap(#base.Width, #base.Height);
using (var g = canvas.ToGraphics()) {
g.DrawImage(#base, 0, 0);
foreach (var item in overlayStreams) {
using (var overlayImage = Image.FromStream(item)) {
try {
Overlay(#base as Bitmap, overlayImage as Bitmap, g);
} catch {
}
}
}
}
var ms = new MemoryStream();
canvas.Save(ms, ImageFormat.Png);
canvas.Dispose();
#base.Dispose();
return ms;
}
/*...*/
/*Tograpics extension*/
public static Graphics ToGraphics(this Image image,
CompositingQuality compositingQuality = CompositingQuality.HighQuality,
SmoothingMode smoothingMode = SmoothingMode.HighQuality,
InterpolationMode interpolationMode = InterpolationMode.HighQualityBicubic) {
var g = Graphics.FromImage(image);
g.CompositingQuality = compositingQuality;
g.SmoothingMode = smoothingMode;
g.InterpolationMode = interpolationMode;
return g;
}
private void Overlay(Bitmap source, Bitmap overlay, Graphics g) {
if (source.Width != overlay.Width || source.Height != overlay.Height)
throw new Exception("Source and overlay dimensions do not match");
var area = new Rectangle(0, 0, source.Width, source.Height);
g.DrawImage(overlay, area, area, GraphicsUnit.Pixel);
}
My questions are
What should I do in order to merge images to achieve the result as in the screenshot?
How can I lower the size of the result image?
Is the System.Drawing a suitable tool for this or is there any better tool for working with png for .NET?
The answers to your questions are:
1) Just call method ToGraphics with a argument CompositingQuality.Default instead of using default argument values like in the example:
using (var g = canvas.ToGraphics(compositingQuality: CompositingQuality.Default))
The problem is with CompositingQuality.HighQuality is that it makes a composition of both images into one, but you want to make an overlay, not to make a composition of two images.
2) The size will be similar to the one you specified and that can not be changed, it is due to a image format.
3) If you are programming in c# for desktop, than System.Drawing is the best choice as far as I know.

Cast Graphics to Image in C#

I have a pictureBox on a Windows Form.
I do the following to load a PNG file into it.
Bitmap bm = (Bitmap)Image.FromFile("Image.PNG", true);
Bitmap tmp;
public Form1() {
InitializeComponent();
this.tmp = new Bitmap(bm.Width, bm.Height);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.DrawImage(this.bm, new Rectangle(0, 0, tmp.Width, tmp.Height), 0, 0, tmp.Width, tmp.Height, GraphicsUnit.Pixel);
}
However, I need to draw things on the image and then have the result displayed again. Drawing rectangles can only be done via the Graphics class.
I'd need to draw the needed rectangles on the image, make it an instance of the Image class again and save that to this.bm
I can add a button that executes this.pictureBox1.Refresh();, forcing the pictureBox to be painted again, but I can't cast Graphics to Image. Because of that, I can't save the edits to the this.bm bitmap.
That's my problem, and I see no way out.
What you need to do is use the Graphics.FromImage method, which will allow you to draw directly on the image instead of the temporary Graphics object create from within the Paint method:
using (Graphics g = Graphics.FromImage(this.bm))
{
g.DrawRectangle(...);
}
Do this instead of (or in addition to) hooking the Paint method of the PictureBox. This way, you won't need to use a temporary image or Graphics object at all, and when you're finished modifying the original bitmap (this.bm) then you can invoke pictureBox1.Refresh to force the image to be re-displayed.

Categories