zoukankan      html  css  js  c++  java
  • 给图片添加文字水印

    功能需求

    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;
        }
    }
    View Code

    水印图片透明度设置和旋转(下面这段代码和上面一段代码都位于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);
            }
    
        }
    View Code

    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:UserschiwenjunDesktop1.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°效果:

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


     

    扩展

    上面的代码很好的实现了在图片上添加单行水印的效果,若要实现多行水印可以通过对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;
            }
        }
    View Code

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

    string path = @"C:UserschiwenjunDesktop1.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#图像旋转
    设置图片透明度的四种方法

    版权声明

    本文为作者原创,版权归作者雪飞鸿所有。 转载必须保留文章的完整性,且在页面明显位置处标明原文链接

    如有问题, 请发送邮件和作者联系。

  • 相关阅读:
    Java的静态块与实例块(转)
    Programming Ability Test学习 1031. Hello World for U (20)
    Programming Ability Test学习 1011. World Cup Betting (20)
    Programming Ability Test学习 1027. Colors in Mars (20)
    Programming Ability Test学习 1064. Complete Binary Search Tree (30)
    Programming Ability Test学习 1008. Elevator (20)
    【maven详解-生命周期】Maven的生命周期和插件
    【maven详解-插件】maven插件学习之源码插件Source Xref
    $(document).ready(){}、$(fucntion(){})、(function(){})(jQuery)onload()的区别
    你还没真的努力过,就轻易输给了懒惰
  • 原文地址:https://www.cnblogs.com/Cwj-XFH/p/6039098.html
Copyright © 2011-2022 走看看