由于在绘图过程中旋转有问题,我们可以换个角度去做这个事。我们完全可以先旋转好图片,用旋转好的图片,来打水印。这样反而简单了。思路来自下面的教程。
简介
给图片添加水印是比较常用的功能,通常是用于给图片添加版权的信息或者作者信息。
本文将重点向大家介绍怎么使用GDI+(Graphics)给图像添加图片水印和文字水印。
图片水印
技术要点
控制图片水印位置
一般水印位置最多为9个,如下图所示,红色矩形区域代表有效绘图区域,9个位置就是根据红色矩形宽高和水印图片宽高(或者旋转后所占区域宽高)计算而来,切记,红色矩形左上角的坐标不是(0,0),这个坐标怎么得到后面的内容里会提到。
代码如下:
/// <summary>
/// 获取图片水印位置,及small在big里的位置
/// 如果small的高度大于big的高度,返回big的高度
/// 如果small的宽度大于big的宽度,返回big的宽度
/// </summary>
/// <param name="pos">
/// 1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下
/// </param>
/// <returns></returns>
public Rectangle GetRectangleByPostion(Rectangle big, Rectangle small, int pos)
{
if (big.Width < small.Width)
{
small.Width = big.Width;
}
if (big.Height < small.Height)
{
small.Height = big.Height;
}
Rectangle retVal = small;
switch (pos)
{
case 1: retVal.X = 0; retVal.Y = 0; break;
case 2: retVal.X = (big.Width - small.Width) / 2; retVal.Y = 0; break;
case 3: retVal.X = big.Width - small.Width; retVal.Y = 0; break;
case 4: retVal.X = 0; retVal.Y = (big.Height - small.Height) / 2; break;
case 6: retVal.X = big.Width - small.Width; retVal.Y = (big.Height - small.Height) / 2; break;
case 7: retVal.X = 0; retVal.Y = big.Height - small.Height; break;
case 8: retVal.X = (big.Width - small.Width) / 2; retVal.Y = big.Height - small.Height; break;
case 9: retVal.X = big.Width - small.Width; retVal.Y = big.Height - small.Height; break;
default: retVal.X = (big.Width - small.Width) / 2; retVal.Y = (big.Height - small.Height) / 2; break;
}
retVal.X += big.X;
retVal.Y += big.Y;
return retVal;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
控制图片水印透明度
Graphics.DrawImage方法中有个参数可以传入ImageAttributes,ImageAttributes可以设置颜色矩阵,从而实现DrawImage时透明效果,代码如下:
/// <summary>
/// 获取一个带有透明度的ImageAttributes
/// </summary>
/// <param name="opcity"></param>
/// <returns></returns>
public ImageAttributes GetAlphaImgAttr(int opcity)
{
if (opcity < 0 || opcity > 100)
{
throw new ArgumentOutOfRangeException("opcity 值为 0~100");
}
//颜色矩阵
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,(float)opcity / 100,0},
new float[]{0,0,0,0,1}
};
ColorMatrix colorMatrix = new ColorMatrix(matrixItems);
ImageAttributes imageAtt = new ImageAttributes();
imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
return imageAtt;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
控制图片水印旋转角度
这个问题要说清楚比较费劲,图像处理工具类里有个方法是直接获取图像旋转N度后图像,方法如下:
/// <summary>
/// 获取原图像绕中心任意角度旋转后的图像
/// </summary>
/// <param name="rawImg"></param>
/// <param name="angle"></param>
/// <returns></returns>
public Image GetRotateImage(Image srcImage, int angle)
///代码略
1
2
3
4
5
6
7
8
获取旋转图像的原理详见本系列的另一篇文章《C#中基于GDI+(Graphics)图像处理系列之任意角度旋转图像》
图片水印方法的代码
注意,传入的参数中padding值决定有效绘图区域,假如,图片大小为200*120,padding值为10,那么有绘图区域就是(10,10,180,100),”控制图片水印位置”章节中,红色矩形左上角的坐标就是(10,10),如下图:
/// <summary>
/// 给图片加入图片水印,且设置水印透明度,旋转角度
/// </summary>
/// <param name="destPath">保存地址</param>
/// <param name="srcPath">源文件地址,如果想覆盖源图片,两个地址参数一定要一样</param>
/// <param name="waterPath">水印图片地址</param>
/// <param name="pos">设置水印位置,1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下</param>
/// <param name="padding">跟css里的padding一个意思</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="opcity">不透明度 100 为完全不透明,0为完全透明</param>
/// <param name="angle">顺时针旋转角度</param>
/// <param name="error"></param>
/// <param name="mimeType"></param>
/// <returns></returns>
public bool DrawWaterImage(string destPath, string srcPath, string waterPath, int pos, int padding, int quality, int opcity, int angle, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
Image srcImage = null;
Image waterImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取原图
srcImage = Image.FromFile(srcPath, false);
//获取水印图片
waterImage = Image.FromFile(waterPath, false);
var waterRect = new Rectangle(0, 0, waterImage.Width, waterImage.Height);
//定义画布
destImage = new Bitmap(srcImage);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图画到画布上
graphics.DrawImage(srcImage, new Rectangle(0, 0, destImage.Width, destImage.Height), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//不透明度大于0,则画水印
if (opcity > 0)
{
//获取可以用来绘制水印图片的有效区域
Rectangle validRect = new Rectangle(padding, padding, srcImage.Width - padding * 2, srcImage.Height - padding * 2);
//如果要进行旋转
if (angle != 0)
{
Image rotateImage = null;
try
{
//获取水印图像旋转后的图像
rotateImage = GetRotateImage(waterImage, angle);
if (rotateImage != null)
{
//旋转后图像的矩形区域
var rotateRect = new Rectangle(0, 0, rotateImage.Width, rotateImage.Height);
//计算水印图片的绘制位置
var destRect = GetRectangleByPostion(validRect, rotateRect, pos);
//如果不透明度>=100,那么直接将水印画到当前画布上.
if (opcity == 100)
{
graphics.DrawImage(rotateImage, destRect, rotateRect, GraphicsUnit.Pixel);
}
else
{
//如果不透明度在0到100之间,设置透明参数
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将旋转后的图片画到画布上
graphics.DrawImage(rotateImage, destRect, 0, 0, rotateRect.Width, rotateRect.Height, GraphicsUnit.Pixel, imageAtt);
}
}
}
catch (Exception ex)
{
error = ex.Message;
return retVal;
}
finally
{
if (rotateImage != null)
rotateImage.Dispose();
}
}
else
{
//计算水印图片的绘制位置
var destRect = GetRectangleByPostion(validRect, waterRect, pos);
//如果不透明度=100,那么直接将水印画到当前画布上.
if (opcity == 100)
{
graphics.DrawImage(waterImage, destRect, waterRect, GraphicsUnit.Pixel);
}
else
{
//如果不透明度在0到100之间,设置透明参数
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将水印图片画到画布上
graphics.DrawImage(waterImage, destRect, 0, 0, waterRect.Width, waterRect.Height, GraphicsUnit.Pixel, imageAtt);
}
}
}
//如果两个地址相同即覆盖,则提前Dispose源资源
if (destPath.ToLower() == srcPath.ToLower())
{
srcImage.Dispose();
}
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
if (waterImage != null)
waterImage.Dispose();
}
return retVal;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
注意:如果发现有的方法没有具体实现,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》获取完整的图像处理工具类源码
文字水印
文字水印与图片水印在实现上有所不同,主要原因是因为图片的宽高是固定的,而文字即使字数确定,显示出来的行数无法确定,另外Graphics.DrawString本身是不支持透明的,后面有方法解决这个问题。
技术要点
控制文字水印位置
与图片水印不同,文字的水印位置可以认为是对齐方式,1左上,2中上,3右上,4左中等等,同也有设置内边距的功能(padding)即控制有效绘图区域,如下图:
/// <summary>
/// 获取文字水印位置
/// </summary>
/// <param name="pos">
/// 1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下
/// </param>
/// <returns></returns>
public StringFormat GetStringFormat(int pos)
{
StringFormat format = new StringFormat();
switch (pos)
{
case 1: format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Near; break;
case 2: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Near; break;
case 3: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Near; break;
case 4: format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Center; break;
case 6: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Center; break;
case 7: format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Far; break;
case 8: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Far; break;
case 9: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Far; break;
default: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; break;
}
return format;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
控制文字水印透明度
由于DrawString不支持透明度,水印文字的透明需要借助DrawImage,实现步骤如下:
(1)定义一个与目标图片大小一样的临时画布,var transImg = new Bitmap(destImage);
(2)在有效绘图区域内,用DrawString将字画到临时画布上(使用StringFormat控制对齐方式)
(3)将临时画布绘画到目标图布上(控制透明)。
文字水印方法的代码
/// <summary>
/// 给图片加入文字水印,且设置水印透明度
/// </summary>
/// <param name="destPath">保存地址</param>
/// <param name="srcPath">源文件地址,如果想覆盖源图片,两个地址参数一定要一样</param>
/// <param name="text">文字</param>
/// <param name="font">字体,为空则使用默认,注意,在创建字体时 GraphicsUnit.Pixel </param>
/// <param name="brush">刷子,为空则使用默认</param>
/// <param name="pos">设置水印位置,1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下</param>
/// <param name="padding">跟css里的padding一个意思</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="opcity">不透明度 100 为完全不透明,0为完全透明</param>
/// <param name="error"></param>
/// <param name="mimeType"></param>
/// <returns></returns>
public bool DrawWaterText(string destPath, string srcPath, string text, Font font, Brush brush, int pos, int padding, int quality, int opcity, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
if (font == null)
{
font = new Font("微软雅黑", 20, FontStyle.Bold, GraphicsUnit.Pixel);//统一尺寸
}
if (brush == null)
{
brush = new SolidBrush(Color.White);
}
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
//定义画布,大小与源图像一样
destImage = new Bitmap(srcImage);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, destImage.Width, destImage.Height), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果水印字不为空,且不透明度大于0,则画水印
if (!string.IsNullOrEmpty(text) && opcity > 0)
{
//获取可以用来绘制水印图片的有效区域
Rectangle validRect = new Rectangle(padding, padding, srcImage.Width - padding * 2, srcImage.Height - padding * 2);
//获取绘画水印文字的格式,即文字对齐方式
StringFormat format = GetStringFormat(pos);
//如果不透明度==100,那么直接将字画到当前画布上.
if (opcity == 100)
{
graphics.DrawString(text, font, brush, validRect, format);
}
else
{
//如果不透明度在0到100之间,就要实现透明效果,文字没法透明,图片才能透明
//则先将字画到一块临时画布,临时画布与destImage一样大,先将字写到这块画布上,再将临时画布画到主画布上,同时设置透明参数
Bitmap transImg = null;
Graphics gForTransImg = null;
try
{
//定义临时画布
transImg = new Bitmap(destImage);
//获取高清Graphics
gForTransImg = GetGraphics(transImg);
//绘制文字
gForTransImg.DrawString(text, font, brush, validRect, format);
//**获取带有透明度的ImageAttributes
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将这块临时画布画在主画布上
graphics.DrawImage(transImg, new Rectangle(0, 0, destImage.Width, destImage.Height), 0, 0, transImg.Width, transImg.Height, GraphicsUnit.Pixel, imageAtt);
}
catch (Exception ex)
{
error = ex.Message;
return retVal;
}
finally
{
if (transImg != null)
transImg.Dispose();
if (gForTransImg != null)
gForTransImg.Dispose();
}
}
}
//如果两个地址相同即覆盖,则提前Dispose源资源
if (destPath.ToLower() == srcPath.ToLower())
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
注意:如果发现有的代码中没有具体实现,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》获取完整的图像处理工具源码
完整示例程序源码下载
http://download.csdn.net/detail/lhtzbj12/9730116
示例程序截图
如果想查阅本系列其他文章,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》