Search code examples
c#winformsobjectbitmapdispose

Properly disposing of Bitmap object


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.


Solution

  • 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();
        }
    }