日志未经声明,均为AlloVince原创。版权采用『 知识共享署名-非商业性使用 2.5 许可协议』进行许可。Share on facebookShare on google_plusone_shareShare on sinaweiboShare on douban
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');
下面就实际用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
→ 
RGBA alpha blend
Alpha图形叠加算法Matlab+Verilog实现
Alpha通道是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域,其中黑表示全透明,白表示不透明,灰表示半透明。
半透明混合算法目前在常用到的算法是AlphaBlend。其算法如下:假设一幅图象是A,另一幅透明的图象是B,那么透过B去看A,看上去的图象C就是B和A的混合图象,设B图象的透明度为alpha(取值为0-1,1为完全透明,0为完全不透明)。Alpha每个通道色彩混合公式如下:
R(x)、G(x)、B(x)分别指颜色x的RGB分量原色值。从上面的公式可以知道,Alpha其实是一个决定混合透明度的数值。改变这个 alpha 值可以得到一个渐变的效果。分离RGB色用"位与"、"移位"运算的方法。
透明的比例按2的N次幂来分级,这样可以快速运算。如果是按256级Alpha = 1/256,B图的权重 = (256-n)/256,则A图的权重= n/256,由式 6-1可以得到红色通道256级半透明算法:

1.2. Alpha算法实现
1.2.1. Matlab代码
% -----------------------------------------------------------------------
% CONFIDENTIAL IN CONFIDENCE
% This confidential and proprietary software may be only used as authorized
% by a licensing agreement from CrazyBingo (Thereturnofbingo).
% In the event of publication, the following notice is applicable:
% Copyright (C) 2011-201x CrazyBingo Corporation
% The entire notice above must be reproduced on all authorized copies.
% Author : CrazyBingo
% Technology blogs : http://blog.chinaaet.com/crazybingo % http://www.cnblogs.com/crazybingo % Eamil Address : thereturnofbingo@gmail.com
% Filename : RGB_Alpha.m
% Data : 2013-04-22
% Version : 1.0
% Description : Alpha Algorithm of 2 RGB Image.
% Modification History :
% Data By Version Change Description
%=======================================================================
% 13/04/22 CrazyBingo 1.0 Original
% -----------------------------------------------------------------------
clear all; %清除Matlab缓存数据
% -----------------------------------------------------------------------
% Read PC iamge to Matlab
IMG1 = imread('E:\Matlab_Information\Matlab_Project\BMP\Fish2.bmp'); % 读取RGB文件,X光的鱼图
IMG2 = imread('E:\Matlab_Information\Matlab_Project\BMP\Water.bmp'); % 读取RGB文件,绿叶湖泊
h = size(IMG1,1); % 读取图像高度
w = size(IMG1,2); % 读取图像宽度
figure(1);
subplot(1,2,1);
imshow(uint8(IMG1));
subplot(1,2,2);
imshow(uint8(IMG2));
% -----------------------------------------------------------------------
% IMG1 add tO IMG2 with alpha algorthm
ALPHA_PARAM = 0.6;
IMG1 = double(IMG1);
IMG2 = double(IMG2);
IMG3 = zeros(h,2,3); for i = 1 : h for j = 1 : w
IMG3(i,j,1) = IMG1(i,j,1)*ALPHA_PARAM + IMG2(i,j,1)*(1-ALPHA_PARAM);
IMG3(i,j,2) = IMG1(i,j,2)*ALPHA_PARAM + IMG2(i,j,2)*(1-ALPHA_PARAM);
IMG3(i,j,3) = IMG1(i,j,3)*ALPHA_PARAM + IMG2(i,j,3)*(1-ALPHA_PARAM);
end
end
% -------------------------------------------------------------------------
figure(2);
imshow(uint8(IMG3));
title('Alpha Image with IMG1 & IMG2');
imwrite(IMG3,'E:\Matlab_Information\Matlab_Project\RGB_Alpha_Process\RGB_Alpha.bmp'); %保存重建后的BMP
1.2.2. 效果图

1.2.3. 半透明算法的FPGA实现
在Verilog中,通过组合逻辑,运用内嵌乘法器,以及移位运算,可以快速实现对两幅图像的透明度的实时控制。设计中设定了256级半透明级数,通过按键来控制alpha_data的参数,从而实现调节两幅图像之间的透明度。Verilog算法如下所示:
1
2
3
4
5
6
7
|
wire
[15:0] red_data = ((
8'd255
-alpha_data)* lcd_data_reg[15:11]+ alpha_data * lcd_data_in[15:11])>> 8;
wire
[15:0] gre_data = ((
8'd255
-alpha_data)*lcd_data_reg[10:5]+ alpha_data * lcd_data_in[10:5])>> 8;
wire
[15:0] blu_data = ((
8'd255
-alpha_data)*lcd_data_reg[4:0]+ alpha_data * lcd_data_in[4:0])>> 8;
assign
lcd_data = disp_area?{red_data[4:0],gre_data[5:0], blu_data[4:0]} : lcd_data_reg;
|
为测试半透明算法,将分形图与彩条叠加,通过外部按键的调节,验证了此半透明算法的正确性。VGA半透明图形叠加如下图所示,此时即可见分形图,又可见8条彩条,两者相互融合。

图 6-1 半透明算法验证图
介绍几种常见的将两张图像混合在一起形成一张新的图像的算法,
首先看一下下面算法演示中要使用的两张图像:


为了得到更好的混合效果,我选择了两张一样大小的图片。
方法一:
通过简单对于像素点的像素相乘得到输出像素值,代码演示如下:
- private int modeOne(int v1, int v2) {
- <span style="white-space:pre"> </span>return (v1 * v2) / 255;
- }
方法一的效果如下:

方法二:
通过计算两个像素之和再减去方法一的输出值,代码如下:
- private int modeTwo(int v1, int v2) {
- return v1 + v2 - v1 * v2 / 255;
- }
方法二的效果如下:

方法三:
通过像素值128这个特殊的中值来求取输出值,代码如下:
- private int modeThree(int v1, int v2) {
- return (v2 < 128) ? (2 * v1 * v2 / 255):(255 - 2 * (255 - v1) * (255 - v2) / 255);
- }
方法三的效果如下:

方法四:
与方法三不同,中值127.5被用在计算等式中,代码如下:
- private int modeFour(double v1, double v2) {
- if ( v1 > 127.5 ){
- return (int)(v2 + (255.0 - v2) * ((v1 - 127.5) / 127.5) * (0.5 - Math.abs(v2-127.5)/255.0));
- }else{
- return (int)(v2 - v2 * ((127.5 - v1) / 127.5) * (0.5 - Math.abs(v2-127.5)/255.0));
- }
- }
方法四的效果如下:

方法五:
中值计算考虑,是方法一的升级版本,使得混合更加精细,代码如下:
- private int modeFive(double v1, double v2) {
- if ( v1 > 127.5 ){
- return (int)(v2 + (255.0 - v2) * ((v1 - 127.5) / 127.5));
- }else{
- return (int)(v2 * v1 / 127.5);
- }
- }
方法五的效果如下:

滤镜源代码如下:
- package com.gloomyfish.filter.study;
-
- import java.awt.image.BufferedImage;
-
-
-
-
-
-
-
- public class ImageBlendFilter extends AbstractBufferedImageOp {
-
- public final static int MULTIPLY_PIXEL = 1;
- public final static int SCREEN_PIXEL = 2;
- public final static int OVERLAY_PIXEL = 3;
- public final static int SOFTLIGHT_PIXEL = 4;
- public final static int HARDLIGHT_PIXEL = 5;
-
- private int mode;
- private BufferedImage secondImage;
- public ImageBlendFilter() {
- mode = 1;
- }
-
- public void setBlendMode(int mode) {
- this.mode = mode;
- }
-
- public void setSecondImage(BufferedImage image) {
- this.secondImage = image;
- }
-
-
- @Override
- public BufferedImage filter(BufferedImage src, BufferedImage dest) {
- checkImages(src);
- int width = src.getWidth();
- int height = src.getHeight();
-
- if ( dest == null )
- dest = createCompatibleDestImage( src, null );
-
- int[] input1 = new int[width*height];
- int[] input2 = new int[secondImage.getWidth() * secondImage.getHeight()];
- int[] outPixels = new int[width*height];
- getRGB( src, 0, 0, width, height, input1);
- getRGB( secondImage, 0, 0, secondImage.getWidth(), secondImage.getHeight(), input2);
- int index = 0;
- int ta1 = 0, tr1 = 0, tg1 = 0, tb1 = 0;
- for(int row=0; row<height; row++) {
- for(int col=0; col<width; col++) {
- index = row * width + col;
- ta1 = (input1[index] >> 24) & 0xff;
- tr1 = (input1[index] >> 16) & 0xff;
- tg1 = (input1[index] >> 8) & 0xff;
- tb1 = input1[index] & 0xff;
- int[] rgb = getBlendData(tr1, tg1, tb1, input2, row, col);
- outPixels[index] = (ta1 << 24) | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
-
- }
- }
- setRGB( dest, 0, 0, width, height, outPixels );
- return dest;
- }
-
- private int[] getBlendData(int tr1, int tg1, int tb1, int[] input,int row, int col) {
- int width = secondImage.getWidth();
- int height = secondImage.getHeight();
- if(col >= width || row >= height) {
- return new int[]{tr1, tg1, tb1};
- }
- int index = row * width + col;
-
- int tr = (input[index] >> 16) & 0xff;
- int tg = (input[index] >> 8) & 0xff;
- int tb = input[index] & 0xff;
- int[] rgb = new int[3];
- if(mode == 1) {
- rgb[0] = modeOne(tr1, tr);
- rgb[1] = modeOne(tg1, tg);
- rgb[2] = modeOne(tb1, tb);
- }
- else if(mode == 2) {
- rgb[0] = modeTwo(tr1, tr);
- rgb[1] = modeTwo(tg1, tg);
- rgb[2] = modeTwo(tb1, tb);
- }
- else if(mode == 3) {
- rgb[0] = modeThree(tr1, tr);
- rgb[1] = modeThree(tg1, tg);
- rgb[2] = modeThree(tb1, tb);
- }
- else if(mode == 4) {
- rgb[0] = modeFour(tr1, tr);
- rgb[1] = modeFour(tg1, tg);
- rgb[2] = modeFour(tb1, tb);
- }
- else if(mode == 5) {
- rgb[0] = modeFive(tr1, tr);
- rgb[1] = modeFive(tg1, tg);
- rgb[2] = modeFive(tb1, tb);
- }
- return rgb;
- }
-
- private int modeOne(int v1, int v2) {
- return (v1 * v2) / 255;
- }
-
- private int modeTwo(int v1, int v2) {
- return v1 + v2 - v1 * v2 / 255;
- }
-
- private int modeThree(int v1, int v2) {
- return (v2 < 128) ? (2 * v1 * v2 / 255):(255 - 2 * (255 - v1) * (255 - v2) / 255);
- }
-
- private int modeFour(double v1, double v2) {
- if ( v1 > 127.5 ){
- return (int)(v2 + (255.0 - v2) * ((v1 - 127.5) / 127.5) * (0.5 - Math.abs(v2-127.5)/255.0));
- }else{
- return (int)(v2 - v2 * ((127.5 - v1) / 127.5) * (0.5 - Math.abs(v2-127.5)/255.0));
- }
- }
-
- private int modeFive(double v1, double v2) {
- if ( v1 > 127.5 ){
- return (int)(v2 + (255.0 - v2) * ((v1 - 127.5) / 127.5));
- }else{
- return (int)(v2 * v1 / 127.5);
- }
- }
-
- private void checkImages(BufferedImage src) {
- int width = src.getWidth();
- int height = src.getHeight();
- if(secondImage == null || secondImage.getWidth() > width || secondImage.getHeight() > height) {
- throw new IllegalArgumentException("the width, height of the input image must be great than blend image");
- }
- }
-
- }
文章转载出处
http://avnpc.com/pages/photoshop-layer-blending-algorithm
http://blog.chinaaet.com/detail/33131
http://blog.csdn.net/jia20003/article/details/8236800