用C#实现简单的字幕动态叠加效果
陈健
引言
最近和同学们去KTV时,注意到电视上显示歌词时,字幕随音乐的变化而动态叠加,我就想在程序中实现字幕动态叠加的效果。通过努力,我已经可以在程序中实现简单的字幕动态叠加了。我用GDI+实现了我的程序。
我利用路径输出字幕,然后定义了一个Region对象,用Timer让这个区域递增,改变区域和路径的交集, 通过填充它们的交集达到字幕动态叠加的效果。
GDI+编程基础
字幕叠加,应当是属于图形、图像处理的范畴,所以我用GDI+实现我的目标。下面简单的介绍一下.NET中的GDI+。
GDI+ 使您可以创建图形、绘制文本以及将图形图像作为对象操作。GDI+ 旨在提供较好的性能并且易于使用。可以使用 GDI+ 在 Windows 窗体和控件上呈现图形图像。GDI+ 已完全替代 GDI,目前是在 Windows 窗体应用程序中以编程方式呈现图形的唯一方法。也可以在设计时使用图像编辑器来创建图像以便应用程序以后使用。
GDI+基类库非常大,我在这不可能具体解释,也没必要,想真正熟悉GDI+可参考MSDN,并多用其中的知识编程练习。
下表列出了GDI+基类中的主要名称空间,并作了简单说明。
名称空间 |
简单说明 |
System.Drawing |
提供对GDI+ 基本图形功能的访问,包含大多数类、结构、委托、枚举等 |
System.Drawing.Design |
包含扩展设计时用户界面 (UI) 逻辑和绘制的类。 |
System.Drawing.Drawing2D |
提供高级的二维和矢量图形功能。此命名空间包含梯度型画刷、Matrix 类(用于定义几何变换)和 GraphicsPath 类等 |
System.Drawing.Imaging |
提供高级 GDI+ 图像处理功能。基本图形功能由 System.Drawing 命名空间提供 |
System.Drawing.Printing |
提供与打印相关的服务 |
System.Drawing.Text |
提供高级GDI+ 排版功能, 基本图形功能由 System.Drawing 命名空间提供。该命名空间中的类允许用户创建和使用多种字体 |
路径和区域介绍
我在实现我的目标时,用到了路径和区域。下面介绍一下路径和区域。
路径是通过组合直线、矩形和简单的曲线而形成的。以下基本构造块已被证明对于绘制图片是非常有用的:
- 线
- 矩形
- 椭圆
- 弧线
- 多边形
- 基数样条
- 贝塞尔样条
在 GDI+ 中,要绘制路径,需要有 Graphics 对象、Pen 对象和 GraphicsPath 对象。Graphics 对象提供 DrawPath 方法,Pen 对象存储用于呈现路径的线条属性,例如,宽度和颜色。GraphicsPath 对象存储构成路径的直线和曲线序列。Pen 对象和 GraphicsPath 对象作为参数传递到 DrawPath 方法。
除了向路径添加直线、矩形和曲线外,还可以向路径添加路径。您还可以把其他两个项目加入路径:字符串和扇形。扇形是椭圆内的一部分。
下面显示了一个用弧形、基数样条、字符串和扇形创建的路径:
以下是实现上面路径的C#代码:
GraphicsPath myGraphicsPath = new GraphicsPath();
Point[] myPointArray = {new Point(5, 30), new Point(20, 40), new Point(50, 30)};
FontFamily myFontFamily = new FontFamily("Times New Roman");
PointF myPointF = new PointF(50, 20);
StringFormat myStringFormat = new StringFormat();
Pen myPen = new Pen(Color.Red);
myGraphicsPath.AddArc(0, 0, 30, 20, -90, 180);
myGraphicsPath.StartFigure();
myGraphicsPath.AddCurve(myPointArray);
myGraphicsPath.AddString("a string in a path", myFontFamily, 0, 50, myPointF, myStringFormat);
myGraphicsPath.AddPie(400, 10, 40, 40, 40, 110);
myGraphics.DrawPath(myPen, myGraphicsPath);
区域是输出设备显示区域的一部分。区域可以是简单的(单个矩形)或复杂的(多边形和闭合曲线的组合)。下面的插图显示了两个区域:一个利用矩形构造,另一个利用路径构造。
区域常用于剪辑和命中检测。剪辑需要将绘制限制到显示区域的一个特定区域,通常是需要更新的部分。命中检测需要通过检查来确定按下鼠标按钮时光标是否在屏幕的特定区域中。
您可以从矩形或路径中构造区域。您也可以通过合并现有的区域来创建复杂区域。Region 类提供了以下合并区域的方法:Intersect、Union、Xor、Exclude 和 Complement。
两个区域的交集是同时属于两个区域的所有点的集合。并集是属于一个或另一个或两个区域的所有点的集合。区域的补集是不在该区域的所有点的集合。下面的插图显示了前面插图中两个区域的交集和并集。
适用于一对区域的 Xor 方法可生成一个区域,其中包含属于一个区域或另一个区域但不同时属于两个区域的所有点。适用于一对区域的 Exclude 方法可生成了一个区域,其中包含在第一个区域中而不在第二个区域中的所有点。下面的插图显示了通过将 Xor 和 Exclude 方法应用于该主题开始处的两个区域而产生的区域。
要填充区域,需要有 Graphics 对象、Brush 对象和 Region 对象。Graphics 对象提供 FillRegion 方法,Brush 对象存储填充的属性,例如,颜色或图案。
实现程序与演示截图
我所提供的代码只是简单的演示字幕动态叠加,不能表达真正的音乐步骤,有兴趣的朋友可以实现它。
下面是Timer的Tick事件中的代码:
Graphics myGraphics = this.CreateGraphics();
GraphicsPath myGraphicsPath = new GraphicsPath(); //声明路径myGraphicsPath
FontFamily myFontFamily = new FontFamily("宋体");
Point myPoint = new Point(50, 20);
StringFormat myStringFormat = new StringFormat();
myGraphicsPath.AddString("字幕动态叠加", myFontFamily, 1, 50, myPoint, myStringFormat);
Pen myPen = new Pen(Color.Black, 1);
myGraphics.DrawPath(myPen, myGraphicsPath); //绘制路径myGraphicsPath
SolidBrush myBrash = new SolidBrush(Color.Red);
rec.Width += 1; //此处的rec在类的开始出声明并初始化 Rectangle rec = new Rectangle(50, 20, 0, 50);
Region re = new Region(rec); //声明Region对象并用rec初始化
re.Intersect(myGraphicsPath); // Region 对象re更新为其自身与myGraphicsPath的交集
myGraphics.FillRegion(myBrash, re); //用myBrash填充re的内部
myGraphics.Dispose();
程序运行效果截图:
我在演示时发现,字幕叠加时有一定的闪烁感,很遗憾我暂时还没找到解决的方法,如果您知道如何解决或您有更好的实现字幕动态叠加的方法,希望赐教!
作者:陈健
E-Mail: freechen12@gmail.com
MSN: ym51777@yahoo.com.cn