轮廓文本






4.98/5 (268投票s)
如何绘制带轮廓的文本
在此阅读文章的第二部分:链接。
目录
- 引言
- GDI+ 的初始化和反初始化
- 使用通用 GDI+ 绘制单一轮廓文本
- 使用渐变色绘制单一轮廓文本
- 使用通用 GDI+ 绘制双重轮廓文本
- 使用通用 GDI+ 绘制文本发光
- Postscript OpenType 字体
- 使用 DirectWrite 绘制轮廓文本
- 绘制阴影如何?
- 使用 OutlineText 绘制单一轮廓
- 使用 OutlineText 绘制渐变色单一轮廓文本
- 使用 OutlineText 绘制双重轮廓
- 使用 OutlineText 绘制文本发光
- 伪3D文本
- 真实3D文本(正交)
- 旋转斜体文本
- 扩散阴影和示例代码
- PngOutlineText 类
- MeasureString 和 GdiMeasureString
- OpenGL 演示
- Codeplex
- 参考文献
- 源代码更改日志
- 历史
引言
我是一个狂热的动漫迷(日本动画)。由于我不懂日语,我看的动漫都有英文字幕。这些粉丝字幕的动漫拥有最漂亮的字体和文本。下面是《天体海盗》的截图,这是一部关于高中天文学部的动漫。
我对轮廓文本着迷。我在网上搜索了一个可以让我制作轮廓文本的库。遗憾的是,我没有找到。我找到的那些,对我来说太难改装成我的通用用途,而且我也不完全理解它们注释稀疏的代码。我决定卷起袖子,编写自己的轮廓文本库。在我之前的文章《如何使用未安装的字体》中,读者 knoami 评论并请求使用 C# 实现相同的功能。这次考虑到 C# 用户,本文中的每个 C++ 代码示例都附带了一个 C# 代码示例。事不宜迟,我们现在开始吧!
GDI+ 的初始化和反初始化
在 C++ 应用程序中使用 GDI+ 之前,我们需要对其进行初始化。下面是在构造函数中初始化 GDI+ 并在析构函数中反初始化 GDI+ 的示例。
// class declaration
class CTestOutlineTextApp
{
// ...
private:
// data members
Gdiplus::GdiplusStartupInput m_gdiplusStartupInput;
ULONG_PTR m_gdiplusToken;
};
// default constructor
CTestOutlineTextApp::CTestOutlineTextApp()
{
Gdiplus::GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
}
// destructor
CTestOutlineTextApp::~CTestOutlineTextApp()
{
Gdiplus::GdiplusShutdown(m_gdiplusToken);
}
对于 .NET Windows 窗体应用程序,GDI+ 的初始化和反初始化是自动为开发人员处理的。
使用通用 GDI+ 绘制单一轮廓文本
在本教程中,我主要使用 Arial 字体,因为 Arial 字体随每个 Windows 操作系统提供,因此示例代码可以直接使用。要绘制轮廓文本,我们必须使用其 AddString
方法将字符串添加到 GraphicsPath
对象中,以便我们可以获得其路径来绘制其轮廓。我们必须首先绘制文本的轮廓。为此,我们使用 Graphics
类的 DrawPath
方法。最后,我们使用 Graphics
类的 FillPath
方法绘制文本主体。很简单,对吧?
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf), &fontFamily,
FontStyleRegular, 48, Gdiplus::Point(10,10), &strformat );
Pen pen(Color(234,137,6), 6);
graphics.DrawPath(&pen, &path);
SolidBrush brush(Color(128,0,255));
graphics.FillPath(&brush, &path);
}
这是等效的 C# 代码,使用 GDI+ 的 System::Drawing
类来绘制轮廓文本。你会注意到它与上面的 C++ 代码非常相似。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Regular, 48.0f, new Point(10, 10), strformat);
Pen pen = new Pen(Color.FromArgb(234, 137, 6), 6);
e.Graphics.DrawPath(pen, path);
SolidBrush brush = new SolidBrush(Color.FromArgb(128, 0, 255));
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
pen.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
上面的文本有一个问题,特别是“A”;“A”的顶部有一个尖锐的顶点。如果字体字形中存在尖锐的边缘或拐角,并且轮廓相当粗或比文本主体更粗,则会出现此问题。上面的尖锐顶点来自“A”内部三角形的轮廓。下面是 C++ 代码,后面是 C# 代码,以重现该问题。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"ABC";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf), &fontFamily,
FontStyleRegular, 48, Gdiplus::Point(10,10), &strformat );
Pen pen(Color(234,137,6), 6);
graphics.DrawPath(&pen, &path);
SolidBrush brush(Color(128,0,255));
graphics.FillPath(&brush, &path);
}
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "ABC";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Regular, 48.0f, new Point(10, 10), strformat);
Pen pen = new Pen(Color.FromArgb(234, 137, 6), 6);
e.Graphics.DrawPath(pen, path);
SolidBrush brush = new SolidBrush(Color.FromArgb(128, 0, 255));
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
pen.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
幸运的是,我有一个解决此问题的方法。我们可以将 GDI+ 画笔的 LineJoin
属性设置为 LineJoinRound
以避免尖锐的边缘和拐角。缺点是每个边缘都会被圆角化,而不是像字体那样清晰锐利。下面是调用 SetLineJoin
方法的 C++ 代码。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"ABC";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf), &fontFamily,
FontStyleRegular, 48, Gdiplus::Point(10,10), &strformat );
Pen pen(Color(234,137,6), 6);
pen.SetLineJoin(LineJoinRound);
graphics.DrawPath(&pen, &path);
SolidBrush brush(Color(128,0,255));
graphics.FillPath(&brush, &path);
}
这是等效的 C# 代码,通过将 LineJoin
属性设置为 LineJoin.Round
来解决此问题。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "ABC";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Regular, 48.0f, new Point(10, 10), strformat);
Pen pen = new Pen(Color.FromArgb(234, 137, 6), 6);
pen.LineJoin = LineJoin.Round;
e.Graphics.DrawPath(pen, path);
SolidBrush brush = new SolidBrush(Color.FromArgb(128, 0, 255));
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
pen.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
使用渐变色绘制单一轮廓文本
我们可以为文本颜色选择渐变或纹理画刷,而不是纯色画刷。下面是一个 C++ 示例,展示了如何实现这一点。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf), &fontFamily,
FontStyleBold, 48, Gdiplus::Point(10,10), &strformat );
Pen pen(Color(0,0,160), 5);
pen.SetLineJoin(LineJoinRound);
graphics.DrawPath(&pen, &path);
LinearGradientBrush brush(Gdiplus::Rect(10, 10, 30, 60),
Color(132,200,251), Color(0,0,160), LinearGradientModeVertical);
graphics.FillPath(&brush, &path);
}
下面是一个 C# 示例,展示了如何选择渐变画刷。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Bold, 48.0f, new Point(10, 10), strformat);
Pen pen = new Pen(Color.FromArgb( 0, 0, 160), 5);
pen.LineJoin = LineJoin.Round;
e.Graphics.DrawPath(pen, path);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(10,10,30,70),
Color.FromArgb(132,200,251),
Color.FromArgb(0,0,160), LinearGradientMode.Vertical);
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
pen.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
使用通用 GDI+ 绘制双重轮廓文本
要实现双重轮廓文本,您必须先渲染外部轮廓,然后渲染内部轮廓,使用 DrawPath
,然后调用 FillPath
绘制文本主体。下面是实现此目的的 C++ 代码。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf),
&fontFamily, FontStyleRegular, 48, Gdiplus::Point(10,10), &strformat );
Pen penOut(Color(32, 117, 81), 12);
penOut.SetLineJoin(LineJoinRound);
graphics.DrawPath(&penOut, &path);
Pen pen(Color(234,137,6), 6);
pen.SetLineJoin(LineJoinRound);
graphics.DrawPath(&pen, &path);
SolidBrush brush(Color(128,0,255));
graphics.FillPath(&brush, &path);
}
这是绘制双重轮廓文本的等效 C# 代码
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Regular, 48.0f, new Point(10, 10), strformat);
Pen penOut = new Pen(Color.FromArgb(32, 117, 81), 12);
penOut.LineJoin = LineJoin.Round;
e.Graphics.DrawPath(penOut, path);
Pen pen = new Pen(Color.FromArgb(234, 137, 6), 6);
pen.LineJoin = LineJoin.Round;
e.Graphics.DrawPath(pen, path);
SolidBrush brush = new SolidBrush(Color.FromArgb(128, 0, 255));
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
penOut.Dispose();
pen.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
使用通用 GDI+ 绘制文本发光
要绘制文本发光,您必须从一支具有较低 alpha 值(介于 24 到 64 之间)的细笔开始绘制轮廓,然后反复使用较粗的笔绘制轮廓。最后,使用 FillPath
方法绘制文本主体。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
GraphicsPath path;
path.AddString(pszbuf, wcslen(pszbuf), &fontFamily,
FontStyleRegular, 48, Gdiplus::Point(10,10), &strformat );
for(int i=1; i<8; ++i)
{
Pen pen(Color(32, 0, 128, 192), i);
pen.SetLineJoin(LineJoinRound);
graphics.DrawPath(&pen, &path);
}
SolidBrush brush(Color(255,255,255));
graphics.FillPath(&brush, &path);
}
这是绘制文本发光的等效 C# 代码
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
SolidBrush brushWhite = new SolidBrush(Color.White);
e.Graphics.FillRectangle(brushWhite, 0, 0,
this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
GraphicsPath path = new GraphicsPath();
path.AddString(szbuf, fontFamily,
(int)FontStyle.Regular, 48.0f, new Point(10, 10), strformat);
for(int i=1; i<8; ++i)
{
Pen pen = new Pen(Color.FromArgb(32, 0, 128, 192), i);
pen.LineJoin = LineJoin.Round;
e.Graphics.DrawPath(pen, path);
pen.Dispose();
}
SolidBrush brush = new SolidBrush(Color.FromArgb(255, 255, 255));
e.Graphics.FillPath(brush, path);
brushWhite.Dispose();
fontFamily.Dispose();
path.Dispose();
brush.Dispose();
e.Graphics.Dispose();
}
Postscript OpenType 字体
在你急于制作自己的轮廓库之前,我必须告诉你 GDI+ 的一个陷阱。GDI+ 无法处理 Postscript OpenType 字体;GDI+ 只能处理 TrueType 字体。我一直在寻找解决方案,并找到了 Sjaak Priester 的《让 GDI+ 对字体不那么挑剔》。他的方法是解析字体文件以获取其字形并绘制其轮廓。遗憾的是,我无法使用他的代码,因为他的库使用的是限制性 GNU 许可证,而我希望我的代码可以免费供所有人使用。注意:这就是 Winform 开发人员使用 <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.textrenderer.aspx" target="new">TextRenderer</a>
类显示文本,而不是 GDI+ 类的原因。我绞尽脑汁寻找解决方案。由于 GDI(不是 GDI+)可以显示 Postscript OpenType 字体,并且 GDI 通过 BeginPath/EndPath/GetPath 支持路径提取,我决定只使用它将路径导入 GDI+。下面是 GDI+ 路径和 GDI 路径的比较。注意:两者都由 GDI+ 渲染,只是它们的路径提取方式不同;一个使用 GDI+,另一个使用 GDI 获取文本路径。
上面使用的是 GDI+ 路径,下面使用的是 GDI 路径。看起来 GDI 路径文本更大,并且有些不准确(这里不明显,因为它取决于字体)。(注意:我意识到,如果您使用 Graphics::DrawString
绘制文本,它们的大小与 GDI 路径文本大致相同;是 GDI+ 路径文本更小!) 然而,GDI 路径可以实现旋转斜体文本效果,如下图所示,这是 GDI+ 无法做到的,因为 GDI GraphicsPath
的 AddString
接受 FontFamily
对象,而不是 Font
对象。如果您需要使用 PostScript OpenType 字体,我的 OutlineText
类提供了 GdiDrawString
方法。下面的效果是 Franklin Gothic Demi 字体,大小 36,斜体文本逆时针旋转 10 度。
使用 DirectWrite 绘制轮廓文本
众所周知,Direct2D 和 DirectWrite 是 Vista 和 Windows 7 的下一代图形和文本 API。我已向 Tom Mulcahy(Microsoft 的 Windows 7 Direct2D 开发人员)发送了电子邮件。以下是 Tom Mulcahy 给我的电子邮件回复。
(感谢 Tom Mulcahy)实现此目的的方法是将文本内容获取为
ID2D1GeometrySink
(参见IDwriteFontFace::GetGlyphRunOutline
)。然后可以调用ID2D1RenderTarget::DrawGeometry
绘制文本轮廓(指定任何颜色和宽度)。接下来调用ID2D1RenderTarget::FillGeometry
填充文本(同样可以指定任何颜色)。
注意:文章后面提到的文本设计器轮廓文本库将在 Windows 7 发布后更新为 DirectWrite
。
绘制阴影如何?
老实说,文本阴影是使用单一轮廓文本代码绘制的。有一个问题:阴影是半透明的。如果我们使用第一个代码示例来渲染阴影,它会变成下面的图像。这是因为字体主体和字体轮廓的某些区域重叠,所以它们被渲染了两次,因此会更暗。
我的解决方案是分别渲染阴影文本主体和阴影文本轮廓,如下图所示,并将它们组合起来;像素中显示阴影文本主体优先;只有当阴影文本主体未渲染时,才渲染阴影文本轮廓。阴影渲染更复杂,不带阴影实现的 0.1.0 版 OutlineText.cpp 文件大小仅为 3KB,有 164 行代码,而带阴影实现的 0.2.0 版 OutlineText.cpp 文件大小为 23KB,有 865 行代码!因此,我不会在这里展示其代码,如果您有兴趣,可以下载并阅读源代码。
使用 OutlineText 绘制单一轮廓
这是使用 OutlineText
类的 C++ 代码,通过 TextOutline
和 DrawString
方法显示单一轮廓文本。
#include "TextDesigner/OutlineText.h"
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial Black");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
OutlineText text;
text.TextOutline(Color(255,128,64),Color(200,0,0),8);
text.EnableShadow(true);
CRect rect;
GetClientRect(&rect);
text.SetShadowBkgd(Color(255,255,0),rect.Width(),rect.Height());
text.Shadow(Color(128,0,0,0), 4, Point(4,8));
text.DrawString(&graphics,&fontFamily,FontStyleItalic,
48, pszbuf, Gdiplus::Point(10,10), &strformat);
}
这是使用 OutlineText
类的等效 C# 代码,通过 TextOutline
和 DrawString
方法显示单一轮廓文本。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
FontFamily fontFamily = new FontFamily("Arial Black");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
OutlineText text = new OutlineText();
text.TextOutline(Color.FromArgb(255, 128, 64), Color.FromArgb(200, 0, 0), 8);
text.EnableShadow(true);
text.SetShadowBkgd(Color.FromArgb(255, 255, 0), this.Size.Width, this.Size.Height);
text.Shadow(Color.FromArgb(128, 0, 0, 0), 4, new Point(4, 8));
text.DrawString(e.Graphics, fontFamily,
FontStyle.Italic, 48, szbuf, new Point(10, 10), strformat);
fontFamily.Dispose();
e.Graphics.Dispose();
}
使用 OutlineText 绘制渐变色单一轮廓文本
我们可以为文本颜色选择渐变或纹理画刷,而不是纯色画刷。我们可以使用 MeasureString
方法计算文本将占用的宽度和高度,这个返回的宽度和高度将是渐变画刷的宽度和高度。但是,我们必须再次调用 TextOutline
方法来设置画刷。需要调用 TextOutline
两次的原因是 MeasureString
需要 TextOutline
信息才能正确测量字符串。不用担心:TextOutline
相当轻量级,它只是设置一些信息。下面是一个 C++ 示例,展示了如何实现。
#include <Gdiplus.h>
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
using namespace Gdiplus;
using namespace TextDesigner;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial Black");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
OutlineText text;
text.EnableShadow(true);
CRect rect;
GetClientRect(&rect);
text.SetShadowBkgd(Color(255,255,0),rect.Width(),rect.Height());
text.Shadow(Color(128,0,0,0), 4, Point(4,8));
text.TextOutline(Color(0,0,0), Color(0,0,160),5);
text.MeasureString(
&graphics,
&fontFamily,
FontStyleItalic,
48,
pszbuf,
Gdiplus::Point(10,10),
&strformat,
&fDestWidth,
&fDestHeight);
float fDestWidth = 0.0f;
float fDestHeight = 0.0f;
LinearGradientBrush brush(Gdiplus::Rect(10, 10, fDestWidth, fDestHeight),
Color(132,200,251), Color(0,0,160), LinearGradientModeVertical);
text.TextOutline(&brush, Color(0,0,160),5);
text.DrawString(&graphics,&fontFamily,FontStyleItalic,
48, pszbuf, Gdiplus::Point(10,10), &strformat);
}
下面是一个 C# 示例,演示如何使用 OutlineText
选择渐变画刷。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
OutlineText outlineText = new OutlineText();
outlineText.TextOutline(
Color.FromArgb(255, 128, 192),
Color.FromArgb(255, 0, 0, 160),
4);
outlineText.EnableShadow(true);
//Rem to SetNullShadow() to release memory if a previous shadow has been set.
outlineText.SetNullShadow();
outlineText.Shadow(Color.FromArgb(128, 0, 0, 0), 4, new Point(4, 8));
Color m_clrBkgd = Color.FromArgb(255, 255, 255);
outlineText.SetShadowBkgd(m_clrBkgd, this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial Black");
StringFormat strFormat = new StringFormat();
float fDestWidth = 0.0f;
float fDestHeight = 0.0f;
outlineText.MeasureString(
e.Graphics,
fontFamily,
FontStyle.Italic,
48,
"Text Designer",
new Point(10, 10),
strFormat,
ref fDestWidth,
ref fDestHeight);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(10, 10,
(int)fDestWidth, (int)fDestHeight),
Color.FromArgb(132,200,251), Color.FromArgb(0,0,160),
System.Drawing.Drawing2D.LinearGradientMode.Vertical);
outlineText.TextOutline(
brush,
Color.FromArgb(255, 0, 0, 160),
4);
outlineText.DrawString(e.Graphics, fontFamily,
FontStyle.Italic, 48, "Text Designer",
new Point(10, 10), strFormat);
e.Graphics.Dispose();
}
这些是 TestOutlineText
应用程序中用于显示上述内容的设置。我在此列出这些设置,因为有时即使我也对如何使用 TestOutlineText
显示某些轮廓文本效果感到有点迷茫。通过在此列出设置,我希望读者能熟悉此应用程序,以便他们可以尝试他们想要的轮廓效果。请注意,如果启用阴影,TestOutlineText
应用程序的滚动和调整大小将变得卡顿,因为阴影渲染是计算密集型操作。我编写了一个 PngOutlineText
类来解决此问题,我将在本文末尾稍后讨论它。
使用 OutlineText 绘制双重轮廓
为了实现双重轮廓文本,您必须指定外部轮廓和内部轮廓。这是使用 TextDblOutline
和 DrawString
方法显示双重轮廓文本的 C++ 代码。
#include "TextDesigner/OutlineText.h"
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial Black");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
OutlineText text;
text.TextDblOutline(Color(255,255,255),Color(0,128,128),Color(0,255,0),4,4);
text.EnableShadow(true);
CRect rect;
GetClientRect(&rect);
text.SetShadowBkgd(Color(255,128,192),rect.Width(),rect.Height());
text.Shadow(Color(128,0,0,0), 4, Point(4,8));
text.DrawString(&graphics,&fontFamily,FontStyleRegular,
48, pszbuf, Gdiplus::Point(10,10), &strformat);
}
这是使用 TextDblOutline
和 DrawString
方法显示双重轮廓文本的 C# 代码。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
FontFamily fontFamily = new FontFamily("Arial Black");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
OutlineText text = new OutlineText();
text.TextDblOutline(Color.FromArgb(255, 255, 255),
Color.FromArgb(0, 128, 128), Color.FromArgb(0, 255, 0), 4, 4);
text.EnableShadow(true);
text.SetShadowBkgd(Color.FromArgb(255, 128, 192), this.Size.Width, this.Size.Height);
text.Shadow(Color.FromArgb(128, 0, 0, 0), 4, new Point(4, 8));
text.DrawString(e.Graphics, fontFamily,
FontStyle.Bold, 48, szbuf, new Point(10, 10), strformat);
fontFamily.Dispose();
e.Graphics.Dispose();
}
这些是显示双重轮廓文本的设置。
使用 OutlineText 绘制文本发光
这是使用 TextGlow
和 DrawString
方法显示文本发光的 C++ 代码。文本发光通常不与阴影一起显示,因为阴影会干扰发光效果,所以我禁用了阴影,并且没有设置任何阴影设置。
#include "TextDesigner/OutlineText.h"
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
FontFamily fontFamily(L"Arial Black");
StringFormat strformat;
wchar_t pszbuf[] = L"Text Designer";
OutlineText text;
text.TextGlow(Color(191,255,255),Color(24,0,128,128),14);
text.EnableShadow(false);
text.DrawString(&graphics,&fontFamily,FontStyleRegular,
48, pszbuf, Gdiplus::Point(10,10), &strformat);
}
这是使用 TextGlow
和 DrawString
方法显示文本发光的类似 C# 代码。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
FontFamily fontFamily = new FontFamily("Arial Black");
StringFormat strformat = new StringFormat();
string szbuf = "Text Designer";
OutlineText text = new OutlineText();
text.TextGlow(Color.FromArgb(191, 255, 255), Color.FromArgb(24, 0, 128, 128), 14);
text.EnableShadow(false);
text.DrawString(e.Graphics, fontFamily, FontStyle.Bold,
48, szbuf, new Point(10, 10), strformat);
fontFamily.Dispose();
e.Graphics.Dispose();
}
这些是显示文本发光的设置。
如果您好奇,这是带阴影的文本发光效果。
伪3D文本
您可以通过使用更大、不透明且与轮廓颜色相同的阴影来实现模拟的 3D 文本。当然,如果您仔细观察,就会发现它根本不像 3D。
真实3D文本(正交)
使用 PngOutlineText
类可以轻松制作真实的 3D 文本。通过重复且对角地渲染相同颜色的文本来实现挤出部分。通过对角渲染,我的意思是将起始绘制点在 x 和 y 方向上偏移 1 像素来渲染文本。最后,我们将在原始位置渲染真实文本。下面的示例代码通过使用相同的 PngOutlineText
对象来实现此目的。它为 DrawActualText
中的最终文本设置了新的 TextOutline
参数。您会注意到,在 DrawDiagonal
和 DrawActualText
方法中,我将 PNG 图像位块传输到从 ARGB Bitmap
对象创建的 graphics
对象中,以便生成的 3D 文本将“保存”在 ARGB Bitmap
对象中。然后在 OnPaint
方法中,我只需位块传输该 ARGB Bitmap
对象,而不再使用 PngOutlineText
。要绘制轮廓文本,PngOutlineText
是首选;OutlineText
太慢了,因为它必须在每次客户端区域失效需要重绘时重新计算和重绘文本。顺便说一句,下面的示例代码是从剪贴板粘贴的示例代码修改而来的,而剪贴板中的示例代码又是从 WYSIWYG“复制 C++ 代码”按钮复制的。真是自食其力!
#include "../TextDesigner/PngOutlineText.h"
Gdiplus::Bitmap m_bitmap(420,100,PixelFormat32bppARGB);
BOOL CScratchPadDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
using namespace Gdiplus;
Graphics graphics(&m_bitmap);
PngOutlineText pngOutlineText;
DrawDiagonal(graphics, pngOutlineText, 6);
DrawActualText(graphics, pngOutlineText);
return TRUE;
}
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
using namespace Gdiplus;
CPaintDC dc(this);
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
// Fill background with white colour.
CRect rect;
GetClientRect(&rect);
SolidBrush brushWhite(Color(255,255,255));
graphics.FillRectangle(&brushWhite, 0, 0, rect.Width(), rect.Height());
graphics.DrawImage(&m_bitmap, 10, 10, m_bitmap.GetWidth(), m_bitmap.GetHeight());
}
void CScratchPadDlg::DrawDiagonal(Gdiplus::Graphics& graphics,
PngOutlineText& pngOutlineText, int nDiagonal)
{
using namespace Gdiplus;
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
pngOutlineText.TextOutline(
Color(0,0,0),
Color(255,0,0,0),
4);
pngOutlineText.EnableShadow(false);
FontFamily fontFamily(L"Arial Black");
StringFormat strFormat;
Bitmap* pPngImage = new Gdiplus::Bitmap(m_bitmap.GetWidth(),
m_bitmap.GetHeight(),PixelFormat32bppARGB);
pngOutlineText.SetPngImage(pPngImage);
pngOutlineText.DrawString(&graphics,&fontFamily,
FontStyleRegular, 48, L"Text Designer",
Gdiplus::Point(10,10), &strFormat);
for(int i=0; i<nDiagonal; ++i)
graphics.DrawImage(pPngImage, i, i, pPngImage->GetWidth(),
pPngImage->GetHeight());
if(pPngImage)
delete pPngImage;
pPngImage = NULL;
}
void CScratchPadDlg::DrawActualText(Gdiplus::Graphics& graphics,
PngOutlineText& pngOutlineText)
{
using namespace Gdiplus;
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
pngOutlineText.TextOutline(
Color(178,0,255),
Color(255,0,0,0),
4);
pngOutlineText.EnableShadow(false);
FontFamily fontFamily(L"Arial Black");
StringFormat strFormat;
Bitmap* pPngImage = new Gdiplus::Bitmap(m_bitmap.GetWidth(),
m_bitmap.GetHeight(),PixelFormat32bppARGB);
pngOutlineText.SetPngImage(pPngImage);
pngOutlineText.DrawString(&graphics,&fontFamily,
FontStyleRegular, 48, L"Text Designer",
Gdiplus::Point(10,10), &strFormat);
graphics.DrawImage(pPngImage, 0, 0, pPngImage->GetWidth(), pPngImage->GetHeight());
if(pPngImage)
delete pPngImage;
pPngImage = NULL;
}
对于上述 C++ 代码示例,我没有提供 C# 等效代码示例,因为在以前版本的 .NET API 中无法进行位块传输,因为 .NET API 会为每个传入的托管对象创建本机副本。例如,如果您提供一个带有内部 Bitmap
对象的 Graphics
对象,PngOutlineText
不会使用该 Graphics
对象进行绘制,而是使用该 Graphics
对象的本机副本进行绘制。因此,内部 Bitmap
将不会被渲染。从那时起,我向 PngOutlineText
类添加了 GetCopyOfInternalPng
方法,以便能够使用 GDI+ 对渲染的 PNG 副本进行位块传输。
我还添加了一个名为 Extrude
的方法来轻松制作 3D 文本,但该方法仍然不如之前“多次位块传输 PNG”的方法快,因为 ExtrudeStrategy
类是通用的,它不知道 OutlineText
还是 PngOutlineText
类在使用它,因此无法进行任何优化。注意:要使用 Extrude
,您必须 EnableShadow
,因为 Extrude
被视为一种阴影。请注意,要实现 3D 文本效果,阴影颜色必须完全不透明(即 255),并且当 x 和 y 偏移的绝对值相等时(例如,x=4,y=4 或 x-4,y=4),3D 挤出效果看起来最好。下面是从 WYSIWYG 剪贴板代码复制功能获得的 C++ 和 C# 示例代码。
#include "TextDesigner/OutlineText.h"
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
using namespace Gdiplus;
using namespace TextDesign;
CPaintDC dc(this);
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
OutlineText m_OutlineText;
m_OutlineText.TextOutline(
Color(255,128,192),
Color(255,128,0,0),
4);
m_OutlineText.EnableShadow(true);
//Rem to SetNullShadow() to release memory if a previous shadow has been set.
m_OutlineText.SetNullShadow();
m_OutlineText.Extrude(
Gdiplus::Color(255,128,0,0),
4,
Gdiplus::Point(8,8));
CRect rect;
this->GetClientRect(&rect);
Color m_clrBkgd(255, 255, 255);
m_OutlineText.SetShadowBkgd(m_clrBkgd,rect.Width(),rect.Height());
FontFamily fontFamily(L"Arial Black");
StringFormat strFormat;
m_OutlineText.DrawString(&graphics,&fontFamily,
FontStyleRegular, 48, L"Text Designer",
Gdiplus::Point(10, 10), &strFormat);
}
这是调用 Extrude
实现 3D 挤出文本的等效 C# 代码。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
OutlineText outlineText = new OutlineText();
outlineText.TextOutline(
Color.FromArgb(255, 128, 192),
Color.FromArgb(255, 128, 0, 0),
4);
outlineText.EnableShadow(true);
//Rem to SetNullShadow() to release memory if a previous shadow has been set.
outlineText.SetNullShadow();
outlineText.Extrude(
Color.FromArgb(255, 128, 0, 0),
4,
new Point(8, 8));
Color m_clrBkgd = Color.FromArgb(255, 255, 255);
outlineText.SetShadowBkgd(m_clrBkgd, this.ClientSize.Width, this.ClientSize.Height);
FontFamily fontFamily = new FontFamily("Arial Black");
StringFormat strFormat = new StringFormat();
outlineText.DrawString(e.Graphics, fontFamily,
FontStyle.Regular, 48, "Text Designer",
new Point(10, 10), strFormat);
e.Graphics.Dispose();
}
这是实现 3D 挤出文本的设置。您必须勾选“挤出文本”复选框:(参见红色矩形!)
旋转斜体文本
我们必须使用 GdiDrawString
方法来显示旋转斜体文本,因为 GdiDrawString
接受一个 LOGFONT
结构,它允许我们通过 lfEscapement
和 lfOrientation
指定旋转角度。而 DrawString
方法使用 GraphicsPath
的 AddString
方法,它接受一个字体族对象而不是字体对象,我们无法指定旋转角度。
#include "TextDesigner/OutlineText.h"
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
CPaintDC dc(this);
using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
wchar_t pszbuf[] = L"Text Designer";
LOGFONTW logfont;
memset(&logfont, 0, sizeof(logfont));
wcscpy_s(logfont.lfFaceName, L"Arial Black");
logfont.lfHeight = -MulDiv(48, dc.GetDeviceCaps(LOGPIXELSY), 72);
logfont.lfEscapement = 100;
logfont.lfOrientation = 100;
logfont.lfItalic = 1;
OutlineText text;
text.TextOutline(Color(64,193,255),Color(0,0,0),8);
text.EnableShadow(false);
CRect rect;
GetClientRect(&rect);
text.EnableShadow(true);
text.SetShadowBkgd(Color(255,255,255),rect.Width(),rect.Height());
text.Shadow(Color(128,0,0,0), 8, Point(4,4));
text.GdiDrawString(&graphics, &logfont, pszbuf, Gdiplus::Point(10,100));
}
显示旋转文本的 C# 代码已移除,因为新的 C# 库尚未实现此功能,这可能涉及 P/Invoke 数据类型,我担心这可能存在可移植性问题。
这些是显示旋转文本的设置。
扩散阴影和示例代码
我已将扩散阴影添加到轮廓文本库中。点击绿色矩形所示的复选框以启用扩散阴影。注意:您必须调整阴影 alpha 值(范围从 12 到 32)和阴影厚度(范围从 8 到 12)才能实现扩散阴影效果。扩散阴影是使用文本发光效果实现的,因此阴影厚度表示阴影颜色将被渲染多少次。因此,根据经验,阴影厚度越高,阴影 alpha 值越低。我还实现了 WYSIWYG 示例代码生成。点击红色矩形所示的“复制 C++ 代码”和“复制 C# 代码”按钮,将代码复制到剪贴板并粘贴到您的代码编辑器中!您可能仍然需要在编辑器中编辑代码以使其符合您的要求,例如,将局部对象更改为类的成员对象。如果示例代码崩溃,请尝试将位图大小设置为相同,并请向我报告此崩溃以及重现步骤。注意:如果发生崩溃,那是因为示例代码错误,而不是文本设计器轮廓库出了问题。
PngOutlineText 类
我编写了一个 PngOutlineText
类,它将文本和阴影渲染到一个带有 Alpha 通道(PixelFormat32bppARGB
格式)的 Bitmap
对象中,这样您就不必在每次需要再次渲染文本时重新生成文本,因为轮廓文本生成通常需要很长时间,尤其是带有阴影的文本。使用 PngOutlineText
,您必须调用 SetPngImage
方法来设置 PixelFormat32bppARGB
格式图像,供 PngOutlineText
渲染。在第一次 DrawString
或 GdiDrawString
之后,您只需将此图像位块传输到您的图形对象,而不是再次通过 DrawString
或 GdiDrawString
生成相同的轮廓文本。我创建 PngOutlineText
类用于视频渲染,通常是 30fps 或 60fps。如果您在 TestOutlineText
应用程序中使用大的图像背景,您会发现调整应用程序大小和滚动图像不流畅。如果您勾选“启用 PNG 渲染”复选框(参见下面突出显示的红色矩形),调整大小和滚动会变得流畅,因为 TestOutlineText
应用程序会检测设置是否未修改,如果未修改,它将只位块传输透明文本图像。您可以使用 SavePngImage
方法将图像保存为 PNG 图像。如果您在任何图像编辑器(如 Paint.Net 或 Adobe Photoshop)中打开图像,您会看到棋盘格,这是 PNG 图像的透明部分。
版本 2 预览版 6 已将 GDI 路径添加到 C# 库中。下面是使用 GDI(而不是 GDI+)进行渐变文本的 C++ 和 C# 代码。示例代码放在这里是因为 GDI 的自动 C# 代码生成不正确。
void CScratchPadDlg::OnPaint()
{
//CDialog::OnPaint();
using namespace Gdiplus;
using namespace TextDesigner;
CPaintDC dc(this);
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
OutlineText m_OutlineText;
m_OutlineText.TextGradOutline(
Color(255,128,64),
Color(255,64,0,64),
Color(255,255,128,255),
10);
m_OutlineText.EnableShadow(true);
//Rem to SetNullShadow() to release memory if a previous shadow has been set.
m_OutlineText.SetNullShadow();
m_OutlineText.Shadow(
Gdiplus::Color(128,0,0,0), 8,
Gdiplus::Point(4,4));
FontFamily fontFamily(L"Arial Black");
StringFormat strFormat;
float fStartX = 0.0f;
float fStartY = 0.0f;
float fDestWidth = 0.0f;
float fDestHeight = 0.0f;
m_OutlineText.MeasureString(
&graphics,
&fontFamily,
FontStyleRegular,
48,
L"TEXT DESIGNER",
Gdiplus::Point(10, 10),
&strFormat,
&fStartX,
&fStartY,
&fDestWidth,
&fDestHeight);
CRect rect;
this->GetClientRect(&rect);
Color m_clrBkgd(255, 255, 255);
m_OutlineText.SetShadowBkgd(m_clrBkgd,fDestWidth,fDestHeight);
FontFamily fontFamily(L"Arial Black");
StringFormat strFormat;
LinearGradientBrush gradientBrush(Gdiplus::Rect(fStartX, fStartY, fDestWidth-(fStartX-10), fDestHeight-(fStartY-10)),
Color(255, 128, 64), Color(255, 0, 0), LinearGradientModeVertical );
m_OutlineText.TextGradOutline(
&gradientBrush,
Color(255,64,0,64),
Color(255,255,128,255),
10);
m_OutlineText.DrawString(&graphics,&fontFamily,
FontStyleRegular, 48, L"TEXT DESIGNER",
Gdiplus::Point(10, 10), &strFormat);
}
接下来是 C# 代码。
private void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//Drawing the back ground color
Color m_clrBkgd = Color.FromArgb(255, 255, 255);
SolidBrush brushBkgnd = new SolidBrush(m_clrBkgd);
e.Graphics.FillRectangle(brushBkgnd, 0, 0, this.ClientSize.Width, this.ClientSize.Width);
PngOutlineText m_PngOutlineText = new PngOutlineText();
m_PngOutlineText.SetPngImage(new Bitmap(ClientSize.Width, ClientSize.Height));
m_PngOutlineText.TextGradOutline(
Color.FromArgb(255, 128, 64),
Color.FromArgb(255, 64, 0, 64),
Color.FromArgb(255, 255, 128, 255),
10);
m_PngOutlineText.EnableReflection(false);
m_PngOutlineText.EnableShadow(true);
//Rem to SetNullShadow() to release memory if a previous shadow has been set.
m_PngOutlineText.SetNullShadow();
m_PngOutlineText.Shadow(
Color.FromArgb(128, 0, 0, 0), 8,
new Point(4, 4));
LOGFONT m_LogFont = new LOGFONT();
m_LogFont.lfFaceName = "Arial Black";
m_LogFont.lfHeight = -48;
m_LogFont.lfOrientation = 0;
m_LogFont.lfEscapement = 0;
m_LogFont.lfItalic = false;
float fStartX = 0.0f;
float fStartY = 0.0f;
float fDestWidth = 0.0f;
float fDestHeight = 0.0f;
m_PngOutlineText.GdiMeasureString(
e.Graphics,
m_LogFont,
"TEXT DESIGNER",
new Point(10, 10),
ref fStartX,
ref fStartY,
ref fDestWidth,
ref fDestHeight);
m_PngOutlineText.SetShadowBkgd(m_clrBkgd, (int)fDestWidth+10, (int)fDestHeight+10);
LinearGradientBrush gradientBrush = new LinearGradientBrush(new RectangleF(fStartX, fStartY, fDestWidth - (fStartX - 10), fDestHeight - (fStartY - 10)),
Color.FromArgb(255, 128, 64), Color.FromArgb(255, 0, 0), LinearGradientMode.Vertical);
m_PngOutlineText.TextGradOutline(
gradientBrush,
Color.FromArgb(255, 64, 0, 64),
Color.FromArgb(255, 255, 128, 255),
10);
m_PngOutlineText.GdiDrawString(
e.Graphics,
m_LogFont,
"TEXT DESIGNER",
new Point(10, 10));
e.Graphics.DrawImage(m_PngOutlineText.GetPngImage(), new Point(0, 0));
brushBkgnd.Dispose();
e.Graphics.Dispose();
}
MeasureString 和 GdiMeasureString
我为 PngOutlineText
实现了 MeasureString
和 GdiMeasureString
方法。请不要使用 Graphics::MeasureString
,因为 Graphics::MeasureString
用于 Graphics::DrawString
方法。在使用 MeasureString
和 GdiMeasureString
获取所需的最小宽度和高度后,您应该在宽度和高度上添加一些空间,例如 5 像素。MeasureString
和 GdiMeasureString
参数分别与 DrawString
和 GdiDrawString
相似,只是多了 2 个用于获取宽度和高度的参数。MeasureString
系列方法的使用方式如下。首先调用 MeasureString
获取宽度和高度,然后分配一个稍大的 PixelFormat32bppARGB
格式 Bitmap
。将您的阴影背景设置为与此 PixelFormat32bppARGB Bitmap
相同的大小。任何位图都可以作为阴影背景,因为它不影响 PngOutlineText
的渲染,但是对于 OutlineText
,您需要裁剪背景的一部分作为阴影背景。设置轮廓文本属性后,在 (0,0) 位置 DrawString
或 GdiDrawString
文本,然后将 PixelFormat32bppARGB Bitmap
在您希望文本出现的位置 Graphics::DrawImage
。
上述方法对于无阴影轮廓文本或阴影位于右下角(即正 x 和 y 偏移)的轮廓文本是有效的。如果偏移量中的一个或两个为负值,则效果不佳。想象一下,您在 (0,0) 点绘制文本,阴影偏移量在 (-4,-4) 点,因此部分阴影可能无法看到。因此,如果阴影偏移量在 (-4,-4) 点,您在 (4,4) 点 DrawString
文本,然后将最终的 PixelFormat32bppARGB Bitmap
在原始位置减去 (4,4) 点的位置 Graphics::DrawImage
。这样,无论您的阴影偏移到顶部、底部、左侧还是右侧,文本都将始终出现在相同的位置。下面是实现此目的的代码。请注意,下面的代码未出现在示例代码中,因为我不想让初学者对如何使用 PngOutlineText
类感到困惑,因为 PngOutlineText
示例代码已经是所有代码中最长的。
float fWidth=0.0f;
float fHeight=0.0f;
m_PngOutlineText.MeasureString(&graphics,&fontFamily,FontStyleBold,
72, m_szText, Gdiplus::Point(0,0), &strFormat,
&fWidth, &fHeight);
m_pPngImage = new Bitmap(fWidth+5.0f, fHeight+5.0f, PixelFormat32bppARGB);
if(!m_pPngImage)
return;
m_PngOutlineText.SetPngImage(m_pPngImage);
m_PngOutlineText.SetNullShadow();
m_PngOutlineText.SetShadowBkgd(
Gdiplus::Color(GetRValue(m_clrBkgd),GetGValue(m_clrBkgd),GetBValue(m_clrBkgd)),
m_pPngImage->GetWidth(), m_pPngImage->GetHeight());
if(!m_bEnableShadow)
{
m_PngOutlineText.DrawString(
&graphics,&fontFamily,fontStyle,m_nFontSize,
m_szText,Gdiplus::Point(0,0), &strFormat);
graphics.DrawImage(m_pPngImage, (float)m_nTextPosX, (float)m_nTextPosY,
(float)m_pPngImage->GetWidth(), (float)m_pPngImage->GetHeight());
}
else
{
int nShadowOffsetX = 0;
if(m_nShadowOffsetX<0)
nShadowOffsetX = -m_nShadowOffsetX;
int nShadowOffsetY = 0;
if(m_nShadowOffsetY<0)
nShadowOffsetY = -m_nShadowOffsetY;
m_PngOutlineText.DrawString(&graphics,&fontFamily,
fontStyle,m_nFontSize,m_szText,Gdiplus::Point
(nShadowOffsetX,nShadowOffsetY), &strFormat);
graphics.DrawImage(m_pPngImage, (float)
(m_nTextPosX-nShadowOffsetX), (float)(m_nTextPosY-nShadowOffsetY),
(float)m_pPngImage->GetWidth(), (float)m_pPngImage->GetHeight());
}
请注意,上述方法不适用于 GdiDrawString
的旋转斜体文本效果。有关如何使旋转斜体文本效果起作用的信息,请在 TestOutlineText
项目的 MyScrollView.cpp 中搜索 GdiMeasureStringRealHeight
方法调用,这是一个使其起作用的快速 hack。我没有将其放在这里,因为它会使代码示例复杂化。
OpenGL 演示
我制作了一个 OpenGL 演示,展示了如何使用带 Alpha 通道的 PNG 图像作为纹理。请注意,文本图像是即时生成的。我曾经在 muvee 工作,这是一家专门制作自动视频编辑软件的公司,他们使用 OpenGL 通过照片制作视频特效。我发现他们在自动编辑视频中使用的文字标题相当平淡。我希望他们能在下一版 muvee 软件中采用我的 Text Designer Outline Text 库。
CodePlex
文本设计器轮廓文本开源库托管在 CodePlex,网址为:http://outlinetext.codeplex.com/。CodePlex 上的库将比本文中的库更新更快,因此请务必不时查看 CodePlex 库以获取更新的源代码。文本设计器当前版本为 0.3.0。在达到 1.0.0 版本之前,还有很多工作(多 70%)要做。该库的最终目标是达到 WPF 文本效果那样先进。
参考文献
- Sjaak Priester 著《让 GDI+ 对字体不那么挑剔:世界上除了 TrueType 还有更多》
- VC++ 示例:GetPath、BeginPath、EndPath、True Type 字体、绘制文本轮廓
源代码更改日志
- 版本 0.3.6(第 15 次次要发布)
- 版本 2 预览。版本 2 只有 2 个类,即 Canvas 和 MaskColor。为版本 2 添加了 WPF 支持。
- 为 C++ MFC、C# Winform 和 C# WPF 添加了 3 个演示(Aquarion、Be Happy 和 Dirty)。
- 版本 0.3.0(第 9 次次要发布)
- 增加了为文本主体选择画刷的功能,例如渐变画刷或纹理画刷
- 添加了 CSharp 库
TextDesignerCSLibrary
。托管 C++dotNetOutline
库已弃用并将很快移除。
- 版本 0.2.9(第 8 次次要发布)
- 将 3D 文本添加到
OutlineText
、PngOutlineText
、dotNetOutlineText
和dotNetPngOutlineText
。 - 将 C++ 类添加到
TextDesign
命名空间 - 为
Extrude
(3D 文本功能)添加了所见即所得代码示例
- 将 3D 文本添加到
- 版本 0.2.8(第 7 次次要发布)
- 解决了 C++ 和 C#
PngOutlineText
示例代码中DrawImage
位置不正确的 bug。
- 解决了 C++ 和 C#
- 版本 0.2.7(第 6 次次要发布)
- 更改了
PngOutlineText
,用户现在必须调用Graphics::DrawImage
方法。DrawString
和GdiDrawString
方法现在不进行渲染。 - 更改了
TestOutlineText
,使其使用MeasureString
和GdiMeasureString
来使PngOutlineText
代码使用一个小的矩形进行渲染,而不是整个客户端矩形。 - 添加了一个
GdiMeasureStringRealHeight
方法,作为PngOutlineText
旋转斜体文本的临时解决方案。
- 更改了
- 版本 0.2.6(第 5 次次要发布)
- 添加了 C# 示例代码生成功能,C# 代码将复制到剪贴板(Beta 版)
- 版本 0.2.5
- 添加了 C++ 示例代码生成功能,C++ 代码将复制到剪贴板(测试版)
- 版本 0.2.4(第 4 次次要发布)
- OpenGL 演示,动态生成文本 PNG 图像,而不是从预渲染的 PNG 图像文件读取。演示开始时(灰色窗口)有明显暂停,因为文本 PNG 图像在内存中生成。
- 版本 0.2.3
- 为 .NET 类添加了
GdiMeasureString
和MeasureString
方法 - 为 .NET 类添加了扩散阴影方法
- 使
dotNetOutlineText
类和dotNetPngOutlineText
类继承自接口类
- 为 .NET 类添加了
- 版本 0.2.2(第 3 次次要发布)
- 添加了扩散阴影
- 版本 0.2.1(第 2 次次要发布)
- 修复了
GdiDrawString
方法中的路径内存泄漏 - 添加了
GdiMeasureString
和MeasureString
- 使
OutlineText
类和PngOutlineText
类继承自abstract
类
- 修复了
- 版本 0.2.0
- 首次公开发布
历史
- 2018年8月14日
- UWP Win2D 版本 0.5.0 Beta 仅支持
DirectXPixelFormat.B8G8R8A8UIntNormalized
。 - GDI/GDI+ 版本 2.1.1 Beta,通过将部分数组索引计算移出内循环进行了次要优化。
- UWP Win2D 版本 0.5.0 Beta 仅支持
- 2015年10月4日
- 为 C# 添加了 GDI(非 GDI+)路径代码示例
- 代码下载更新至版本 2 预览版 6
- 2010年2月17日
- 添加了关于如何初始化 GDI+ 的章节
- 将所有过时的 C++/CLI
dotNetOutlineText
示例替换为 C#TextDesignerCSLibrary
代码 - 添加了关于如何选择渐变画刷的通用章节
- 添加了关于如何选择渐变画刷的章节
- 2009年10月19日
- 扩展了关于如何制作真实 3D 挤出文本的章节
- 2009年10月13日
- 新增关于如何制作真实 3D 文本的章节
- 2009年10月7日
- 解释了如何将
MeasureString
和GdiMeasureString
方法用于PngOutlineText
对象。 - 更新了一些 C# 代码,在
OnPaint
方法结束时释放对象。 - 更新了一些解释。
- 添加了扩散阴影以及可复制到剪贴板以实现所见即所得的 C++ 和 C# 示例代码。
- 解释了如何将
- 2009年9月22日
- 在 CodeProject 上首次发布
- 文本设计器轮廓文本库版本 0.2.0