php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #23815 imagecopymerge doesn't respect alpha-channel in PNG-24 file
Submitted: 2003-05-26 11:43 UTC Modified: 2017-01-22 20:23 UTC
Votes:32
Avg. Score:4.4 ± 0.8
Reproduced:25 of 26 (96.2%)
Same Version:9 (36.0%)
Same OS:8 (32.0%)
From: bjorn at smokingmedia dot com Assigned: pajoye (profile)
Status: Suspended Package: GD related
PHP Version: 5.2.9 OS: Linux pluto 2.4.18lvm-r1
Private report: No CVE-ID: None
 [2003-05-26 11:43 UTC] bjorn at smokingmedia dot com
[[There are similar bugs in the system, but they are either closed or said to be fixed... i think it's still not fixed..]]

i wanted to do the following. Use a transparent PNG-24(24 bits) file, with an alpha-channel (with a drop-shadow) and merge it to a background color. (see sample code).

this works:
imagecopy($backgroundimage, $image_id, 0, 0, 0, 0, $im_X, $im_Y);

this doesn't work:
imagecopymerge($backgroundimage, $image_id, 0, 0, 0, 0, $im_X, $im_Y, 99);
-> with any number instead of 99


sample link: 
[change the hex color code to change backgroundcolor]
this is Okay
http://www.smokingmedia.com/test_gd_fb/test?bg=ffee22

this is not okay:
http://www.smokingmedia.com/test_gd_fb/test_nk?bg=ffee22


sample code:

<?php

if(!isset($_GET[bg]) || $_GET[bg] == ""){
   $bg = "eeeeee";
   }
   else{
       $bg = $_GET[bg];
       }

$im = "test01.png";

//
// function to convert hex colorcode to decimal
//

function colordecode($hex){

   $code[r] = hexdec(substr($hex, 0 ,2));
   $code[g] = hexdec(substr($hex, 2 ,2));
   $code[b] = hexdec(substr($hex, 4 ,2));

   return $code;

} // end func colordecode

// create the resource id
$image_id = imageCreateFromPNG($im);

// get image size
$im_X = ImageSX($image_id);
$im_Y = ImageSY($image_id);

// create a truecolor background image of the right size
$backgroundimage = imagecreatetruecolor($im_X, $im_Y);

// get the desired backgroundcolor:
$code = colordecode($bg);
$backgroundcolor = ImageColorAllocate($backgroundimage, $code[r], $code[g], $code[b]);
ImageFilledRectangle($backgroundimage, 0, 0, $im_X, $im_Y, $backgroundcolor);

// merge the two together with alphablending on!
ImageAlphaBlending($backgroundimage, true);

// this works:
imagecopy($backgroundimage, $image_id, 0, 0, 0, 0, $im_X, $im_Y);

// this is where things go wrong:
//imagecopymerge($backgroundimage, $image_id, 0, 0, 0, 0, $im_X, $im_Y, 99);

// output the image:
Header( "Content-type: image/png");
ImagePNG($backgroundimage);

// destroy the memory
ImageDestroy($backgroundimage);
ImageDestroy($image_id);
?>

Patches

Pull Requests

Pull requests:

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-04-23 17:52 UTC] checat at yandex dot ru
PHP version: 5.2.9
CentOS 4

Reproduce code:
<?php
    $bg = imagecreatefrompng('http://guppi.spb.ru/php-bug-23815/bg.png');
    $over = imagecreatefrompng('http://guppi.spb.ru/php-bug-23815/over.png');
    imagecopymerge($bg, $over, 0,0,0,0,32,32,100);
    imagecopymerge($bg, $over, 16,0,0,0,32,32,50);
    imagepng($bg, 'imagecopymerge.png');
?>

Expected result:
Proper image: 
http://guppi.spb.ru/php-bug-23815/proper.png

Actual result:
Wrong image:
http://guppi.spb.ru/php-bug-23815/imagecopymerge.png
 [2009-04-23 18:08 UTC] pajoye@php.net
Site not accessible (timeout)
 [2009-05-04 12:14 UTC] checat at yandex dot ru
Please try again: site with images is up and running.

I've also uploaded files to alternate location, get them from http://checat.narod.ru/php-bug-23815/
 [2009-07-20 01:52 UTC] steve dot denim at redmonkey dot org
I have run into the same problem and can reproduce the example from checat at yandex dot ru.

I have also provided additional examples and a patch for the solution I have come with.

For the examples I've used two image files 'tux.png' for the background (destination) image and 'ff-logo-sm.png' for the overlay (source) image, these can be found at www.redmonkey.org/php-bug-23815/tux.png and www.redmonkey.org/php-bug-23815/ff-logo-sm.png respectively.

If I run these images through imagecopy() with the following code..

$bg  = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, true);
imagesavealpha($bg, true);
imagecopy($bg, $over, 276, 300, 0, 0, 123, 119);
imagepng($bg, 'tux-fox-imagecopy.png');

The alpha channels of both images seem to be handleed and merged/blended in a way that I think most users would expect, the resulting image can be found at www.redmonkey.org/php-bug-23815/tux-fox-imagecopy.png

However, if I run the two images through imagecopymerge with the following code..

$bg  = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, true);
imagesavealpha($bg, true);
imagecopymerge($bg, $over, 276, 300, 0, 0, 123, 119, 100);
imagepng($bg, 'tux-fox-imagecopymerge-100-without-patch.png');

The resulting image is not what I would expect, in this case, it seems that the alpha channel of the destination image is maintained but the alpha channel of the source image is completely ignored, the resulting image can be found at www.redmonkey.org/php-bug-23815/tux-fox-imagecopymerge-100-without-patch.png

Applying a 50% reduction in opacity..

$bg  = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, true);
imagesavealpha($bg, true);
imagecopymerge($bg, $over, 276, 300, 0, 0, 123, 119, 50);
imagepng($bg, 'tux-fox-imagecopymerge-50-without-patch.png');

Also has the same issue, the resulting image can be found at www.redmonkey.org/php-bug-23815/tux-fox-imagecopymerge-50-without-patch.png

After applying my patch the results from imagcopymerge are more inline with what I persoanlly would expect in that the alpha channels of both images are maintained during the copy/merge process.

With the following code..

$bg  = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, true);
imagesavealpha($bg, true);
imagecopymerge($bg, $over, 276, 300, 0, 0, 123, 119, 100);
imagepng($bg, 'tux-fox-imagecopymerge-100-with-patch.png');

The resulting image can be viewed at www.redmonkey.org/php-bug-23815/tux-fox-imagecopymerge-100-with-patch.png

And with the following code..

$bg  = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, true);
imagesavealpha($bg, true);
imagecopymerge($bg, $over, 276, 300, 0, 0, 123, 119, 50);
imagepng($bg, 'tux-fox-imagecopymerge-50-with-patch.png');

The resulting image can be viewed at www.redmonkey.org/php-bug-23815/tux-fox-imagecopymerge-50-with-patch.png

The patch as applied to ext/gd/libgd/gd.c is as follows.....


--- php-5.3.0/ext/gd/libgd/gd.c	2009-05-27 08:17:54.000000000 +0100
+++ php-5.3.0-build/ext/gd/libgd/gd.c	2009-07-19 22:28:37.312702552 +0100
@@ -2255,6 +2255,83 @@
 	int ncR, ncG, ncB;
 	toy = dstY;
 	
+	if (pct == 100) {
+		/* no opacity adjustment required pass through to gdImageCopy() */
+		gdImageCopy(dst, src, dstX, dstY, srcX, srcY, w, h);
+		return;
+	}
+
+	if (pct == 0) {
+		/* 0% opacity? nothing needs to be done */
+		return;
+	}
+
+	if (src->trueColor && dst->trueColor) {
+		/* support for maintaining the alpha (transparency) of both source and
+		 * destination images (assuming they are true colour) while opacity blending.
+		 */
+		gdImagePtr  srcback;
+		int         ca, cr, cg, cb;
+		float       na;
+		float       ac;
+
+		/* we adjust the alpha levels on a copy of the source image, the copy
+		 * only needs to be as large as the crop area if there is one
+		 */
+		srcback = gdImageCreateTrueColor (w, h);
+		if (srcback==NULL) {
+			return;
+		}
+
+		gdImageAlphaBlending(srcback, 0);
+		gdImageSaveAlpha(srcback, 1);
+		gdImageCopy(srcback, src, 0, 0, srcX, srcY, w, h);
+
+		/* we need to loop through the src image to get the max transparency level */
+		int mt = 0;
+
+		for (y = 0; y < h; y++) {
+			for (x = 0; x < w; x++) {
+				c  = gdImageGetTrueColorPixel (srcback, x, y);
+				ca = gdImageAlpha(srcback, c);
+
+				mt = ca > mt ? ca : mt;
+			}
+		}
+
+		/* src has no transparency? set to use full alpha range */
+		mt = mt == gdAlphaOpaque ? gdAlphaMax : mt;
+
+		/* alpha correction factor */
+		ac = (float)mt / gdAlphaMax;
+
+		/* loop through the image again and set/adjust alpha channel level */
+		for (y = 0; y < h; y++) {
+			for (x = 0; x < w; x++) {
+				c  = gdImageGetTrueColorPixel (srcback, x, y);
+				ca = gdImageAlpha(srcback, c);
+				cr = gdImageRed(srcback, c);
+				cg = gdImageGreen(srcback, c);
+				cb = gdImageBlue(srcback, c);
+
+				na = (ca + gdAlphaMax - (gdAlphaMax * ((float)pct / 100))) * ac;
+				na = (na > 127.0f)? 127.0f : ((na < 0.0f)? 0.0f: na);
+
+				int nc = gdImageColorAllocateAlpha(srcback, cr, cg, cb, (int)na);
+				if (nc == -1) {
+					gdImageColorClosestAlpha(srcback, cr, cg, cb, (int)na);
+				}
+
+				gdImageSetPixel (srcback, x, y, nc);
+			}
+		}
+
+		/* finally dispatch to gdImageCopy to do the actual copying */
+		gdImageCopy(dst, srcback, dstX, dstY, 0, 0, w, h);
+		gdImageDestroy(srcback);
+		return;
+	}
+
 	for (y = srcY; y < (srcY + h); y++) {
 		tox = dstX;
 		for (x = srcX; x < (srcX + w); x++) {
 [2009-07-20 05:44 UTC] steve at redmonkey dot org
To make life a little easier I've put the notes and examples together on a simple web page at http://www.redmonkey.org/php-bug-23815/

After investigating the code base a little further I've realised my patch solution can be made more efficient as there is no need to make a copy of the source or pass the image over to gdImageCopy once the new alpha level has been set as we've already done the all the work and can simply set the pixels RGBA index within the second image scan.

The revised patch (which is also available from a link on the web page) is as follows....

--- php-5.3.0/ext/gd/libgd/gd.c	2009-05-27 08:17:54.000000000 +0100
+++ php-5.3.0-build/ext/gd/libgd/gd.c	2009-07-20 05:54:21.709936176 +0100
@@ -2255,6 +2255,67 @@
 	int ncR, ncG, ncB;
 	toy = dstY;
 	
+	if (pct == 100) {
+		/* no opacity adjustment required pass through to gdImageCopy() */
+		gdImageCopy(dst, src, dstX, dstY, srcX, srcY, w, h);
+		return;
+	}
+
+	if (pct == 0) {
+		/* 0% opacity? nothing needs to be done */
+		return;
+	}
+
+	if (src->trueColor && dst->trueColor) {
+		/* support for maintaining the alpha (transparency) of both source and
+		 * destination images (assuming they are true colour) while opacity blending.
+		 */
+		int         ca, cr, cg, cb;
+		float       na;
+		float       ac;
+
+		/* we need to loop through the src image to get the max transparency level */
+		int mt = 0;
+
+		for (y = 0; y < h; y++) {
+			for (x = 0; x < w; x++) {
+				c  = gdImageGetTrueColorPixel (src, srcX + x, srcY + y);
+				ca = gdImageAlpha(src, c);
+
+				mt = ca > mt ? ca : mt;
+			}
+		}
+
+		/* src has no transparency? set to use full alpha range */
+		mt = mt == gdAlphaOpaque ? gdAlphaMax : mt;
+
+		/* alpha correction factor */
+		ac = (float)mt / gdAlphaMax;
+
+		/* loop through the image again and set/adjust alpha channel level */
+		for (y = 0; y < h; y++) {
+			for (x = 0; x < w; x++) {
+				c  = gdImageGetTrueColorPixel (src, srcX + x, srcY + y);
+				ca = gdImageAlpha(src, c);
+				cr = gdImageRed(src, c);
+				cg = gdImageGreen(src, c);
+				cb = gdImageBlue(src, c);
+
+				na = (ca + gdAlphaMax - (gdAlphaMax * ((float)pct / 100))) * ac;
+				na = (na > gdAlphaMax)? gdAlphaMax : ((na < gdAlphaOpaque)? gdAlphaOpaque: na);
+
+				int nc = gdImageColorAllocateAlpha(src, cr, cg, cb, (int)na);
+				if (nc == -1) {
+					gdImageColorClosestAlpha(src, cr, cg, cb, (int)na);
+				}
+
+				gdImageSetPixel (dst, dstX + x, dstY + y, nc);
+			}
+		}
+
+		return;
+	}
+
 	for (y = srcY; y < (srcY + h); y++) {
 		tox = dstX;
 		for (x = srcX; x < (srcX + w); x++) {
 [2009-07-20 08:43 UTC] pajoye@php.net
imagecopymerge was not meant to support the alpha channel but to emulate it via pct. It was also not meant to use both the alpha or the pct value to blend an image over another.
 [2009-07-20 12:10 UTC] steve at redmonkey dot org
Thanks, understood. Although, I do think it would be a useful feature, perhaps there's scope for an 'imagecopymergealpha' type function in the future?
 [2009-12-10 18:23 UTC] andre at webkr dot de
So what does the "it implements alpha transparency for true colour images" in "When pct  = 0, no action is taken, when 100 this function behaves identically to imagecopy() for pallete images, while it implements alpha transparency for true colour images." mean anyway?
 [2009-12-10 18:35 UTC] andre at webkr dot de
Ah, I see. It's imagecopy() which implements alpha transparency while imagecopymerge() does not.
 [2010-05-06 06:44 UTC] setvik at gmail dot com
I have the same need to merge alpha images with the opacity of the top one 
reduced, and like the others in this thread, I assumed imagecopymerge would 
respect the alpha settings in the image. Based on comments here and in the docs 
( http://php.net/manual/en/function.imagecopymerge.php), others are making the 
same assumption.

@pajoye - The inclusion of alpha support whether in imagecopymerge or an 
additional function would be very valuable for working with images, especially 
watermarks with complex transparency. Any chance imagecopymerge can be patched 
to support this? Or should an additional function be written and the 
documentation for imagecopymerge updated? 

What's your take?
 [2010-12-20 09:39 UTC] jani@php.net
-Package: Feature/Change Request +Package: GD related
 [2012-10-04 07:24 UTC] cleggmatt at gmail dot com
Good idea. Added as an extra function and created a pull request on github 
https://github.com/php/php-src/pull/211
 [2017-01-22 20:23 UTC] cmb@php.net
-Status: Assigned +Status: Suspended
 [2017-01-22 20:23 UTC] cmb@php.net
IMHO, imagecopymerge() should finally be removed in the long run.
Its purpose is to emulate alpha, what was necessary before GD
introduced truecolor images with proper alpha channels. Therefore,
I'm against adding further alpha support to imagecopymerge() or
even an additional function.

Instead, imagefilter() could be used to either increase the alpha
values or to decrease the color values. IMG_FILTER_COLORIZE already
allows this, but one can't specify a percentage, so can't get the
exact same effect. The following example shows that one could already
come a long way, though:

    <?php
    
    $bg = imagecreatefrompng(__DIR__ . '/23815-bg.png');
    $over = imagecreatefrompng(__DIR__ . '/23815-over.png');
    imagecopy($bg, $over, 0, 0, 0, 0, 16, 32);
    
    imagealphablending($over, false);
    imagefilter($over, IMG_FILTER_COLORIZE, 0, 0, 0, +32);
    
    imagecopy($bg, $over, 16, 0, 0, 0, 16, 32);
    imagepng($bg, __DIR__ . '/23815-out.png');

Anyhow, this is a general libgd issue (not particularly related to
PHP's bundled libgd), so I have filed
<https://github.com/libgd/libgd/issues/370> and suspend this
feature request.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 08 07:01:27 2024 UTC