November 4, 2008

Watermark Your Images with another Image Using PHP and GD Library

The following code sample demonstrates the use of GD library to watermark images on the fly. The method demonstrated here to watermark an uploaded image is to overlay the original image with another image, preferably a transparent PNG image.

PHP provides a rich set of functions to create and alter images on the fly. These functions require the GD library, which is bundled with PHP since version 4.3.

The Complete Example

The working code sample requires the following items:

  1. HTML form that posts an image to the PHP script
  2. The PHP Script
  3. The Watermark Image
HTML Form

The HTML form needs a file upload element: <input type="file">. You must also specify the correct encoding type: enctype="multipart/form-data" for the form.

<form action="watermark-image.php" method="post" enctype="multipart/form-data">
    Select a file to upload for processing<br>
    <input type="file" name="File1"><br>
    <input type="submit" value="Submit File">
</form>
The PHP Script

The following code handles the file upload and performs the image processing.

<?php
/*
 * PHP function to image-watermark an image
 * http://salman-w.blogspot.com/2008/11/watermark-your-images-with-another.html
 *
 * Writes the given watermark image on the specified image
 * and saves the result as another image
 */

define('WATERMARK_OVERLAY_IMAGE', 'watermark.png');
define('WATERMARK_OVERLAY_OPACITY', 50);
define('WATERMARK_OUTPUT_QUALITY', 90);

function create_watermark($source_file_path, $output_file_path)
{
    list($source_width, $source_height, $source_type) = getimagesize($source_file_path);
    if ($source_type === NULL) {
        return false;
    }
    switch ($source_type) {
        case IMAGETYPE_GIF:
            $source_gd_image = imagecreatefromgif($source_file_path);
            break;
        case IMAGETYPE_JPEG:
            $source_gd_image = imagecreatefromjpeg($source_file_path);
            break;
        case IMAGETYPE_PNG:
            $source_gd_image = imagecreatefrompng($source_file_path);
            break;
        default:
            return false;
    }
    $overlay_gd_image = imagecreatefrompng(WATERMARK_OVERLAY_IMAGE);
    $overlay_width = imagesx($overlay_gd_image);
    $overlay_height = imagesy($overlay_gd_image);
    imagecopymerge(
        $source_gd_image,
        $overlay_gd_image,
        $source_width - $overlay_width,
        $source_height - $overlay_height,
        0,
        0,
        $overlay_width,
        $overlay_height,
        WATERMARK_OVERLAY_OPACITY
    );
    imagejpeg($source_gd_image, $output_file_path, WATERMARK_OUTPUT_QUALITY);
    imagedestroy($source_gd_image);
    imagedestroy($overlay_gd_image);
}

/*
 * Uploaded file processing function
 */

define('UPLOADED_IMAGE_DESTINATION', 'originals/');
define('PROCESSED_IMAGE_DESTINATION', 'images/');

function process_image_upload($Field)
{
    $temp_file_path = $_FILES[$Field]['tmp_name'];
    $temp_file_name = $_FILES[$Field]['name'];
    list(, , $temp_type) = getimagesize($temp_file_path);
    if ($temp_type === NULL) {
        return false;
    }
    switch ($temp_type) {
        case IMAGETYPE_GIF:
            break;
        case IMAGETYPE_JPEG:
            break;
        case IMAGETYPE_PNG:
            break;
        default:
            return false;
    }
    $uploaded_file_path = UPLOADED_IMAGE_DESTINATION . $temp_file_name;
    $processed_file_path = PROCESSED_IMAGE_DESTINATION . preg_replace('/\\.[^\\.]+$/', '.jpg', $temp_file_name);
    move_uploaded_file($temp_file_path, $uploaded_file_path);
    $result = create_watermark($uploaded_file_path, $processed_file_path);
    if ($result === false) {
        return false;
    } else {
        return array($uploaded_file_path, $processed_file_path);
    }
}

/*
 * Here is how to call the function(s)
 */

$result = process_image_upload('File1');
if ($result === false) {
    echo '<br>An error occurred during file processing.';
} else {
    echo '<br>Original image saved as <a href="' . $result[0] . '" target="_blank">' . $result[0] . '</a>';
    echo '<br>Watermarked image saved as <a href="' . $result[1] . '" target="_blank">' . $result[1] . '</a>';
}
?>
The Watermark Image

Sample watermark image

The watermark image should be in one of the following recommended formats:

  • PNG-8 (recommended)
    Colors: 256 or less
    Transparency: On/Off
  • GIF
    Colors: 256 or less
    Transparency: On/Off
  • JPEG
    Colors: True color
    Transparency: n/a

The imagecopymerge function does not properly handle the PNG-24 images; it is therefore not recommend.

If you are using Adobe Photoshop to create watermark images, it is recommended that you use "Save for Web" command with the following settings:

  • File Format: PNG-8, non-interlaced
  • Color Reduction: Selective, 256 colors
  • Dithering: Diffusion, 88%
  • Transparency: On, Matte: None
  • Transparency Dither: Diffusion Transparency Dither, 100%

How it Works

The function relies on the imagecopymerge function available in GD library. This function copies a rectangular region from one image onto the other. The important thing is that this function allows you to specify the opacity of the image that is rendered on the destination image.

Visit the following URLs to see documentation of the functions used in this example:

Example Output

Example 1: Original Image

Example 1: Before watermark

Example 1: Watermarked image showing four different combinations of alignment and opacity

Example 1: After watermark

 

Example 2: Original Image

Example 2: Before watermark

Example 2: Watermarked image showing four different combinations of alignment and opacity

Example 2: After watermark

Variations of the Function

The example above will place the watermark image on the lower-right corner of the original image with 50% opacity. To experiment with other alignment options, you can modify the line containing imagecopymerge as follows:

<?php
/*
 * ALIGN TOP, LEFT
 */
imagecopymerge(
    $source_gd_image,
    $overlay_gd_image,
    0,
    0,
    0,
    0,
    $overlay_width,
    $overlay_height,
    WATERMARK_OVERLAY_OPACITY
);
/*
 * ALIGN TOP, RIGHT
 */
imagecopymerge(
    $source_gd_image,
    $overlay_gd_image,
    $source_width - $overlay_width,
    0,
    0,
    0,
    $overlay_width,
    $overlay_height,
    WATERMARK_OVERLAY_OPACITY
);
/*
 * ALIGN BOTTOM, RIGHT
 */
imagecopymerge(
    $source_gd_image,
    $overlay_gd_image,
    $source_width - $overlay_width,
    $source_height - $overlay_height,
    0,
    0,
    $overlay_width,
    $overlay_height,
    WATERMARK_OVERLAY_OPACITY
);
/*
 * ALIGN BOTTOM, LEFT
 */
imagecopymerge(
    $source_gd_image,
    $overlay_gd_image,
    0,
    $source_height - $overlay_height,
    0,
    0,
    $overlay_width,
    $overlay_height,
    WATERMARK_OVERLAY_OPACITY
);
?>

Limitations of the Script

  • The example currently supports GIF, JPEG and PNG file handling. Additional image type support can be added rather easily.
  • The example does not generate unique file names and therefore overwrites existing uploaded or thumbnail images.
  • The example does not show any error messages. All "return false" need to be replaced by appropriate error handling.
  • 256 color watermark images with transparency often have visible artifacts around the edges after rendering.
  • 24-bit PNG watermark images are not rendered properly.
  • Additional watermark alignment options are not discussed. I am leaving that as an exercise. Hint: it is as simple as dividing a few numbers by 2.