SYSK 222: CPU Impact of Animated GIFs

Animation, whether via an animated gif (a sequence of frames) or custom drawing, does not come for free. As a rule of thumb, the more colors and the more frames your animated gif has, the larger the memory footprint and the higher the CPU utilization. With custom painting, the more complex the drawing, the higher is the CPU utilization.

 

So, how bit is the CPU utilization impact?

 

I have conducted a simple test, where I used an animated gif file with 8 frames, each image 25x25 pixels, frames rotated every 0.05 sec, and file size optimized by using transparency color. For comparison, I created a custom control that basically displayed the same animated image (see code below).

 

In the first test run, I used one image and monitored CPU utilization once/sec using perfmon. The animated gif was showing roughly a 0.681% CPU utilization; and custom drawing was recorded as 0% most of the time with spikes to 0.750%. Keep in mind that the animation speed was 20 times faster than perfmon sampling.

 

In the second test run, I used 60 instances of animated gif and compared it against 60 instances of custom drawing implementation. Animated gif recorded an average CPU utilization of 37.729%, varying from 33.594% to 43.750; while custom drawing showed a significantly larger range from 23.438% to 50.781% with the average CPU utilization of 40.183%. Keep in mind that the quality of the implementation does make a difference, and in my case, I threw something together in a few minutes… so, there might be some opportunities for improvement.

 

The tests were run on a Toshiba Tecra M5, 2 GHz, 2 GB.

 

My “rules of thumb”:

1. Use animated GIFs over custom drawing

2. Limit animated GIFs to 4-8 frames and 256 colors or less

 

Note: http://www.napyfab.com/ajax-indicators/ site has a number of useful, and not too memory/CPU hungry animated GIFs.

 

 

Custom drawing code used in testing:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Drawing;

using System.Data;

using System.Text;

using System.Windows.Forms;

namespace AnimatedGIFTest

{

    public partial class CustomDrawing : UserControl

    {

        private bool _firstDraw = true;

        private bool _disposed = false;

        private Timer _timer;

        private Point _centerPoint;

        private int _currentDotNumber = 0;

        private const int DotCount = 8;

        private const int DotRadius = 2;

        private const int Radius = 11;

        public CustomDrawing()

        {

            InitializeComponent();

            this.Resize += new EventHandler(CustomDrawing_Resize);

            _centerPoint = new Point(this.Width / 2, this.Height / 2);

            _timer = new Timer();

            _timer.Tick += new EventHandler(Timer_Tick);

            _timer.Interval = 80;

            if (this.DesignMode == false)

                _timer.Enabled = true;

            base.Paint += new PaintEventHandler(CustomDrawing_Paint);

        }

        void CustomDrawing_Resize(object sender, EventArgs e)

        {

            _centerPoint = new Point(this.Width / 2, this.Height / 2);

        }

        Point GetPoint(Point pointCenter, int radius, float angle)

        {

            // Transform degrees to radians by using (2*Math.PI*angle/360) logic

            float x = (float)Math.Cos(2 * Math.PI * angle / 360) * radius + pointCenter.X;

            float y = -(float)Math.Sin(2 * Math.PI * angle / 360) * radius + pointCenter.Y;

            return new Point((int)x, (int)y);

        }

        Color DotColor(int dotNumber)

        {

            Color result;

            // 8 dots: 3 black, 2 gray, 3 white

            switch (dotNumber + _currentDotNumber)

            {

                case 1:

                case 5:

                    result = Color.Gray;

                    break;

                case 2:

                case 3:

                case 4:

                    result = Color.White;

         break;

                default:

                    result = Color.Black;

                    break;

            }

            return result;

        }

        int GetDotNumber(int offset)

        {

            int result;

            if (_currentDotNumber + offset >= DotCount)

                result = DotCount - (_currentDotNumber + offset);

            else

                result = _currentDotNumber + offset;

            return result;

        }

        void CustomDrawing_Paint(object sender, PaintEventArgs e)

        {

            Pen pen = null;

            SolidBrush fillBrush = null;

            try

            {

                e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

                pen = new Pen(Color.Black);

                fillBrush = new SolidBrush(Color.Black);

                float angle;

                int[] drawPoints;

                if (_firstDraw == true)

                    drawPoints = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };

        else

                    drawPoints = new int[] { _currentDotNumber, GetDotNumber(1), GetDotNumber(4), GetDotNumber(5) };

                foreach (int i in drawPoints)

                {

                    // Only repaint changed dots

            angle = 360 - (360 * (i) / 8) + 90;

                    Point pt = GetPoint(_centerPoint, Radius, angle);

                    Rectangle drawRect = new Rectangle(pt.X - DotRadius,

                            pt.Y - DotRadius,

                     DotRadius * 2 + 1,

                            DotRadius * 2 + 1);

                    if (e.ClipRectangle.IntersectsWith(drawRect))

                    {

                        fillBrush.Color = DotColor(i);

                        e.Graphics.DrawEllipse(pen, drawRect);

                        e.Graphics.FillEllipse(fillBrush, drawRect);

                    }

                }

                _firstDraw = false;

            }

            finally

            {

                if (pen != null)

   pen.Dispose();

                if (fillBrush != null)

                    fillBrush.Dispose();

            }

        }

        void Timer_Tick(object sender, EventArgs e)

        {

            float angle;

            _currentDotNumber = GetDotNumber(1);

            // Only repaint changed dots

            int[] drawPoints = new int[] { _currentDotNumber, GetDotNumber(1), GetDotNumber(4), GetDotNumber(5) };

            foreach (int i in drawPoints)

            {

                angle = 360 - (360 * (i) / 8) + 90;

                Point pt = GetPoint(new Point(this.Width / 2, this.Height / 2), Radius, angle);

                base.Invalidate(new Rectangle(pt.X - DotRadius,

                            pt.Y - DotRadius,

                   DotRadius * 2 + 1,

                            DotRadius * 2 + 1));

            }

            _firstDraw = false;

        }

    }

}