1 // Licensed to the .NET Foundation under one or more agreements.
2 // See the LICENSE file in the project root for more information.
3 //
4 // System.Drawing.ImageAnimator.cs
5 //
6 // Authors:
7 //    Dennis Hayes (dennish@Raytek.com)
8 //    Sanjay Gupta (gsanjay@novell.com)
9 //    Sebastien Pouliot  <sebastien@ximian.com>
10 //
11 // (C) 2002 Ximian, Inc
12 // Copyright (C) 2004,2006-2007 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
34 using System.Collections;
35 using System.Drawing.Imaging;
36 using System.Threading;
37 
38 namespace System.Drawing
39 {
40 
41     class AnimateEventArgs : EventArgs
42     {
43 
44         private int frameCount;
45         private int activeFrame;
46         private Thread thread;
47 
AnimateEventArgs(Image image)48         public AnimateEventArgs(Image image)
49         {
50             frameCount = image.GetFrameCount(FrameDimension.Time);
51         }
52 
53         public Thread RunThread
54         {
55             get { return thread; }
56             set { thread = value; }
57         }
58 
GetNextFrame()59         public int GetNextFrame()
60         {
61             if (activeFrame < frameCount - 1)
62                 activeFrame++;
63             else
64                 activeFrame = 0;
65 
66             return activeFrame;
67         }
68     }
69 
70     public sealed class ImageAnimator
71     {
72 
73         static Hashtable ht = Hashtable.Synchronized(new Hashtable());
74 
ImageAnimator()75         private ImageAnimator()
76         {
77         }
78 
Animate(Image image, EventHandler onFrameChangedHandler)79         public static void Animate(Image image, EventHandler onFrameChangedHandler)
80         {
81             // must be non-null and contain animation time frames
82             if (!CanAnimate(image))
83                 return;
84 
85             // is animation already in progress ?
86             if (ht.ContainsKey(image))
87                 return;
88 
89             PropertyItem item = image.GetPropertyItem(0x5100); // FrameDelay in libgdiplus
90             byte[] value = item.Value;
91             int[] delay = new int[(value.Length >> 2)];
92             for (int i = 0, n = 0; i < value.Length; i += 4, n++)
93             {
94                 int d = BitConverter.ToInt32(value, i) * 10;
95                 // follow worse case (Opera) see http://news.deviantart.com/article/27613/
96                 delay[n] = d < 100 ? 100 : d;
97             }
98 
99             AnimateEventArgs aea = new AnimateEventArgs(image);
100             WorkerThread wt = new WorkerThread(onFrameChangedHandler, aea, delay);
101             Thread thread = new Thread(new ThreadStart(wt.LoopHandler));
102             thread.IsBackground = true;
103             aea.RunThread = thread;
104             ht.Add(image, aea);
105             thread.Start();
106         }
107 
CanAnimate(Image image)108         public static bool CanAnimate(Image image)
109         {
110             if (image == null)
111                 return false;
112 
113             int n = image.FrameDimensionsList.Length;
114             if (n < 1)
115                 return false;
116 
117             for (int i = 0; i < n; i++)
118             {
119                 if (image.FrameDimensionsList[i].Equals(FrameDimension.Time.Guid))
120                 {
121                     return (image.GetFrameCount(FrameDimension.Time) > 1);
122                 }
123             }
124             return false;
125         }
126 
StopAnimate(Image image, EventHandler onFrameChangedHandler)127         public static void StopAnimate(Image image, EventHandler onFrameChangedHandler)
128         {
129             if (image == null)
130                 return;
131 
132             if (ht.ContainsKey(image))
133             {
134                 AnimateEventArgs evtArgs = (AnimateEventArgs)ht[image];
135                 evtArgs.RunThread.Abort();
136                 ht.Remove(image);
137             }
138         }
139 
UpdateFrames()140         public static void UpdateFrames()
141         {
142             foreach (Image image in ht.Keys)
143                 UpdateImageFrame(image);
144         }
145 
146 
UpdateFrames(Image image)147         public static void UpdateFrames(Image image)
148         {
149             if (image == null)
150                 return;
151 
152             if (ht.ContainsKey(image))
153                 UpdateImageFrame(image);
154         }
155 
156         // this method avoid checks that aren't requied for UpdateFrames()
UpdateImageFrame(Image image)157         private static void UpdateImageFrame(Image image)
158         {
159             AnimateEventArgs aea = (AnimateEventArgs)ht[image];
160             image.SelectActiveFrame(FrameDimension.Time, aea.GetNextFrame());
161         }
162     }
163 
164     class WorkerThread
165     {
166 
167         private EventHandler frameChangeHandler;
168         private AnimateEventArgs animateEventArgs;
169         private int[] delay;
170 
WorkerThread(EventHandler frmChgHandler, AnimateEventArgs aniEvtArgs, int[] delay)171         public WorkerThread(EventHandler frmChgHandler, AnimateEventArgs aniEvtArgs, int[] delay)
172         {
173             frameChangeHandler = frmChgHandler;
174             animateEventArgs = aniEvtArgs;
175             this.delay = delay;
176         }
177 
LoopHandler()178         public void LoopHandler()
179         {
180             try
181             {
182                 int n = 0;
183                 while (true)
184                 {
185                     Thread.Sleep(delay[n++]);
186                     frameChangeHandler(null, animateEventArgs);
187                     if (n == delay.Length)
188                         n = 0;
189                 }
190             }
191             catch (ThreadAbortException)
192             {
193                 Thread.ResetAbort(); // we're going to finish anyway
194             }
195         }
196     }
197 }
198