给图片添加文字水印

rkqp9592 7年前
   <h2><strong>功能需求</strong></h2>    <ol>     <li>在图片的给定位置上添加文字水印</li>     <li>水印可以旋转和设置透明度</li>    </ol>    <p>先说说自己的实现思路:</p>    <ol>     <li>先创建具有透明背景色的文字水印图像</li>     <li>将水印图像添加到原图像中</li>    </ol>    <h2><strong>实现</strong></h2>    <p>首先创建一个接口,用于约束水印的创建方式:</p>    <pre>  <code class="language-java">public interface IWatermark  {      Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle);  }</code></pre>    <p>具体实现:</p>    <pre>  <code class="language-java">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>      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;      }        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;      }  }</code></pre>    <p>水印图片透明度设置和旋转(下面这段代码和上面一段代码都位于 Watermark 类中,因为代码量较大,所以分开来展示):</p>    <pre>  <code class="language-java">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);          }        }</code></pre>    <p>整个 Watermark 类对外只暴露 Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency) 一个方法,向图片中添加水印只需创建 Watermark 实例,然后调用该方法即可。具体实现代码如下:</p>    <pre>  <code class="language-java">//.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);</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c03170be004cc430d76c29e171dab558.png"></p>    <p style="text-align:center">原图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6bff1de0b0131f7ac7af5aa690d9deb1.png"></p>    <p style="text-align:center">添加水印效果图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/78e4514e50a6dfce26fa3e8b7f4c1d46.png"></p>    <p style="text-align:center">水印顺时针旋转55 <sup>0</sup> 效果</p>    <p>旋转前后,水印图像的宽和高会发生变化,如下图所示:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5d1acdd41f1049d10ef7d26b1b64e41f.png"></p>    <p style="text-align:center">水印图片旋转前后宽高变化</p>    <h2><strong>扩展</strong></h2>    <p>上面的代码很好的实现了在图片上添加单行水印的效果,若要实现多行水印可以通过对 Watermark 类的扩展来实现。</p>    <p>创建类 MultiLineWatermark 继承自 Watermark ,然后覆写属性 WatermarkCanvas 来指定水印画布的大小;覆写方法 CreateWatermark 来实现多行水印效果。</p>    <pre>  <code class="language-java">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;          }      }</code></pre>    <p>具体的使用方式和调用 Watermark 类似,代码如下:</p>    <pre>  <code class="language-java">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);</code></pre>    <p>多行水印的文字是居中显示的:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/cb7c5daa97655951780a0f35deed4aad.png"></p>    <p style="text-align:center">多行水印效果图</p>    <p>若没有使用#标记换行,当一行字数超过指定的最大字数时,会自动换行。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/705251056f9d325654c62dd77f2c9794.png"></p>    <p style="text-align:center">自动换行效果</p>    <p>这篇文章是对自己项目中添加水印功能的记录,通篇以代码为主,看起来可能会感觉比较枯燥。</p>    <p>功能的实现没有太多难点,唯有一点感受较深,就是水印图像宽和高的计算。.NET(.NET Framework 4.5)中字体大小的度量单位默认是Point,而图像的度量单位是Pixel,单位的不同导致水印图像尺寸的计算出现偏差,这一点折磨我很久。</p>    <p> </p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/bc182bdfea73</p>    <p> </p>