0

I'm building some software that requires image panning, zooming, and brightness controls. I set up a small project to to build a simple brightness control after finding a lot of examples online, so please ignore that I'm doing everything in the main form

When I start to use the trackbar controller, which fires the event below when the value (-100 to 100) is changed, memory usage stacks up into the Gigabytes in seconds. It'll then sit there and never free the memory. Moving the trackbar again eats up even more memory

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
    {
        float value = trackBarBrightness.Value * 0.01f;
        float[][] colorMatrixElements =
        {
            new float[] {
                1,
                0,
                0,
                0,
                0
            },
            new float[] {
                0,
                1,
                0,
                0,
                0
            },
            new float[] {
                0,
                0,
                1,
                0,
                0
            },
            new float[] {
                0,
                0,
                0,
                1,
                0
            },
            new float[] {
                value,
                value,
                value,
                0,
                1
            }
        };

        ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
        ImageAttributes imageAttributes = new ImageAttributes();

        imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        //Image img = this.pictureEdit.Image;
        Image img = originalImage;
        Graphics g = default(Graphics);
        Bitmap bm = new Bitmap(Convert.ToInt32(img.Width), Convert.ToInt32(img.Height));
        g = Graphics.FromImage(bm);
        g.DrawImage(img, new Rectangle(0, 0, bm.Width, bm.Height), 0, 0, bm.Width, bm.Height, GraphicsUnit.Pixel, imageAttributes);
        pictureEdit.Image = bm;
    }

Any guidance would be much appreciated

p3tch
  • 1,414
  • 13
  • 29
  • 2
    Because you do not dispose previous images and graphics. Each image will stay in memory until GC will collect it (if not referenced by anyone else). As a side tip: debounce changes, you do not need to recalculate that often. – Adriano Repetti Jul 08 '17 at 21:11
  • Use GC.Collect(); you can test easily if it is really too much stuff that is not yet garbage collected or not, but I do not think it is the array or image not yet collected. It feels more like pictureEdit.Image = bm could store multiple images? Just a hunch could be wrong:). – ipavlu Jul 08 '17 at 21:15
  • Can´t see any memory-intensive stuff in your code unless your image is some 1000Megapixels big. Are you sure this the the code where your memory gets lost? – MakePeaceGreatAgain Jul 08 '17 at 21:18
  • Calling GC.Collect() at the end of the method is keeping it where it should be. Is it bad practice to call it, and why wouldn't it automatically collect? I've left it running for 5 minutes and the memory usage didn't drop once – p3tch Jul 08 '17 at 21:21
  • @HimBromBeere The image could be large and even driven by trackball could be raised pretty often. – ipavlu Jul 08 '17 at 21:21
  • @p3tch I said, "YOU CAN TEST IT". Was that suggestion it will correct the problem? No no. – ipavlu Jul 08 '17 at 21:29
  • @ipavlu Sure, but that´s impossible to read from code within the question. OP doesn´t show how often he executes the method and what he´s doing with the image. – MakePeaceGreatAgain Jul 08 '17 at 21:31
  • Calling it explicitly makes it the certainty, that collecting garbage is not the issue. It could be the disposal problem, it could be caused by something that collects image changes and grows. Unless memory profiled, it could be even something else:). – ipavlu Jul 08 '17 at 21:32
  • @HimBromBeere it's just called whenever the trackbar's value is changed, which can be quite a lot :) There's really nothing to the program is literally 1 trackbar control, 1 pictureedit control, and an event on the trackbar. I can upload the project if you think it'd help, but there's not much else to it than what I posted – p3tch Jul 08 '17 at 21:41
  • 2
    @ipavlu no that is not good advice. You should never call GC.Collect() (unless you really know what you are doing and as a last resort) – CodingYoshi Jul 08 '17 at 21:43
  • GDI resources esp. Btimaps (and anything IDisposable) must be disposed of by __you__. Also note that even for things it will clear eventually, GC won't kick in unless it thinks it has to. So if you have lots of RAM it'll wait longer than if it is scarce. You could start the code with `if (pictureEdit.Image != null) pictureEdit.Image.Dispose();` – TaW Jul 08 '17 at 21:59
  • Possible duplicate of [Right way to dispose Image/Bitmap and PictureBox](https://stackoverflow.com/questions/2808753/right-way-to-dispose-image-bitmap-and-picturebox) – mjwills Jul 08 '17 at 23:25
  • @CodingYoshi Love people like you, NEVER and then firing holes into YOUR OWN NEVER! It is a tool and as long as it is present, I WILL CONSIDER ITS USE! It already established that the problem is not garbage collection been slow or disposal problem(those classes do not have issues with collection, disposal is just cleaner and faster to free managed/unmanaged memory), only issue was what I said 3 hours ago, the last line in the method, giving away bitmap. In general If you are clumsy, DO NOT USE KNIFE! BTW my advice was positive, information bringer,your was negative, shooting me for 2 upvotes:) – ipavlu Jul 09 '17 at 00:45
  • @ipavlu are you upset because I shared some good knowledge with you? Sorry if I upset you-was not my intention. – CodingYoshi Jul 09 '17 at 00:50
  • @CodingYoshi It did not spike my blood pressure, but I really love logic. If somebody says NO, NEVER, DONOT, CANNOT and does not explain why and even shoots holes into own NEVER, then I do not follow. Especially when I suggested from the beginning, that it is an exceptional use for the testing/checking purpose. From there, all the beating of dead meat is pointless;). – ipavlu Jul 09 '17 at 01:18

3 Answers3

4

The issue is that you are not calling Dispose on your Bitmap. A Bitmap in .Net is essentially just a small managed object that wraps around a large unmanged GDI+ bitmap (source). It is this GDI+ bitmap that is storing the pixel data and, therefore, responsible for the memory consumption. The managed .Net Bitmap, being just a wrapper, is much smaller in comparison.

The reason GC.Collect() is freeing the memory is because you are forcing the GC to free all those bitmaps that are no longer being referenced which is in turn releasing all the unmanaged memory they are pointing to. The reason the GC is not doing this for you automatically is because the GC only tracks managed memory not unmanaged memory. So from the GC's point of view all these managed bitmaps you have left laying around are small and not taking up a lot of room so it is not important that it collect them. When, in fact, they are quite large. From the MSDN:

If a small managed object allocates a large amount of unmanaged memory, the runtime takes into account only the managed memory, and thus underestimates the urgency of scheduling garbage collection.

By calling Dispose on your Bitmap when you no longer need it you will immediately free the unmanaged memory being used by the GDI+ bitmap. The managed .Net Bitmap will still hang around until the GC gets around to collecting it but who cares, that's tiny.

Jason Boyd
  • 6,839
  • 4
  • 29
  • 47
  • Thank you for the insight into why GC wasn't doing it automatically – p3tch Jul 08 '17 at 22:06
  • The only issue is now that I dispose the bitmap, because the pictureedit's image value is referenced to it, it clears it. What would be the best work around to this? – p3tch Jul 08 '17 at 22:22
  • What type is `pictureEdit`? Is it a custom class that you created? If so it is holding a reference to an object that that implements `IDisposable` (the `Bitmap`) and should therefore implement `IDisposable` itself. Your `IDisposable` implementation should then dispose of the `Bitmap` when `pictureEdit` is disposed. – Jason Boyd Jul 08 '17 at 22:29
  • pictureEdit is clearly an object, that is accessible for each run through the track ball event, how can he dispose of that one? It seems as some kind long living beast that does "something" and I asked about it 3 hours ago exactly because it is only object created in that method that is outliving the method and got no answer for that... – ipavlu Jul 09 '17 at 00:30
  • Just a small correction: `By calling Dispose on your Bitmap when you no longer need it you will immediately free the unmanaged memory`. You will not immediately free it, but it is just signalling the GC that you no longer need it and it can be collected. When? It is upto the GC. – CodingYoshi Jul 09 '17 at 01:25
  • pictureEdit is devexpress' picturebox... literally the first result in google – p3tch Jul 09 '17 at 01:31
  • so yeah, assigning the control's (imagebox, image edit, whatever you want to call it - it shows a picture in the form) image value to the bitm,ap, then disposing the bitmap, removes the image from the pictureedit/box – p3tch Jul 09 '17 at 01:33
  • The `Graphics` object should also be disposed... or better yet, be used in a `using` statement. – Nyerguds Sep 06 '17 at 12:26
1

Bitmap is inheriting from Image and Image has a finalizer! That makes it automatically Gen1 when created and can take minutes to dispose and free if the GC does not feel memory pressure.

The fast pace of updating is actually creating many instances and they have to be collected by finalizer thread one by one. Sometimes it takes even 15 minutes, sometimes sooner.

The solution is to limit the number of Bitmaps at any moment and there is needed only one at any moment, latest one and when replaced, the previous can be disposed and the current presented until next update.

Then you should do something like this:

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
{
    //your current code here up to (excluding): pictureEdit.Image = bm;
    IDisposable PreviousImage = pictureEdit.Image;
    pictureEdit.Image = bm;
    PreviousImage?.Dispose();
}

or this, if the PictureBox is write only:

private IDisposable PreviousImage {get;set;}

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
{
    //your current code here up to (excluding): pictureEdit.Image = bm;
    pictureEdit.Image = bm;
    PreviousImage?.Dispose();
    PreviousImage = bm;
}
ipavlu
  • 1,617
  • 14
  • 24
0

Don't see you are using any loops and so not sure but you can consider using a memory profiler and see but I see you are not disposing off the created Image object all. I mean the below two lines. You should explicitly call Dispose() on them or use a Using(...) { ... } construct for better disposal

Image img = originalImage;
Graphics g = default(Graphics);
Rahul
  • 76,197
  • 13
  • 71
  • 125
  • The code is inside a ValueChanged event listener, so that counts as "repeated code". But why would the _created_ Image object be disposed? That'd make you end up with a no-longer-valid image. Instead, the image that was in `pictureEdit.Image` should be disposed when it gets replaced. – Nyerguds Sep 06 '17 at 12:29