You are probably reading this page for one of three reasons:

a) You, like myself, have run into the problem of merging images using GD.
b) You have extensive background in image processing and/or colour theory (or color theory) and are wondering who I am and why I have published such rubbish.
c) Or you are simply bored.

If you fall into category "a" I hope you find this info useful. However, I must warn you that I have no background, nor knowledge, on colour theory or image processing. I simply wanted to create watermarked images and ran into some issues (as explained below). After spending a Sunday afternoon figuring out how to fix my issue (or rather work around my problem), I thought I'd put this simple page together as a reference for myself. If it proves useful for others, then that's even better.

However, if you do happen to fall in category "b", and the info I have provided is completely wrong within the context in which I'm presenting it, I would appreciate hearing from you with a better way of accomplishing this task.

Background

I take a lot of photographs. So much so that to sort through them all using an image editing tool such as Gimp or Photoshop, select the few worthy of displaying/sharing, and merge a watermark on them, would simply take far too much time. I am also too lazy. Because of this, I tend to write scripts or small little utility programs to do batch processing of these photographs: Resize, rotate, watermark, etc. That way I can get the photos on a page where I can share with friends, family, etc., then I can worry about removing the crappy shots at some later time.

I am also not a big fan of reinventing the wheel. If there is a tool out there that does what I need, is reasonably well-written and open-source (it's a deal-breaker if it is not), I will try to use it.

The Tool

libgd and its Perl interface module, GD, are a pretty handy set of tools for image processing. However, I noticed that GD's image copy/merge functions weren't quite doing what I expected them to do (read: after reading the man page, my expectation was other than the results produced).

There are the GD::Image->copy() and the GD::Image->copyMerge() functions, which take a source and destination image and merge them together. The first, does almost exactly what I want. Given a watermark image with transparency/alpha channel (e.g., a PNG file) it will merge it with the destination image and do the proper alpha-blending.

Watermark Image
(The Watermark)
Watermarked Image
(Watermarked Image)

The Problem

So what is the problem? Well, I would like to have some level of control over the transparency of the watermark being applied. Rather than go back to Gimp to adjust the overall transparency of the watermark image, I would prefer being able to adjust it within my script, through GD's interface. This means I need a way to adjust the amount of colour merged between the two images.

At first glance it seemed that GD::Image->copyMerge() would do exactly that. Unfortunately it doesn't do proper alpha-blending. As the comment in the gd library source indicates:

/* This function is a substitute for real alpha channel operations,
   so it doesn't pay attention to the alpha channel. */
BGD_DECLARE(void) gdImageCopyMerge (gdImagePtr dst, gdImagePtr src,...

 

copyMerge() Image
(copyMerge() Image at 55%)

As you see the GD::Image->copyMerge() didn't take into account any of the alpha channel information.

So the solution I found was to go through each pixel of my watermark image and adjust the alpha channel value manually.

Alpha Adjusted Image - to 5%
(Alpha Adjusted Image - to 5% transparency)
Alpha Adjusted Image - to 55%
(Alpha Adjusted Image - to 55% transparency)
Alpha Adjusted Image - to 90%
(Alpha Adjusted Image - to 90% transparency)

This works, but it will cost you execution speed, especially if the script is to run against a large directory of images waiting to be watermarked. One solution would be to retain a copy of the watermark with the desired transparency (e.g., in memory, or save to local disk), and iterate through the images being watermarked.

I suspect that if this is actually done in the core gd library function, it would be as quick as the GD::Image->copy() or GD::Image->copyMerge() function calls. I have not yet tried this.

Here are simple scripts that generated the sample images seen above.
Perl source for basic GD::Image->copy() example: gd_copy_v0.pl
Perl source for GD::Image->copyMerge() example: gd_copy_v1.pl
Perl source for manual alpha value adjustment example: gd_copy_v2.pl


[Updated: Sat Aug 18 19:59:55 PDT 2007]

The Better Solution

I created patches for libgd and GD modules which add a new interface function named gdImageBlend(). This new interface is similar to gdImageCopyMerge() method. gdImageBlend() only works on true-colour images and only if the destination image has its alphaBlendingFlag set, otherwise it calls gdImageCopyMerge() instead.

The gdImageBlend() (or GD::Image->blend()) takes as its last parameter the desired opacity for the source image. Valid range for the opacity parameter is 0 to 100, where 0 means complete transparency, which basically causes the function to return, and 100 means full opacity, in which case it calls gdImageCopy() and returns. For any other value between 0 and 100 gdImageBlend() will go through each pixel of the source image and adjust it based on the opacity value.

Increase in performance using the newly added gdImageBlend() function is considerable. On a test case where doing the alpha adjustment in my Perl script took 2.15s, the blending of the same two images, using the gdImageBlend() method took 0.18 seconds!

Note again that I do not have any background in digital image processing nor colour theory, so my method may be flawed, but the results it produces seem to be correct, or at least "close enough". So if you think these patches are useful to you, feel free to use them but at your own risk!

I have submitted these patches to the gd-devel mailing list (my post). Hopefully my patches will be accepted into the core product, and if there are any mistakes in my patches, the right people (the gd development team) will make necessary corrections when applying the patches to their source tree.

Patch to gd-2.0.34/gd.c: patch-gd_c
Patch to gd-2.0.34/gd.h: patch-gd_h
Patch to GD-2.30/GD.pm: patch-GD_pm
Patch to GD-2.30/GD.xs: patch-GD_xs

An example of what gdImageBlend() (or GD::Image->blend()) can enable one to do is this following image. The same watermark image is blended four times at different opacity values. At 100% it is a direct gdImageCopy() call through gdImageBlend().

Watermark at 4 different opacity values

Here is the Perl source producing this example: gd_blend_test.pl

Code Snippet

my $iw = GD::Image->new('watermark.jpg');
my $is = GD::Image->new('photo.jpg');
...
$is->blend($iw, $dx, $dy, $sx, $sy, $sw, $sh, 100);
$is->blend($iw, $dx + $sw, $dy, $sx, $sy, $sw, $sh, 65);
$is->blend($iw, $dx, $dy + $sh, $sx, $sy, $sw, $sh, 35);
$is->blend($iw, $dx + $sw, $dy + $sh, $sx, $sy, $sw, $sh, 10);
Copyright © 2000 - 2017 sidster.com
Software by boxsoft®

Valid XHTML 1.0 Strict