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
這樣一個簡單的運算,將新的顏色填充到原來的位置,就完成了一次“正片疊底”的混合。看看效果:
+ =
圖層混合(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');
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