How To Make Image Watermarking Work With C#

This tutorial shows how image watermarking works by implementing it with C# and we describe various purposes that watermarks have as well.


Andraz Krzisnik
How To Make Image Watermarking Work With C#

Image watermarking is one of the tools we can use to discourage illegal duplication of our content. In essence, we’re inserting one or more images – watermarks, on top of our image. For example, you’ve probably already seen an image that has a logo displayed on top. That logo is the watermark.

Watermarks serve a couple of different purposes, but mainly for protection of their owners rights like copyright identification. There are also other uses for them. We can use them for user identification or fingerprinting, which is useful for identifying sources of illegal copies.

We can also use them for authenticity determination or in other words, we can see if an image has been altered in anyway. If it was, the watermark would have been destroyed or corrupted.

There are websites, like Unsplash.com, where we can get images for the content we’re creating, but each image has a set of rules that determine how you can use it. Watermarking enables us to embed these rules in the image itself so it serves us as a copy protection.

Image watermarking with visible watermarks

These kind of watermarks are most obvious and probably most common as well. Visible watermark is an opeque image, which we place on top of our image. This is also the simplest way to watermark an image and it’s performed in the spatial domain.

I wrote a function that puts a watermark on top of an image. Furthermore, we can set the opacity of the watermark with the folowing formula.

formula for visible image watermarking
Formula for setting desired opacity of visible watermarks

The alpha variable controls the opacity of the watermark and we need to set it between 0 and 1.

public static Bitmap AddVisibleWatermark(this Bitmap image, Bitmap watermark, double opacity)
     {
         int w = image.Width;
         int h = image.Height;
         BitmapData image_data = image.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.ReadOnly,
             PixelFormat.Format24bppRgb);

         int bytes = image_data.Stride * image_data.Height;
         byte[] buffer = new byte[bytes];
         byte[] result = new byte[bytes];

         Marshal.Copy(image_data.Scan0, buffer, 0, bytes);
         image.UnlockBits(image_data);

         int ww = watermark.Width;
         int wh = watermark.Height;

         if (w < ww || h < wh)
         {
             watermark = watermark.ScaleImage(w, h);
             ww = watermark.Width;
             wh = watermark.Height;
         }

         BitmapData wmark_data = watermark.LockBits(
             new Rectangle(0, 0, ww, wh),
             ImageLockMode.ReadOnly,
             PixelFormat.Format24bppRgb);

         int wbytes = wmark_data.Stride * wmark_data.Height;
         byte[] wbuffer = new byte[wbytes];
         Marshal.Copy(wmark_data.Scan0, wbuffer, 0, wbytes);
         watermark.UnlockBits(wmark_data);

         result = buffer;

         for (int i = 0; i < ww; i++)
         {
             for (int j = 0; j < wh; j++)
             {
                 int wposition = i * 3 + j * wmark_data.Stride;
                 int position = i * 3 + j * image_data.Stride;
                 for (int k = 0; k < 3; k++)
                 {
                     result[position + k] = (byte)((1 - opacity) * result[position + k] + opacity * wbuffer[wposition + k]);
                 }
             }
         }

         Bitmap res_img = new Bitmap(w, h);
         BitmapData res_data = res_img.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.WriteOnly,
             PixelFormat.Format24bppRgb);
         Marshal.Copy(result, 0, res_data.Scan0, bytes);
         res_img.UnlockBits(res_data);
         return res_img;
     }

Invisible watermarks

As you might guess, invisible watermarks are imperceptible to human eye, but they can be recevered with a suitable decoding algorithm. We can assure invisibility of the watermark by inserting it as visually redundant information.

In other words, we clear last bits of each byte and put watermark image data in those bits. So in order to recover it, we need to 0 out bits that carry important image information and scale to full intensity range the information carried in the last least important bits.

Here is also a function that hides a watermark in those bits, with the following formula.

formula for invisible watermarks
Formula for adding invisible watermarks
public static Bitmap AddInvisibleWatermark(this Bitmap image, Bitmap watermark)
     {
         int w = image.Width;
         int h = image.Height;

         BitmapData image_data = image.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.ReadOnly,
             PixelFormat.Format24bppRgb);

         int bytes = image_data.Stride * image_data.Height;
         byte[] buffer = new byte[bytes];
         byte[] result = new byte[bytes];

         Marshal.Copy(image_data.Scan0, buffer, 0, bytes);
         image.UnlockBits(image_data);

         int ww = watermark.Width;
         int wh = watermark.Height;

         if (w < ww || h < wh)
         {
             watermark = watermark.ScaleImage(w, h);
             ww = watermark.Width;
             wh = watermark.Height;
         }

         BitmapData wmark_data = watermark.LockBits(
             new Rectangle(0, 0, ww, wh),
             ImageLockMode.ReadOnly,
             PixelFormat.Format24bppRgb);

         int wbytes = wmark_data.Stride * wmark_data.Height;
         byte[] wbuffer = new byte[wbytes];

         Marshal.Copy(wmark_data.Scan0, wbuffer, 0, wbytes);
         watermark.UnlockBits(wmark_data);

         result = buffer;
         for (int i = 0; i < ww; i++)
         {
             for (int j = 0; j < wh; j++)
             {
                 int wposition = i * 3 + j * wmark_data.Stride;
                 int position = i * 3 + j * image_data.Stride;
                 for (int k = 0; k < 3; k++)
                 {
                     result[position + k] = (byte)((4 * result[position + k]) / 4 + wbuffer[wposition + k] / 64);
                 }
             }
         }

         Bitmap res_img = new Bitmap(w, h);
         BitmapData res_data = res_img.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.WriteOnly,
             PixelFormat.Format24bppRgb);
         Marshal.Copy(result, 0, res_data.Scan0, bytes);
         res_img.UnlockBits(res_data);

         return res_img;
     }

Conclusion

Hopefully this tutorial helped you with better understanding image watermarking.

You can also download the demonstration project and try it out yourself.

Related Articles

Thresholding

How To Make Otsu Thresholding Algorithm With C#

Otsu thresholding is a global thresholding method, with which we find optimal threshold intensity to ensure the maximum separability.

Posted on by Andraz Krzisnik
C# Tutorial

C# Tutorial: How To Use Log Transformation on Images

What is Log Transformation Anyway? Log transformation in image processing is a part of gray level transformations. It works by transforming each pixel individually. It is usually...

Posted on by Andraz Krzisnik