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

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

原图 → 滤色

参考

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值