65.9K
CodeProject 正在变化。 阅读更多。
Home

Watermarker:嵌入图像和文本水印

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (22投票s)

2008年12月6日

CPOL

6分钟阅读

viewsIcon

97789

downloadIcon

4550

一个 C# 类,封装了 GDI+ 函数,以便轻松地对图像进行水印处理。

screen.jpg

引言

在我进行一个项目时,需要实现一个功能,即用图像的 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,则水印通过 PositionXPositionY 属性定位。默认值为 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
}

使用代码

让我们在图像的右上角添加一个半透明徽标水印,并在右下角添加一些版权文本。

我们将使用我漂亮的双胞胎女儿的照片作为源,并使用以下图像作为徽标(我不太擅长艺术,所以抱歉这个丑陋的徽标 :-))

logo.png

这是嵌入徽标和版权信息的代码:

// 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;

这是我们得到的结果:

result.jpg

基本上就是这样。希望有人觉得这有用。演示项目图标取自 Famfamfam Silk Icons 集。

历史

初始版本。

© . All rights reserved.