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.
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");
m_watermark = GetWatermarkImage(watermark);
m_watermark.RotateFlip(m_rotateFlip);
Point waterPos = GetWatermarkPosition();
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();
attributes.SetColorMatrix(colorMatrix);
if (m_transparentColor != Color.Empty) {
attributes.SetColorKey(m_transparentColor, m_transparentColor);
}
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.
private Image GetWatermarkImage(Image watermark) {
if (m_margin.All == 0 && m_scaleRatio == 1.0f)
return 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.
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.
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:
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:
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();
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:
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:
public void DrawText(string text) {
Image textWatermark = GetTextWatermark(text);
DrawImage(textWatermark);
}
private Image GetTextWatermark(string text) {
Brush brush = new SolidBrush(m_fontColor);
SizeF size;
using (Graphics g = Graphics.FromImage(m_image)) {
size = g.MeasureString(text, m_font);
}
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.
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 Image |
Gets watermarked image |
WatermarkPosition Position |
Gets 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 PositionX |
Gets or sets the watermark X coordinate; used only if Position = WatermarkPosition.Absolute . Default value is 0. |
int PositionY |
Gets or sets the watermark Y coordinate; used only if Position = WatermarkPosition.Absolute . Default value is 0 |
float Opacity |
Gets or sets the watermark opacity. A float from 0.0 to 1.0 (0.0 for completely transparent). Default value is 1.0 |
float ScaleRatio |
Gets or sets the watermark scaling ratio. A float greater than 0; works for image watermarks only. Default value is 1.0 |
Color TransparentColor |
Gets or sets the color used for watermark transparency. Default value is Color.Empty . |
RotateFlipType RotateFlip |
Gets or sets the watermark rotation and flipping options; see the MSDN documentation for the RotateFlipType enumeration details. Default value is RotateFlipType.RotateNoneFlipNone . |
Padding Margin |
Gets or sets the watermark margins. Default value is new Padding(0) . |
Font Font |
Gets or sets the watermark text font. Used only when watermarking text. Default value is Microsoft Sans Serif; 10pt . |
Color FontColor |
Gets 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:
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 :-))
Here is the code to embed the logo and the copyright message:
Watermarker watermarker = new Watermarker("kids.jpg");
watermarker.Position = WatermarkPosition.TopRight;
watermarker.Margin = new Padding(20);
watermarker.Opacity = 0.8f;
watermarker.TransparentColor = Color.Red;
watermarker.DrawImage("logo.png");
watermarker.Position = WatermarkPosition.BottomRight;
watermarker.Margin = new Padding(0);
watermarker.Font = new Font(FontFamily.GenericSansSerif, 60,
FontStyle.Bold | FontStyle.Italic);
watermarker.FontColor = Color.LemonChiffon;
watermarker.DrawText("© Copyright 2008. Lev Danielyan");
pictureBox1.Image = watermarker.Image;
Here is what we'll get as a result:
This is basically it. Hope someone finds this usable. The demo project icons are taken from the Famfamfam Silk Icons set.
History
Initial version.