Dynamic Watermarking with PHP

One of the things you can do with PHP’s many functions for image handling and manipulation is create an image watermarking class. This will allow you to add a watermark to images, which can be useful for a number of purposes. Keep reading to learn how.

A downloadable file for this article is available here.


PHP features a wide array of functions for image handling and manipulation. In today’s article, we are going to use those functions to create an image watermarking class. This class will operate on two images: a source image and a watermark. As an optional third parameter our class will also accept an alpha value – allowing our watermark to contain alpha transparency.

This should be an enjoyable exercise, but hopefully it will also be one that is very useful. For example, let us imagine a scenario where you are hired to create a searchable inventory system for a stock photography website. Obviously you will need to protect your client’s photographs –- offering full quality images only to paying customers. To do this, you could create multiple copies of each image, or you could simply implement a watermarking script like the one we are about to create. This script could then add a watermark to any unpurchased images, while leaving those that have been purchased unmarked.

Perhaps you can already think of a situation where you could use this type of script – perhaps not. In either case, developing it will be a good learning excercise. Most important, it will be fun! So let’s get started.

{mospagebreak title=Cast and Crew}

Before we begin, let’s take a look at our cast and crew – that is, the functions we will be using to directly manipulate our images. A quick list of those functions is as follows:

# gives us the width & height values for a given image
# creates a new, true-color, image object
# returns RGB (+ alpha) info for specific pixel of an image  
# (similar as above), returns index of color for specific pixel
# sets a pixel to the specified color
# given RGB values & an image object, the below functions will return a color index value ( that can then be passed to imagesetpixel() )

As you can see, the above functions are each very narrow in their focus. Many of them are also probably self-explanatory. Don’t worry if any appear confusing though; their purposes will become clearer when we began placing them within our application.

The Story-Line

Now that we’ve defined the goals of our project, let’s take a step back and talk about the way we will be achieving these goals. At this point it’s okay to be very abstract; we’ll focus on the details shortly.

To begin, our application receives two image objects –- one the source image, and another the watermark image. The first thing we will need to do with these images is to determine their width and height. Once we have that information,  we can center-align our watermark. (For the purposes of our application, we are assuming that the watermark will always be smaller than the image we are attempting to protect/brand – probably a safe assumption in most cases.)

Next, our application should super-impose our watermark image on our source image. In order to do this, it will need to average the colors for any overlapping portions of those two images. This will give our resulting image the appearance of alpha transparency.

Finally, we’ll return our modified image for display in a browser. This way, our script can be called directly from within an “<img…>” tag.

Hopefully we’ve not discussed anything too complex so far. So now let’s take a look at the code.

{mospagebreak title=The Nuts and Bolts}

Just to keep things simple, let’s start by creating our image watermarking class. This class, called “watermarking”, should be created inside a file named “api.watermark.php”. Go ahead and create that file now, and insert the following lines of code:

class watermark{
      # given two images, return a blended watermarked image
function create_watermark() { }
      # average two colors given an alpha
      function _get_ave_color() {   }    
      # return closest pallette-color match for RGB values
      function _get_image_color() { }
} # END watermark API

So far we’ve just created a shell into which we can insert our code. Let’s begin by taking a look at our “create_watermark” function. Go ahead and replace our placeholder “create_watermark” function with the following code:

# given two images, return a blended watermarked image
function create_watermark( $main_img_obj, $watermark_img_obj, $alpha_level = 100 ) {
      $alpha_level      /= 100;     # convert 0-100 (%) alpha to decimal
      # calculate our images dimensions
      $main_img_obj_w   = imagesx( $main_img_obj );
      $main_img_obj_h   = imagesy( $main_img_obj );
      $watermark_img_obj_w    = imagesx( $watermark_img_obj );
      $watermark_img_obj_h    = imagesy( $watermark_img_obj );
      # determine center position coordinates
      $main_img_obj_min_x     = floor( ( $main_img_obj_w / 2 ) – ( $watermark_img_obj_w / 2 ) );
      $main_img_obj_max_x     = ceil( ( $main_img_obj_w / 2 ) + ( $watermark_img_obj_w / 2 ) );
      $main_img_obj_min_y     = floor( ( $main_img_obj_h / 2 ) – ( $watermark_img_obj_h / 2 ) );
      $main_img_obj_max_y     = ceil( ( $main_img_obj_h / 2 ) + ( $watermark_img_obj_h / 2 ) );
      # create new image to hold merged changes
      $return_img = imagecreatetruecolor( $main_img_obj_w, $main_img_obj_h );
      # walk through main image
      # return the resulting, watermarked image for display
      return $return_img;
} # END create_watermark()
The first thing we did, as you may have noticed, was expand our function to receive 3 incoming parameters:
$main_img_obj           # plain image which our script should watermark
$watermark_img_obj      # watermark image, can be alpha-transparent
$alpha_level            # watermark alpha value, (0-100, default = 100)

(It is important to note at this time that our function is expecting image objects as incoming parameters, not simply paths to an image – but we will discuss this in greater detail shortly.)

Next we gathered some simple width and height information about each of our incoming images.  We then used that information to determine X and Y coordinates for center aligning our watermark on top of our main image.

Next our function creates a new, true color image object with the same dimensions as our main image object. This new image, “return_img”, will be used to store the merged image information from our other two images.

Our next major step is to walk through the images and merge them together, but we aren’t quite ready for that yet. Instead we’ve simply inserted a placeholder comment, “ADD CODE HERE”, until we are ready to return and complete that block of code.

Finally we simply return our modified image for display by the page that’s calling it. So far what we’ve done has been pretty basic. Before we move on to the core of our application though, let’s take a look at one more thing: our helper functions.

{mospagebreak title=Helper Functions}

Just to keep things simple, we’ve introduced two helper functions into our application. These functions are narrow in focus, each one performing a very specific task. By using them, however, we are able to clean up the code within our “create_watermark” function. Let’s add them to our class, then we’ll take a look at each:

# average two colors given an alpha
function _get_ave_color( $color_a, $color_b, $alpha_level ) {
      return round( ( ( $color_a * ( 1 – $alpha_level ) ) + ( $color_b  * $alpha_level ) ) );
} # END _get_ave_color()
# return closest pallette-color match for RGB values
function _get_image_color($im, $r, $g, $b) {
      $c=imagecolorexact($im, $r, $g, $b);
      if ($c!=-1) return $c;
      $c=imagecolorallocate($im, $r, $g, $b);
      if ($c!=-1) return $c;
      return imagecolorclosest($im, $r, $g, $b);
} # EBD _get_image_color()

Our first function, “_get_ave_color”, accepts two color values, along with our (optional) user-specified alpha level, and returns a weighted average of the colors it has been passed. This function will help us blend the images we have been passed by averaging their colors at overlapping points.

Next, “_get_image_color”, accepts an image object along with red, green, and blue color values. Using a few of the built-in PHP image-manipulation methods, this function then returns the closest available match (to the color specified) that exists within our image object’s color palette.

In order to do this, it checks several things. First, if an exact match can be found, it is returned. If not, it will attempt to allocate a new palette color to match the one specified. If that fails as well, then as a last resort our function will simply return the closest pre-existing color match found within the images palette.

If this seems a little confusing, don’t worry. It will make more sense once we put the pieces together.

{mospagebreak title=A Union of Images}

Our class is almost complete! All that’s left is to finish off the “create_watermark” function so that it will merge our two images. Let’s replace the “ADD CODE HERE” placeholder with the following code (we’ll go over it in greater detail shortly):

# walk through main image
for( $y = 0; $y < $main_img_obj_h; $y++ ) {
      for( $x = 0; $x < $main_img_obj_w; $x++ ) {
            $return_color     = NULL;
            # determine the correct pixel location within our watermark
            $watermark_x      = $x – $main_img_obj_min_x;
            $watermark_y      = $y – $main_img_obj_min_y;
            # fetch color information for both of our images
            $main_rgb = imagecolorsforindex( $main_img_obj, imagecolorat( $main_img_obj, $x, $y ) );
            # if our watermark has a non-transparent value at this pixel intersection
            # and we’re still within the bounds of the watermark image
            if (  $watermark_x >= 0 && $watermark_x < $watermark_img_obj_w &&
                              $watermark_y >= 0 && $watermark_y < $watermark_img_obj_h ) {
                  $watermark_rbg = imagecolorsforindex( $watermark_img_obj, imagecolorat( $watermark_img_obj, $watermark_x, $watermark_y ) );
                  # using image alpha, and user specified alpha, calculate average
                  $watermark_alpha  = round( ( ( 127 – $watermark_rbg['alpha'] ) / 127 ), 2 );
                  $watermark_alpha  = $watermark_alpha * $alpha_level;
                  # calculate the color ‘average’ between the two – taking into account the specified alpha level
                  $avg_red          = $this->_get_ave_color( $main_rgb['red'],            $watermark_rbg['red'],        $watermark_alpha );
                  $avg_green  = $this->_get_ave_color( $main_rgb['green'],      $watermark_rbg['green'],      $watermark_alpha );
                  $avg_blue         = $this->_get_ave_color( $main_rgb['blue'],      $watermark_rbg['blue'],       $watermark_alpha );
                  # calculate a color index value using the average RGB values we’ve determined
                  $return_color     = $this->_get_image_color( $return_img, $avg_red, $avg_green, $avg_blue );
            # if we’re not dealing with an average color here, then let’s just copy over the main color
            } else {
                  $return_color     = imagecolorat( $main_img_obj, $x, $y );
            } # END if watermark
            # draw the appropriate color onto the return image
            imagesetpixel( $return_img, $x, $y, $return_color );
      } # END for each X pixel
} # END for each Y pixel 

We just added a lot of code, so let’s break it down into smaller bits and discuss what each bit is doing.

First our script sets up a series of ‘for’ loops that will walk through each pixel of our main image object. As it walks through, it also calculates the corresponding pixel-coordinates for our watermark image.

Next it retrieves RGB color information for the pixel we are currently examining on our main image. If this pixel is not within an overlapping area (and thus should not be watermarked), our method simply writes a duplicate pixel to our return image. However, if the pixel we are currently examining falls within an overlapping area, we will also need to determine the blended color value.

To determine a blended color value, we first retrieve an RGB value for our watermark image, using the coordinate information we calculated at the beginning of our ‘for’ loops. Next we update our watermark’s alpha value based on our user-specified alpha value. (If no alpha level is passed to our function, the watermark’s alpha will remain untouched). We then use our helper function,  “_get_ave_color”, to determine a weighted average of the color information for our two images. Finally we use this weighted average, along with our other helper function “_get_image_color”, to determine the closest available color match found within our merged image. This weighted color is then added to our merged image object, “return_img”, at which point our loop executes itself again.

Once the ‘for’ loops have finished executing, we will have constructed a brand new image containing merged information from both of the user-specified image objects – aka, a watermarked image.

But don’t take my word for it – test it out. Let’s talk about how.

{mospagebreak title=Taking a Test Drive}

In order to test our image watermarking class, we’ll create two very simple files. The first one we will call “watermark_test.php”. It should contain the following:

<!– original image –>
<img src=”main.jpg”>
<!– watermarked image –>
<img src=”image.php?main=main.jpg&watermark=watermark.png”>

The purpose of our “watermark_test.php” file is very simple: first, display our original image, and second, display a watermarked copy of that image. In order to do that, we will need two image objects. For our example, I have chosen to use “main.jpg” and “watermark.png”, but you may use any two of your choosing.

You may have also noticed that our second image tag references a PHP file as its source, instead of an image. This is because our script will be creating and returning an image to be displayed by the browser. We can, therefore, call it directly as if it were an image. Let’s go ahead and create our “image.php” file now:

      # include our watermerking class
      include ‘api.watermark.php’;
      $watermark              = new watermark();
      # create image objects using our user-specified images
      # NOTE: we’re just going to assume we’re dealing with a JPG and a PNG here – for example purposes
      $main_img_obj                       = imagecreatefromjpeg(  $_GET['main']                 );
      $watermark_img_obj      = imagecreatefrompng(   $_GET['watermark']      );
      # create our watermarked image – set 66% alpha transparency for our watermark
      $return_img_obj               = $watermark->create_watermark( $main_img_obj, $watermark_img_obj, 66 );
      # display our watermarked image – first telling the browser that it’s a JPEG,
      # and that it should be displayed inline
      header( ‘Content-Type: image/jpeg’ );
      header( ‘Content-Disposition: inline; filename=’ . $_GET['src'] );
      imagejpeg( $return_img_obj, ”, 50 );

For those of you who may not have much experience with PHP’s ‘header’ function, the above script may appear a little confusing. Not to worry. All we’re doing with the header function is telling the browser that the content we are about to display is an image and to treat it as such.

As you can see, our script makes a few assumptions about the images we have been passed. Namely, that our main image is a JPEG and our watermarking image is a PNG. These assumptions are okay to make for testing purposes, but you could easily expand the script to handle any number of image formats for a production environment.

As previously mentioned, our “image.php” file creates image objects to pass to our watermarking class (as opposed to passing just the plaintext image path or location). Our watermarking class could have been expanded to receive the path/location, and create the objects itself — but we opted not to for simplicity’s sake. Further information about how to create image objects using several of the more popular image-for-the-web formats may be found here:




Go ahead and run the test file by opening “watermark_test.php” in your browser. It should show you a copy of your original image, followed by a copy of a newly created watermarked image. How exciting!

In Summary

We’ve covered a lot today, but hopefully we haven’t lost anyone. For those of you who may like to research PHP’s image manipulation functions in a little more detail, I recommend visiting the online documentation found here: http://www.php.net/manual/en/ref.image.php

If you have not been following along, but would like to download a copy of the source code we have just created, please feel free to do so here; it is the same file you can find at the beginning of this article.

For a quick demo of the script we have just created, feel free to point your browser here: http://portfolio.boynamedbri.com/php/watermark/watermark_test.php

As usual, thanks for taking the time to read this article. I hope you had fun – and learned something useful. If you have any questions, please feel free to ask! Until next time…

Google+ Comments

Google+ Comments