Watermarking an image

By evan on Feb 15, 2014

Note: This is a post from my old website, I came across it today and it’s still relevant.

Let’s suppose you have a ton of images that you want to protect from being copied.

The obvious solution is to watermark the images. Unfortunately the standard watermarking procedure of editing the image in a photo-editing application is time consuming, to the point of being impossible when dealing with hundreds or thousands of images, of varying sizes.

On a site like http://landmhighland.com there are a lot of images, all of varying sizes. Preferring not to reinvent the wheel, I looked around for a ready made script or tutorial that would dynamically generate a watermark proportional to the source image.

Most standard watermark scripts have a single sized watermark regardless of the source image. This didn’t work for this site, where there is sometimes 30 different sized images on the page at a time all scaled down to the same size. The watermarks went from covering 10% of one image, to 80% of the next.

Obviously a better solution was needed, so I put together a custom PHP script. In this tutorial, we’ll go over a few GD and PHP functions as we watermark images on the fly.

The Script

The script begins with a header() call to tell the Web browser that we are going to output an image in JPEG format. Without this line, a page calling the script via the HTML <img> tag will receive a broken image and, if the script is accessed directly, the user will see the image data as plain text. That’s not good.

Specifying the content type is unnecessary with most PHP scripts because “text/html” is the default content type for PHP scripts.

header('content-type: image/jpeg');

Now that the browser is ready for an image, we can start to calculate the mathematics of the watermark placement and load the images.

The Image

The script needs to be told which image it is going to make a watermark on. The easiest way to do this is via a GET request. The script is called like this:

watermark.php?src="image.jpg"

Now, there is a small issue that may come up by using that. Due to the nature of GET requests, it is not directly compatible with any file name containing ?, or &. This can be remedied by using urlencode() in parent scripts, or by  avoiding those characters.

The next couple of lines determine the file format of the image. This script is only meant to be used with .jpg images, the $extension variable will be used later on.

$image = $_GET['src'];
$path_parts = pathinfo($image);
$extension=strtolower($path_parts['extension']);

The Watermark

This script is meant to be efficent. If there’s thousands of watermarked images, we don’t want to recreate them every time. The script will save the image later on. The next line creates $watermarked, a string containing the expected location of a premade watermarked image.

$watermarked = $path_parts['dirname'].'/'.$path_parts['filename'].'_watermarked.'.$path_parts['extension'];

Since we only want to create a new watermarked image if one doesn’t already exist,  we check to see if the image exists, and if there is a watermark.

if(file_exists($image)&& !file_exists($watermarked)){

Now on to the fun stuff. The imagecreatefromjpeg() function will load a jpeg into memory so it can be manipulated. The imagesx() function determines then the width of the image. This will be used later on.

if ($extension == 'jpg') {
    $im = imagecreatefromjpeg($_GET['src']);
    $size = imagesx($im);

We also need to load the watermark itself. As I mentioned in the beginning, it should be named watermark.png and in the same directory as the php file. If you want to store the watermark somewhere else, this is the place to change it. The ImageAlphaBlending() and ImageSaveAlpha() functions ensure the transparency of the png is kept.

$stamp = ImageCreateFromPNG("watermark.png");
 ImageAlphaBlending($stamp,true);
 ImageSaveAlpha($stamp,true);

Just as imagesx() determines the width of an image, imagesy() determines the height. Here we’re saving the height and width of the image to use in just a second.

$w = imagesx($stamp);
$h = imagesy($stamp);

Calculating proportions

Now, this is probably the most important part of the entire script. The whole idea is to watermark any sized image, with a proportionally sized watermark. The first line calculates what percentage of the image the watermark currently covers.

The next two figure out how much it should cover. I wanted the watermark to cover roughly 1/4 of the image. That’s 1/2 of the width, and 1/2 of the height. We want to keep the aspect ratio of the image however, so we use a ternary operator there,  so that it’s only compared to the larger side.

$percent = $size / (($w>$h)?$w:$h);
 $nw = intval($w*$percent/2);
 $nh = intval($h*$percent/2);

Resizing the image

All this whole next bit does, is changes the size of the watermark while keeping the color and transparency intact.

$stamp_resized = ImageCreateTrueColor($nw,$nh);

ImageAlphaBlending($stamp_resized,false);
ImageSaveAlpha($stamp_resized,true);

if(!empty($transparent_color))
 {
     $transparent_new = ImageColorAllocate($stamp_resized,$transparent_color['red'],$transparent_color['green'],$transparent_color['blue']);
     $transparent_new_index = ImageColorTransparent($stamp_resized,$transparent_new);
      ImageFill($stamp_resized, 0,0, $transparent_new_index);
 }

if(ImageCopyResized($stamp_resized,$stamp, 0,0,0,0, $nw,$nh, $w,$h ))
 {
      ImageDestroy($stamp);
      $stamp = $stamp_resized;
 }

Whew. That’s over with. The watermark is the correct size, so now we have to overlay the images. We want to leave a bit of a margin so the watermark isn’t right on the edge.

$marge_right = 10;
$marge_bottom = 10;

Creating the new image

Next, we copy the watermark onto the image.

imagecopy($im, $stamp, imagesx($im) - $nw - $marge_right, imagesy($im) - $nh - $marge_bottom, 0, 0, $nw, $nh);

And then save the image, and close the brackets. We’re almost done.

imagejpeg($im, $watermarked);
 imagedestroy($im);
 }
 }

Finishing up

At this point, we have the url to the watermarked image, and if it didn’t exist at the beginning of the script, it does now. The very last thing that needs done is actually sending the image data to the client.

fpassthru(fopen($watermarked, 'rb'));

And we’re done! The image has been watermarked, if the same image is opened again, it’s already saved. If a different image is opened, it will watermark it.

Leave a Reply

Your email address will not be published. Required fields are marked *