C# PictureBox with animated GIF skips Frames - c#

i have the Problem that my animated gif i used as IMAGE Value in my PictureBox Control is shown slower than showing in Explorer.
I'm using C# Winforms.
Problem here should be that some frames are skipped in execution.
Can someone confirm this issue and maybe have a solution for that?
My Picture Box is for Preloading and works fully in Background Thread.
Is it maybe possible to read the Frames one by one from the gif and animate it selfmade to picturebox?
Thanks!

Use this code. Since 25 frames per second is displayed, I set the timer to 40, which means one frame every 40 milliseconds. (1000ms / 25 frames = 40ms)
step 1. This method shows how to use
static Image[] images;
int frameCount = 0;
private void Btn_Click(object sender, EventArgs e)
{
//get gif image
object ezgif_com_video_to_gif = Resources.ResourceManager.GetObject("ezgif_com_video_to_gif");
images = getFrames((Image)ezgif_com_video_to_gif);//convert to frames array
//show frames
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = 40;
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
step 2. add timer tick
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
pictureBox1.Image = images[frameCount];
frameCount++;
if (frameCount > images.Length - 1)
frameCount = 0;
}
step 3. convert gif to frames
Image[] getFrames(Image originalImg)
{
int numberOfFrames = originalImg.GetFrameCount(FrameDimension.Time);
Image[] frames = new Image[numberOfFrames];
for (int i = 0; i < numberOfFrames; i++)
{
originalImg.SelectActiveFrame(FrameDimension.Time, i);
frames[i] = ((Image)originalImg.Clone());
}
return frames;
}

Related

Using a timer and milliseconds to fade a form away but the duration is doubled

I am making my own little custom message box, essentially its just a small box showing up with a message for X amount of time, and then fades away for Y duration.
What is happening tho is that the fade away takes double as long as its supposed to and I cannot figure out why. Can someone look at my code and spot why its taking double the amount of time to fade the form away than it is expected?
//Initiate and set up the message bubble.
public static void InitiateBubble(String displayText, Double showTime = 1000, Double fadeTime = 2000) {
Bubble bubble = new Bubble(displayText);
bubble.showTime = showTime;
bubble.fadeTime = fadeTime;
bubble.Show();
bubble.showTimer = new Timer();
bubble.showTimer.Interval = (int)bubble.showTime;
bubble.showTimer.Tick += bubble.startFadeAway;
bubble.showTimer.Start();
}
//Leaves some time on screen before starting to fade away
private void startFadeAway(object sender, EventArgs e) {
showTimer.Stop();
fadeAwayTimer = new Timer();
fadeAwayTimer.Interval = 10;
fadeAwayTimer.Tick += fadeAway;
fadeAwayTimer.Start();
}
//slowly fades the contorle away until it disapears.
private void fadeAway(object sender, EventArgs e) {
double opacity = Opacity;
opacity -= (10 / fadeTime);
if (opacity < 0) {
Close();
}
else {
Opacity = opacity;
}
}
If the user sets the fade interval to 1 second (1000 milliseconds), and we set the timer interval to 1/10th of a second (100 milliseconds), then we need to fade the opacity by 10% every interval (since the interval is triggered 10 times per second). So we would set Opacity -= .1 on each iteration.
If the user sets the fade interval to 2 seconds (2000 milliseconds), and we still have the timer interval set to 1/10th of a second, then we need to fade the opacity by only 5% every interval, so we would set Opacity -= .05 on each iteration.
Seeing this relationship, we can discover that:
var amountToReduceOpacity = 1.0 / fadeTime * interval;
Note: as γηράσκω δ' αεί πολλά διδασκόμε mentioned above, the resolution on the winform timer is around 17 milliseconds, so if we set it to less than this, the fade will slow down dramatically because we will have calculated the rate for a very fast timer (meaning that we won't fade very much each iteration), but it will execute more slowly. On my machine, setting it to 50 looks just fine.
Now we can use this formula to always fade the form by the correct amount each interval. Here's a sample Form that does basically what you're doing above (note that I dropped a label and two timers on the form, and named them: lblDisplay, showTimer, and fadeTimer):
public partial class Bubble : Form
{
private readonly double amountToReduceOpacity;
private readonly int fadeInterval = 50;
// Custom form constructor takes in all three required settings
public Bubble(string displayText, int showTime, int fadeTime)
{
InitializeComponent();
lblDisplay.AutoSize = true;
lblDisplay.Text = displayText;
lblDisplay.Left = ClientRectangle.Width / 2 - lblDisplay.Width / 2;
lblDisplay.Top = ClientRectangle.Height / 2 - lblDisplay.Height / 2;
showTimer.Interval = showTime;
fadeTimer.Interval = fadeInterval;
amountToReduceOpacity = 1.0 / fadeTime * fadeInterval;
}
// The Shown event starts the first timer
private void Bubble_Shown(object sender, EventArgs e)
{
showTimer.Start();
}
// The shownTimer starts the fadeTimer
private void showTimer_Tick(object sender, EventArgs e)
{
showTimer.Stop();
BackColor = Color.Red; // Just so we see when the fade starts
fadeTimer.Start();
}
// The fade timer reduces opacity on each iteration until it's zero
private void fadeTimer_Tick(object sender, EventArgs e)
{
Opacity -= amountToReduceOpacity;
if (Opacity <= 0) Close();
}
}
Then on the client side we can just do something like:
private void button1_Click(object sender, EventArgs e)
{
Bubble bubble = new Bubble("Help me, I'm Fading!", 1000, 2000);
bubble.Show();
}

How to make an animation look consistent and smooth

`So I have a total of 6 images that when animated, creates the illusion of a person walking. The problem is its not smooth, refresh(), invalidate(),update() have failed me. How do I go about this.
namespace Runner
{
public partial class Form1 : Form
{
Keys moveRight;
Keys moveLeft;
public static bool isMovingR = false;
public static bool isMovingL = false;
Bitmap stnd = new Bitmap(Properties.Resources.Standing);
static Bitmap wlk_1_RL = new Bitmap(Properties.Resources.Walk_1_RL);
static Bitmap wlk_2_RL = new Bitmap(Properties.Resources.Walk_2_RL);
static Bitmap wlk_3_RL = new Bitmap(Properties.Resources.Walk_3_RL);
static Bitmap wlk_4_LL = new Bitmap(Properties.Resources.Walk_4_LL);
static Bitmap wlk_5_LL = new Bitmap(Properties.Resources.Walk_5__LL);
static Bitmap wlk_6_LL = new Bitmap(Properties.Resources.Walk_6_LL);
Graphics gfx;
Animation animate = new Animation(new Bitmap[] { wlk_1_RL, wlk_2_RL, wlk_3_RL,
wlk_4_LL, wlk_5_LL, wlk_6_LL });
Timer timer = new Timer();
int imageX = 5;
int imageY = 234;
public Form1()
{
InitializeComponent();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
moveRight = Keys.D;
moveLeft = Keys.A;
if (Keys.D == moveRight)
{
isMovingR = true;
timer.Enabled = true;
timer.Interval = 50;
timer.Tick += timer1_Tick;
//imageX += 5;
Refresh();
} else if (Keys.A == moveLeft)
{
isMovingL = true;
imageX -= 5;
Refresh();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
gfx = this.CreateGraphics();
gfx.DrawImage(animate.Frame2Draw(), imageX, imageY);
//Refresh(); Invalidate(); Update();
}
}
}
Now, the problem is being able to make the animation consistent and smooth
UPDATE...animation class
public class Animation
{
int slctdImg = 0;
public static Bitmap[] images;
Bitmap frame2Draw;
public Animation(Bitmap[] frames)
{
foreach (Bitmap btm in frames)
{
btm.MakeTransparent();
}
images = frames;
}
public Bitmap Frame2Draw()
{
if (slctdImg < images.Length)
{
frame2Draw = images[slctdImg];
slctdImg++;
}
if (slctdImg >= images.Length)
{
slctdImg = 0;
frame2Draw = images[slctdImg];
}
return frame2Draw;
}
}
Many issues:
I wonder why you would call MakeTransparent on each Tick?? I doubt it does what you expect.. It is changing pixels and rather expensive; you ought to cache the images instead.
Nor why you create an array of bitmap on each Tick??? And since it is always created the same way it always displays only the 1st image.. This should answer your question.
Further issues:
Using this.CreateGraphics(); will fail to create a persistent result although that may not be your aim as you try to animate.
Remember that a Timer.Interval can't run faster than 15-30 ms; also that winforms is notoriously bad at animation.
Remember that c# is zero-based, so this slctdImg > images.Length should probably be slctdImg >= images.Length
Here is what you should do instead:
Move the instantiation either to the form load or maybe to a key event.
Move the drawing to the Paint event of the form.
In the Tick count up frames and/or position and trigger the Paint by calling Invalidate on the form!
Update:
One more issue is the way you hook up the Tick event each time the right key is pressed. Hooking up an event multiple times will result in it running multiple times; this will create to gaps/jumps in your animation..
Either add an unhook each time before hooking up (it will fail quietly the 1st time)
timer.Tick -= timer1_Tick;
timer.Tick += timer1_Tick;
or (better) hook it up only once in the original set up!

How to change a picturebox once a gif ends

I have a PictueBox and I have some dice, I would like to play an animation for the "rolling" of the dice, I did a .gif with the dice, but after the dice stop rolling, I want the actual dice number that I got, I have a random funcion that handles that.
My question is, I press the "Roll Dice" button, it plays the animation and after the animation ends I should set int the picturebox the dice that actually came. but it immediately chnages to the dice number that actually came, skipping the animation;
This is how it works:
dice1.Image = Resources.DiceAnimation; //Here the gif is called to be played
int x = rollDice(); //Here I roll the dice
switch (x){
case 1: dice.Image = resources.diceFace1; //Image set depending on x
break
case 2: //etc...
}
There might be two things needed to do that.
Firstly, you may need to ensure that your PictureBox receives a gif image and it knows it. To do this, please check this answer and this answer. The posts have code to show GifImage frame by frame:
public class GifImage
{
private Image gifImage;
private FrameDimension dimension;
private int frameCount;
private int currentFrame = -1;
private bool reverse;
private int step = 1;
public GifImage(string path)
{
gifImage = Image.FromFile(path);
//initialize
dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
//gets the GUID
//total frames in the animation
frameCount = gifImage.GetFrameCount(dimension);
}
public bool ReverseAtEnd {
//whether the gif should play backwards when it reaches the end
get { return reverse; }
set { reverse = value; }
}
public Image GetNextFrame()
{
currentFrame += step;
//if the animation reaches a boundary...
if (currentFrame >= frameCount || currentFrame < 1) {
if (reverse) {
step *= -1;
//...reverse the count
//apply it
currentFrame += step;
}
else {
currentFrame = 0;
//...or start over
}
}
return GetFrame(currentFrame);
}
public Image GetFrame(int index)
{
gifImage.SelectActiveFrame(dimension, index);
//find the frame
return (Image)gifImage.Clone();
//return a copy of it
}
}
Use it like this (note that you need a Timer object):
private GifImage gifImage = null;
private string filePath = #"C:\Users\Jeremy\Desktop\ExampleAnimation.gif";
public Form1()
{
InitializeComponent();
//a) Normal way
//pictureBox1.Image = Image.FromFile(filePath);
//b) We control the animation
gifImage = new GifImage(filePath);
gifImage.ReverseAtEnd = false; //dont reverse at end
}
private void button1_Click(object sender, EventArgs e)
{
//Start the time/animation
timer1.Enabled = true;
}
//The event that is animating the Frames
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Image = gifImage.GetNextFrame();
}
Secondly, to know how long you want to run your GIF image, you may need to Get Frame Duration of GIF image like this:
double delayIn10Ms; //declare somewhere
//Initialize on your form load
PropertyItem item = img.GetPropertyItem (0x5100); // FrameDelay in libgdiplus
// Time is in 1/100th of a second
delayIn10Ms = (item.Value [0] + item.Value [1] * 256) * 10;
Then use the delayIn10Ms time plus, probably, a little bit more time to stop your timer. You may also want to check when was the last time your timer Ticks and store it. If it exceeds the given delay time, then you should stop your timer and start it again on dice roll, after image assignment in your switch case.
DateTime currentTick = DateTime.Min;
DateTime startTick = DateTime.Min;
private void timer1_Tick(object sender, EventArgs e)
{
currentTick = DateTime.Now;
if ((currentTick - startTick).TotalSeconds / 100 < delayIn10Ms)
pictureBox1.Image = gifImage.GetNextFrame();
else
timer1.Stop(); //stop the timer
}
//And somewhere else you have
timer1.Start(); //to start the timer
int x = rollDice(); //Here I roll the dice
switch (x){
case 1: dice.Image = resources.diceFace1; //Image set depending on x
break
case 2: //etc...
}
You can make a timer with the Interval property set to the length of the animation and set it's Tag to 0 and in the timer write the code:
if(timer.Tag == "0")
timer.Tag == "1";
else if(timer.Tag == "1")
{
int x = rollDice();
switch (x)
{
case 1: dice.Image = resources.diceFace1; break;
case 2: //etc...
}
timer.Tag == "0";
timer.Stop();
}

How to show a specific frame of a GIF image in C#?

I want to show a frame of a gif image. I searched and found that the following code should work, but it doesn't work. it detects the number of frames correctly but it shows the whole frames of gif instead of the specified frame. thanks everybody.
Image[] frames = new Image[36];
Image GG = Image.FromFile(#"C:\Users\Administrator\TEST C#\TEST2frame2\chef.gif");
FrameDimension dimension = new FrameDimension(GG.FrameDimensionsList[0]);
// Number of frames
int frameCount = GG.GetFrameCount(dimension);
label1.Text = frameCount.ToString();
// Return an Image at a certain index
GG.SelectActiveFrame(dimension, 1);
frames[1] = ((Image)GG.Clone());
pictureBox1.Image = frames[1];
Use your own code up until the call of SelectActiveFrame() and after that change to this lines:
frames[0] = new Bitmap(GG);
pictureBox1.Image = frame[0];
This should do the trick. Please do not forget do dispose the created Images.
Oh it works, but not as you expect it to.
When you set an active frame of a gif image it actually restarts its animation from that frame. You have to stop it when you change a frame, by setting the pictureBox.IsEnabled to false, for instance. Try the following code
private Image img;
public Form1()
{
InitializeComponent();
img = Image.FromFile(#"C:\Users\Administrator\TEST C#\TEST2frame2\chef.gif");
pictureBox1.Image = img;
}
private void button1_Click(object sender, EventArgs e)
{
var dim = new FrameDimension(img.FrameDimensionsList[0]);
img.SelectActiveFrame(dim, 1);
pictureBox1.Enabled = false;
}
Try pressing the button in different moments and you will see that the active image frame will change.

c# using Microsoft.DirectX.AudioVideoPlayback how to play next video after one is finished

i am able to put a video in my windows form.
my question is how do i make it when it finishes playing the video,it starts to play another video? meaning like in a sequence. after it finishes, play another video.
so far i have manage to play a video and it just loops the video.
any ideas?
this is my code so far:
public partial class Form1 : Form
{
Video video;
public Form1()
{
InitializeComponent();
Initializevid1();
}
public void Initializevid1()
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video("C:\\Users\\Dave\\Desktop\\WaterDay1.wmv");
// set the panel as the video object’s owner
video.Owner = viewport;
// stop the video
video.Play();
video.Ending +=new EventHandler(BackLoop);
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
}
private void BackLoop(object sender, EventArgs e)
{
//video.CurrentPosition = 0;
}
When playing video sequences in AudioVideoPlayback:
Create a list of videos to be displayed (using file paths), preferably in a listbox.
Use an integer to get file path from the listbox.items index.
Ensure video is disposed before loading next video.
Increment integer every time a video is played.
Use an if statement to see if it is the end of the sequence.
As a personal preference (not sure how much difference it makes) I would resize video before playing
So from your code: (haven't tested this, but in theory, I think it should work)
public partial class Form1 : Form
{
Video video;
Int SeqNo = 0;
public Form1()
{
InitializeComponent();
Initializevid1();
}
public void Initializevid1()
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video(listbox1.Items[SeqNo].Text);
// set the panel as the video object’s owner
video.Owner = viewport;
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
// stop the video
video.Play();
// start next video
video.Ending +=new EventHandler(BackLoop);
}
private void BackLoop(object sender, EventArgs e)
{
// increment sequence number
SeqNo += 1; //or '++SeqNo;'
//check video state
if (!video.Disposed)
{
video.Dispose();
}
//check SeqNo against listbox1.Items.Count -1 (-1 due to SeqNo is a 0 based index)
if (SeqNo <= listbox1.Items.Count -1)
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video(listbox1.Items[SeqNo].Text);
// set the panel as the video object’s owner
video.Owner = viewport;
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
// stop the video
video.Play();
// start next video
video.Ending +=new EventHandler(BackLoop);
}
}
You can use the same video object created earlier to open the second video file in the BackLoop() function.
So, the code should look like something this:
private void BackLoop(object sender, EventArgs e)
{
video.Open("C:\\Users\\Dave\\Desktop\\WaterDay2.wmv", true);
}
I used this post to adapt it to my needs and this was my solution:
Video _SegaVideo;
Video _IntroVideo;
public _FrmMain()
{
InitializeComponent();
_SegaVideo = new Video(#"video\SEGA.AVI");
_SegaVideo.Owner = _VideoPanel;
_SegaVideo.Play();
_SegaVideo.Ending += new EventHandler(_SegaVideoEnding);
}
private void _SegaVideoEnding(object sender, EventArgs e)
{
_IntroVideo = new Video(#"video\INTRO.AVI");
_IntroVideo.Owner = _VideoPanel;
_IntroVideo.Play();
}

Categories