转自:http://avnpc.com/pages/photoshop-layer-blending-algorithm
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
这样一个简单的运算,将新的颜色填充到原来的位置,就完成了一次“正片叠底”的混合。看看效果:
+ =
首先因为所有的图层混合的处理流程都是差不多的,唯一不同的是颜色通道的算法,因此我们将上面的图片处理抽象一下,将颜色通达算法做成一个可以替换的部分。
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代表下图层(基层)。
(B > A) ? A : B
取A与B中当前通道颜色值较小的一个,整体会变暗。
public static function layerDarken($A, $B)
{
return $B > $A ? $A : $B;
}
→
(A * B) / 255
这种方式混合会得到一个比两个图层都暗的颜色。
public static function layerMultiply($A, $B)
{
return $A * $B / 255;
}
→
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 < 255) ? 0 : (A + B - 255)
比变暗效果更加强烈,深色几乎被转成黑色,浅色也全部被加深。
public static function layerSubtract($A, $B)
{
return $A + $B < 255 ? 0 : $A + $B - 255;
}
→
(B > A) ? B : A
取A与B中当前通道颜色值较大的一个,整体效果就会偏亮。
public static function layerLighten($A, $B)
{
return $B > $A ? $B : $A;
}
→
255 - (((255 - A) * (255 - B)) >> 8))
与正片叠底正好相反,滤色会由两个颜色得到一个比较亮的颜色。
public static function layerScreen($A, $B)
{
return 255 - ( ((255 - $A) * (255 - $B)) >> 8);
}
→
(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)));
}
→
min(255, (A + B))
public static function layerAdd($A, $B)
{
return min(255, ($A + $B));
}
→
(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);
}
→
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 ));
}
→
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);
}
→
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)) )))
) ;
}
→
min(255, max(0, ($B + 2 * $A) - 1))
public static function layerLinearLight($A, $B)
{
return min(255, max(
0, (($B + 2 * $A) - 255)
));
}
→
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)));
}
→
(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 ;
}
→
abs(A - B)
取A与B差值的绝对值,会得到一个与AB有色彩反差的颜色。
public static function layerDifference($A, $B)
{
return abs($A - $B);
}
→
A + B - 2 * A * B / 255
public static function layerExclusion($A, $B)
{
return $A + $B - 2 * $A * $B / 255;
}
→