So I have a custom Button class in a Winforms app written in C# 4.0. The button holds a static image normally, but when a refresh operation takes place it switches to an animated AJAX-style button. In order to animate the image I have a timer set up that advances the image animation on every tick and sets that as the button image. That's the only way I can get it to work. The solution I have isn't that efficient; any advice would be helpful.
So two questions:
1. Is there an easier way - as in am I overlooking some feature I should be using?
2. Is there a better way to manually animate the image?
Find code below of what I'm doing on each tick. The first area of improvement: maybe copy each frame of the image to a list? Note the _image.Dispose(); failing to dispose of a local Image was causing memory leaks.
Thanks for any advice here. I will post something online and link it once I have an efficient solution.
private void TickImage()
{
if (!_stop)
{
_ticks++;
this.SuspendLayout();
Image = null;
if(_image != null)
_image.Dispose();
//Get the animated image from resources and the info
//required to grab a frame
_image = Resources.Progress16;
var dimension = new FrameDimension(_image.FrameDimensionsList[0]);
int frameCount = _image.GetFrameCount(dimension);
//Reset to zero if we're at the end of the image frames
if (_activeFrame >= frameCount)
{
_activeFrame = 0;
}
//Select the frame of the animated image we want to show
_image.SelectActiveFrame(dimension, _activeFrame);
//Assign our frame to the Image property of the button
Image = _image;
this.ResumeLayout();
_activeFrame++;
_ticks--;
}
}
I guess Winforms is not so reach in animation capabilities on its own. If you want more advanced animation use may consider some third-party solutions.
I think you should not load image from resource every tick. The better way is to preload image frames one and hold the reference. Then use it to set appropriate frame every tick.
As I've recently tested, animated gifs are animating well without any additional coding, at least on standard buttons. But if you still need to aminate frames manually, you may try something like this:
// put this somewhere in initialization
private void Init()
{
Image image = Resources.Progress16;
_dimension = new FrameDimension(image.FrameDimensionsList[0]);
_frameCount = image.GetFrameCount(_dimension);
image.SelectActiveFrame(_dimension, _activeFrame);
Image = image;
}
private void TickImage()
{
if (_stop) return;
// get next frame index
if (++_activeFrame >= _frameCount)
{
_activeFrame = 0;
}
// switch image frame
Image.SelectActiveFrame(_dimension, _activeFrame);
// force refresh (NOTE: check if it's really needed)
Invalidate();
}
Another option is to use ImageList property with preloaded static frames and cycle ImageIndex propery just as you do it with SelectActiveFrame above.
Related
My project consists of a video player and a image viewer, both fullscreen in a panel with only one visible at a time. When the video pauses I capture the frame, display it in the image viewer and swap which control is visible. The problem is, when the image viewer is shown you see the previous image for a brief second before the new image is painted.
Does anyone have any suggestions on how I can prevent this? Ideally I want it to be seemless when the controls swap.
I've already set the image viewer to be double buffered.
Edit:
Code, doesn't really show much here.
private void ShowImageView()
{
this.axWindowsMediaPlayer.Visible = false;
this.imageViewer.Visible = true;
}
private void ShowVideoView()
{
this.axWindowsMediaPlayer.Visible = true;
this.imageViewer.Visible = false;
}
I've managed to solve it, the image viewer had a Image Changed event that I hadn't spotted (I'd tried using the Image Loaded event). Subscribing to the image changed event and only showing the control then, seems to give me the transistion I was looking for.
I am making use of the AForge class library.
From this library I am using VideoSourcePlayer to take photos with the webcam.
My purpose is to create a function that allows the user to photograph images to establish them as company logo.
Not only can you choose images from the computer, but you can also capture images from the outside through the camera, since you may only want to transfer a logo of a physical support (paper) to the program.
As commented earlier in SO, (how to pause a video file played using videosourceplayer), VideoSourcePlayer does not have a Pause method or any function that allows to freeze the image.
Yes, it is true that it has the GetCurrentFrame() method, but that only gets a Bitmap from the current frame that must be passed to a PictureBox.
But I want that when the user clicks the button Capture the image of the VideoSourcePlayer simulate being frozen, and when the user presses the Delete button because he did not like the photo, then the image stops being frozen and recovers its movement.
Logic is like pausing or playing a video.
Well, there's no method for it, so I decided to look for another way to get this, and ...
If a picture is taken, use a PictureBox that contains the last frame and that is displayed on the VideoSourcePlayer, but if it is deleted, then the PictureBox is removed and the VideoSourcePlayer is returned with video.
private readonly Bitmap EmptyBitmap;
private void CaptureBtn_Click(object sender, EventArgs e)
{
Bitmap bitmap = this.VideoSource.GetCurrentVideoFrame();
ShowTakedFrame(bitmap, false);
}
private void ShowTakedFrame(Bitmap Frame, bool remove)
{
var picture = new System.Windows.Forms.PictureBox();
picture.Size = this.VideoSource.Size;
picture.Location = this.VideoSource.Location;
if (!remove)
{
this.VideoSource.Stop();
picture.Image = Frame;
this.Controls.Remove(VideoSource);
this.Controls.Add(picture);
}
else
{
this.Controls.Remove(picture);
this.Controls.Add(VideoSource);
this.VideoSource.VideoSource = this.CaptureDevice;
this.VideoSource.Start();
}
}
private void DeleteBtn_Click(object sender, EventArgs e)
{
ShowTakedFrame(EmptyBitmap, true);
}
My problem is that when capturing the photo, the image is a few seconds after the moment when you press the Capture button and when you delete the captured image, using the Delete button, the video of the VideoSourcePlayer is frozen.
Can someone help me with this?
The problem is that when you remove the PictureBox and add the VideoSourcePlayer, it creates a new object, that is, one that does not have the configuration properties of the previous one. My recommendation is that you create the capture in a different form.
I was curious as to how to create an animated .gif in C# using the imagemagick library class. This is what i am using so far:
using (MagickImageCollection collection = new MagickImageCollection())
{
//collection.CacheDirectory = #"C:\MyProgram\MyTempDir";
// Add first image and set the animation delay to 100ms
//MagickNET.Initialize(#"C:\Users\johsam\Downloads\Magick\MagickScript.xsd");
collection.Add("Koala.jpg");
collection[0].AnimationDelay = 1;
// Add second image, set the animation delay to 100ms and flip the image
collection.Add("Desert.jpg");
collection[1].AnimationDelay = 100;
collection[1].Flip();
// Optionally reduce colors
QuantizeSettings settings = new QuantizeSettings();
settings.Colors = 256;
collection.Quantize(settings);
// Optionally optimize the images (images should have the same size).
collection.Optimize();
// Save gif
collection.Write("test.Animated.gif");
}
The issue is that although it creates a .gif there is no moving image when you open it. How do you go about stringing the images together to create a moving image?
The code you are using, appears to be the example provided with the codeplex site. The logic appears to work as intended, but my presumption is that the animation delay, on the initial image in the collection, is too small (1 ms). You would arguably only see the second image. Please increase your animation delay (to 100ms), and confirm. Once completed, you can adjust the delay appropriately, to an interval that produces the desired output.
I need to show an animated gif, which I retrieve as a BLOB from a database (rather than a file), on a PictureBox, but I can't block the UI. The user can press a button that stops the referred animation.
So I create a UserControl that contains the PictureBox, where I can set the image to be shown. When I put this control on the form and starts the animation, everything occurs as expected, except for the fact that I can't press the button, 'cause the form is blocked.
When I start the animation from a Thread, the button are enabled and clickable, but in this case the animation "flickers".
I can easily change the PictureBox to another component, since it can show animated gifs. I can also transform the animated gif to some other format, if needed...
Note that
My images are animated gifs that came from BLOB's on a database and not normal files
I do not want (in fact, It's forbidden, by requirements) create a file with the images (even a temporary file)
Any thoughts?
My images are retrieved from a SQLite database, where they are stored as BLOBs. My requirements forbid the creation of files, even temporary ones. So I did it working exclusively on memory.
This code gets the image, decomposes it on frames and sets the image of the PictureBox to be each frame at a time:
byte[] img = limg.getImage();
if (img != null)
{
MemoryStream ms = new MemoryStream(img);
newImg = System.Drawing.Image.FromStream(ms);
GifImage gi = new GifImage(newImg);
for (int i = 0; i < gi.GetFrameCount; i++)
{
this.SetPicture(gi.GetNextFrame(), limg.getWord());
}
}
And this is the code that sets the image to the PictureBox (note the Sleep, that allows adjust the speed of the animation):
private void SetPicture(System.Drawing.Image img, string label)
{
if (pbOutput.InvokeRequired)
{
pbOutput.Invoke(new MethodInvoker(
delegate()
{
pbOutput.Image = img;
pbOutput.Refresh();
Thread.Sleep(Interval);
}));
}
else
{
pbOutput.Image = img;
pbOutput.Invalidate();
Thread.Sleep(Interval);
}
}
The problem, as said on the question, is the flickering of the image caused by this code.
The solution: substitutes the call of SetPicture by the code of the function. That way, I don't call a function, and put the code of it "inline" the main loop.
The flickering does not occurs anymore.
I am very new to Xamarin. What I am trying to do is build a simple application that has 3 images on it. Each image needs to slide horizontally to show the next image. I got this idea from a children's game called "mix and match". The game lets you change the head, body and feet of a cartoon animals.
So if I have a set of 5 images that are split into thirds (head,body,feet), how can I display the images on the screen so I can get them to slide?
I tried to stack 3 gallery controls but I could not get the images to fill the screen.
The screen should look something like this;
Can this be built in Xamarin for Android or IOS?
Thanks for any help.
For iOS you could use UIPageControl. You would have a different UIPageControl object for Head, Body, Feet. The idea is that you will have an UIView with a UIScrollView (also three of them). UIScrollView will contain all the different parts of body (e.g. heads). PageControl will handle the swipe gesture recognition and animations and you will set which part of the UIScrollView should be shown in UIPageControls event ValueChanged. The code will look something like this:
In ViewDidLoad:
First you add the five head parts to UIScrollView. I assume that you have these five head parts in an array of UIViews called headPageViews.
RectangleF frame;
for (int i=0; i<5; i++)
{
UIView pageView = headPageViews[i];
frame = headPageViews[i].Frame;
// change Frame.X so that the views are next to each other in scrollView
frame.X = i * this.scrollViewHead.Frame.Width;
pageView.Frame = frame;
this.scrollViewHead.AddSubViews(pageView);
}
In ViewDidLoad: Next you set up the UIPageControl which you added through interface builder:
this.pageControlHead = new UIPageControl(frame);
this.pageControlHead.HidesForSinglePage = true;
this.pageControlHead.ValueChanged += HandlePageControlHeadValueChanged;
this.pageControlHead.Pages = 5; this.viewHead.AddSubview(this.pageControlHead);
Somewhere in code: At last you handle the ValueChanged event:
private void HandlePageControlHeadValueChanged(object sender, EventArgs e)
{
this.scrollViewHead.SetContentOffset(new PointF(this.pageControlHead.CurrentPage * this.scrollViewHead.Frame.Width, 0), true);
}
Repeat these steps for other body parts and you should have the desired effect.
You can also use 3 UIScrollViews in a UIViewController positioned as the image you have added, and set that UIScrollView to enable paging.
set the cropped sections of image in those UIScrollViews, then you would be able to achieve it easily.
Hope this helps.