PHP GD使用一张图片作为掩码来覆盖另一张图片,包括透明度。

44

我正在尝试创建一个PHP脚本,该脚本接受一张图片:

image1
http://i.stack.imgur.com/eNvlM.png

然后应用PNG图像:

mask
http://i.stack.imgur.com/iJr2I.png

作为遮罩。

最终结果需要保持透明度:

result
http://i.stack.imgur.com/u0l0I.png

如果可能的话,我想在GD中完成这个过程,目前ImageMagick不是很可选。

我该怎么做?

phalacee的帖子(在“PHP / GD中,如何从一个图像复制圆形到另一个图像?”中)似乎是正确的方向,但我特别需要使用一张图像作为遮罩,而不是形状。

6个回答

58

Matt,

如果您使用黑色背景上的椭圆形白色填充而不是透明背景上的黑色填充,则可以使用以下函数进行操作。

<?php
// Load source and mask
$source = imagecreatefrompng( '1.png' );
$mask = imagecreatefrompng( '2.png' );
// Apply mask to source
imagealphamask( $source, $mask );
// Output
header( "Content-type: image/png");
imagepng( $source );

function imagealphamask( &$picture, $mask ) {
    // Get sizes and set up new picture
    $xSize = imagesx( $picture );
    $ySize = imagesy( $picture );
    $newPicture = imagecreatetruecolor( $xSize, $ySize );
    imagesavealpha( $newPicture, true );
    imagefill( $newPicture, 0, 0, imagecolorallocatealpha( $newPicture, 0, 0, 0, 127 ) );

    // Resize mask if necessary
    if( $xSize != imagesx( $mask ) || $ySize != imagesy( $mask ) ) {
        $tempPic = imagecreatetruecolor( $xSize, $ySize );
        imagecopyresampled( $tempPic, $mask, 0, 0, 0, 0, $xSize, $ySize, imagesx( $mask ), imagesy( $mask ) );
        imagedestroy( $mask );
        $mask = $tempPic;
    }

    // Perform pixel-based alpha map application
    for( $x = 0; $x < $xSize; $x++ ) {
        for( $y = 0; $y < $ySize; $y++ ) {
            $alpha = imagecolorsforindex( $mask, imagecolorat( $mask, $x, $y ) );
            $alpha = 127 - floor( $alpha[ 'red' ] / 2 );
            $color = imagecolorsforindex( $picture, imagecolorat( $picture, $x, $y ) );
            imagesetpixel( $newPicture, $x, $y, imagecolorallocatealpha( $newPicture, $color[ 'red' ], $color[ 'green' ], $color[ 'blue' ], $alpha ) );
        }
    }

    // Copy back to original picture
    imagedestroy( $picture );
    $picture = $newPicture;
}

?>

我知道这篇帖子已经关闭了一段时间,但是你们运行这个脚本时运行时间是多少呢?平均需要20秒左右...我的源/掩模图像大小为250 x 170像素...你们得到的大概也是这个大小吧? - Danny Bullis
忽略吧,我不确定我当时做错了什么,但现在它运作得非常好。谢谢大家! - Danny Bullis
3
应该将$alpha = 127 - floor( $alpha[ 'red' ] / 2 ); $color = imagecolorsforindex( $picture, imagecolorat( $picture, $x, $y ) );这两行代码替换为$color = imagecolorsforindex( $picture, imagecolorat( $picture, $x, $y ) ); $alpha = 127 - floor((127-$color['alpha']) * ($alpha[ 'red' ]/255));以防止丢失源图像的 alpha 通道。 - Sami
@Jules_Text,你能告诉我为什么在这一行使用了 red 索引而不是 greenblue 索引吗? $alpha = 127 - floor( $alpha[ 'red' ] / 2 ); - tirenweb
@ziiweb 没有理由选择其中之一。灰色的红、蓝和绿色级别相同。 - Matrix

13

这里是对该脚本的一些升级 - 发现如果源图像本身具有透明度,则使用上述脚本的遮罩会绘制一个背面像素,而不是源图像的透明像素。下面的扩展脚本考虑了源图像的透明度,并将其保留。

// Load source and mask
$source = imagecreatefrompng( '1.png' );
$mask = imagecreatefrompng( '2.png' );
// Apply mask to source
imagealphamask( $source, $mask );
// Output
header( "Content-type: image/png");
imagepng( $source );

function imagealphamask( &$picture, $mask ) {
// Get sizes and set up new picture
$xSize = imagesx( $picture );
$ySize = imagesy( $picture );
$newPicture = imagecreatetruecolor( $xSize, $ySize );
imagesavealpha( $newPicture, true );
imagefill( $newPicture, 0, 0, imagecolorallocatealpha( $newPicture, 0, 0, 0, 127 ) );

// Resize mask if necessary
if( $xSize != imagesx( $mask ) || $ySize != imagesy( $mask ) ) {
    $tempPic = imagecreatetruecolor( $xSize, $ySize );
    imagecopyresampled( $tempPic, $mask, 0, 0, 0, 0, $xSize, $ySize, imagesx( $mask ), imagesy( $mask ) );
    imagedestroy( $mask );
    $mask = $tempPic;
}

// Perform pixel-based alpha map application
for( $x = 0; $x < $xSize; $x++ ) {
    for( $y = 0; $y < $ySize; $y++ ) {
        $alpha = imagecolorsforindex( $mask, imagecolorat( $mask, $x, $y ) );

            if(($alpha['red'] == 0) && ($alpha['green'] == 0) && ($alpha['blue'] == 0) && ($alpha['alpha'] == 0))
            {
                // It's a black part of the mask
                imagesetpixel( $newPicture, $x, $y, imagecolorallocatealpha( $newPicture, 0, 0, 0, 127 ) ); // Stick a black, but totally transparent, pixel in.
            }
            else
            {

                // Check the alpha state of the corresponding pixel of the image we're dealing with.    
                $alphaSource = imagecolorsforindex( $source, imagecolorat( $source, $x, $y ) );

                if(($alphaSource['alpha'] == 127))
                {
                    imagesetpixel( $newPicture, $x, $y, imagecolorallocatealpha( $newPicture, 0, 0, 0, 127 ) ); // Stick a black, but totally transparent, pixel in.
                } 
                else
                {
                    $color = imagecolorsforindex( $source, imagecolorat( $source, $x, $y ) );
                    imagesetpixel( $newPicture, $x, $y, imagecolorallocatealpha( $newPicture, $color[ 'red' ], $color[ 'green' ], $color[ 'blue' ], $color['alpha'] ) ); // Stick the pixel from the source image in
                }


            }
    }
}

// Copy back to original picture
imagedestroy( $picture );
$picture = $newPicture;
}

我不得不在函数内将$source更改为$picture,除此之外,这个函数运行得很好! - OSK
这是错误的!你只是从遮罩图像中替换了 alpha 通道。这将会对渐变遮罩和半透明源产生错误的结果。 - NoSkill

10

我喜欢你的脚本,去除完全透明像素的额外色彩信息是个好主意。我应该指出一个小错误(依我之见),如果有人想使用这种方法。

$color = imagecolorsforindex( $source, imagecolorat( $source, $x, $y ) );

应该是这样的

$color = imagecolorsforindex( $picture, imagecolorat( $picture, $x, $y ) );

而且我不确定为什么要在这里检查RGB值,如果像素是完全透明的

if(($alpha['red'] == 0) && ($alpha['green'] == 0) && ($alpha['blue'] == 0) && ($alpha['alpha'] == 0))
...

我不确定从掩码文件进行 alpha 混合是否能很好地与您的方法配合使用,因为仅当所有 rgba 值均等于 0 时才会使用它。

Jules 的脚本也很好,但它期望掩码是掩码的灰度表示(这是相当普遍的做法)。

在 Matt 的查询中,他要求一个脚本只获取现有图像的 alpha 透明度,并将其应用于另一个图像。这里是对 Jules 的脚本进行简单修改,只取掩码图像中的 alpha 通道,并保留源图像的 alpha 通道。

<?php
// Load source and mask
$source = imagecreatefrompng( '1.png' );
$mask = imagecreatefrompng( '2.png' );
// Apply mask to source
imagealphamask( $source, $mask );
// Output
header( "Content-type: image/png");
imagepng( $source );

function imagealphamask( &$picture, $mask ) {
    // Get sizes and set up new picture
    $xSize = imagesx( $picture );
    $ySize = imagesy( $picture );
    $newPicture = imagecreatetruecolor( $xSize, $ySize );
    imagesavealpha( $newPicture, true );
    imagefill( $newPicture, 0, 0, imagecolorallocatealpha( $newPicture, 0, 0, 0, 127 ) );

    // Resize mask if necessary
    if( $xSize != imagesx( $mask ) || $ySize != imagesy( $mask ) ) {
        $tempPic = imagecreatetruecolor( $xSize, $ySize );
        imagecopyresampled( $tempPic, $mask, 0, 0, 0, 0, $xSize, $ySize, imagesx( $mask ), imagesy( $mask ) );
        imagedestroy( $mask );
        $mask = $tempPic;
    }

    // Perform pixel-based alpha map application
    for( $x = 0; $x < $xSize; $x++ ) {
        for( $y = 0; $y < $ySize; $y++ ) {
            $alpha = imagecolorsforindex( $mask, imagecolorat( $mask, $x, $y ) );
            //small mod to extract alpha, if using a black(transparent) and white
            //mask file instead change the following line back to Jules's original:
            //$alpha = 127 - floor($alpha['red'] / 2);
            //or a white(transparent) and black mask file:
            //$alpha = floor($alpha['red'] / 2);
            $alpha = $alpha['alpha'];
            $color = imagecolorsforindex( $picture, imagecolorat( $picture, $x, $y ) );
            //preserve alpha by comparing the two values
            if ($color['alpha'] > $alpha)
                $alpha = $color['alpha'];
            //kill data for fully transparent pixels
            if ($alpha == 127) {
                $color['red'] = 0;
                $color['blue'] = 0;
                $color['green'] = 0;
            }
            imagesetpixel( $newPicture, $x, $y, imagecolorallocatealpha( $newPicture, $color[ 'red' ], $color[ 'green' ], $color[ 'blue' ], $alpha ) );
        }
    }

    // Copy back to original picture
    imagedestroy( $picture );
    $picture = $newPicture;
}

?>

这是错误的!你只是简单地从遮罩图像中替换了 alpha 通道。这将会对渐变遮罩和半透明源产生错误的结果。 - NoSkill

3

1
for ($y = 0; $y < $ySize; $y++) {
  $alpha = imagecolorsforindex($mask, imagecolorat($mask, $x, $y));
  $alpha = 127 - floor($alpha['red'] / 2);
  if (127 == $alpha) {
    continue;
  }
  $color = imagecolorsforindex($picture, imagecolorat($picture, $x, $y));
  imagesetpixel($newPicture, $x, $y, imagecolorallocatealpha(
    $newPicture, $color['red'], $color['green'], $color['blue'], $alpha));
}

这是第一个函数的一个小升级。由于您已经有一个透明图像,因此不需要复制掩码像素。这将有助于稍微提高执行效率。


这是错误的!你只是从遮罩图像中替换了 alpha 通道。这将会对渐变遮罩和半透明源产生错误的结果。 - NoSkill

-2
一种获得类似效果的不同方法是将PNG文件粘贴到具有唯一背景颜色的新图像上,以暂时去除透明度,并将PNG图像的透明颜色设置为黑色圆形颜色。然后,当您将其放置在JPEG图像上时,将新的透明颜色设置为掩码的颜色。
// Load the Black Circle PNG image
$png = imagecreatefrompng( 'mask.png' );
$width = imagesx( $png );
$height = imagesy( $png );

// Create a mask image
$mask = imagecreatetruecolor( $width, $height );
// We'll use Magenta as our new transparent colour - set it as the solid background colour.
$magenta = imagecolorallocate( $mask, 255, 0, 255 );
imagefill( $mask, 0, 0, $magenta );

// Copy the png image onto the mask. Destroy it to free up memory.
imagecopyresampled( $mask, $png, 0, 0, 0, 0, $width, $height, $width, $height );
imagedestroy( $png );

// Set the black portion of the mask to transparent.
$black = imagecolorallocate( $mask, 0, 0, 0 );
imagecolortransparent( $mask, $black );

// Load JPEG image.
$jpg = imagecreatefromjpeg( 'image.jpg' );
$j_width = imagesx( $jpg );
$j_height = imagesx( $jpg );

// Enable alpha blending and copy the png image
imagealphablending( $jpg, true );
imagecopyresampled( $jpg, $mask, 0, 0, 0, 0, $j_width, $j_height, $width, $height );
imagedestroy( $mask );

// Set the new transparent colour and output new image to browser as a png.
$magenta = imagecolorallocate( $jpg, 255, 0, 255 );
imagecolortransparent( $jpg, $magenta );
imagepng( $jpg );

如果重新采样或半透明像素让您感到沮丧,您可以禁用混合并在 $mask 图像上绘制一个透明的形状,而不是使用 png 作为遮罩。
// Load JPEG Image.
$jpg = imagecreatefromjpeg( 'image.jpg' );
$width = imagesx( $jpg );
$height = imagesx( $jpg );

// Create mask at same size with an opaque background.
$mask = imagecreatetruecolor( $width, $height );
$magenta = imagecolorallocate( $mask, 255, 0, 255 );
imagefill( $mask, 0, 0, $magenta );

// Disable alpha blending and draw a transparent shape onto the mask.
$transparent = imagecolorallocatealpha( $mask, 255, 255, 255, 127 );
imagealphablending( $mask, false );
imagefilledellipse( $mask, round( $width / 2 ), round( $height / 2 ), $width, $height, $transparent );

// Paste the mask onto the original image and set the new transparent colour.
imagealphablending( $jpg, true );
imagecopyresampled( $jpg, $mask, 0, 0, 0, 0, $width, $height, $width, $height );
imagedestroy( $mask );
$magenta = imagecolorallocate( $jpg, 255, 0, 255 );
imagecolortransparent( $jpg, $magenta );

// Output new image to browser as a png.
imagepng( $jpg );

注意:上述代码未经测试,但应该能够满足您的需求。


没有 alpha-blending,它会产生锯齿状的图形。 - NoSkill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接