Click here to Skip to main content
Click here to Skip to main content

Creating a Watermarked Photograph with GDI+ for .NET

By , 25 Sep 2002
 

Introduction

Often when placing photographic images on a website it is necessary to permanently overlay a watermark and/or Copyright statement on top of that image. Such an insertion helps to identify the person that took the photograph and indicate the organization that holds the Copyright. Doing this task by hand can become time consuming and is often inconsistent. Using some simple techniques there is an easy way to accomplish this programmatically using C# and GDI+.

Overview

I am going to show you a variety of techniques for manipulating an image. The following is a high level list of some of those techniques:

  • Inserting text on top of an image positioned relative to the size on an image
  • Dynamically choosing a System.Drawing.Font size to maximize readability
  • Manipulating the opacity of a String of text
  • Replacing a specific color in a bitmap to achieve transparency
  • Changing the opacity of an image through a 5x5 ColorMatrix

Define Images

Small photo
(Photo courtesy of AP wire)

The first step in this process is to load a photographic image for which you would like to apply the watermark.  This image can be virtually any size and resolution.  For this example we will use an image that has a width of 449 pixels and a height of 346.  The resolution is 72 dpi.

When the Main method is instantiated, the two variable of type string are defined. The first will define where to find the photograph, watermark and output the new image. The second will define the Copyright string we will use as part of our watermark.

string WorkingDirectory = @"C:\Projects\WaterMark";
string Copyright = "Copyright © 2002 
                 - AP Photo/David Zalubowski";

The following creates an Image object from the specified file and then defines a variable for both its Width and Height. These dimensions are then used to build a Bitmap object with a 24 bits per pixel format for the color data.  Lastly this Bitmap is then used to create a new Graphics object from the specified Bitmap image.

Image imgPhoto = Image.FromFile(WorkingDirectory 
              + "\\watermark_photo.jpg");
int phWidth = imgPhoto.Width; int phHeight =
                                  imgPhoto.Height;

Bitmap bmPhoto = new Bitmap(phWidth, phHeight, 
                     PixelFormat.Format24bppRgb);
bmPhoto.SetResolution(72, 72); 

Graphics grPhoto = Graphics.FromImage(bmPhoto);

Watermark Image This code loads the watermark image that has been saved as a BMP and set with a background color of Green (A=0,R=0,G=255,B=0). Once again it defines a variable for both its Width and Height
(Image courtesy of MLB.com)
Image imgWatermark = new Bitmap(WorkingDirectory
                     + "\\watermark.bmp");
int wmWidth = imgWatermark.Width;
int wmHeight = imgWatermark.Height;

Step #1 - Watermark Text

This code draws the imgPhoto to the Graphics object positioning it (x= 0,y=0) at 100% of its original size. All future drawing will occur on top of the original photograph.

grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
grPhoto.DrawImage(
	imgPhoto,                               
	new Rectangle(0, 0, phWidth, phHeight), 
	0,                                      
	0,                                       
	phWidth,                                
	phHeight,                               
	GraphicsUnit.Pixel);

To maximize the size of the Copyright message we will test 7 different Font sizes to determine the largest possible size we can use for the width of our Photograph.  To effectively do this, we will define an array of integers then iterate through those values measuring the Copyright string in the various point sizes.  Once we have determined the largest possible size we will exit the loop and draw the text.

int[] sizes = new int[]{16,14,12,10,8,6,4};
Font crFont = null; 
SizeF crSize = new	SizeF(); 
for (int i=0 ;i<7; i++)
{ 
    crFont = new Font("arial", sizes[i],
                                 FontStyle.Bold);
    crSize = grPhoto.MeasureString(Copyright,
                                         crFont);

	if((ushort)crSize.Width < (ushort)phWidth)
		break;
}

Since all photographs will have varying heights, determine a position 5% from the bottom of the image.  Use the Copyright strings height to determine an appropriate y-coordinate for which to draw the string.  Determine its x-coordinate by calculating the centre of the image then define a StringFormat object and set the StringAlignment to Center. 

int yPixlesFromBottom = (int)(phHeight *.05);
float yPosFromBottom = ((phHeight - 
           yPixlesFromBottom)-(crSize.Height/2));
float xCenterOfImg = (phWidth/2);

StringFormat StrFormat = new StringFormat();
StrFormat.Alignment = StringAlignment.Center;

Now that we have all of the necessary positioning coordinates create a SolidBrush with a Color of 60% Black (alpha value of 153).  Draw the Copyright string at the appropriate position offset 1 pixel to the right and 1 pixel down. This offset will create a shadow effect.  Repeat this process using a White Brush drawing the same text directly on top of the previously drawn string.

SolidBrush semiTransBrush2 = 
    new SolidBrush(Color.FromArgb(153, 0, 0,0)); 

grPhoto.DrawString(Copyright,                    
    crFont,                                      
    semiTransBrush2,                             
    new PointF(xCenterOfImg+1,yPosFromBottom+1), 
    StrFormat);

SolidBrush semiTransBrush = new SolidBrush(
             Color.FromArgb(153, 255, 255, 255));

grPhoto.DrawString(Copyright,                 
    crFont,                                   
    semiTransBrush,                           
    new PointF(xCenterOfImg,yPosFromBottom),  
    StrFormat);

Step #2 - Watermark Image

Create a Bitmap based on the previously modified photograph. Load this Bitmap into a new Graphic Object
Bitmap bmWatermark = new Bitmap(bmPhoto); 
bmWatermark.SetResolution(
                imgPhoto.HorizontalResolution, 
                    imgPhoto.VerticalResolution);

Graphics grWatermark =
                 Graphics.FromImage(bmWatermark);

To achieve a translucent watermark we will apply two color manipulations by defining an ImageAttributes object and setting two of its properties. The first step in manipulating the watermark image is to replace the background color with one that is transparent (Alpha=0, R=0, G=0, B=0). To do this we will use a Colormap and define a RemapTable. As previously shown my watermark was defined with a background of 100% Green this will be the color we search for and replace with transparency.

ImageAttributes imageAttributes =
                           new ImageAttributes();
ColorMap colorMap = new ColorMap();

colorMap.OldColor=Color.FromArgb(255, 0, 255, 0);
colorMap.NewColor=Color.FromArgb(0, 0, 0, 0);
ColorMap[] remapTable = {colorMap};

imageAttributes.SetRemapTable(remapTable,
                         ColorAdjustType.Bitmap);

The second color manipulation is used to change the opacity of the watermark. This is done by applying a 5x5 matrix that contains the coordinates for the RGBA space. By setting the 3rd row and 3rd column to 0.3f we achieve a level of opacity.  The result is a watermark which slightly shows the underlying image.

float[][] colorMatrixElements = { 
   new float[] {1.0f,  0.0f,  0.0f,  0.0f, 0.0f},
   new float[] {0.0f,  1.0f,  0.0f,  0.0f, 0.0f},
   new float[] {0.0f,  0.0f,  1.0f,  0.0f, 0.0f},
   new float[] {0.0f,  0.0f,  0.0f,  0.3f, 0.0f},
   new float[] {0.0f,  0.0f,  0.0f,  0.0f, 1.0f}
};

ColorMatrix wmColorMatrix = new
                ColorMatrix(colorMatrixElements);

imageAttributes.SetColorMatrix(wmColorMatrix, 
                       ColorMatrixFlag.Default, 
                         ColorAdjustType.Bitmap);
	

With both color manipulations added to the imageAttributes object we can now draw the watermark in the upper right hand corner of the photograph. We will offset the image 10 pixels down and 10 pixels to the left.

int xPosOfWm = ((phWidth - wmWidth)-10);
int yPosOfWm = 10;

grWatermark.DrawImage(imgWatermark, 
    new Rectangle(xPosOfWm,yPosOfWm,wmWidth,
                                     wmHeight),
    0,                  
    0,                   
    wmWidth,            
    wmHeight,		    
    GraphicsUnit.Pixel, 
    imageAttributes);

Or last and final step will be to replace the original Image with the new Bitmap, dispose of both Graphic objects then save this Image to the file system.
imgPhoto = bmWatermark;
grPhoto.Dispose();
grWatermark.Dispose();

\\watermark_final.jpg", 
imgPhoto.Save(WorkingDirectory + "
	ImageFormat.Jpeg);
imgPhoto.Dispose();
imgWatermark.Dispose();

That's it! Compile the project, run it, and see what happens! The code is fairly straightforward if it all makes sense then these techniques can be used for 100's of different image manipulations. The possibilities are endless.

Revision History

26 Sep 2002 - Initial Revision

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Joel Neubeck
Web Developer
United States United States
Member
Joel is a Microsoft.NET system architect and Director of Development for a leading interactive development firm located in Tempe, Arizona. Our firm designs and develops a wide range of custom web applications which leverage Flash, Ajax, ASP.NET and the Microsoft.Net Framework.
In his free time, he enjoys spending time with his wife and children.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionGreat TutorialmemberCBroos10 Mar '13 - 6:39 
This tutorial has proven to be quite useful to me. Thank you for taking the time to write this.
GeneralGreat articlememberfilip_andry1 Mar '13 - 15:42 
Thanks for the article. This helps me a lot. I will use this code for my applications.Thumbs Up | :thumbsup:
AnswerEnhancement and question [modified]memberFransDep12 Feb '13 - 3:41 
Hello,
First, thank for sharing our article.
 
I have try on test images and it's work most of the time.
 
I have encounter an error "GDI error" and I have change imgPhoto.Save(destFileName, ImageFormat.Jpeg); by Bitmap bm = new Bitmap(imgPhoto) and it works.
 
But sometime, I have an 'Out of memory' exception on this new line of code and I cannot find any solution.
The input image are more than 250MB.
If anybody have an brilliant idea ?
 
Regards.
 
Edit: resolved with
using (Stream s = File.OpenRead(@"\myfile.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);

modified 15 Feb '13 - 8:38.

GeneralGreat work!memberMember 77240962 Dec '12 - 20:49 
Thanks for sharing this wonderful tutorial. Laugh | :laugh:
GeneralMy vote of 5!memberMember 956657721 Nov '12 - 1:50 
Great article and code!
 
regards
Hallvard
GeneralMy vote of 5memberhcheng_qi29 Dec '11 - 16:43 
Wonderful. I wanna use it on my website. Thanks.
GeneralMy vote of 5memberhitesh140715 Nov '11 - 11:52 
Great article. Thanks so much. You saved my day Thumbs Up | :thumbsup:
QuestionHere's how I used it for my ASP.NET WebsitememberMember 818886925 Aug '11 - 6:27 
I modified slightly to make a class and added it to my App_Code. I didn't use the Copyright, only because I didn't need it.
 
From the codebehind I do this
 
BasePath = "~/aFileInMySolution/";
PhotoFilename = "origImage.jpg";
WatermarkFilename = "anImageLogo.png";
Copyright = "Copyright 2011";
 
WatermarkMyImage myImage = new WatermarkMyImage();
string newFileName = myImage.AddWatermarkLayer(Server.MapPath(BasePath), PhotoFilename, WatermarkFilename, Copyright);
backgroundPath = System.Web.VirtualPathUtility.ToAbsolute(BasePath + newFileName);
 
Here's the method in my WatermarkMyImage class:
 
public string AddWatermarkLayer(string basePath, string photoFilename, string watermarkFilename, string copyrightString)
{
//Create the image object from the path
System.Drawing.Image imgPhoto = System.Drawing.Image.FromFile(basePath + photoFilename);
 
//Get the dimensions of imgPhoto
int phWidth = imgPhoto.Width;
int phHeight = imgPhoto.Height;
 
//Create a new object from the imgPhoto
Bitmap bmPhoto = new Bitmap(phWidth, phHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
bmPhoto.SetResolution(72, 72);
Graphics grPhoto = Graphics.FromImage(bmPhoto);
 
//Load the watermark image saved as .bmp and set with background color of green (Alpha=0, R=106, G=125, B=106)
System.Drawing.Image imgWatermark = System.Drawing.Image.FromFile(basePath + watermarkFilename);
 
//Size of imgWatermark
int wmWidth = imgWatermark.Width;
int wmHeight = imgWatermark.Height;
 
//Draws the imgPhoto to the graphics object position at (x-0, y=0) 100% of original
grPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
grPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, phWidth, phHeight), 0, 0, phWidth, phHeight, GraphicsUnit.Pixel);
 
//To maximize the size of the Logo text using seven different fonts
int[] sizes = new int[] { 16, 14, 12, 10, 8, 6, 4 };
Font crFont = null;
SizeF crSize = new SizeF();
 
//Determines the largest possible size of the font
for (int i = 0; i < 7; i++)
{
crFont = new Font("arial", sizes[i], FontStyle.Bold);
crSize = grPhoto.MeasureString(copyrightString, crFont);
 
if ((ushort)crSize.Width < (ushort)phWidth)
break;
}
 
//For photos with varying heights, determines a five percent position from bottom of image
int yPixlesFromBottom = (int)(phHeight * .05);
float yPosFromBottom = (int)((phHeight - yPixlesFromBottom) - (crSize.Height / 2));
float xCenterOfImg = (phWidth / 2);
 
StringFormat StrFormat = new StringFormat();
StrFormat.Alignment = StringAlignment.Center;
 
//Create a brush with 60% Black (Alpha 153)
SolidBrush semiTransparBrushOne = new SolidBrush(Color.FromArgb(153, 0, 0, 0));
 
//Creates a shadow effect
grPhoto.DrawString(copyrightString, crFont, semiTransparBrushOne, new PointF(xCenterOfImg + 1, yPosFromBottom + 1), StrFormat);
 
//Create a brush with 60% White (Alpha 153)
SolidBrush semiTransparBrushTwo = new SolidBrush(Color.FromArgb(153, 255, 255, 255));
 
//Draws the same text on top of the previous
grPhoto.DrawString(copyrightString, crFont, semiTransparBrushTwo, new PointF(xCenterOfImg + 1, yPosFromBottom + 1), StrFormat);
 
//Set the above into a bitmap
Bitmap bmWatermark = new Bitmap(bmPhoto);
bmWatermark.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);
 
Graphics grWatermark = Graphics.FromImage(bmWatermark);
 
//Apply two color manipulations for the watermark
ImageAttributes imageAttributes = new ImageAttributes();
ColorMap colorMap = new ColorMap();
 
colorMap.OldColor = Color.FromArgb(255, 0, 255, 0);
colorMap.NewColor = Color.FromArgb(0, 0, 0, 0);
System.Drawing.Imaging.ColorMap[] remapTable = { colorMap };
 
imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
 
//Change the opacity of watermark by setting 3rd row, 3rd col to .3f
float[][] colorMatrixElements = {
new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
new float[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f},
new float[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
new float[] {0.0f, 0.0f, 0.0f, 0.3f, 0.0f},
new float[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f}
};
 
ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements);
 
imageAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
 
//Draw the watermark in the upper right hand corner of photo
int xPosOfWm = ((phWidth - wmWidth) - 10);
int yPosOfWm = 10;
 
grWatermark.DrawImage(imgWatermark, new Rectangle(xPosOfWm, yPosOfWm, wmWidth, wmHeight), 0, 0, wmWidth, wmHeight, GraphicsUnit.Pixel, imageAttributes);
 
//Finally, replace the original image with new
imgPhoto = bmWatermark;
grPhoto.Dispose();
grWatermark.Dispose();
 
//Save as original name but concatenate _watermarked to it
string newFilename = "watermarked_" + photoFilename;
 
imgPhoto.Save(basePath + newFilename, ImageFormat.Jpeg);
imgWatermark.Dispose();
imgPhoto.Dispose();
 
return newFilename;

}
 
Thank you so much!
GeneralGreat article!memberminhhieu_dotnet11 Nov '10 - 22:21 
Great article. Thanks so much Thumbs Up | :thumbsup:
I'm using it at http://amthuc247.vn
And http://hoangdong.com.vn
QuestionC# and watermarking?memberlapadets27 Sep '10 - 7:26 
Greetings,
 
I am currently working on a watermarking application. I am considering a perceptual and non-perceptual watermarks. The thing I am hesitating on is the choice of programming language. Is C# good for such application in general? I am not very familiar whether I can do low-level programming with C#, as compared with C++? What would you suggest?
 
Regards!

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 26 Sep 2002
Article Copyright 2002 by Joel Neubeck
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid