功能需求
- 在圖片的給定位置上添加文字水印
- 水印可以旋轉(zhuǎn)和設(shè)置透明度
先說(shuō)說(shuō)自己的實(shí)現(xiàn)思路:
- 先創(chuàng)建具有透明背景色的文字水印圖像
- 將水印圖像添加到原圖像中
實(shí)現(xiàn)
首先創(chuàng)建一個(gè)接口,用于約束水印的創(chuàng)建方式:
public interface IWatermark
{
Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle);
}
具體實(shí)現(xiàn):
public class Watermark : IWatermark
{
//水印畫(huà)布
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)為計(jì)量單位
/// </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)為計(jì)量單位
/// </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)為計(jì)量單位
/// </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;
}
}
水印圖片透明度設(shè)置和旋轉(zhuǎn)(下面這段代碼和上面一段代碼都位于Watermark
類(lèi)中鲁僚,因?yàn)榇a量較大盔沫,所以分開(kāi)來(lái)展示):
public class Watermark : IWatermark
{
protected Bitmap Rotate(Bitmap rawImg, int angle)
{
angle = angle % 360;
//弧度轉(zhuǎn)換
double radian = TranslateAngleToRadian(angle);
//原圖的寬和高
int width = rawImg.Width;
int height = rawImg.Height;
//旋轉(zhuǎn)之后圖像的寬和高
Rectangle rotateRec = RecalculateRectangleSize(width, height, angle);
int rotateWidth = rotateRec.Width;
int rotateHeight = rotateRec.Height;
//目標(biāo)位圖
Bitmap targetImg = new Bitmap(rotateWidth, rotateHeight);
Graphics targetGraphics = Graphics.FromImage(targetImg);
//計(jì)算偏
Point Offset = new Point((rotateWidth - width) / 2, (rotateHeight - height) / 2);
//構(gòu)造圖像顯示區(qū)域:讓圖像的中心與窗口的中心點(diǎn)一致
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);
//恢復(fù)圖像在水平和垂直方向的平移
targetGraphics.TranslateTransform(-centerPoint.X, -centerPoint.Y);
targetGraphics.DrawImage(rawImg, rect);
//重至繪圖的所有變換
targetGraphics.ResetTransform();
targetGraphics.Save();
targetGraphics.Dispose();
return targetImg;
}
/// <summary>
/// 設(shè)置圖像透明度四康,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
類(lèi)對(duì)外暴露了API:Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)
洪碳,向圖片中添加水印只需創(chuàng)建Watermark
實(shí)例,然后調(diào)用該方法即可叼屠。具體實(shí)現(xiàn)代碼如下:
//.NET中瞳腌,F(xiàn)ont尺寸的默認(rèn)單位是Point,這里統(tǒng)一使用Pixel作為計(jì)量單位
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);
旋轉(zhuǎn)前后镜雨,水印圖像的寬和高會(huì)發(fā)生變化嫂侍,如下圖所示:
擴(kuò)展
上面的代碼很好的實(shí)現(xiàn)了在圖片上添加單行水印的效果,若要實(shí)現(xiàn)多行水印可以通過(guò)對(duì)Watermark
類(lèi)的擴(kuò)展來(lái)實(shí)現(xiàn)荚坞。
創(chuàng)建類(lèi)MultiLineWatermark
繼承自Watermark
挑宠,然后覆寫(xiě)屬性WatermarkCanvas
來(lái)指定水印畫(huà)布的大小西剥;覆寫(xiě)方法CreateWatermark
來(lái)實(shí)現(xiàn)多行水印效果痹栖。
public class MultiLineWatermark : Watermark
{
protected int _canvasWidth = 0;
protected int _canvasHeight = 0;
//每行水印所允許的最大字?jǐn)?shù)
protected int _lineMaxLength = 0;
//水印所允許的最大字?jǐn)?shù)
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;
}
}
具體的使用方式和調(diào)用Watermark
類(lèi)似,代碼如下:
string path = @"C:\Users\chiwenjun\Desktop\1.PNG";
//以#作為換行標(biāo)記
string markText = "字體:#微軟雅黑雅黑雅黑";
Font font = new Font("微軟雅黑", 40, FontStyle.Bold, GraphicsUnit.Pixel);
//若字?jǐn)?shù)超過(guò)每行所允許的最大值瞭空,超出部分被忽略
int lineMaxLength = 7;
//超出的字?jǐn)?shù)會(huì)被忽略
int wordMaxLength = 14;
//行高揪阿,用于計(jì)算水印圖像的高
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);
多行水印的文字是居中顯示的:
若沒(méi)有使用#標(biāo)記換行,當(dāng)一行字?jǐn)?shù)超過(guò)指定的最大字?jǐn)?shù)時(shí)咆畏,會(huì)自動(dòng)換行南捂。
這篇文章是對(duì)自己項(xiàng)目中添加水印功能的記錄,通篇以代碼為主旧找,看起來(lái)可能會(huì)感覺(jué)比較枯燥溺健。
功能的實(shí)現(xiàn)沒(méi)有太多難點(diǎn),唯有一點(diǎn)感受較深钮蛛,就是水印圖像寬和高的計(jì)算鞭缭。.NET(.NET Framework 4.5)中字體大小的度量單位默認(rèn)是Point剖膳,而圖像的度量單位是Pixel,單位的不同導(dǎo)致水印圖像尺寸的計(jì)算出現(xiàn)偏差岭辣,這一點(diǎn)折磨我很久吱晒。
圖像旋轉(zhuǎn)和透明度設(shè)置的兩個(gè)方法Rotate
和SetAlpha
是在網(wǎng)友代碼基礎(chǔ)上修改得到,非本人原創(chuàng)沦童,代碼原文已在參考文章中列出仑濒,在此對(duì)兩位網(wǎng)友表示感謝。