动画GIF的帧速率似乎低于预期

时间:2015-12-11 12:53:42

标签: c# winforms time animated-gif

我有一个winforms应用程序,其上有一个gif,用于让用户了解停止进程。

问题是它的播放速度比其他应用程序(chrome,Internet Explorer)上的速度慢得多。

我在PictureBoxLabel上尝试了gif,但结果速度相同。经过一番研究后,我遇到了this问题以及传说中的@Hans Passant的答案,但不幸的是,应用他建议的样板代码没有任何区别。

以下是简单的再现代码:

public partial class Form1 : Form
{
    public Form1 ()
    {
        InitializeComponent();
        timeBeginPeriod(timerAccuracy);
    }

    protected override void OnFormClosed ( FormClosedEventArgs e )
    {
        timeEndPeriod(timerAccuracy);
        base.OnFormClosed(e);
    }

    // Pinvoke:
    private const int timerAccuracy = 10;
    [System.Runtime.InteropServices.DllImport("winmm.dll")]
    private static extern int timeBeginPeriod ( int msec );
    [System.Runtime.InteropServices.DllImport("winmm.dll")]
    public static extern int timeEndPeriod ( int msec );
}

如果需要,设计师代码:

partial class Form1
{
    private System.ComponentModel.IContainer components = null;

    protected override void Dispose ( bool disposing )
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    private void InitializeComponent ()
    {
        System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
        this.pictureBox1 = new System.Windows.Forms.PictureBox();
        this.label1 = new System.Windows.Forms.Label();
        ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
        this.SuspendLayout();
        //
        // pictureBox1
        //
        this.pictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
        this.pictureBox1.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox1.Image")));
        this.pictureBox1.Location = new System.Drawing.Point(8, 9);
        this.pictureBox1.Name = "pictureBox1";
        this.pictureBox1.Size = new System.Drawing.Size(166, 119);
        this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
        this.pictureBox1.TabIndex = 0;
        this.pictureBox1.TabStop = false;
        //
        // label1
        //
        this.label1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
        this.label1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
        this.label1.Image = ((System.Drawing.Image)(resources.GetObject("label1.Image")));
        this.label1.Location = new System.Drawing.Point(180, 9);
        this.label1.Name = "label1";
        this.label1.Size = new System.Drawing.Size(158, 119);
        this.label1.TabIndex = 1;
        //
        // Form1
        //
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(346, 134);
        this.Controls.Add(this.label1);
        this.Controls.Add(this.pictureBox1);
        this.Name = "Form1";
        this.Text = "Form1";
        ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.PictureBox pictureBox1;
    private System.Windows.Forms.Label label1;
}

两个GIF以相同的速度播放,但低于实际的GIF。在应用此代码时,我应该注意其他任何要点吗?

2 个答案:

答案 0 :(得分:4)

你只能猜到,我怀疑任何人都会有很多运气得到一个重复:

  • timeBeginPeriod()在技术上可能会失败,尽管当你要求10毫秒时这是非常不寻常的,请确认它返回0.
  • 如果图像很大,那么它可能无法足够快地更新。或者你的UI线程被其他职责占用太多。 gif的像素格式与现代机器上的视频适配器的像素格式不匹配。每次更新帧时都会完成转换。它相当昂贵,特别是如果你也强制重新缩放图像(即PictureBox.SizeMode!= Normal)。使用任务管理器验证您的UI线程没有刻录100%核心。
  • 您可以通过从提升的命令提示符运行powercfg /energy来获得有效计时器周期的第二意见。在您的应用运行时这样做。它会拖延一分钟,然后生成一个HTML文件,您可以使用浏览器查看该文件。根据“平台计时器解决方案:计时器请求堆栈”标题报告,请求期间值应为10000.请注意其他进程或驱动程序也可能已提出请求。

答案 1 :(得分:1)

setTimeout(function(){ $('#abc').fadeIn(); $('#abc').addClass('noHighlight'); },1000); 是一个非常重量级的控件,我建议使用像PictureBox这样的东西来代替动画GIF。另外,我读过Panel的内部动画定时器是低分辨率的,这意味着选择<100ms的更新间隔会导致它更新为100ms更新。

相反,你可以自己控制绘画和动画。这使用PInvoke,因为它使用了一些内核计时器方法。示例代码如下:

PictureBox

注意:我们将定时器调用的using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using System.Windows.Forms; ... public partial class Form1 : Form { [DllImport("kernel32.dll")] static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer, IntPtr TimerQueue, WaitOrTimerDelegate Callback, IntPtr Parameter, uint DueTime, uint Period, uint Flags); [DllImport("kernel32.dll")] static extern bool ChangeTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, uint DueTime, uint Period); [DllImport("kernel32.dll")] static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, IntPtr CompletionEvent); public delegate void WaitOrTimerDelegate(IntPtr lpParameter, bool TimerOrWaitFired); // Holds a reference to the function to be called when the timer // fires public static WaitOrTimerDelegate UpdateFn; public enum ExecuteFlags { /// <summary> /// The callback function is queued to an I/O worker thread. This flag should be used if the function should be executed in a thread that waits in an alertable state. /// The callback function is queued as an APC. Be sure to address reentrancy issues if the function performs an alertable wait operation. /// </summary> WT_EXECUTEINIOTHREAD = 0x00000001, }; private Image gif; private int frameCount = -1; private UInt32[] frameIntervals; private int currentFrame = 0; private static object locker = new object(); private IntPtr timerPtr; public Form1() { InitializeComponent(); // Attempt to reduce flicker - all control painting must be // done in overridden paint methods this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); // Set the timer callback UpdateFn = new WaitOrTimerDelegate(UpdateFrame); } private void Form1_Load(object sender, EventArgs e) { // Replace this with whatever image you're animating gif = (Image)Properties.Resources.SomeAnimatedGif; // How many frames of animation are there in total? frameCount = gif.GetFrameCount(FrameDimension.Time); // Retrieve the frame time property PropertyItem propItem = gif.GetPropertyItem(20736); int propIndex = 0; frameIntervals = new UInt32[frameCount]; // Each frame can have a different timing - retrieve each of them for (int i = 0; i < frameCount; i++) { // NB: intervals are given in hundredths of a second, so need // multiplying to match the timer's millisecond interval frameIntervals[i] = BitConverter.ToUInt32(propItem.Value, propIndex) * 10; // Point to the next interval stored in this property propIndex += 4; } // Show the first frame of the animation ShowFrame(); // Start the animation. We use a TimerQueueTimer which has better // resolution than Windows Forms' default one. It should be used // instead of the multimedia timer, which has been deprecated CreateTimerQueueTimer(out this.timerPtr, IntPtr.Zero, UpdateFn, IntPtr.Zero, frameIntervals[0], 100000, (uint)ExecuteFlags.WT_EXECUTEINIOTHREAD); } private void UpdateFrame(IntPtr lpParam, bool timerOrWaitFired) { // The timer has elapsed // Update the number of the frame to show next currentFrame = (currentFrame + 1) % frameCount; // Paint the frame to the panel ShowFrame(); // Re-start the timer after updating its interval to that of // the new frame ChangeTimerQueueTimer(IntPtr.Zero, this.timerPtr, frameIntervals[currentFrame], 100000); } private void ShowFrame() { // We need to use a lock as we cannot update the GIF at the // same time as it's being drawn lock (locker) { gif.SelectActiveFrame(FrameDimension.Time, currentFrame); } this.panel1.Invalidate(); } private void panel1_Paint(object sender, PaintEventArgs e) { base.OnPaint(e); lock (locker) { e.Graphics.DrawImage(gif, panel1.ClientRectangle); } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { DeleteTimerQueueTimer(IntPtr.Zero, timerPtr, IntPtr.Zero); } } 设置为Period,因为如果将其设置为100000(表示一次性定时),则只会触发一次,即使您随后致电0

定时器仍然不适合超精确的时序,但这仍然可以让您比ChangeTimerQueueTimer更快地获得更新。