Страницы

Поиск по вопросам

четверг, 9 апреля 2020 г.

Утечка памяти при вращении изображения. Как избежать?

#c_sharp #net

                    
По рекомендации Qwertiy из прошлой темы, выделяю сей вопрос в отдельную.

Собственно проблема в указана в заголовке. Есть код и при его работе постепенно увеличивается
отъедаемый приложением объем оперативной памяти. Чем больше то изображение, с которым
работает код, тем заметней эта утечка. Как исправить ситуацию?

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace ImgRotate
{
    public partial class Form1 : Form
    {
        Bitmap btmpIcn;
        public Form1()
        {
            InitializeComponent();
            timer1.Enabled = true;
            btmpIcn = notifyIcon1.Icon.ToBitmap();
        }

        int an;
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (an >= 350)
                an = 0;
            else
                an += 10;

            notifyIcon1.Icon = Icon.FromHandle(RotateImage(btmpIcn, an).GetHicon());
        }

        /// 
        /// method to rotate an image either clockwise or counter-clockwise
        /// 
        /// the image to be rotated
        /// the angle (in degrees).
        /// NOTE: 
        /// Positive values will rotate clockwise
        /// negative values will rotate counter-clockwise
        /// 
        /// 
        public static Bitmap RotateImage(Image img, float rotationAngle)
        {
            //create an empty Bitmap image
            Bitmap bmp = new Bitmap(img.Width, img.Height);

            //turn the Bitmap into a Graphics object
            Graphics gfx = Graphics.FromImage(bmp);

            //now we set the rotation point to the center of our image
            gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);

            //now rotate the image
            gfx.RotateTransform(rotationAngle);

            gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);

            //set the InterpolationMode to HighQualityBicubic so to ensure a high
            //quality image once it is transformed to the specified size
            gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;

            //now draw our new image onto the graphics object
            gfx.DrawImage(img, new Point(0, 0));

            //dispose of our Graphics object
            gfx.Dispose();

            //return the image
            return bmp;
        }
    }
}

    


Ответы

Ответ 1



Если посмотреть исходники .NET Framework, то видно, что вызов Icon.FromHandle(IntPtr) транслируется в вызов Icon(IntPtr, takeOwnership=false). Чтобы избежать утечки памяти, надо для каждого IntPtr, который был получен при вызове GetHicon, вызывать DestroyIcon, после того как IntPtr перестал быть нужен. [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); И у Bitmap, который больше не нужен, надо вызывать метод Dispose(); Чтобы каждый раз по таймеру не создавать новые Bitmap, как я уже говорил в комментарии тут, надо создать необходимые изображения, собрать их в коллекцию и использовать их для вывода в tray. Можно конвертировать Bitmap в Icon не вызывая GetHicon. см. "Fast and high quality Bitmap to icon converter". UPDATE using System.Drawing; using System.Collections.Generic; using System.Runtime.InteropServices; class Pic : IDisposable { [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); // см. ниже @ public readonly int Angle; public readonly Bitmap Image; IntPtr Hicon; public Icon Icon; Bitmap RotateImage(Image prev, int an) { throw new NotImplementedException(); // todo } public Pic(int angle, Image original) { this.Image = RotateImage(original, angle); this.Angle = angle; this.Hicon = this.Image.GetHicon(); this.Icon = Icon.FromHandle(this.Hicon); } public void Dispose() { if (this.Hicon == IntPtr.Zero) return; this.Icon.Dispose(); DestroyIcon(this.Hicon); // см. ниже @ this.Hicon = IntPtr.Zero; this.Image.Dispose(); } } public partial class Form1 : Form { Dictionary pics; int an = 0; public Form1() { pics = new Dictionary(); pics.Add(new Pic(0, Image=notifyIcon1.Icon.ToBitmap()) ); } void timer1_Tick(object sender, EventArgs e) { var prev = pics[an]; if (an >= 350) an = 0; else an += 10; Pic p; if (pics.TryGetValue(an, out p) == false) { p = new Pic(an, prev.Image); pics.Add(p); } notifyIcon1.Icon = p.Icon; } } @ для гарантированного вызова DestroyIcon надо использовать специальную обертку над неуправляемым ресурсом. Пример -- тут.

Ответ 2



Это не совсем утечка памяти. Со временем мусор будет чиститься, а память освобождаться. Однако, для объектов, реализующих IDisposable надо вызывать Dispose для освобождения ресурсов (в первую очередь unmanaged-ресурсов), как только объект стал не нужен. Вместо явного вызова Dispose следует использовать блок using, поскольку в нём Dispose вызывается в finally, т. е. его вызов почти гарантирован. Кроме того, это более красивая конструкция в плане кода. Что касается приведённого кода, то, во-первых, Bitmap обладает unmanaged-ресурсами, но твой код не пытается освободить их вызовом Dispose - ты уничтожаешь только Graphics. Однако, я бы действовал другим способом. Ясно, что есть ограниченное количество картинок, поэтому я бы сразу создал массив Bitmap'ов с разными поворотами, а в таймере просто менял иконку. Нет смысла на каждой итерации выполнять поворот заново. И сомнения вызывает необходимость преобразования Icon в Bitmap.

Комментариев нет:

Отправить комментарий