• [转载]C#版可调节的文字阴影特效


    原文地址: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(00, 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

    效果图和《GDI+ 在Delphi程序的应用 -- 可调节的文字阴影特效》是一样的,为避免你麻烦,我还是把那边的效果图做个链接,分别为2种字体的文字输出效果:

     

            我是2007年元月开始写BLOG的,到目前刚好一年,包括这篇,共凑合了50篇文章,把我业余编程生涯近20年的“老底”和“新得”几乎都抖光了,春节前,这也是最后一篇文章了,明年不知又“研究”点什么,呵呵,反正我是无事的闲人,总得找点事打发时光。在这里,我先向各位拜个早早年了!祝大家来年事事如意,心想事成!

            再次声明,第一次写C#代码,写得不好不要笑话我 ^_^。还是那句老话,有问题和指教,请留言或直接写信给我:maozefa@hotmail.com


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/maozefa/archive/2008/01/15/2044341.aspx

    QQ:67042248

    天之道,利而不害;圣人之道,为而不争.

  • 相关阅读:
    第三周作业
    面向过程(或者叫结构化)分析方法与面向对象分析方法到底区别在哪里?请根据自己的理解简明扼要的回答。
    移动APP开发使用什么样的原型设计工具比较合适?
    java 从上至下打印二叉树
    Qt applendPlainText()/append() 多添加一个换行解决方法
    tolua 转换 std::shared_ptr
    cmake add_custom_command 使用
    Boost使用笔记(Smart_ptr)
    webpack4 安装
    git安装管理
  • 原文地址:https://www.cnblogs.com/kk1230/p/1582660.html
Copyright © 2020-2023  润新知