Can a PictureBox show animated GIF in Windows Application? - c#

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.

Related

Showing a lot of picturebox in a form and playing gif, causes the form and gifs to freeze

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 am having trouble trying to optimise a function to split a GIF into an array of the Image object

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!

Saving a bitmap from a variable gives "invalid parameter"

I need to save an image to disk that came from a web-cam between 5 and 10 seconds ago from when the "save" command comes in via serial port.
To get there, I have the webcam going into a pictureBox.Image (using opencv4), and then 2 Bitmap variables. Every 5 seconds a timer ticks, and Stored_bitmap_2 = Stored_bitmap_1, then Stored_bitmap_1 = (bitmap) pictureBox.Image.
When the right serial command comes in, I try to
Stored_image_2.Save("C:\Users\GreenWorld\Desktop\test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
and I get a run-time error of invalid parameter.
When I do the same thing in a stand-alone project with some buttons (inside the button-click event handler), it works every time.
When I do this inside the serialPort_DataReceived handler, I get all kinds of cross-thread errors. So, I moved the save attempt to its own subroutine, and that fixed that but now this.
I am by no means a professional programmer, I'm an engineer with a simple problem and I can usually write a little simplistic code to fix my immediate issue. Please go easy on me in the explanation :-)
Sample code:
using OpenCvSharp;
using OpenCvSharp.Extensions;
namespace Weld_picture
{
public partial class Form1 : Form
{
// Create class-level accessible variables
int Welding_camera_ID = 1;
VideoCapture capture;
Mat frame;
Bitmap image;
private Thread camera;
bool isCameraRunning = false;
string Serial_command = "";
Bitmap Stored_image_1;
Bitmap Stored_image_2;
bool Image_saved = false;
string Station_ID = "GWM-PWS-01";
string File_name = "";
private void CaptureCamera() // from someone else's sample code
{
camera = new Thread(new ThreadStart(CaptureCameraCallback));
camera.Start();
}
private void CaptureCameraCallback() // from someone else's sample code
{
frame = new Mat();
capture = new VideoCapture(Welding_camera_ID);
capture.Open(Welding_camera_ID);
if (capture.IsOpened())
{
while (isCameraRunning)
{
capture.Read(frame);
image = BitmapConverter.ToBitmap(frame);
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = image;
}
}
}
public Form1()
{
InitializeComponent();
SerialPort1.Open();
}
private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string dummy;
char CR = (char)0x0D;
while (SerialPort1.BytesToRead > 0)
{
Serial_command += (char)SerialPort1.ReadByte();
}
while (Serial_command.IndexOf(CR) > 0)
{
dummy = Serial_command.Substring(0, Serial_command.IndexOf(CR));
Serial_command = Serial_command.Substring(Serial_command.IndexOf(CR) + 1, (Serial_command.Length - (Serial_command.IndexOf(CR) + 1)));
Serial_command.Trim();
dummy.Trim();
proc_Process_serial_data(dummy);
}
}
//*************************************************************************************************************************************
/* the first timer is a 5-second interval. It's the "memory" function so that if/when the save-to-disk is triggered I can store the last-time-shutter-open image */
//*************************************************************************************************************************************
private void Timer_picture_interval_Tick(object sender, EventArgs e)
{
checkBox1.Checked = !checkBox1.Checked;
Timer_picture_interval.Stop();
Stored_image_2 = Stored_image_1;
Stored_image_1 = (Bitmap) pictureBox1.Image;
Timer_picture_interval.Start();
}
//*************************************************************************************************************************************
// the second timer is a 30-second interval. It's the way to turn capture off if the PLC/camera box somehow goes off-line
//*************************************************************************************************************************************
private void Timer_camera_powerdown_Tick(object sender, EventArgs e)
{
if (isCameraRunning)
capture.Release();
isCameraRunning = false;
Timer_picture_interval.Stop();
}
//*************************************************************************************************************************************
private void proc_Process_serial_data(string Serial_string)
{
if (Serial_string.IndexOf("Still here") > 0)
{
if (!isCameraRunning)
CaptureCamera();
isCameraRunning = true;
}
if (Serial_string.IndexOf("Sun's up") > 0)
{
Timer_picture_interval.Start();
Timer_camera_powerdown.Start();
}
if (Serial_string.IndexOf("It's dark") > 0)
{
if ((Stored_image_2 != null) && (!Image_saved)) // in case there's 2 subsequent requests to save the same thing (weld stutter)
{
File_name = "C:\\Users\\GreenWorld\\Desktop\\" + Station_ID + " D" + DateTime.Now.ToString("yyyy_MM_dd THH_mm_ss") + ".jpg";
Stored_image_2.Image.Save("C:\\Users\\GreenWorld\\Desktop\\test.bmp" , System.Drawing.Imaging.ImageFormat.Bmp );
Image_saved = true;
Timer_picture_interval.Stop();
}
Timer_camera_powerdown.Start();
}
}
}
}
You likely be able to fix the cros-threading error by simply calling your proc_Process_serial_data() method within an Invoke() call.
Change:
proc_Process_serial_data(dummy);
To:
this.Invoke((MethodInvoker)delegate ()
{
proc_Process_serial_data(dummy);
});
Also, these two lines aren't actually doing anything:
Serial_command.Trim();
dummy.Trim();
To Trim() the strings, you have capture the returned strings and re-assign them to the original variables:
Serial_command = Serial_command.Trim();
dummy = dummy.Trim();

Bitmap animation stops working, after running a few frames

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();

Display image feed of `float[,]` thermal images from FLIR camera

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

Categories

Resources