merging images while maintaing alpha channels
Posted: Sat Jul 18, 2009 3:53 pm
I ran into a problem with both imagecopy() and imagecopymerge() in the way they operate when both source and destination images have alpha channels.
Ideally I need to maintain and merge alpha channels while also having the flexibility of imagecopymerge's opacity level parameter. Imaging is not really my area of interest and to be honest my head is starting to hurt trying to work out what should be happening in terms of merging levels.
The issue, and what I have so far......
Using imagecopymerge....
This results in the alpha channel of the destination image being retained but the alpha channel of the source image is ignored....

Using imagecopy.....
This results in the alpha channels of both images being retained but as the source image takes priority, the level of it's alpha channel is applied without any merging of the underlying/destination image...

So I've come up with 'imagecopymerge_alpha' (callng code, resulting image then actual function code below in that order).....
Which initially has promising results.......

However, there is an issue when I start adding opacity levels other than 100% into the mix, the function code is below followed by some sample images at varying opacity levels. I'm wondering if I've missed a trick somewhere and this could be done with PHP's inbuilt functions? Or if anyone has ran into a similar problem and has already come up with a solution to the opacity problem?
With 80% opacity...

With 40% opacity...

With 20% opacity....

With 10% opacity....

Ideally I need to maintain and merge alpha channels while also having the flexibility of imagecopymerge's opacity level parameter. Imaging is not really my area of interest and to be honest my head is starting to hurt trying to work out what should be happening in terms of merging levels.
The issue, and what I have so far......
Using imagecopymerge....
Code: Select all
$bg = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, false);
imagesavealpha($bg, true);
imagecopymerge($bg, $over, 276, 300, 0, 0, 123, 119, 100);
imagepng($bg, 'tux-fox-imagecopymerge.png');

Using imagecopy.....
Code: Select all
$bg = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, false);
imagesavealpha($bg, true);
imagecopy($bg, $over, 276, 300, 0, 0, 123, 119);
imagepng($bg, 'tux-fox-imagecopy.png');
This results in the alpha channels of both images being retained but as the source image takes priority, the level of it's alpha channel is applied without any merging of the underlying/destination image...

So I've come up with 'imagecopymerge_alpha' (callng code, resulting image then actual function code below in that order).....
Code: Select all
$bg = imagecreatefrompng('tux.png');
$over = imagecreatefrompng('ff-logo-sm.png');
imagealphablending($bg, false);
imagesavealpha($bg, true);
imagecopymerge_alpha($bg, $over, 276, 300, 0, 0, 123, 119, 100);
imagepng($bg, 'tux-fox-imagecopymerge_alpha.png');

However, there is an issue when I start adding opacity levels other than 100% into the mix, the function code is below followed by some sample images at varying opacity levels. I'm wondering if I've missed a trick somewhere and this could be done with PHP's inbuilt functions? Or if anyone has ran into a similar problem and has already come up with a solution to the opacity problem?
Code: Select all
/**
* merge two true colour images while maintaining alpha transparency of both
* images.
*
* known issues : Opacity values other than 100% get a bit screwy, the source
* composition determines how much this issue will annoy you.
* if in doubt, use as you would imagecopy_alpha (i.e. keep
* opacity at 100%)
*
* @access public
*
* @param resource $dst Destination image link resource
* @param resource $src Source image link resource
* @param int $dstX x-coordinate of destination point
* @param int $dstY y-coordinate of destination point
* @param int $srcX x-coordinate of source point
* @param int $srcY y-coordinate of source point
* @param int $w Source width
* @param int $h Source height
* @param int $pct Opacity or source image
******************************************************************************/
function imagecopymerge_alpha($dst, $src, $dstX, $dstY, $srcX, $srcY, $w, $h, $pct)
{
$pct /= 100;
/* sanity check before going any further */
$pct = max(min(1, $pct), 0);
if ($pct == 0)
{
/* would anyone really attempt to call us with 0% opacity? */
return;
}
$toy = $dstY;
for ($y = $srcY; $y < ($srcY + $h); $y++)
{
$tox = $dstX;
for ($x = $srcX; $x < ($srcX + $w); $x++)
{
/* get source image's pixel RGBA index */
$src_c = imagecolorat($src, $x, $y);
if ($src_c === false)
{
/* we must be 'out of bounds' on source image so skip */
/* essentially leaving a fully transparent area through to */
/* destination image. is this correct behaviour or should we */
/* fill black/white ?? */
$tox++;
continue;
}
/* get source alpha channel level and decide if we need to continue */
$src_a = ($src_c >> 24) & 0xFF;
if ($pct < 1)
{
/* fake opacity level by adjusting alpha channel level */
$src_a = ($src_a == 0) ? 127 - (127 * $pct) : $src_a + (127 * $pct);
$src_a = ($src_a > 127) ? 127 : (int)$src_a;
}
if ($src_a == 127)
{
/* fully transparent areas of source image can be skipped */
$tox++;
continue;
}
$src_r = ($src_c >> 16) & 0xFF;
$src_g = ($src_c >> 8) & 0xFF;
$src_b = ($src_c) & 0xFF;
/* get destination image's pixel RGBA index */
$dst_c = imagecolorat($dst, $tox, $toy);
$dst_a = ($dst_c >> 24) & 0xFF;
$dst_r = ($dst_c >> 16) & 0xFF;
$dst_g = ($dst_c >> 8) & 0xFF;
$dst_b = ($dst_c) & 0xFF;
/* alpha multiplier */
$alpha = $src_a / 127;
/* RGB compensation for alpha channel level */
$new_r = $src_r - ($src_r * $alpha) + ($dst_r * $alpha);
$new_g = $src_g - ($src_g * $alpha) + ($dst_g * $alpha);
$new_b = $src_b - ($src_b * $alpha) + ($dst_b * $alpha);
/* sanity check nothing gets above 255 */
$new_r = ($new_r > 255 )? 255 : (int)$new_r;
$new_g = ($new_g > 255 )? 255 : (int)$new_g;
$new_b = ($new_b > 255 )? 255 : (int)$new_b;
/* alpha channel compensation, I doubt it's as simple as this. */
/* this requires further investigation, with some of our test */
/* images applying even just a little reduction in opacity starts */
/* to intoduce artifacts around the edges of source's non */
/* transparent image area when the merge area of destination */
/* image is fully transparent. it also gets very 'screwy' with */
/* small opacity levels. it works for us at the moment but it's */
/* not right and I suspect the issue lies here but it may also be */
/* related to some of the compensation calculations above */
$new_a = min($src_a, $dst_a);
if ($new_a > 0)
{
$new_a = $dst_a * $alpha;
$new_a = ($new_a > 127) ? 127 : (int)$new_a;
}
/* finally get and set this pixel's RGBA colour index */
$rgba = ImageColorAllocateAlpha($dst, $new_r, $new_g, $new_b, $new_a);
if ($rgba == -1) {
$rgba = ImageColorClosestAlpha($dst, $new_r, $new_g, $new_b, $new_a);
}
imagesetpixel($dst, $tox, $toy, $rgba);
$tox++;
}
$toy++;
}
}

With 40% opacity...

With 20% opacity....

With 10% opacity....



