Unfortunately we can extend neither System.Drawing.Image
nor System.Drawing.Bitmap
so overriding image.FrameDimensionsLists
and similar members is out of question, and as Hans Passant mentioned, None of the Windows image encoders support the time dimension by itself. However I believe in this specific case based on
I know that I can play the animation if I have the frames from any
source by doing it manually, but I want to do it in a compatible way:
If I could produce such a Bitmap, I could simply use it on a Button,
Label, PictureBox or any other existing control, and the built-in
ImageAnimator could also handle it automatically.
We can get away by implementing a new class which can handle Animations and then implicitly cast it to Bitmap. We can use extension methods to automatically activate animations for controls. I know this method is hacky, but I thought maybe it would worth mentioning it.
Here is a rough implementation and sample usage.
AnimatedBitmap: handles frame and time based animation base on provided sequences:
public class Sequence
{
public Image Image { get; set; }
public int Delay { get; set; }
}
public class AnimatedBitmap:IDisposable
{
private readonly Bitmap _buffer;
private readonly Graphics _g;
private readonly Sequence[] _sequences;
private readonly CancellationTokenSource _cancelToken;
public event EventHandler FrameUpdated;
protected void OnFrameUpdated()
{
if (FrameUpdated != null)
FrameUpdated(this, EventArgs.Empty);
}
public AnimatedBitmap(int width, int height, params Sequence[] sequences)
{
_buffer = new Bitmap(width, height, PixelFormat.Format32bppArgb) {Tag = this};
_sequences = sequences;
_g=Graphics.FromImage(_buffer);
_g.CompositingMode=CompositingMode.SourceCopy;
_cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(Animate
, TaskCreationOptions.LongRunning
, _cancelToken.Token);
}
private void Animate(object obj)
{
while (!_cancelToken.IsCancellationRequested)
foreach (var sequence in _sequences)
{
if (_cancelToken.IsCancellationRequested)
break;
_g.Clear(Color.Transparent);
_g.DrawImageUnscaled(sequence.Image,0,0);
_g.Flush(FlushIntention.Flush);
OnFrameUpdated();
Thread.Sleep(sequence.Delay);
}
_g.Dispose();
_buffer.Dispose();
}
public AnimatedBitmap(params Sequence[] sequences)
: this(sequences.Max(s => s.Image.Width), sequences.Max(s => s.Image.Height), sequences)
{
}
public void Dispose()
{
_cancelToken.Cancel();
}
public static implicit operator Bitmap(AnimatedBitmap animatedBitmap)
{
return animatedBitmap._buffer;
}
public static explicit operator AnimatedBitmap(Bitmap bitmap)
{
var tag = bitmap.Tag as AnimatedBitmap;
if (tag != null)
return tag;
throw new InvalidCastException();
}
public static AnimatedBitmap CreateAnimation(Image[] frames, int[] delays)
{
var sequences = frames.Select((t, i) => new Sequence {Image = t, Delay = delays[i]}).ToArray();
var animated=new AnimatedBitmap(sequences);
return animated;
}
}
AnimationController: handles control animation updating
public static class AnimationController
{
private static readonly List<Control> Controls =new List<Control>();
private static CancellationTokenSource _cancelToken;
static AnimationController()
{
_cancelToken = new CancellationTokenSource();
_cancelToken.Cancel();
}
private static void Animate(object arg)
{
while (!_cancelToken.IsCancellationRequested)
{
Controls.RemoveAll(c => !(c.BackgroundImage.Tag is AnimatedBitmap));
foreach (var c in Controls)
{
var control = c;
if (!control.Disposing)
control.Invoke(new Action(() => control.Refresh()));
}
Thread.Sleep(40);
}
}
public static void StartAnimation(this Control control)
{
if (_cancelToken.IsCancellationRequested)
{
_cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(Animate
, TaskCreationOptions.LongRunning
, _cancelToken.Token);
}
Controls.Add(control);
control.Disposed += Disposed;
}
private static void Disposed(object sender, EventArgs e)
{
(sender as Control).StopAnimation();
}
public static void StopAnimation(this Control control)
{
Controls.Remove(control);
if(Controls.Count==0)
_cancelToken.Cancel();
}
public static void SetAnimatedBackground(this Control control, AnimatedBitmap bitmap)
{
control.BackgroundImage = bitmap;
control.StartAnimation();
}
}
and here is the sample usage:
public Form1()
{
InitializeComponent();
var frame1 = Image.FromFile(@"1.png");
var frame2 = Image.FromFile(@"2.png");
var animatedBitmap= new AnimatedBitmap(
new Sequence {Image = frame1, Delay = 33},
new Sequence {Image = frame2, Delay = 33}
);
// or we can do
//animatedBitmap = AnimatedBitmap.CreateAnimation(new[] {frame1, frame2}, new[] {1000, 2000});
pictureBox1.SetAnimatedBackground(animatedBitmap);
button1.SetAnimatedBackground(animatedBitmap);
label1.SetAnimatedBackground(animatedBitmap);
checkBox1.SetAnimatedBackground(animatedBitmap);
//or we can do
//pictureBox1.BackgroundImage = animatedBitmap;
//pictureBox1.StartAnimation();
}