Click here to Skip to main content
15,867,453 members
Articles / Multimedia / GDI+
Article

Watermarker: Embedding image and text watermarks

Rate me:
Please Sign up or sign in to vote.
4.41/5 (23 votes)
6 Dec 2008CPOL6 min read 93.4K   4.5K   66   23
A C# class wrapping GDI+ funtions for easy image watermarking.

screen.jpg

Introduction

When I was working on one of my projects, I needed to implement a function for watermarking images with stuff like image's EXIF data and various logos. I checked the MSDN documentation and figured out how to do it, but, in my opinion, writing the code with GDI+ means the code was not very straightforward and was missing some usability. So, I thought it would be nice to have some kind of a wrapper class that would encapsulate all the aspects of watermarking an image with another image or some text. After searching for something like that, I came to a conclusion that it'll be fun to write such a wrapper myself, and after doing so, I thought I might as well share it with others.

The Watermarker class is capable of embedding picture and text (multiline, Unicode etc.) watermarks. The class also supports opacity, margins, scaling, numerous predefined positions, transparency, rotating and flipping etc.

Please see the attached project for the class source code, as well as a demo project.

How this works

Before we dive into the code, I would like to mention that the source image, the image that we want to watermark, is cloned in the Watermarker constructor. This is being done because we wouldn't want to modify the source image as a result of our transformations. Instead, there is a public Image property defined, which can be used to get the watermarked image.

Embedding a picture watermark

First, let's see how the picture watermarks are being drawn on the image. Here is the full code of the public DrawImage(Image) method, which actually does all the image manipulation, and then I'll describe the code in some more detail.

C#
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);
    }
}

Before we actually draw the watermark on the image, we need to tune the watermark image, taking into account the margins and scaling settings. The private method GetWatermarkImage(Image) returns the original watermark image if the margin and scaling settings are default; otherwise, a new bitmap is created with the new sizes, which include the margins and the scaling and has the same horizontal and vertical resolution as the original watermark image. The original watermark image is drawn on the newly created bitmap canvas afterwards.

C#
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;
}

Next, we rotate and/or flip the watermark, which is done using the GDI+ RotateFlip(RotateFlipType) method.

C#
// Rotates and/or flips the watermark
m_watermark.RotateFlip(m_rotateFlip);

Next, the watermark coordinates are calculated with the private GetWatermarkPosition method, which works with the already transformed instance of the watermark image, so the image already has new margins and is scaled and rotated. If we calculate the coordinates of the image before we transform it (meaning, transformations that affect image sizes), we will obviously get the wrong coordinates.

C#
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);
}

Now, let's apply opacity and color transparency to our watermark image. To do that, we will use the ImageAttributes class from the .NET Framework. This class can do a whole lot more than just setting the transparent color or making the image opaque, so I would recommend checking the MSDN documentation for a detailed description.

Setting the transparent color is pretty straightforward, we just need to use the ImageAttributes.SetTransparentColor(Color) method like this:

C#
// Set the transparent color 
attributes.SetColorKey(m_transparentColor, m_transparentColor);

To achieve the watermark opacity, we will need to perform operations with the RGBA color space, and to do so, we will use the GDI+ ColorMatrix class. The ColorMatrix class is a 5x5 matrix that contains the coordinates for the RGBA space. This matrix can be used for numerous image transformations like tuning image brightness and contrast, making an image grayscale, or controlling individual color intensities. The M[0,0], M[1,1], and M[2,2] elements in the matrix control the intensities of the Red, Green, and Blue colors, respectively, whereas the M[3,3] controls the Alpha channel. We will not need to manipulate the color channels for making our image opaque, so our ColorMatrix will be defined as follows:

C#
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);

where the Alpha channel is controlled with a [0.0:1.0] floating point number. A more detailed discussion of the ColorMatrix class is out of the scope of this article, but there is a lot written about it, so it won't be a problem to get more information on color transformations.

We are actually done with all the transformation at this point, and we can draw the watermark on the image canvas:

C#
// 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);
}

Embedding a text watermark

Of course, we can use the GDI+ Graphics.DrawString() method to draw the text watermark directly on the source image, but, in this case, we will not be able to apply all the image transformations as we did for picture watermarks. So, one way to do that is to create an image with the text watermark first, and than add the image to the source image using the previously described Watermarker.DrawImage method. Here is the code for adding a text watermark, with a description of the code:

C#
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;
}

The problem with creating a bitmap for the text watermark is that we cannot figure out the resulting bitmap size right away. To do that, we can use the GDI+ Graphics.MeasureString method, and get the width and height of a rectangle needed to keep the text we want to draw. Then, we create a new bitmap of the calculated size and the horizontal and vertical resolution of the source image, and draw the text on its canvas.

C#
// 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);
}

And voila, we are all set to draw the bitmap containing the text watermark on the source image!

Watermarker Class

Constructors

Watermarker(Image image)Initialize class with an Image instance
Watermarker(string filename)Initialize class with an image file

Public Properties

Image ImageGets watermarked image
WatermarkPosition PositionGets or sets the watermark position. See the WatermarkPosition enumeration for predefined positions. If WatermarkPosition.Absolute is set, the watermark is being positioned with the PositionX and PositionY properties. The default value is WatermarkerPosition.Absolute.
int PositionXGets or sets the watermark X coordinate; used only if Position = WatermarkPosition.Absolute. Default value is 0.
int PositionYGets or sets the watermark Y coordinate; used only if Position = WatermarkPosition.Absolute. Default value is 0
float OpacityGets or sets the watermark opacity. A float from 0.0 to 1.0 (0.0 for completely transparent). Default value is 1.0
float ScaleRatioGets or sets the watermark scaling ratio. A float greater than 0; works for image watermarks only. Default value is 1.0
Color TransparentColorGets or sets the color used for watermark transparency. Default value is Color.Empty.
RotateFlipType RotateFlipGets or sets the watermark rotation and flipping options; see the MSDN documentation for the RotateFlipType enumeration details. Default value is RotateFlipType.RotateNoneFlipNone.
Padding MarginGets or sets the watermark margins. Default value is new Padding(0).
Font FontGets or sets the watermark text font. Used only when watermarking text. Default value is Microsoft Sans Serif; 10pt.
Color FontColorGets or sets the watermark text font color. Used only when watermarking text. Default value is Color.Black.

Public Methods

void DrawImage(Image watermark)Watermark an image.
void DrawImage(string filename)Watermark an image (load watermark image from file).
void DrawText(string text)Watermark text.
void ResetImage()Reset the source image, removing all previously drawn watermarks.

WatermarkPosition enumeration

The WatermarkPosition enumeration is defined as follows:

C#
public enum WatermarkPosition {
    Absolute,
    TopLeft,
    TopRight,
    TopMiddle,
    BottomLeft,
    BottomRight,
    BottomMiddle,
    MiddleLeft,
    MiddleRight,
    Center
}

Using the code

Let's add a watermark with a semi-transparent logo in the top right corner and some copyright text in the bottom right corner of the image.

We will use a photo of my beautiful twin daughters as the source, and the following image as a logo (I'm not much of an artist, so sorry for the ugly logo :-))

logo.png

Here is the code to embed the logo and the copyright message:

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

Here is what we'll get as a result:

result.jpg

This is basically it. Hope someone finds this usable. The demo project icons are taken from the Famfamfam Silk Icons set.

History

Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Virage Logic Corporation
Armenia Armenia
I'm a developer focusing on Quality Assurance at the Virage Logic Corporation.
My main responsibilities include the development and maintenance of an environment for automated quality assurance of the Verilog IP provided by our company.

Comments and Discussions

 
QuestionPerfect project - 5 high Pin
Bo Vistisen17-Apr-20 6:07
Bo Vistisen17-Apr-20 6:07 
QuestionHI Pin
Member 1301855822-Feb-17 20:38
Member 1301855822-Feb-17 20:38 
QuestionGreat project! Pin
Member 1285271022-Feb-17 7:14
Member 1285271022-Feb-17 7:14 
QuestionGreat Job Pin
lizard010119-Mar-13 13:21
lizard010119-Mar-13 13:21 
QuestionInvisible Watermark Pin
Loubna-ElKozah8-Feb-13 22:37
Loubna-ElKozah8-Feb-13 22:37 
QuestionCan we encrypt to watermarked image at one side and Decrypt at another side entring a security key... Pin
sushil Kumar Dhayal25-May-12 1:56
sushil Kumar Dhayal25-May-12 1:56 
SuggestionResize watermark Pin
X_Splinter16-Mar-12 2:05
X_Splinter16-Mar-12 2:05 
GeneralMy vote of 5 Pin
davesrinivas14-Feb-12 6:46
davesrinivas14-Feb-12 6:46 
really good work done by you brother.
GeneralMy vote of 4 Pin
Mavewei25-Jul-11 3:02
Mavewei25-Jul-11 3:02 
GeneralMy vote of 5 Pin
thatraja7-Jun-11 3:00
professionalthatraja7-Jun-11 3:00 
GeneralGreat Job.. One question Pin
Member 334296911-May-10 18:07
Member 334296911-May-10 18:07 
GeneralFast opacity changing... Pin
Demaker28-Jan-10 20:08
Demaker28-Jan-10 20:08 
GeneralGood job Pin
tnla21-Oct-09 8:42
tnla21-Oct-09 8:42 
QuestionGreat work! How to watermark diagonally? Pin
DesignerInCore9-Sep-09 16:49
DesignerInCore9-Sep-09 16:49 
Generalwater marking in vedios Pin
sushilabhanvar6-Apr-09 21:39
sushilabhanvar6-Apr-09 21:39 
GeneralAvoid text scaling Pin
serhhio6-Mar-09 0:03
serhhio6-Mar-09 0:03 
Generalbatch watermarking? =) Pin
ferdna8-Dec-08 6:24
ferdna8-Dec-08 6:24 
GeneralRe: batch watermarking? =) Pin
Lev Danielyan8-Dec-08 19:22
Lev Danielyan8-Dec-08 19:22 
GeneralRe: batch watermarking? =) Pin
ferdna9-Dec-08 4:56
ferdna9-Dec-08 4:56 
GeneralRe: batch watermarking? =) Pin
Lev Danielyan9-Dec-08 5:35
Lev Danielyan9-Dec-08 5:35 
GeneralRe: batch watermarking? =) Pin
Hardy Wang23-Dec-08 2:03
Hardy Wang23-Dec-08 2:03 
GeneralMy vote of 2 Pin
Rob Smiley6-Dec-08 10:21
Rob Smiley6-Dec-08 10:21 
GeneralRe: My vote of 2 Pin
Lev Danielyan7-Dec-08 0:24
Lev Danielyan7-Dec-08 0:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.