I have tried to create an animated bitmap on windows forms application. A timer object is set to an interval of 100ms, and the code works like this:
I have a 2560x2560 bitmap that is my map, a picture box called 'pb' contains this map and the size of the picture box is 800x800 with image stretch parameter on to give better resolution. I have a bitmap array of 7 elements containing the frames for a torch. The idea is that i draw the current torch bitmap onto the map, set the image of 'pb' to the map and call invalidate procedure to redraw it. Then the bitmap with torch drawn onto it is reverted to the original map bitmap 'org_btm'.
The code is below:
private void animationtick_Tick(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(btm))
{ g.DrawImage(torch_anim[torch_anim_c], new Point(20*64, 20*64)); }
pb.Image = btm;
pb.Invalidate();
btm = org_btm;
if (torch_anim_c < 6)
{
torch_anim_c++;
}
else
{
torch_anim_c = 0;
}
pb.Invalidate();
}
'torch_anim_c' is the index counter of the bitmap array.
So the problem that occurs is that the torch works for the first few frames and stops working there after, being stuck on 1 frame, when i run the code in debugger with a break point, it shows that the code runs through even when the image is stuck, and the program is responsive with other functions still working.
Do you have any ideas how to fix this?
Thank you in advance.
I have a snippet of the map with the stuck torch animation:
Torch Stuck Snippet
edit: the 'Point()' is 20*64 because the torch size is 64x64 and it's at position 20 and the map is 40*40 tiles.
convert it to gif format using photoshop and then use this class
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);
dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
frameCount = gifImage.GetFrameCount(dimension);
}
public bool ReverseAtEnd {
get { return reverse; }
set { reverse = value; }
}
public Image GetNextFrame()
{
currentFrame += step;
if (currentFrame >= frameCount || currentFrame < 1) {
if (reverse) {
step *= -1;
currentFrame += step;
}
else {
currentFrame = 0;
}
}
return GetFrame(currentFrame);
}
public Image GetFrame(int index)
{
gifImage.SelectActiveFrame(dimension, index);
return (Image)gifImage.Clone();
}
}
Initialize it at formload ot whenever
GifImage gifImage = new GifImage(filePath);
set the Reverse variable to true if youn want to reverse it at the end
at timer tick use that code
pb.Image = gifImage.GetNextFrame();
change pb.Invalidate(); with pb.Update();
Related
I try to write an application with a large number of GIF animations in a form (30-40 picture boxes and each .gif image is 200-500kb). However, when I use so much Picturebox and run the program, the form freezes too much, and the gif animations that normally need to pass to the next image in 100MS, move to the next image within 5-6 seconds. How to handle this situation. Thank you
I tried to show pictures ,in gif, one by one that way but i had same result
public class Class1 : PictureBox
{
public Class1()
{
}
public Image[] imagelist { get; set; } = new Image[10];
public int currentimage;
private int imagecount = 1;
protected override void OnCreateControl()
{
base.OnCreateControl();
this.Image = imagelist[0];
imagecount = imagelist.TakeWhile(r => r != null).Count();
}
public void NextImage()
{
currentimage = (currentimage + 1) % imagecount;
this.Image = imagelist[currentimage];
}
}
public Form1()
{
InitializeComponent();
array = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(f => f.GetValue(this) != null&&f.FieldType == typeof(Class1)).Select(f => f.GetValue(this)).Cast<Class1>().ToArray();
Thread = new Thread(Start);
Thread.Start();
}
private Thread Thread;
private void Start()
{
Thread.Sleep(400);
while (true)
{
this.Invoke((MethodInvoker)(() =>
{
foreach (Class1 class1 in array)
{
class1.NextImage();
}
}));
Thread.Sleep(100);
}
}
Use 'BeginInvoke' instead 'Invoke'. Invoke makes your ui wait until action finished and it will make your app not responding or slow. Use 'BeginInvoke' often in windows apps.
or
I think it's because your pictures has different size than your picturebox or has different w/h ratio from picturebox so it had to calculate the streching and takes time. So you can do
if your picturebox has const width and height, resize your image or gif with same size with picturebox
or change to normal and not do any change on picturebox sizemode
I have a function which is pretty much a variable-renamed version of the code snippet from https://codingvision.net/c-get-frames-from-a-gif
I am using this to split up the images in a GIF so I am able to iterate over them and play them in a PictureBox to solve the issue highlighted here: Gif Animated Files in C# have Lower framerates than they should
My splitting function:
private Image[] __loading_gif_list;
private int __frame_count;
private int __frame_amount;
private Image[] get_frames(Image _image)
{
int frame_count = _image.GetFrameCount(FrameDimension.Time);
Image[] frames = new Image[frame_count];
for (int i = 0; i < frame_count; ++i)
{
_image.SelectActiveFrame(FrameDimension.Time, i);
frames[i] = (Image)_image.Clone();
}
return frames;
}
My Form Load function:
private void form_main_Load(object sender, EventArgs e)
{
__loading_gif_list = get_frames(Properties.Resources.loading);
__frame_amount = Properties.Resources.loading.GetFrameCount(FrameDimension.Time);
loading_picbox.Enabled = false;
}
and my iteration function (a timer can also be used, however I was simply experimenting with this implementation)
private async void loader_updater()
{
while (true)
{
if (__frame_count >= __frame_amount) { __frame_count = 1; }
else { ++__frame_count; }
loading_picbox.Image = __loading_gif_list[__frame_count - 1];
await Task.Delay(10);
}
}
The split function for a 113 frame GIF takes insanely long to complete and the entire program uses an excess of 600mb!! of memory for a 700kb GIF. I am unsure how to optimise the memory usage and speed.
Thank you very much!
I am making a windows form application in C# and i'm having some problems with the movement of images with transparency. I have 4 pictureboxes with .png images with transparent background (changing the BackColor and parent properties) and I use the following code to move them:
public class runner {
public PictureBox PictBox = null;
public int Pos = 0;
public int MaxLenght = 800;
...
public bool Run()
{
int distance = Rand.Next(1, 5);
PictBox.Location = new Point(PictBox.Location.X + distance, PictBox.Location.Y);
Pos += distance;
if (Pos >= MaxLenght) return true;
else return false;
}
}
...
public Form1()
{
InitializeComponent();
runners = new Runner[]{new Runner(pictureBox3), new Runner(pictureBox4), new Runner(pictureBox5), new Runner(pictureBox6)};
...
int n = 0;
while (!victory){
foreach(Runner runn in runners)
{
n +=1;
if (runn.Run())
{
victory = true;
break;
}
}
}
The problem is that if the images have transparent backcolor only the first and the last "frames" are showed (the image in the first position and the image in the last position once the while ends). If you know how to solve the problem please help me D:
Thanks in advance.
PD: The problem appears when I change the BackColor property to transparent, the parent property doesn't affect.
I have been working with a FLIR Thermovision camera the last couple days and have pulled together a very simple application that has a few aspects found in many different places (most of which here on stackoverflow).
Topics
hosting an ActiveX component in wpf application
Float[,] array to BitmapImage
Displaying a binded bitmap in a wpf image control by using MemoryStream and BitmapImage
1. Active X control
The Flir Thermovision SDK 2.6 comes with an ActiveX component dll. AxCAMCTRLLib.dll. In a WinForms application you can simply add the tool to the tool box and click and drag the component onto the form. This will automatically add the correct references to the project. To use it in a wpf application this won't work. In hind sight this seems pretty easy but it wasn't listed in their documentation.
First I had to navigate to the AxCAMCTRLLib.dll manually and add it to the references. Then add a new window to the project. This will be a hidden window used only to host the activeX component. This also requires WindowsFormsIntegration reference to hose ActiveX components.
using CAMCTRLLib;
using AxCAMCTRLLib;
namespace camView
{
public partial class CameraCtrl : Window
{
public AxCAMCTRLLib.AxLVCam camera;
private System.Windows.Forms.Integration.WindowsFormsHost host;
public CameraCtrl()
{
InitializeComponent();
host = new System.Windows.Forms.Integration.WindowsFormsHost();
camera = new AxCAMCTRLLib.AxLVCam();
host.Child = camera;
this.grid1.Children.Add(host);
}
}
}
Now in the MainWindow I can create, show then promptly hide a new window CameraCtrl and have access to the public ActiveX control.
public MainWindow()
{
InitializeComponent();
camCtrl = new CameraCtrl();
camCtrl.BeginInit();
camCtrl.Show();
camCtrl.Hide();
camCtrl.ShowInTaskbar = false;
camCtrl.ShowActivated = false;
}
OnClosing method in MainWindow has to be modified to close the hidden window as well.
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
camCtrl.Close();
base.OnClosing(e);
}
With that I can now access all of the control methods contained in the activex object.
2. Float[,] array to BitmapImage
The output images from the camera can be returned in a number of different formats, but for the particular camera I'm using it returns an object that contains a float[,]. Since it's thermal the output pixel values represent temperatures. That means they must be normalized and then converted first to Bitmap then stored in a MemoryStream then added to the source of a BitmapImage. The method I used is the following.
private BitmapImage setDisplayImage(float[,] image)
{
float[,] tempStoreImage = new float[240 , 320];
float max = -10000.0f, min = 10000.0f;
BitmapImage localBitmap;
for (int j = 0; j < 320; j++)
{
for (int i = 0; i < 240; i++)
{
tempStoreImage[i,j] = image[j,i];//have to transpose the image from cam
if (tempStoreImage[i,j] > max)
{
max = tempStoreImage[i,j];
}
if (tempStoreImage[i,j] < min)
{
min = tempStoreImage[i,j];
}
}
}
if(max != min)//can't divide by zero
{
System.Drawing.Bitmap newBitmap = new System.Drawing.Bitmap(320, 240);
for (int i = 0; i < 240; i++)
{
for (int j = 0; j < 320; j++)
{
tempStoreImage[i,j] = (float)Math.Round((double)(tempStoreImage[i,j] - min) * 255 / (max - min));//Normalize and set between 0 - 255
System.Drawing.Color newColor = System.Drawing.Color.FromArgb((int)tempStoreImage[i, j],0, 0, 0);//Gray scale color using alpha channel
newBitmap.SetPixel(j, i, newColor);
}
}
System.Drawing.Image img = (System.Drawing.Image)newBitmap;
MemoryStream stream = new MemoryStream();
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);//add Bitmap to memory stream
stream.Position = 0;
localBitmap = new BitmapImage();
localBitmap.BeginInit();
localBitmap.StreamSource = stream; //
localBitmap.EndInit();
}
else localBitmap = new BitmapImage();//dark image
return localBitmap;
}
3. Displaying the image
I created a simple helper class:
class BindData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
BitmapImage _image;
public BitmapImage Image
{
get { return _image; }
set
{
_image = value;
_image.Freeze();
OnPropertyChanged("Image");
}
}
}
And then created a static helper class object in the MainWindow (probably doesn't need to be static but I plan to use it in other classes.) BindData bind = new BindData() and set the image1.DataContext = bind. Then set the binding and window size to match my array:
<Image Height="240" HorizontalAlignment="Left" Margin="204,21,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="320" Source="{Binding Image}"/>
And finally captured images using a System.Timers.Timer:
private void cameraCap()
{
if (continueDisplay)
{
captureTimer.Stop();
lock (camCtrl)
{
object yo = camCtrl.camera.GetImage(3);
bind.Image = setDisplayImage(yo as float[,]);
}
captureTimer.Start();
}
else
captureTimer.Stop();
}
private void capture_Click(object sender, RoutedEventArgs e)
{
continueDisplay = true;
captureTimer.Start();
}
private void kill_Click(object sender, RoutedEventArgs e)
{
continueDisplay = false;
}
Couple things I ran into with using the timer. First the application timing and the camera timing are not the same so, I stop the timer at the beginning of a capture and start it back up after the end. Luckily the thread waits for the camera to return an image. This takes care of lag for the most part. Second the _image.Freeze() statement is essential. Without it you will get "Must create DependencySource on same Thread as the DependencyObject." error once initiated. The Freeze method makes the image available to other threads.
This does belong on codereview.stackexchange.com
I would like to show a animated gif in .Net Winform. How to do this?
I previously used VB 6.0.
Put a PictureBox on a form and then specify a picture file with a Gif extension. Or:
Programatically animate a gif Image loading frames into a PictureBox with code, here's the Gif class:
VB.NET
Imports System.Drawing.Imaging
Imports System.Drawing
Public Class GifImage
Private gifImage As Image
Private dimension As FrameDimension
Private frameCount As Integer
Private currentFrame As Integer = -1
Private reverse As Boolean
Private [step] As Integer = 1
Public Sub New(path As String)
gifImage = Image.FromFile(path)
'initialize
dimension = New FrameDimension(gifImage.FrameDimensionsList(0))
'gets the GUID
'total frames in the animation
frameCount = gifImage.GetFrameCount(dimension)
End Sub
Public Property ReverseAtEnd() As Boolean
'whether the gif should play backwards when it reaches the end
Get
Return reverse
End Get
Set
reverse = value
End Set
End Property
Public Function GetNextFrame() As Image
currentFrame += [step]
'if the animation reaches a boundary...
If currentFrame >= frameCount OrElse currentFrame < 0 Then
If reverse Then
[step] *= -1
'...reverse the count
'apply it
currentFrame += [step]
Else
currentFrame = 0
'...or start over
End If
End If
Return GetFrame(currentFrame)
End Function
Public Function GetFrame(index As Integer) As Image
gifImage.SelectActiveFrame(dimension, index)
'find the frame
Return DirectCast(gifImage.Clone(), Image)
'return a copy of it
End Function
End Class
C#
using System.Drawing.Imaging;
using System.Drawing;
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 < 0) {
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
}
}
C# usage:
Open a Winform project drag and drop in a PictureBox, a Timer and a Button, with the GifImage.cs the class is shown above.
public partial class Form1: Form
{
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();
}
}
Developing on #JeremyThompson's answer I would like to add a code snippet to show how you can implement the first approach, because it is a lot simpler, and does not require you to manually animate the gif, seeing that the PictureBox has a built-in feature to handle such a scenario.
Just add a PictureBox to your form, and in the form constructor assign the image path to the PictureBox.ImageLocation
C#
public PictureForm()
{
InitializeComponent();
pictureBoxGif.ImageLocation = "C:\\throbber.gif";
}
VB.Net
Public Sub New()
InitializeComponent()
pictureBoxGif.ImageLocation = "C:\throbber.gif"
End Sub
In my oppinion this is a much simpler solution, especially for someone who is new to .NET.
I've played around with this and the animation plays provided that you don't perform another long running operation on the same thread. The moment you perform another long running operation, you'd want to do it in another thread.
The simplest way to do this, is to use the BackgroundWorker component that you can drag onto the form from your toolbox. You'd then put your long running operation code in the DoWork() event of the BackgroundWorker. The final step would be to invoke your code by calling the RunWorkerAsync() method of the BackgroundWorker instance.