PHP GD использует одно изображение для маскировки другого изображения, включая прозрачность

Я пытаюсь создать 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 ответов


Мэтт,

Если вы делаете свой png с овальной белой заливкой на черном фоне вместо черной заливки с прозрачным фоном, это делает следующая функция.

<?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;
}

?>

вот небольшое обновление до этого скрипта - я обнаружил, что если исходное изображение имеет прозрачность, маска (используя скрипт выше) отображает задний пиксель вместо прозрачного пикселя исходного изображения. Приведенный ниже расширенный скрипт учитывает прозрачность исходного изображения и сохраняет его.

// 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;
}

мне нравится ваш скрипт, хорошая идея удалить дополнительную информацию о цвете, когда пиксель полностью прозрачен. Я должен указать только на небольшую ошибку (IMO), хотя, если кто-то хочет использовать этот метод.

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

должно быть

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

также я не на 100% уверен, почему вы проверяете значения rgb здесь, если пиксель 100% прозрачный

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

и я не уверен, что Альфа-смешивание из файла маски будет хорошо работать с вашим методом, так как он используется только при rgba все значения равны 0.

сценарий Жюля тоже довольно хорош, хотя он ожидает, что маска будет серым масштабным представлением маски (что довольно распространенная практика).

в запросе Мэтта он был после сценария, который захватывает только альфа-прозрачность из существующего изображения и применяет его к другому изображению. Вот простой мод сценария Жюля, чтобы захватить Альфа из изображения маски и сохранить Альфа исходного изображения.

<?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;
}

?>

есть библиотека под названием WideImage, которая поддерживает Альфа-маски http://wideimage.sourceforge.net/documentation/manipulating-images/


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));
}

вот небольшое обновление для первой функции. Поскольку у вас уже есть прозрачное изображение, вам не нужно копировать замаскированные пиксели. Это немного поможет казни.


другой способ получить аналогичный эффект-вставить файл 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 );

Если передискретизация или полупрозрачные пиксели получают вас вниз, вместо использования png в качестве маски, вы можете отключить смешивание и нарисуйте прозрачную форму на $mask изображение вместо этого.

// 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 );

Примечание: приведенный выше код непроверен, но, надеюсь, вы сделаете то, что вам нужно.