原文地址:http://blog.csdn.net/maozefa/archive/2008/01/15/2044341.aspx
本来春节前不准备写BLOG文章了,可前几天有几个搞C#的朋友来信说,对文章《GDI+ 在Delphi程序的应用 -- 可调节的文字阴影特效》的内容很感兴趣,但苦于对Delphi不熟悉,想请我帮忙将其改为C#版的。可他们哪里知道,我从未用C#写过代码(因我只是个业余编程爱好者,C#好像不适合我,我儿子是搞java的,对C#也不怎么熟),好在五年前我买过一本《C#入门经典》,只好赶鸭子上架,对着书边琢磨边改写,费了好大的功夫,才勉强改编成下面这个样子(也幸亏我对C/C++还熟悉,也幸好C#是此系列的语言),请那些朋友以及对本文内容有兴趣的朋友再根据自己的需要去改造。
既然第一次实际接触了C#,有个问题正好请教大家,就是关于C#垃圾回收的问题,因我写的TextShadow类中2个函数中用到了GDI+的局部对象,而且有可能被用户反复使用,这些局部对象是等系统自动回收好呢,还是我在函数结束时提前回收呢?在我的那本《C#入门经典》第一版中多处强调,对GDI+对象,“总是要调用Dispose()”,“或使用using结构”,“否则应用程序就可能耗尽Windows资源”。对于函数中频繁使用的对象不要等系统自动回收,这个道理我是明白的,但函数中只使用一次的对象(特别是GDI+对象)是否也要自己Dispose()呢,我在网上也看了一些C#代码,一般都没有自己释放,所以我糊涂了,但小心无大错,所以我在2个函数中还是自己释放了(肯定不会错,但是否有必要),请各位C#高手们看我下面的代码后指点指点,我是否多此一举了呢?先在这里谢了。
下面是我改写的C#文字阴影类TextShadow:
using
System;
using
System.Drawing;
using
System.Drawing.Imaging;
///
<summary>
///
Summary description for TextShadow
///
</summary>
public
class
TextShadow
{
private
int
radius
=
5
;
private
int
distance
=
10
;
private
double
angle
=
60
;
private
byte
alpha
=
192
;
///
<summary>
///
高斯卷积矩阵
///
</summary>
private
int
[] gaussMatrix;
///
<summary>
///
卷积核
///
</summary>
private
int
nuclear
=
0
;
///
<summary>
///
阴影半径
///
</summary>
public
int
Radius
{
get
{
return
radius;
}
set
{
if
(radius
!=
value)
{
radius
=
value;
MakeGaussMatrix();
}
}
}
///
<summary>
///
阴影距离
///
</summary>
public
int
Distance
{
get
{
return
distance;
}
set
{
distance
=
value;
}
}
///
<summary>
///
阴影输出角度(左边平行处为0度。顺时针方向)
///
</summary>
public
double
Angle
{
get
{
return
angle;
}
set
{
angle
=
value;
}
}
///
<summary>
///
阴影文字的不透明度
///
</summary>
public
byte
Alpha
{
get
{
return
alpha;
}
set
{
alpha
=
value;
}
}
///
<summary>
///
对文字阴影位图按阴影半径计算的高斯矩阵进行卷积模糊
///
</summary>
///
<param name="bmp">
文字阴影位图
</param>
private
unsafe
void
MaskShadow(Bitmap bmp)
{
if
(nuclear
==
0
)
MakeGaussMatrix();
Rectangle r
=
new
Rectangle(
0
,
0
, bmp.Width, bmp.Height);
//
克隆临时位图,作为卷积源
Bitmap tmp
=
(Bitmap)bmp.Clone();
BitmapData dest
=
bmp.LockBits(r, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData source
=
tmp.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try
{
//
源首地址(0, 0)的Alpha字节,也就是目标首像素的第一个卷积乘数的像素点
byte
*
ps
=
(
byte
*
)source.Scan0;
ps
+=
3
;
//
目标地址为卷积半径点(radius, radius)的Alpha字节
byte
*
pd
=
(
byte
*
)dest.Scan0;
pd
+=
(radius
*
(dest.Stride
+
4
)
+
3
);
//
位图实际卷积的部分
int
width
=
dest.Width
-
radius
*
2
;
int
height
=
dest.Height
-
radius
*
2
;
int
matrixSize
=
radius
*
2
+
1
;
//
卷积矩阵字节偏移
int
mOffset
=
dest.Stride
-
matrixSize
*
4
;
//
行尾卷积半径(radius)的偏移
int
rOffset
=
radius
*
8
;
int
count
=
matrixSize
*
matrixSize;
for
(
int
y
=
0
; y
<
height; y
++
)
{
for
(
int
x
=
0
; x
<
width; x
++
)
{
byte
*
s
=
ps
-
mOffset;
int
v
=
0
;
for
(
int
i
=
0
; i
<
count; i
++
, s
+=
4
)
{
if
((i
%
matrixSize)
==
0
)
s
+=
mOffset;
//
卷积矩阵的换行
v
+=
gaussMatrix[i]
*
*
s;
//
位图像素点Alpha的卷积值求和
}
//
目标位图被卷积像素点Alpha等于卷积和除以卷积核
*
pd
=
(
byte
)(v
/
nuclear);
pd
+=
4
;
ps
+=
4
;
}
pd
+=
rOffset;
ps
+=
rOffset;
}
}
finally
{
tmp.UnlockBits(source);
bmp.UnlockBits(dest);
tmp.Dispose();
}
}
///
<summary>
///
按给定的阴影半径生成高斯卷积矩阵
///
</summary>
protected
virtual
void
MakeGaussMatrix()
{
double
Q
=
(
double
)radius
/
2.0
;
if
(Q
==
0.0
)
Q
=
0.1
;
int
n
=
radius
*
2
+
1
;
int
index
=
0
;
nuclear
=
0
;
gaussMatrix
=
new
int
[n
*
n];
for
(
int
x
=
-
radius; x
<=
radius; x
++
)
{
for
(
int
y
=
-
radius; y
<=
radius; y
++
)
{
gaussMatrix[index]
=
(
int
)Math.Round(Math.Exp(
-
((
double
)x
*
x
+
y
*
y)
/
(
2.0
*
Q
*
Q))
/
(
2.0
*
Math.PI
*
Q
*
Q)
*
1000.0
);
nuclear
+=
gaussMatrix[index];
index
++
;
}
}
}
public
TextShadow()
{
//
//
TODO: Add constructor logic here
//
}
///
<summary>
///
画文字阴影
///
</summary>
///
<param name="g">
画布
</param>
///
<param name="text">
文字串
</param>
///
<param name="font">
字体
</param>
///
<param name="layoutRect">
文字串的布局矩形
</param>
///
<param name="format">
文字串输出格式
</param>
public
void
Draw(Graphics g,
string
text, Font font, RectangleF layoutRect, StringFormat format)
{
RectangleF sr
=
new
RectangleF((
float
)(radius
*
2
), (
float
)(radius
*
2
), layoutRect.Width, layoutRect.Height);
//
根据文字布局矩形长宽扩大文字阴影半径4倍建立一个32位ARGB格式的位图
Bitmap bmp
=
new
Bitmap((
int
)sr.Width
+
radius
*
4
, (
int
)sr.Height
+
radius
*
4
, PixelFormat.Format32bppArgb);
//
按文字阴影不透明度建立阴影画刷
Brush brush
=
new
SolidBrush(Color.FromArgb(alpha, Color.Black));
Graphics bg
=
Graphics.FromImage(bmp);
try
{
//
在位图上画文字阴影
bg.TextRenderingHint
=
System.Drawing.Text.TextRenderingHint.AntiAlias;
bg.DrawString(text, font, brush, sr, format);
//
制造阴影模糊
MaskShadow(bmp);
//
按文字阴影角度、半径和距离输出文字阴影到给定的画布
RectangleF dr
=
layoutRect;
dr.Offset((
float
)(Math.Cos(Math.PI
*
angle
/
180.0
)
*
distance),
(
float
)(Math.Sin(Math.PI
*
angle
/
180.0
)
*
distance));
sr.Inflate((
float
)radius, (
float
)radius);
dr.Inflate((
float
)radius, (
float
)radius);
g.DrawImage(bmp, dr, sr, GraphicsUnit.Pixel);
}
finally
{
bg.Dispose();
brush.Dispose();
bmp.Dispose();
}
}
///
<summary>
///
画文字阴影
///
</summary>
///
<param name="g">
画布
</param>
///
<param name="text">
文字串
</param>
///
<param name="font">
字体
</param>
///
<param name="layoutRect">
文字串的布局矩形
</param>
public
void
Draw(Graphics g,
string
text, Font font, RectangleF layoutRect)
{
Draw(g, text, font, layoutRect,
null
);
}
///
<summary>
///
画文字阴影
///
</summary>
///
<param name="g">
画布
</param>
///
<param name="text">
文字串
</param>
///
<param name="font">
字体
</param>
///
<param name="origin">
文字串的输出原点
</param>
///
<param name="format">
文字串输出格式
</param>
public
void
Draw(Graphics g,
string
text, Font font, PointF origin, StringFormat format)
{
RectangleF rect
=
new
RectangleF(origin, g.MeasureString(text, font, origin, format));
Draw(g, text, font, rect, format);
}
///
<summary>
///
画文字阴影
///
</summary>
///
<param name="g">
画布
</param>
///
<param name="text">
文字串
</param>
///
<param name="font">
字体
</param>
///
<param name="origin">
文字串的输出原点
</param>
public
void
Draw(Graphics g,
string
text, Font font, PointF origin)
{
Draw(g, text, font, origin,
null
);
}
}
我在代码中已经写了较详细的注释,应该不用再解释了,但是有一点可以在这里补充一下,有朋友在看了《GDI+ 在Delphi程序的应用 -- 可调节的文字阴影特效》后,曾经问过我,为什么对文字阴影进行高斯卷积操作只是针对位图像素的Alpha,而不对其RGB部分操作了?这个问题其实很简单,因为我采用的文字阴影颜色是黑色的,在画了文字的地方,其ARGB值为0xFF000000(假定文字阴影的Alpha为255),而没有文字的地方是全透明色,其ARGB值为0x00000000。可见,除了Alpha,无论是文字部分,还是透明部分,其R、G、B部分都为0,对它们进行任何的卷积操作的结果只能是“0”!只有通过对Alpha卷积操作,使之产生不同的不透明度,从而显示出来的黑色文字边缘就产生了我们想要的半影调效果。换句话说,如果不采用黑色作阴影颜色,就必须对位图像素的ARGB全部进行卷积模糊,不过其它颜色做阴影色效果不怎么好。
下面是个简单的C#演示程序:
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace TextShadowTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
FontFamily family = new FontFamily("Times New Roman"/*"华文行楷"*/);
Font font = new Font(family, 50, FontStyle.Bold, GraphicsUnit.Pixel);
Brush brush = new LinearGradientBrush(ClientRectangle, Color.Blue, Color.AliceBlue, 90);
e.Graphics.FillRectangle(brush, ClientRectangle);
TextShadow tShadow = new TextShadow();
tShadow.Draw(e.Graphics, "文字阴影特效", font, new PointF(10, (float)ClientRectangle.Height / 3));
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.DrawString("文字阴影特效", font, Brushes.White, new PointF(10, (float)ClientRectangle.Height / 3));
}
}
}
效果图和《GDI+ 在Delphi程序的应用 -- 可调节的文字阴影特效》是一样的,为避免你麻烦,我还是把那边的效果图做个链接,分别为2种字体的文字输出效果:
我是2007年元月开始写BLOG的,到目前刚好一年,包括这篇,共凑合了50篇文章,把我业余编程生涯近20年的“老底”和“新得”几乎都抖光了,春节前,这也是最后一篇文章了,明年不知又“研究”点什么,呵呵,反正我是无事的闲人,总得找点事打发时光。在这里,我先向各位拜个早早年了!祝大家来年事事如意,心想事成!
再次声明,第一次写C#代码,写得不好不要笑话我 ^_^。还是那句老话,有问题和指教,请留言或直接写信给我:[email protected]。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/maozefa/archive/2008/01/15/2044341.aspx