给图片添加文字水印

作者: 雪飞鸿 | 来源:发表于2016-11-07 15:13 被阅读656次

    功能需求

    1. 在图片的给定位置上添加文字水印
    2. 水印可以旋转和设置透明度

    先说说自己的实现思路:

    1. 先创建具有透明背景色的文字水印图像
    2. 将水印图像添加到原图像中

    实现

    首先创建一个接口,用于约束水印的创建方式:

    public interface IWatermark
    {
        Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle);
    }
    

    具体实现:

    public class Watermark : IWatermark
    {
        //水印画布
        protected virtual Rectangle WatermarkCanvas { set; get; }
    
        protected Watermark(){}
    
        public Watermark(string markText, Font font)
        {
            int width = (int)((markText.Length + 1) * font.Size);
            int height = font.Height;
            WatermarkCanvas = new Rectangle(0, 0, width, height);
        }
    
        /// <summary>
        /// 给图片添加水印,文字大小以像素(Pixel)为计量单位
        /// </summary>
        /// <param name="filename">图片文件全名</param>
        public Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)
        {
            return CreateMarkCore(filename, markText, font, brush, positionX, positionY, angle, transparency);
        }
    
        /// <summary>
        /// 绘制文字水印,文字大小以像素(Pixel)为计量单位
        /// </summary>
        public virtual Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle)
        {
            Bitmap watermark = new Bitmap(rectangle.Width, rectangle.Height);
            Graphics graphics = Graphics.FromImage(watermark);
            //消除锯齿
            graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            graphics.DrawString(markText, font, brush, rectangle);
            graphics.Dispose();
            return watermark;
        }
    
        /// <summary>
        /// 给图片添加水印,文字大小以像素(Pixel)为计量单位
        /// </summary>
        /// <param name="filename">图片文件全名</param>
        protected virtual Bitmap CreateMarkCore(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)
        {
            if (!File.Exists(filename))
            {
                throw new FileNotFoundException("文件不存在!");
            }
            Bitmap resultImg;
            using (Bitmap rawImg = new Bitmap(filename))
            {
                using (Bitmap watermarkImg = CreateWatermark(markText, font, brush, WatermarkCanvas))
                using (Bitmap rotateImg = Rotate(watermarkImg, angle))
                {
                    using (Bitmap temp = SetAlpha(rotateImg, transparency))
                    {
                        resultImg = new Bitmap(rawImg.Width, rawImg.Height);
                        using (Graphics newGraphics = Graphics.FromImage(resultImg))
                        {
                            newGraphics.DrawImage(rawImg, 0, 0);
                            newGraphics.DrawImage(temp, positionX, positionY);
                        }
                    }
                }
            }
            return resultImg;
        }
    }
    

    水印图片透明度设置和旋转(下面这段代码和上面一段代码都位于Watermark类中,因为代码量较大,所以分开来展示):

    public class Watermark : IWatermark
    {
            protected Bitmap Rotate(Bitmap rawImg, int angle)
            {
                angle = angle % 360;
                //弧度转换
                double radian = TranslateAngleToRadian(angle);
                //原图的宽和高
                int width = rawImg.Width;
                int height = rawImg.Height;
                //旋转之后图像的宽和高
                Rectangle rotateRec = RecalculateRectangleSize(width, height, angle);
                int rotateWidth = rotateRec.Width;
                int rotateHeight = rotateRec.Height;
                //目标位图
                Bitmap targetImg = new Bitmap(rotateWidth, rotateHeight);
                Graphics targetGraphics = Graphics.FromImage(targetImg);
                //计算偏
                Point Offset = new Point((rotateWidth - width) / 2, (rotateHeight - height) / 2);
                //构造图像显示区域:让图像的中心与窗口的中心点一致
                Rectangle rect = new Rectangle(Offset.X, Offset.Y, width, height);
                Point centerPoint = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
                targetGraphics.TranslateTransform(centerPoint.X, centerPoint.Y);
                targetGraphics.RotateTransform(angle);
                //恢复图像在水平和垂直方向的平移
                targetGraphics.TranslateTransform(-centerPoint.X, -centerPoint.Y);
                targetGraphics.DrawImage(rawImg, rect);
                //重至绘图的所有变换
                targetGraphics.ResetTransform();
                targetGraphics.Save();
                targetGraphics.Dispose();
                return targetImg;
            }
    
            /// <summary>
            /// 设置图像透明度,0:全透明,255:不透明
            /// </summary>
            protected Bitmap SetAlpha(Bitmap rawImg, int alpha)
            {
                if (!(0 <= alpha) && alpha <= 255)
                {
                    throw new ArgumentOutOfRangeException("alpha ranges from 0 to 255.");
                }
                //颜色矩阵
                float[][] matrixItems =
                {
                    new float[]{1,0,0,0,0},
                    new float[]{0,1,0,0,0},
                    new float[]{0,0,1,0,0},
                    new float[]{0,0,0,alpha/255f,0},
                    new float[]{0,0,0,0,1}
                };
                ColorMatrix colorMatrix = new ColorMatrix(matrixItems);
                ImageAttributes imageAtt = new ImageAttributes();
                imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                Bitmap resultImg = new Bitmap(rawImg.Width, rawImg.Height);
                Graphics g = Graphics.FromImage(resultImg);
                g.DrawImage(rawImg, new Rectangle(0, 0, rawImg.Width, rawImg.Height),
                        0, 0, rawImg.Width, rawImg.Height, GraphicsUnit.Pixel, imageAtt);
                g.Dispose();
    
                return resultImg;
            }
    
            protected double TranslateAngleToRadian(float angle)
            {
                return angle * Math.PI / 180;
            }
    
            protected Rectangle RecalculateRectangleSize(int width, int height, float angle)
            {
                double radian = TranslateAngleToRadian(angle);
                double cos = Math.Cos(radian);
                double sin = Math.Sin(radian);
                double newWidth = (int)(Math.Max(Math.Abs(width * cos - height * sin), Math.Abs(width * cos + height * sin)));
                double newHeight = (int)(Math.Max(Math.Abs(width * sin - height * cos), Math.Abs(width * sin + height * cos)));
                return new Rectangle(0, 0, (int)newWidth, (int)newHeight);
            }
    
        }
    
    

    Watermark类对外暴露了API:Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency),向图片中添加水印只需创建Watermark实例,然后调用该方法即可。具体实现代码如下:

    //.NET中,Font尺寸的默认单位是Point,这里统一使用Pixel作为计量单位
    string path = @"C:\Users\chiwenjun\Desktop\1.PNG";
    string markText = "字体:微软雅黑";
    Font font = new Font("微软雅黑", 40, FontStyle.Bold, GraphicsUnit.Pixel);
    Watermark watermark = new Watermark(markText, font);
    Bitmap img = watermark.Mark(path, markText, font, new SolidBrush(Color.FromArgb(0, 0, 0)), 160, 535, 0, 180);
    img.Save(path, ImageFormat.Png);
    
    原图 添加水印效果图 水印顺时针旋转55<sup>0</sup>效果

    旋转前后,水印图像的宽和高会发生变化,如下图所示:

    水印图片旋转前后宽高变化

    扩展

    上面的代码很好的实现了在图片上添加单行水印的效果,若要实现多行水印可以通过对Watermark类的扩展来实现。
    创建类MultiLineWatermark继承自Watermark,然后覆写属性WatermarkCanvas来指定水印画布的大小;覆写方法CreateWatermark来实现多行水印效果。

    
        public class MultiLineWatermark : Watermark
        {
            protected int _canvasWidth = 0;
            protected int _canvasHeight = 0;
            //每行水印所允许的最大字数
            protected int _lineMaxLength = 0;
            //水印所允许的最大字数
            protected int _wordMaxLength = 0;
    
            protected override Rectangle WatermarkCanvas
            {
                get
                {
                    return new Rectangle(0, 0, this._canvasWidth, this._canvasHeight);
                }
            }
    
            public MultiLineWatermark(int canvasWidth, int canvasHeight, int lineMaxLength, int wordMaxLength)
            {
                this._canvasWidth = canvasWidth;
                this._canvasHeight = canvasHeight;
                this._lineMaxLength = lineMaxLength;
                this._wordMaxLength = wordMaxLength;
            }
    
            public override Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle)
            {
                Bitmap watermark = new Bitmap(rectangle.Width, rectangle.Height);
                Graphics graphics = Graphics.FromImage(watermark);
                //消除锯齿
                graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                int lineHeight = _canvasHeight / (_wordMaxLength / _lineMaxLength);
                if (markText.Contains('#'))
                {
                    string[] textList = markText.Split('#');
                    int count = (int)Math.Min(textList.Length, Math.Ceiling(_wordMaxLength * 1.0 / _lineMaxLength));
                    for (int i = 0; i < count; i++)
                    {
                        if (textList[i].Length > _lineMaxLength)
                        {
                            textList[i] = textList[i].Substring(0, _lineMaxLength);
                        }
                        //文字居中
                        graphics.DrawString(textList[i], font, brush, (rectangle.Width - textList[i].Length * font.Size) / 2, i * lineHeight);
                    }
                }
                else
                {
                    //文字居中
                    if (markText.Length <= _lineMaxLength)
                    {
                        graphics.DrawString(markText, font, brush, (rectangle.Width - markText.Length * font.Size) / 2, 0);
                    }
                    else
                    {
                        int count = (int)Math.Min(Math.Ceiling(_wordMaxLength * 1.0 / _lineMaxLength), Math.Ceiling(markText.Length * 1.0 / _lineMaxLength));
                        string[] temp = new string[count];
                        for (int i = 0; i < count; i++)
                        {
                            if (i * _lineMaxLength + _lineMaxLength <= markText.Length)
                            {
                                temp[i] = markText.Substring(i * _lineMaxLength, _lineMaxLength);
                            }
                            else
                            {
                                temp[i] = markText.Substring(i * _lineMaxLength, markText.Length - i * _lineMaxLength);
                            }
                            graphics.DrawString(temp[i], font, brush, (rectangle.Width - temp[i].Length * font.Size) / 2, i * lineHeight);
                        }
                    }
                }
                graphics.Dispose();
                return watermark;
            }
        }
    

    具体的使用方式和调用Watermark类似,代码如下:

    string path = @"C:\Users\chiwenjun\Desktop\1.PNG";
    //以#作为换行标记
    string markText = "字体:#微软雅黑雅黑雅黑";
    Font font = new Font("微软雅黑", 40, FontStyle.Bold, GraphicsUnit.Pixel);
    //若字数超过每行所允许的最大值,超出部分被忽略
    int lineMaxLength = 7;
    //超出的字数会被忽略
    int wordMaxLength = 14;
    //行高,用于计算水印图像的高
    int lineHeight = 55;
    int width = (int)((lineMaxLength + 1) * font.Size);
    int height = (int)(Math.Ceiling(wordMaxLength * 1.0 / lineMaxLength) * lineHeight);
    Watermark watermark = new MultiLineWatermark(width, height, lineMaxLength, wordMaxLength);
    Bitmap img = watermark.Mark(path, markText, font, new SolidBrush(Color.FromArgb(0, 0, 0)), 150, 535, 0, 180);
    img.Save(path, ImageFormat.Png);
    

    多行水印的文字是居中显示的:

    多行水印效果图

    若没有使用#标记换行,当一行字数超过指定的最大字数时,会自动换行。

    自动换行效果

    这篇文章是对自己项目中添加水印功能的记录,通篇以代码为主,看起来可能会感觉比较枯燥。
    功能的实现没有太多难点,唯有一点感受较深,就是水印图像宽和高的计算。.NET(.NET Framework 4.5)中字体大小的度量单位默认是Point,而图像的度量单位是Pixel,单位的不同导致水印图像尺寸的计算出现偏差,这一点折磨我很久。
    图像旋转和透明度设置的两个方法RotateSetAlpha是在网友代码基础上修改得到,非本人原创,代码原文已在参考文章中列出,在此对两位网友表示感谢。

    参考文章:

    C#图像旋转
    设置图片透明度的四种方法

    相关文章

      网友评论

      本文标题:给图片添加文字水印

      本文链接:https://www.haomeiwen.com/subject/sduputtx.html