0

我正在寻找在 Winform 上刷新图像(并绘制一些形状)的最快方法。我现在正在使用 PictureBox,但我愿意接受建议。

我从这里获得的最大 FPS 的方式:“我关于渲染循环的最后一篇文章(希望如此)

这是一个游戏循环模式,但它也可以用于其他目的,例如显示从相机获取的实时图像。

我在不同的位置画了一个红色圆圈。

如您所见,当在 PictureBox 中加载位图时,FPS 会下降很多。尤其是在 SizeMode Zoom 中(我想使用的那个)。

我的 PC FPS 值:

Form size (start size)
Bitmap ON: 140 (Zoom); 1000 (Normal); 135 (Stretch); 880 (Auto); 1000 (Center)
Bitmap OFF: 3400 !!

Form size (after Zoom 100% click)
Bitmap ON: 40 (Zoom); 150 (Normal); 65 (Stretch); 150 (Auto); 150 (Center)
Bitmap OFF: 540 !!

编辑 1:嗯,这个结果是针对 1024x1024 图像的。但是我意识到表单高度没有得到正确的值,我发现这是因为表单大小受屏幕分辨率的限制。所以我编辑了代码以加载 800x600 位图。现在缩放 100% 按钮可以工作了。如您所见,在 100% 缩放时 FPS 为 350,但如果您将 PictureBox 的大小再增加一个像素,则 FPS 降至 35,大约慢了 10 倍 ¬¬!

最大的问题是:如何提高 FPS,特别是在缩放模式下?

PS:我知道 WinForm 不是最好的方法,我也知道 WPF。但现在我正在寻找使用 WinForm 的解决方案。好?原因是我有一个可怕的大项目使用它。我正计划迁移到 WPF,但我仍在学习它。:)

这是完整的代码,所以你可以自己测试它。Form1.cs:

//.NET 4.0 Client Profile
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
//You need to add System.Activities.Presentation.dll
using System.Activities.Presentation.Hosting;

namespace WindowsFormsApplication2
{
    public enum DrawSource
    {
        None = 0,
        PictureBox = 1,
        Grapghics = 2,
    }

    public partial class Form1 : Form
    {
        private readonly Timer _updateFPSTimer;
        private bool _isIdle;
        private Point _location;
        private double _updateCount;
        private readonly Bitmap _backImage;

        public Form1()
        {
            InitializeComponent();

            this.DoubleBuffered = this.ckbDoubleBufferForm.Checked;
            this.pictureBox1.DoubleBuffered = this.ckbDoubleBufferPictureBox.Checked;

            _backImage = new Bitmap(800, 600, PixelFormat.Format24bppRgb);
            Graphics g = Graphics.FromImage(_backImage);
            int penWidth = 10;
            Pen pen = new Pen(Brushes.Blue, penWidth);

            g.DrawRectangle(pen, new Rectangle(new Point(penWidth, penWidth), new Size(_backImage.Size.Width - 2 * penWidth, _backImage.Size.Height - 2 * penWidth)));

            this.cbxSizeMode.DataSource = Enum.GetValues(typeof(PictureBoxSizeMode));
            this.cbxSizeMode.SelectedItem = PictureBoxSizeMode.Zoom;

            this.cbxBitmapDrawSource.DataSource = Enum.GetValues(typeof(DrawSource));
            this.cbxBitmapDrawSource.SelectedItem = DrawSource.PictureBox;

            this.pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
            Application.Idle += Application_Idle;

            _updateFPSTimer = new Timer();
            _updateFPSTimer.Tick += UpdateFPSTimer_Tick;
            _updateFPSTimer.Interval = Convert.ToInt32(1000.0 / 5); //Period in s = 1 s / 5 Hz
            _updateFPSTimer.Start();

        }

        private void Application_Idle(object sender, EventArgs e)
        {
            while (this.ckbRun.Checked && IsAppStillIdle())
                this.pictureBox1.Refresh();
        }

        void UpdateFPSTimer_Tick(object sender, EventArgs e)
        {
            GetFPS();
        }

        private void GetFPS()
        {
            double fps = _updateCount / (Convert.ToDouble(_updateFPSTimer.Interval) / 1000.0);
            _updateCount = 0;
            lblFps.Text = fps.ToString("0.00");
        }

        private void PictureBox1_Paint(object sender, PaintEventArgs e)
        {
            DrawCircle(e.Graphics);
        }

        private void DrawCircle(Graphics g)
        {
            if (this.pictureBox1.Image == null && ((DrawSource)this.cbxBitmapDrawSource.SelectedItem == DrawSource.Grapghics))
                g.DrawImage(_backImage, 0, 0, g.ClipBounds.Width, g.ClipBounds.Height);

            var rect = new Rectangle(_location, new Size(30, 30));

            g.DrawEllipse(Pens.Red, rect);

            _location.X += 1;
            _location.Y += 1;

            if (_location.X > g.ClipBounds.Width)
                _location.X = 0;

            if (_location.Y > g.ClipBounds.Height)
                _location.Y = 0;

            _updateCount++;
        }

        /// <summary>
        /// Gets if the app still idle.
        /// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
        /// </summary>
        /// <returns></returns>
        private bool IsAppStillIdle()
        {
            Message msg;
            return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
        }

        #region  Unmanaged Get PeekMessage
        // http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
        [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

        #endregion

        private void btnZoomReset_Click(object sender, EventArgs e)
        {
            if (_backImage != null)
            {
                //Any smarter way to do this?
                //Note that the maximum Form size is limmited by designe by the Screen resolution.
                //Rectangle screenRectangle = RectangleToScreen(this.ClientRectangle);
                //int titleHeight = screenRectangle.Top - this.Top;
                //Borders
                Size border = new Size();
                border.Width = this.Width - this.pictureBox1.Width;
                border.Height = this.Height - this.pictureBox1.Height;
                this.Width = border.Width + _backImage.Width;
                this.Height = border.Height + _backImage.Height;
                Console.WriteLine("PictureBox size: " + this.pictureBox1.Size.ToString());
            }

        }

        private void SizeMode_SelectedIndexChanged(object sender, EventArgs e)
        {
            PictureBoxSizeMode mode;
            Enum.TryParse<PictureBoxSizeMode>(cbxSizeMode.SelectedValue.ToString(), out mode);

            this.pictureBox1.SizeMode = mode;
        }

        private void DoubleBufferForm_CheckedChanged(object sender, EventArgs e)
        {
            this.DoubleBuffered = this.ckbDoubleBufferForm.Checked;
        }

        private void DoubleBufferPictureBox_CheckedChanged(object sender, EventArgs e)
        {
            this.pictureBox1.DoubleBuffered = this.ckbDoubleBufferPictureBox.Checked;
        }

        private void BitmapDrawSource_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((DrawSource)this.cbxBitmapDrawSource.SelectedItem == DrawSource.PictureBox)
                this.pictureBox1.Image = _backImage;
            else
                this.pictureBox1.Image = null;
        }
    }
}

设计师.CS:

namespace WindowsFormsApplication2
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.pictureBox1 = new WindowsFormsApplication2.PictureBoxDoubleBuffer();
            this.label1 = new System.Windows.Forms.Label();
            this.lblFps = new System.Windows.Forms.Label();
            this.ckbRun = new System.Windows.Forms.CheckBox();
            this.btnZoomReset = new System.Windows.Forms.Button();
            this.label2 = new System.Windows.Forms.Label();
            this.cbxSizeMode = new System.Windows.Forms.ComboBox();
            this.gpbDoubleBuffer = new System.Windows.Forms.GroupBox();
            this.ckbDoubleBufferPictureBox = new System.Windows.Forms.CheckBox();
            this.ckbDoubleBufferForm = new System.Windows.Forms.CheckBox();
            this.cbxBitmapDrawSource = new System.Windows.Forms.ComboBox();
            this.label3 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
            this.gpbDoubleBuffer.SuspendLayout();
            this.SuspendLayout();
            // 
            // pictureBox1
            // 
            this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.pictureBox1.BackColor = System.Drawing.SystemColors.ControlDarkDark;
            this.pictureBox1.DoubleBuffered = true;
            this.pictureBox1.Location = new System.Drawing.Point(8, 84);
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(347, 215);
            this.pictureBox1.TabIndex = 0;
            this.pictureBox1.TabStop = false;
            this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.PictureBox1_Paint);
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(134, 13);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(30, 13);
            this.label1.TabIndex = 3;
            this.label1.Text = "FPS:";
            // 
            // lblFps
            // 
            this.lblFps.AutoSize = true;
            this.lblFps.Location = new System.Drawing.Point(160, 13);
            this.lblFps.Name = "lblFps";
            this.lblFps.Size = new System.Drawing.Size(10, 13);
            this.lblFps.TabIndex = 4;
            this.lblFps.Text = "-";
            // 
            // ckbRun
            // 
            this.ckbRun.Appearance = System.Windows.Forms.Appearance.Button;
            this.ckbRun.AutoSize = true;
            this.ckbRun.Checked = true;
            this.ckbRun.CheckState = System.Windows.Forms.CheckState.Checked;
            this.ckbRun.Location = new System.Drawing.Point(8, 8);
            this.ckbRun.Name = "ckbRun";
            this.ckbRun.Size = new System.Drawing.Size(37, 23);
            this.ckbRun.TabIndex = 5;
            this.ckbRun.Text = "Run";
            this.ckbRun.UseVisualStyleBackColor = true;
            // 
            // btnZoomReset
            // 
            this.btnZoomReset.Location = new System.Drawing.Point(51, 8);
            this.btnZoomReset.Name = "btnZoomReset";
            this.btnZoomReset.Size = new System.Drawing.Size(74, 23);
            this.btnZoomReset.TabIndex = 7;
            this.btnZoomReset.Text = "Zoom 100%";
            this.btnZoomReset.UseVisualStyleBackColor = true;
            this.btnZoomReset.Click += new System.EventHandler(this.btnZoomReset_Click);
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(5, 37);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(57, 13);
            this.label2.TabIndex = 8;
            this.label2.Text = "SizeMode:";
            // 
            // cbxSizeMode
            // 
            this.cbxSizeMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cbxSizeMode.FormattingEnabled = true;
            this.cbxSizeMode.Location = new System.Drawing.Point(73, 34);
            this.cbxSizeMode.Name = "cbxSizeMode";
            this.cbxSizeMode.Size = new System.Drawing.Size(84, 21);
            this.cbxSizeMode.TabIndex = 9;
            this.cbxSizeMode.SelectedIndexChanged += new System.EventHandler(this.SizeMode_SelectedIndexChanged);
            // 
            // gpbDoubleBuffer
            // 
            this.gpbDoubleBuffer.Controls.Add(this.ckbDoubleBufferPictureBox);
            this.gpbDoubleBuffer.Controls.Add(this.ckbDoubleBufferForm);
            this.gpbDoubleBuffer.Location = new System.Drawing.Point(221, 8);
            this.gpbDoubleBuffer.Name = "gpbDoubleBuffer";
            this.gpbDoubleBuffer.Size = new System.Drawing.Size(99, 65);
            this.gpbDoubleBuffer.TabIndex = 10;
            this.gpbDoubleBuffer.TabStop = false;
            this.gpbDoubleBuffer.Text = "Double Buffer";
            // 
            // ckbDoubleBufferPictureBox
            // 
            this.ckbDoubleBufferPictureBox.AutoSize = true;
            this.ckbDoubleBufferPictureBox.Checked = true;
            this.ckbDoubleBufferPictureBox.CheckState = System.Windows.Forms.CheckState.Checked;
            this.ckbDoubleBufferPictureBox.Location = new System.Drawing.Point(9, 38);
            this.ckbDoubleBufferPictureBox.Name = "ckbDoubleBufferPictureBox";
            this.ckbDoubleBufferPictureBox.Size = new System.Drawing.Size(77, 17);
            this.ckbDoubleBufferPictureBox.TabIndex = 8;
            this.ckbDoubleBufferPictureBox.Text = "PictureBox";
            this.ckbDoubleBufferPictureBox.UseVisualStyleBackColor = true;
            this.ckbDoubleBufferPictureBox.CheckedChanged += new System.EventHandler(this.DoubleBufferPictureBox_CheckedChanged);
            // 
            // ckbDoubleBufferForm
            // 
            this.ckbDoubleBufferForm.AutoSize = true;
            this.ckbDoubleBufferForm.Checked = true;
            this.ckbDoubleBufferForm.CheckState = System.Windows.Forms.CheckState.Checked;
            this.ckbDoubleBufferForm.Location = new System.Drawing.Point(9, 15);
            this.ckbDoubleBufferForm.Name = "ckbDoubleBufferForm";
            this.ckbDoubleBufferForm.Size = new System.Drawing.Size(49, 17);
            this.ckbDoubleBufferForm.TabIndex = 7;
            this.ckbDoubleBufferForm.Text = "Form";
            this.ckbDoubleBufferForm.UseVisualStyleBackColor = true;
            this.ckbDoubleBufferForm.CheckedChanged += new System.EventHandler(this.DoubleBufferForm_CheckedChanged);
            // 
            // cbxBitmapDrawSource
            // 
            this.cbxBitmapDrawSource.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cbxBitmapDrawSource.FormattingEnabled = true;
            this.cbxBitmapDrawSource.Items.AddRange(new object[] {
            "PictureBox",
            "Graphics"});
            this.cbxBitmapDrawSource.Location = new System.Drawing.Point(73, 57);
            this.cbxBitmapDrawSource.Name = "cbxBitmapDrawSource";
            this.cbxBitmapDrawSource.Size = new System.Drawing.Size(84, 21);
            this.cbxBitmapDrawSource.TabIndex = 12;
            this.cbxBitmapDrawSource.SelectedIndexChanged += new System.EventHandler(this.BitmapDrawSource_SelectedIndexChanged);
            // 
            // label3
            // 
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(5, 60);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(68, 13);
            this.label3.TabIndex = 11;
            this.label3.Text = "Bitmap draw:";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(361, 304);
            this.Controls.Add(this.cbxBitmapDrawSource);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.gpbDoubleBuffer);
            this.Controls.Add(this.cbxSizeMode);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.btnZoomReset);
            this.Controls.Add(this.ckbRun);
            this.Controls.Add(this.lblFps);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.pictureBox1);
            this.DoubleBuffered = true;
            this.Name = "Form1";
            this.Text = "Max FPS tester";
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
            this.gpbDoubleBuffer.ResumeLayout(false);
            this.gpbDoubleBuffer.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        PictureBoxDoubleBuffer pictureBox1;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label lblFps;
        private System.Windows.Forms.CheckBox ckbRun;
        private System.Windows.Forms.Button btnZoomReset;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.ComboBox cbxSizeMode;
        private System.Windows.Forms.GroupBox gpbDoubleBuffer;
        private System.Windows.Forms.CheckBox ckbDoubleBufferPictureBox;
        private System.Windows.Forms.CheckBox ckbDoubleBufferForm;
        private System.Windows.Forms.ComboBox cbxBitmapDrawSource;
        private System.Windows.Forms.Label label3;
    }
}

PictureBoxDoubleBuffer.cs:

using System.Windows.Forms;

namespace WindowsFormsApplication2
{
   public class PictureBoxDoubleBuffer : PictureBox
   {
       public bool DoubleBuffered
       {
           get { return base.DoubleBuffered; }
           set { base.DoubleBuffered = value; }
       }

       public PictureBoxDoubleBuffer()
       {
           base.DoubleBuffered = true;
       }
    }
}
4

1 回答 1

0

“最大的问题是:如何提高 FPS,特别是在缩放模式下?”

在缩放(和拉伸)模式下,可能会为每个 Refresh()调整Image() 的大小……这是一个非常昂贵的操作。您可以滚动自己的“缩放”模式,创建一个已经缩放的新图像并分配它,将模式保留为“正常”。

这是一个使用另一个 PictureBox 作弊缩放的快速示例(显然,您可以通过计算纵横比等在数学上进行缩放……但这在早上伤害了我的大脑):

    private void cbxSizeMode_SelectedIndexChanged(object sender, EventArgs e)
    {
        PictureBoxSizeMode mode;
        Enum.TryParse<PictureBoxSizeMode>(cbxSizeMode.SelectedValue.ToString(), out mode);
        this.pictureBox1.SizeMode = mode;

        if (this.pictureBox1.SizeMode == PictureBoxSizeMode.Zoom)
        {
            using (PictureBox pb = new PictureBox())
            {
                pb.Size = pictureBox1.Size;
                pb.SizeMode = PictureBoxSizeMode.Zoom;
                pb.Image = _backImage;
                Bitmap bmp = new Bitmap(pb.Size.Width, pb.Size.Height);
                pb.DrawToBitmap(bmp, pb.ClientRectangle);

                this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
                this.pictureBox1.Image = bmp;
            }
        }
        else
        {
            pictureBox1.Image = _backImage;
        }
    }

如果 Zoom 是默认选择,您需要在表单首次运行时调用此方法,否则它将没有自定义缩放图像。

现在您的缩放模式应该会放大!(对不起,忍不住这样做)

于 2013-06-07T17:11:39.887 回答