Blending images with alpha (patches inside)

[email protected] (patrick keshishian)
Newsgroups php.gd.devel
Message-ID <[email protected]>
Greetings,

This is my first time posting to this list.  Please excuse me if
I miss any standard protocol for contributing patches, etc.

I had a need to blend two true-colour images.  A large set of
photos that need to be watermarked, and one watermark image.
Using GD was an obvious choice for a simple Perl script that
would run through directories of photos and applied the watermark
to each photo.

gdImageCopy() does a great job for most use-cases.  However,
it seems there lacks an API by which I could adjust the
transparency of the watermark ('source') image during "blending"
time.

Initially, it seemed to me that gdImageCopyMerge() would be the
function I'm interested in, but it doesn't consider the alpha-
channel of the source image.

I tried writing a wrapper function in my perl program to adjust
the alpha value of each pixel in the source image before calling
gdImageCopy() but there was a great performance hit.

I created these patches which add a new function to libgd named
gdImageBlend().  It accepts almost identical parameters as
gdImageCopyMerge(), with the last parameter indicating the
opacity of the source image.

gdImageBlend() only works on true-colour images.  If it detects
that either image isn't true-colour or the destination image does
not have alphaBlendingFlag set, it will call gdImageCopyMerge()
and return.

Note that I don't have any background in digital imaging nor
colour theory.  My method may be flawed/not exact, but the results
seem correct, or at least "close enough".

Speed improvement is considerable.  A test case where doing this
alpha channel adjustment in the perl wrapper takes 2.15 seconds
takes only 0.18 seconds with the new GD::Image->blend() method.

The patches are made against gd-2.0.34 and GD-2.30. If there is a
good chance they would be considered for adoption, I am willing
to make them against the latest sources.  Though, the patches are
pretty trivial.

I'm not subscribed to your list, feel free to Cc me on replies.

Best regards,
--patrick
patch-gd_c (text/plain, 2.3 KB)
--- ../gd-2.0.34-orig/gd.c	Tue Feb  6 16:33:29 2007
+++ gd.c	Sat Aug 18 15:22:53 2007
@@ -2202,6 +2202,66 @@ BGD_DECLARE(void) gdImageCopy (gdImagePt
     }
 }
 
+/*
+ * Blend two true-colour images and optionally adjust the source image's
+ * alpha channel by opacity.  Valid range for opacity is 0 to 100, where 0
+ * means complete transparency (essentially a no-op) and 100 means copy
+ * the source image as-is (essentially calling gdImageCopy()).
+ * If either of the images is not true-colour, or the destination image
+ * does not have its alphaBlendingFlag set, we call gdImageCopyMerge()
+ * and return.
+ */
+BGD_DECLARE(void)
+gdImageBlend(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX,
+    int srcY, int w, int h, int opacity)
+{
+	int alpha, c, i, x, y;
+	float alpha_adj;
+
+	/* Valid range for opacity is 0 to 100 */
+	if (opacity >= 100) {
+		gdImageCopy(dst, src, dstX, dstY, srcX, srcY, w, h);
+		return;
+	}
+	else if (opacity <= 0)
+		return; /* Complete transparency; i.e., no-op. */
+	/*
+	 * We only handle the case where both images are true-colour and
+	 * destination image has alpha-blending flag set.  Otherwise, we
+	 * simpley call gdImageCopyMerge(...)
+	 */
+	if (!dst->trueColor || !src->trueColor || !dst->alphaBlendingFlag) {
+		gdImageCopyMerge(dst, src, dstX, dstY, srcX, srcY,
+		    w, h, opacity);
+		return;
+	}
+	alpha_adj = (100.0 - opacity) / 100.0;
+	for (y = 0; y < h; ++y) {
+		for (x = 0; x < w; ++x) {
+			c = gdImageGetPixel(src, srcX + x, srcY + y);
+			/*
+			 * I am making an assumption that in true-colour
+			 * images, there is no im->transparent, else we
+			 * could use the following optimization:
+			 *	if (c == src->transparent)
+			 *		continue;
+			 */
+			alpha = (c >> 24) & 0xff;
+			if (alpha == gdAlphaTransparent)
+				continue;
+			/*
+			 * XXX: Can gdAlphaTransparent and gdAlphaMax ever
+			 * be different values?
+			 */
+			alpha += (gdAlphaMax - alpha) * alpha_adj;
+			if (alpha >= gdAlphaMax)
+				continue;
+			c = (c & 0x00ffffff) | (alpha << 24);
+			gdImageSetPixel(dst, dstX + x, dstY + y, c);
+		}
+	}
+}
+
 /* 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, int dstX, int dstY,
patch-gd_h (text/plain, 700 B)
--- ../gd-2.0.34-orig/gd.h	Tue Feb  6 16:33:29 2007
+++ gd.h	Mon Aug 13 04:29:01 2007
@@ -662,6 +662,8 @@ BGD_DECLARE(void) gdImageFillToBorder (g
 BGD_DECLARE(void) gdImageFill (gdImagePtr im, int x, int y, int color);
 BGD_DECLARE(void) gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY,
 		    int srcX, int srcY, int w, int h);
+BGD_DECLARE(void) gdImageBlend(gdImagePtr dst, gdImagePtr src, int dstX,
+    int dstY, int srcX, int srcY, int w, int h, int pct);
 BGD_DECLARE(void) gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY,
 			 int srcX, int srcY, int w, int h, int pct);
 BGD_DECLARE(void) gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX,
patch-GD_pm (text/plain, 1.3 KB)
--- ../GD-2.30-orig/GD.pm	Wed Oct 19 00:49:26 2005
+++ GD.pm	Fri Aug 17 01:02:03 2007
@@ -1118,6 +1118,30 @@ Example:
 	# the rectangle starting at (10,10) in $myImage, merging 50%
 	$myImage->copyMerge($srcImage,10,10,0,0,25,25,50);
 
+=item B<$image-E<gt>blend($sourceImage,$dstX,$dstY,>
+B<			$srcX,$srcY,$width,$height,$opacity)>
+
+This blends two true-color images using using alpha channel information
+from $sourceImage.  This assumes that $image has alphaBlending() set.
+The last parameter indicates the opacity of the source image.  It
+has a valid range between 0 and 100, where 100 would produce
+the same result as calling copy() and 0 is a no-op (i.e., 100%
+transparent sourceImage).  This function is ideal for watermarking
+purpose, where a watermark image (e.g., an RGBA PNG image) can be
+blended onto images at different levels of transparency.
+
+If either of the images is not true-color or alphaBlending() is not set
+for the destination image, the result is same as calling copyMerge().
+
+Example:
+
+	$myImage = new GD::Image($source_file);
+	$myWatermark = new GD::Image($watermark_file);
+	# Copy the watermark image onto the $myImage at
+	# 65% opacity.
+	$opacity = 65;
+	$myImage->blend($myWatermark,10,10,0,0,25,25,$opacity);
+
 =item B<$image-E<gt>copyMergeGray($sourceImage,$dstX,$dstY,>
 
 B<				$srcX,$srcY,$width,$height,$percent)>
patch-GD_xs (text/plain, 526 B)
--- ../GD-2.30-orig/GD.xs	Tue Oct 18 20:18:39 2005
+++ GD.xs	Mon Aug 13 05:00:26 2007
@@ -1946,6 +1946,23 @@ copyResampled(destination,source,dstX,ds
 	}
 
 void
+blend(dst,src,dstX,dstY,srcX,srcY,w,h,pct)
+	GD::Image	dst
+	GD::Image	src
+	int		dstX
+	int		dstY
+	int		srcX
+	int		srcY
+	int		w
+	int		h
+	int		pct
+	PROTOTYPE:	$$$$$$$$$
+	CODE:
+	{
+		gdImageBlend(dst, src, dstX, dstY, srcX, srcY, w, h, pct);
+	}
+
+void
 copyMerge(destination,source,dstX,dstY,srcX,srcY,w,h,pct)
 	GD::Image	destination
 	GD::Image	source
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.