我正在寻找在 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;
}
}
}