如何在DirectX视频(以PictureBox为载体)上绘制/渲染文字?
刚好我之前折腾过WinForms里DirectX视频叠加文字的需求,给你几个实用的解决方案,亲测有效:
解决方案一:利用PictureBox的Paint事件叠加文字
这是最直接的方法,因为你已经在用PictureBox承载DirectX视频,只需要在视频渲染之后,通过GDI+在控件上叠加绘制文字。
步骤1:开启双缓冲避免闪烁
先给你的PictureBox开启双缓冲,不然文字和视频切换时会有明显闪烁:// 可以在窗体构造函数里设置 pictureBox1.DoubleBuffered = true; // 如果上面的设置不生效,也可以通过反射强制开启(WinForms的PictureBox默认双缓冲是关闭的) typeof(PictureBox).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, pictureBox1, new object[] { true });步骤2:订阅Paint事件绘制文字
在PictureBox的Paint事件里,用Graphics.DrawString()绘制你需要的文字,注意要设置合适的样式让文字在视频上更清晰:private void pictureBox1_Paint(object sender, PaintEventArgs e) { // 可选:给文字加半透明背景,提升可读性 using (var bgBrush = new SolidBrush(Color.FromArgb(150, 0, 0, 0))) { e.Graphics.FillRectangle(bgBrush, new Rectangle(20, 20, 250, 40)); } // 绘制文字 using (var textFont = new Font("Segoe UI", 14, FontStyle.Bold)) using (var textBrush = new SolidBrush(Color.White)) { e.Graphics.DrawString("你的自定义文字", textFont, textBrush, new PointF(25, 25)); } }步骤3:触发实时重绘
每次视频帧更新后,需要调用pictureBox1.Invalidate()触发Paint事件,确保文字能叠加在最新的视频帧上:- 如果你的DirectX Video类有帧更新的事件(比如
FrameUpdated),就在该事件里调用Invalidate(); - 如果没有帧事件,就用一个定时器定期触发,比如设置30ms间隔(对应30fps):
var updateTimer = new Timer { Interval = 1000 / 30 }; updateTimer.Tick += (s, args) => pictureBox1.Invalidate(); updateTimer.Start();
- 如果你的DirectX Video类有帧更新的事件(比如
解决方案二:自定义控件整合视频渲染与文字绘制
如果想要更可控的渲染流程,可以自定义一个控件,把DirectX视频渲染和文字绘制整合到同一个控件的OnPaint方法里,避免依赖PictureBox的事件:
public class VideoBackgroundControl : Control { private YourDirectXVideoClass _videoPlayer; // 替换成你实际的DirectX Video类 private Timer _renderTimer; public VideoBackgroundControl() { DoubleBuffered = true; SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); // 初始化DirectX视频播放器,设置渲染句柄为当前控件 _videoPlayer = new YourDirectXVideoClass(); _videoPlayer.SetRenderHandle(this.Handle); } public void PlayVideo(string videoPath) { _videoPlayer.LoadVideo(videoPath); _videoPlayer.Play(); // 启动定时器触发重绘 _renderTimer = new Timer { Interval = 1000 / 30 }; _renderTimer.Tick += (s, e) => Invalidate(); _renderTimer.Start(); } protected override void OnPaint(PaintEventArgs e) { // 第一步:让DirectX渲染最新的视频帧到控件 _videoPlayer.RenderCurrentFrame(); // 第二步:在视频帧上绘制文字 using (var textFont = new Font("Arial", 12, FontStyle.Regular)) using (var textBrush = Brushes.WhiteSmoke) { // 示例:居中绘制文字 var textRect = ClientRectangle; var stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; e.Graphics.DrawString("居中显示的文字", textFont, textBrush, textRect, stringFormat); } base.OnPaint(e); } protected override void Dispose(bool disposing) { if (disposing) { _renderTimer?.Dispose(); _videoPlayer?.Dispose(); } base.Dispose(disposing); } }
使用时直接在窗体里添加这个自定义控件,调用PlayVideo()方法即可。
注意事项
- 渲染顺序:必须先渲染视频帧,再绘制文字,不然文字会被视频覆盖;
- 资源释放:所有GDI+对象(Font、Brush等)一定要用
using包裹,或者手动调用Dispose(),避免内存泄漏; - 性能考量:如果是高分辨率、高帧率的视频,优先选方案一或方案二,方案三(帧转Bitmap再绘制)的性能会差一些,适合低帧率场景。
内容的提问来源于stack exchange,提问作者Nii
相关产品推荐
相关产品推荐

