功能需求
- 在图片的给定位置上添加文字水印
- 水印可以旋转和设置透明度
先说说自己的实现思路:
- 先创建具有透明背景色的文字水印图像
- 将水印图像添加到原图像中
实现
首先创建一个接口,用于约束水印的创建方式:
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: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);
原图:
旋转前后,水印图像的宽和高会发生变化,如下图所示:
扩展
上面的代码很好的实现了在图片上添加单行水印的效果,若要实现多行水印可以通过对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: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,单位的不同导致水印图像尺寸的计算出现偏差,这一点折磨我很久。
图像旋转和透明度设置的两个方法Rotate
和SetAlpha
是在网友代码基础上修改得到,非本人原创,代码原文已在参考文章中列出,在此对两位网友表示感谢。