Watermarker:嵌入图像和文本水印






4.54/5 (22投票s)
一个 C# 类,封装了 GDI+ 函数,以便轻松地对图像进行水印处理。
引言
在我进行一个项目时,需要实现一个功能,即用图像的 EXIF 数据和各种徽标等为图像添加水印。我查阅了 MSDN 文档,并找到了实现方法,但在我看来,使用 GDI+ 编写代码并不十分直接,并且缺少一些可用性。所以,我想如果能有一个包装类,将图像水印处理的所有方面(无论是用另一个图像还是文本)都封装起来,那会很不错。在寻找类似的东西之后,我得出结论,自己编写这样一个包装器会很有趣,并且在完成之后,我认为不妨与他人分享。
Watermarker
类能够嵌入图片和文本(多行、Unicode 等)水印。该类还支持不透明度、边距、缩放、许多预定义位置、透明度、旋转和翻转等。
请参阅随附的项目,其中包含该类的源代码以及演示项目。
工作原理
在我们深入探讨代码之前,我想提一下,源图像(我们要添加水印的图像)在 Watermarker
构造函数中进行了克隆。这样做是因为我们不希望由于转换而修改源图像。相反,定义了一个公共的 Image
属性,可用于获取带水印的图像。
嵌入图片水印
首先,让我们看看图片水印是如何绘制到图像上的。这是公共 DrawImage(Image)
方法的完整代码,它实际上执行所有图像操作,然后我将更详细地描述代码。
public void DrawImage(Image watermark) {
if (watermark == null)
throw new ArgumentOutOfRangeException("Watermark");
if (m_opacity < 0 || m_opacity > 1)
throw new ArgumentOutOfRangeException("Opacity");
if (m_scaleRatio <= 0)
throw new ArgumentOutOfRangeException("ScaleRatio");
// Creates a new watermark with margins (if margins are not
// specified returns the original watermark)
m_watermark = GetWatermarkImage(watermark);
// Rotates and/or flips the watermark
m_watermark.RotateFlip(m_rotateFlip);
// Calculate watermark position
Point waterPos = GetWatermarkPosition();
// Watermark destination rectangle
Rectangle destRect = new Rectangle(waterPos.X, waterPos.Y,
m_watermark.Width, m_watermark.Height);
ColorMatrix colorMatrix = new ColorMatrix(
new float[][] {
new float[] { 1, 0f, 0f, 0f, 0f},
new float[] { 0f, 1, 0f, 0f, 0f},
new float[] { 0f, 0f, 1, 0f, 0f},
new float[] { 0f, 0f, 0f, m_opacity, 0f},
new float[] { 0f, 0f, 0f, 0f, 1}
});
ImageAttributes attributes = new ImageAttributes();
// Set the opacity of the watermark
attributes.SetColorMatrix(colorMatrix);
// Set the transparent color
if (m_transparentColor != Color.Empty) {
attributes.SetColorKey(m_transparentColor, m_transparentColor);
}
// Draw the watermark
using (Graphics gr = Graphics.FromImage(m_image)) {
gr.DrawImage(m_watermark, destRect, 0, 0, m_watermark.Width,
m_watermark.Height, GraphicsUnit.Pixel, attributes);
}
}
在实际将水印绘制到图像上之前,我们需要调整水印图像,考虑到边距和缩放设置。私有方法 GetWatermarkImage(Image)
返回原始水印图像,如果边距和缩放设置为默认值;否则,将创建一个具有新尺寸(包括边距和缩放)的新位图,并具有与原始水印图像相同的水平和垂直分辨率。之后,原始水印图像将绘制到新创建的位图画布上。
private Image GetWatermarkImage(Image watermark) {
// If there are no margins specified
// and scale ration is 1, no need to create a new bitmap
if (m_margin.All == 0 && m_scaleRatio == 1.0f)
return watermark;
// Create a new bitmap with new sizes (size + margins) and draw the watermark
int newWidth = Convert.ToInt32(watermark.Width * m_scaleRatio);
int newHeight = Convert.ToInt32(watermark.Height * m_scaleRatio);
Rectangle sourceRect = new Rectangle(m_margin.Left, m_margin.Top,
newWidth, newHeight);
Rectangle destRect = new Rectangle(0, 0, watermark.Width, watermark.Height);
Bitmap bitmap = new Bitmap(newWidth + m_margin.Left + m_margin.Right,
newHeight + m_margin.Top + m_margin.Bottom);
bitmap.SetResolution(watermark.HorizontalResolution,
watermark.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap)) {
g.DrawImage(watermark, sourceRect,destRect,GraphicsUnit.Pixel);
}
return bitmap;
}
接下来,我们旋转和/或翻转水印,这是使用 GDI+ RotateFlip(RotateFlipType)
方法完成的。
// Rotates and/or flips the watermark
m_watermark.RotateFlip(m_rotateFlip);
接下来,使用私有 GetWatermarkPosition
方法计算水印坐标,该方法处理水印图像的已转换实例,因此图像已经具有新的边距,并且已缩放和旋转。如果我们对图像进行转换(即影响图像尺寸的转换)之前计算图像的坐标,我们显然会得到错误的坐标。
private Point GetWatermarkPosition() {
int x = 0;
int y = 0;
switch (m_position) {
case WatermarkPosition.Absolute:
x = m_x; y = m_y;
break;
case WatermarkPosition.TopLeft:
x = 0; y = 0;
break;
case WatermarkPosition.TopRight:
x = m_image.Width - m_watermark.Width; y = 0;
break;
case WatermarkPosition.TopMiddle:
x = (m_image.Width - m_watermark.Width) / 2; y = 0;
break;
case WatermarkPosition.BottomLeft:
x = 0; y = m_image.Height - m_watermark.Height;
break;
case WatermarkPosition.BottomRight:
x = m_image.Width - m_watermark.Width;
y = m_image.Height - m_watermark.Height;
break;
case WatermarkPosition.BottomMiddle:
x = (m_image.Width - m_watermark.Width) / 2;
y = m_image.Height - m_watermark.Height;
break;
case WatermarkPosition.MiddleLeft:
x = 0; y = (m_image.Height - m_watermark.Height) / 2;
break;
case WatermarkPosition.MiddleRight:
x = m_image.Width - m_watermark.Width;
y = (m_image.Height - m_watermark.Height) / 2;
break;
case WatermarkPosition.Center:
x = (m_image.Width - m_watermark.Width) / 2;
y = (m_image.Height - m_watermark.Height) / 2;
break;
default:
break;
}
return new Point(x, y);
}
现在,让我们对水印图像应用不透明度和颜色透明度。为此,我们将使用 .NET 框架中的 ImageAttributes
类。该类除了设置透明颜色或使图像不透明之外,还可以做更多的事情,因此我建议查阅 MSDN 文档以获取详细说明。
设置透明颜色非常简单,我们只需使用 ImageAttributes.SetTransparentColor(Color)
方法,如下所示:
// Set the transparent color
attributes.SetColorKey(m_transparentColor, m_transparentColor);
为了实现水印不透明度,我们需要对 RGBA 颜色空间执行操作,为此,我们将使用 GDI+ ColorMatrix
类。ColorMatrix
类是一个 5x5 矩阵,包含 RGBA 空间的坐标。该矩阵可用于许多图像转换,例如调整图像亮度对比度、使图像灰度化或控制单个颜色强度。矩阵中的 M[0,0]、M[1,1] 和 M[2,2] 元素分别控制红色、绿色和蓝色颜色的强度,而 M[3,3] 控制 Alpha 通道。我们不需要操作颜色通道来使图像不透明,因此我们的 ColorMatrix
将定义如下:
ColorMatrix colorMatrix = new ColorMatrix(
new float[][] {
new float[] { 1, 0f, 0f, 0f, 0f},
new float[] { 0f, 1, 0f, 0f, 0f},
new float[] { 0f, 0f, 1, 0f, 0f},
new float[] { 0f, 0f, 0f, m_opacity, 0f},
new float[] { 0f, 0f, 0f, 0f, 1}
});
ImageAttributes attributes = new ImageAttributes();
// Set the opacity of the watermark
attributes.SetColorMatrix(colorMatrix);
其中 Alpha 通道由 [0.0:1.0] 浮点数控制。关于 ColorMatrix
类的更详细讨论超出了本文的范围,但有很多关于它的文章,因此获取更多颜色转换信息不会有问题。
至此,我们已经完成了所有转换,现在可以将水印绘制到图像画布上。
// Watermark destination rectangle
Rectangle destRect = new Rectangle(waterPos.X, waterPos.Y,
m_watermark.Width, m_watermark.Height);
using (Graphics gr = Graphics.FromImage(m_image)) {
gr.DrawImage(m_watermark, destRect, 0, 0, m_watermark.Width,
m_watermark.Height, GraphicsUnit.Pixel, attributes);
}
嵌入文本水印
当然,我们可以使用 GDI+ Graphics.DrawString()
方法直接在源图像上绘制文本水印,但在这种情况下,我们将无法应用我们对图片水印所做的所有图像转换。因此,一种方法是首先创建一个带有文本水印的图像,然后使用前面描述的 Watermarker.DrawImage
方法将该图像添加到源图像。以下是添加文本水印的代码及其说明:
public void DrawText(string text) {
// Convert text to image, so we can use opacity etc.
Image textWatermark = GetTextWatermark(text);
DrawImage(textWatermark);
}
private Image GetTextWatermark(string text) {
Brush brush = new SolidBrush(m_fontColor);
SizeF size;
// Figure out the size of the box to hold the watermarked text
using (Graphics g = Graphics.FromImage(m_image)) {
size = g.MeasureString(text, m_font);
}
// Create a new bitmap for the text, and, actually, draw the text
Bitmap bitmap = new Bitmap((int)size.Width, (int)size.Height);
bitmap.SetResolution(m_image.HorizontalResolution,
m_image.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap)) {
g.DrawString(text, m_font, brush, 0, 0);
}
return bitmap;
}
为文本水印创建位图的问题在于我们无法立即确定最终位图的大小。为此,我们可以使用 GDI+ Graphics.MeasureString
方法,获取所需矩形的宽度和高度,以容纳我们要绘制的文本。然后,我们创建一个具有计算大小和源图像水平和垂直分辨率的新位图,并将文本绘制到其画布上。
// Create a new bitmap for the text, and, actually, draw the text
Bitmap bitmap = new Bitmap((int)size.Width, (int)size.Height);
bitmap.SetResolution(m_image.HorizontalResolution,
m_image.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap)) {
g.DrawString(text, m_font, brush, 0, 0);
}
瞧,我们已经准备好将包含文本水印的位图绘制到源图像上!
Watermarker 类
构造函数
Watermarker(Image image) |
使用 Image 实例初始化类 |
Watermarker(string filename) |
使用图像文件初始化类 |
公共属性
Image Image |
获取带水印的图像 |
WatermarkPosition Position |
获取或设置水印位置。有关预定义位置,请参阅 WatermarkPosition 枚举。如果设置 WatermarkPosition.Absolute ,则水印通过 PositionX 和 PositionY 属性定位。默认值为 WatermarkerPosition.Absolute 。 |
int PositionX |
获取或设置水印 X 坐标;仅当 Position = WatermarkPosition.Absolute 时使用。默认值为 0。 |
int PositionY |
获取或设置水印 Y 坐标;仅当 Position = WatermarkPosition.Absolute 时使用。默认值为 0。 |
float Opacity |
获取或设置水印不透明度。一个从 0.0 到 1.0 的 float (0.0 表示完全透明)。默认值为 1.0。 |
float ScaleRatio |
获取或设置水印缩放比例。一个大于 0 的 float ;仅适用于图像水印。默认值为 1.0。 |
Color TransparentColor |
获取或设置用于水印透明度的颜色。默认值为 Color.Empty 。 |
RotateFlipType RotateFlip |
获取或设置水印旋转和翻转选项;有关 RotateFlipType 枚举的详细信息,请参阅 MSDN 文档。默认值为 RotateFlipType.RotateNoneFlipNone 。 |
Padding Margin |
获取或设置水印边距。默认值为 new Padding(0) 。 |
Font Font |
获取或设置水印文本字体。仅在水印文本时使用。默认值为 Microsoft Sans Serif; 10pt 。 |
Color FontColor |
获取或设置水印文本字体颜色。仅在水印文本时使用。默认值为 Color.Black 。 |
公共方法
void DrawImage(Image watermark) |
为图像添加水印。 |
void DrawImage(string filename) |
为图像添加水印(从文件加载水印图像)。 |
void DrawText(string text) |
添加文本水印。 |
void ResetImage() |
重置源图像,移除所有以前绘制的水印。 |
WatermarkPosition 枚举
WatermarkPosition
枚举定义如下:
public enum WatermarkPosition {
Absolute,
TopLeft,
TopRight,
TopMiddle,
BottomLeft,
BottomRight,
BottomMiddle,
MiddleLeft,
MiddleRight,
Center
}
使用代码
让我们在图像的右上角添加一个半透明徽标水印,并在右下角添加一些版权文本。
我们将使用我漂亮的双胞胎女儿的照片作为源,并使用以下图像作为徽标(我不太擅长艺术,所以抱歉这个丑陋的徽标 :-))
这是嵌入徽标和版权信息的代码:
// Create a Watermarker instance
Watermarker watermarker = new Watermarker("kids.jpg");
// Set the properties for the logo
watermarker.Position = WatermarkPosition.TopRight;
watermarker.Margin = new Padding(20);
watermarker.Opacity = 0.8f;
watermarker.TransparentColor = Color.Red;
// Draw the logo
watermarker.DrawImage("logo.png");
// Set the properties for the copyright notice
watermarker.Position = WatermarkPosition.BottomRight;
watermarker.Margin = new Padding(0);
watermarker.Font = new Font(FontFamily.GenericSansSerif, 60,
FontStyle.Bold | FontStyle.Italic);
watermarker.FontColor = Color.LemonChiffon;
// Draw the copyright notice
watermarker.DrawText("© Copyright 2008. Lev Danielyan");
// Load the watermarked image to a PictureBox
pictureBox1.Image = watermarker.Image;
这是我们得到的结果:
基本上就是这样。希望有人觉得这有用。演示项目图标取自 Famfamfam Silk Icons 集。
历史
初始版本。