Photoshop的图层混合(Layer Blending)是实现各种特效的基础之一,在Photoshop新版中已经提供了接近30种图层混合模式,而运用这些图层混合模式则可以将两个图层叠加并且通过一些算法使叠加后的图层呈现新的效果,比如可以通过“变暗”、“正片叠底”使底层图像变暗,通过“叠加”、“柔光”增强底层图片对比度等。

我之前以为这些特效一定经过了复杂的算法,但稍微了解之后才知道图层混合采用的算法其实都简单到难以置信,几乎全是加减乘除就可以搞定。来复习一下计算机图形的基础知识,一张位图由若干像素点组成,而每个像素点,都有自己的颜色与透明度,因此每一个像素都可以分拆为RGB与Alpha四个通道,一般可以采用0-255的值来表示单一通道的颜色值。比如在CSS中,就可以分别指定4个通道的值来定义一个颜色。

color:rgba(153, 134, 117, 0.2);

而两个图层混合,本质上是将两个图层的同一位置的像素点取出,对其RGB通道的值分别进行某种运算,最终生成一个新的RGB值。

来看一个最简单的例子,如果我们想将上层图片top.png与下层图片bottom.png采用PhotoShop中“正片叠底(Multiply)”模式混合,使用php+GD实现:

$top = imagecreatefrompng('top.png');
$bottom = imagecreatefrompng('bottom.png');

$width = imagesx($top);
$height = imagesy($top);
$layer = imagecreatetruecolor($width, $height);
for ($x = 0; $x < $width; $x++) {
    for ($y = 0; $y < $height; $y++) {
        $color = imagecolorat($top, $x, $y);
        $tR = ($color >> 16) & 0xFF;
        $tG = ($color >> 8) & 0xFF;
        $tB = $color & 0xFF;
        $color = imagecolorat($bottom, $x, $y);
        $bR = ($color >> 16) & 0xFF;
        $bG = ($color >> 8) & 0xFF;
        $bB = $color & 0xFF;
        imagesetpixel($layer, $x, $y, imagecolorallocate($layer, $tR * $bR / 255, $tG * $bG / 255, $tB * $bB / 255));
    }
}
header('Content-Type: image/png');
imagepng($layer);

程序做的事情其实非常简单,遍历图片的所有像素,取得上下图层的RGB值,分别进行上*下/255这样一个简单的运算,将新的颜色填充到原来的位置,就完成了一次“正片叠底”的混合。看看效果:

原图 + 图层 = Multiply

图层混合(Layer Blending)模式算法实现

首先因为所有的图层混合的处理流程都是差不多的,唯一不同的是颜色通道的算法,因此我们将上面的图片处理抽象一下,将颜色通达算法做成一个可以替换的部分。

class Blending
{
    public static function layerMultiply($A, $B)
    {
        return $A * $B / 255;
    }
}

function layerBlending($mode, $top = 'top.png', $bottom = 'bottom.png')
{
    $handler = 'Blending::layer' . $mode;
    $top = imagecreatefrompng($top);
    $bottom = imagecreatefrompng($bottom);

    $width = imagesx($top);
    $height = imagesy($top);
    $layer = imagecreatetruecolor($width, $height);
    for ($x = 0; $x < $width; $x++) {
        for ($y = 0; $y < $height; $y++) {
            $color = imagecolorat($top, $x, $y);
            $tR = ($color >> 16) & 0xFF;
            $tG = ($color >> 8) & 0xFF;
            $tB = $color & 0xFF;
            $color = imagecolorat($bottom, $x, $y);
            $bR = ($color >> 16) & 0xFF;
            $bG = ($color >> 8) & 0xFF;
            $bB = $color & 0xFF;
            imagesetpixel($layer, $x, $y, imagecolorallocate($layer, 
                call_user_func($handler, $tR, $bR),
                call_user_func($handler, $tG, $bG),
                call_user_func($handler, $tB, $bB)
            ));
        }
    }
    header('Content-Type: image/png');
    imagepng($layer);
}

我们定义了一个混合模式的算法类Blending,Blending中会有一系列静态方法,方法中入口参数A为上图层,B为下图层,只记载最核心的颜色通道算法,就可以通过替换算法的名称来切换不同的混合模式,比如此时我们应用“正片叠底”只需要:

layerBlending('Multiply');

下面就实际用PHP+GD来尝试实现Photoshop中所有的图层混合(Layer Blending)模式算法吧。在下面所有示例中,A均代表上图层(混合层),B代表下图层(基层)。

1. 变暗 Darken

(B > A) ? A : B

取A与B中当前通道颜色值较小的一个,整体会变暗。

public static function layerDarken($A, $B)
{
    return $B > $A ? $A : $B;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图变暗

2. 正片叠底 Multiply

(A * B) / 255

这种方式混合会得到一个比两个图层都暗的颜色。

public static function layerMultiply($A, $B)
{
    return $A * $B / 255;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图正片叠底

3. 颜色加深 ColorBurn (NG)

B == 0 ? B : max(0, (255 - ((255 - A) << 8 ) / B))

public static function layerColorBurn($A, $B)
{
    return $B == 0 ? $B : max(0, (255 - ((255 - $A) << 8 ) / $B));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图颜色加深

4. 线性加深 LinearBurn | 减去 Subtract

(A + B < 255) ? 0 : (A + B - 255)

比变暗效果更加强烈,深色几乎被转成黑色,浅色也全部被加深。

public static function layerSubtract($A, $B)
{
    return $A + $B < 255 ? 0 : $A + $B - 255;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图线性加深


5. 变亮 Lighten

(B > A) ? B : A

取A与B中当前通道颜色值较大的一个,整体效果就会偏亮。

public static function layerLighten($A, $B)
{
    return $B > $A ? $B : $A;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图变亮

6. 滤色 Screen

255 - (((255 - A) * (255 - B)) >> 8))

与正片叠底正好相反,滤色会由两个颜色得到一个比较亮的颜色。

public static function layerScreen($A, $B)
{
    return 255 - ( ((255 - $A) * (255 - $B)) >> 8);
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图滤色

7. 颜色减淡 ColorDodge

(B == 255) ? B : min(255, ((A << 8 ) / (255 - B)))

public static function layerColorDodge($A, $B)
{
    return $B == 255 ? $B : min(255, (($A << 8 ) / (255 - $B)));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图颜色减淡

8. 线性减淡 LinearDodge | 添加 Add

min(255, (A + B))

public static function layerAdd($A, $B)
{
    return min(255, ($A + $B));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图线性减淡


9. 叠加 Overlay

(B < 128) ? (2 * A * B / 255):(255 - 2 * (255 - A) * (255 - B) / 255)

public static function layerOverlay($A, $B)
{
    return ($B < 128) ? (2 * $A * $B / 255) : (255 - 2 * (255 - $A) * (255 - $B) / 255);
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图叠加

10. 柔光 SoftLight

B < 128 ? (2 * (( A >> 1) + 64)) * (B / 255) : (255 - ( 2 * (255 - ( (A >> 1) + 64 ) ) * ( 255 - B ) / 255 ));

public static function layerSoftLight($A, $B)
{
    return $B < 128 ? 
         (2 * (( $A >> 1) + 64)) * ($B / 255) : 
         (255 - ( 2 * (255 - ( ($A >> 1) + 64 ) )  *  ( 255 - $B ) / 255 ));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图柔光

11. 强光 HardLight

Overlay(B,A) (A < 128) ? (2 * A * B / 255) : (255 - 2 * (255 - A) * (255 - B) / 255)

public static function layerHardLight($A, $B)
{
    return ($A < 128) ? (2 * $A * $B / 255) : (255 - 2 * (255 - $A) * (255 - $B) / 255);
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图柔光

12. 亮光 VividLight

B < 128 ? ColorBurn(A,(2 * B)) : ColorDodge(A,(2 * (B - 128)))

public static function layerVividLight($A, $B)
{
    return $B < 128 ? 
        (
            $B == 0 ? 2 * $B : max(0, (255 - ((255 - $A) << 8 ) / (2 * $B)))
        ) :
        (
            (2 * ($B - 128)) == 255 ? (2 * ($B - 128)) : min(255, (($A << 8 ) / (255 - (2 * ($B - 128)) )))
        ) ;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图亮光

13. 线性光 LinearLight

min(255, max(0, ($B + 2 * $A) - 1))

public static function layerLinearLight($A, $B)
{
    return min(255, max(
        0, (($B + 2 * $A) - 255)
    ));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图线性光

14. 点光 PinLight

max(0, max(2 * B - 255, min(B, 2*A)))

public static function layerPinLight($A, $B)
{
    return max(0, max(2 * $A - 255, min($B, 2 * $A)));
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图点光

15. 实色混合 HardMix

(VividLight(A,B) < 128) ? 0 : 255

public static function layerHardMix($A, $B)
{
    return ($B < 128 ? 
        (
            $B == 0 ? 2 * $B : max(0, (255 - ((255 - $A) << 8 ) / (2 * $B)))
        ) :
        (
            (2 * ($B - 128)) == 255 ? (2 * ($B - 128)) : min(255, (($A << 8 ) / (255 - (2 * ($B - 128)) )))
        ))
        < 128 ? 0 : 255 ;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图实色混合


16. 差值 Difference

abs(A - B)

取A与B差值的绝对值,会得到一个与AB有色彩反差的颜色。

public static function layerDifference($A, $B)
{
    return abs($A - $B);
} 
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图差值

17. 排除 Exclusion

A + B - 2 * A * B / 255

public static function layerExclusion($A, $B)
{
    return $A + $B - 2 * $A * $B / 255;
}
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C
  • A + B = C

原图滤色

参考


 Tags : php Photoshop 图形

Donate:Buy me a coffee  | 文章有帮助,可以请我喝杯咖啡