基于OpenCV实现Photoshop的17种图层混合

时间:2022-12-27 08:56:17

一、图层混合模式是什么?

    所谓图层混合模式就是指一个层与其下图层的色彩叠加方式,在这之前我们所使用的是正常模式,除了正常以外,还有很多种混合模式,它们都可以产生迥异的合成效果。

​​基于OpenCV实现Photoshop的17种图层混合​​

二、PhotoShop的27种混合模式

从很久之前的版本开始,PhotoShop就保持了27种图层混合模式。

基于OpenCV实现Photoshop的17种图层混合

基于OpenCV实现Photoshop的17种图层混合

并且可以进一步分为:普通模式、变暗模式、变亮模式、饱和度模式、差集模式和颜色模式。

 

基于OpenCV实现Photoshop的17种图层混合

 

      1. 正常(Normal)模式
    在“正常”模式下,“混合色”的显示与不透明度的设置有关。当“不透明度”为100%,也就是说完全不透明时,“结果色”的像素将完全由所用的“混合色”代替;当“不透明度”小于100%时,混合色的像素会透过所用的颜色显示出来,显示的程度取决于不透明度的设置与“基色”的颜色。
  2. 溶解(Dissolve)模式
  在“溶解”模式中,根据任何像素位置的不透明度,“结果色”由“基色”或“混合色”的像素随机替换。

  3. 变暗(Darken)模式
  在“变暗”模式中,查看每个通道中的颜色信息,并选择“基色”或“混合色”中较暗的颜色作为“结果色”。比“混合色”亮的像素被替换,比“混合色”暗的像素保持不变。“变暗”模式将导致比背景颜色更淡的颜色从“结果色”中被去掉了。

  4. 正片叠底(Multiply)模式
  在“正片叠底”模式中,查看每个通道中的颜色信息,并将“基色”与“混合色”复合。“结果色”总是较暗的颜色。任何颜色与黑色复合产生黑色。任何颜色与白色复合保持不变。

  5. 颜色加深(Clolor Burn)模式
  在“颜色加深”模式中,查看每个通道中的颜色信息,并通过增加对比度使基色变暗以反映混合色,如果与白色混合的话将不会产生变化。

  6. 线性加深(Linear Burn)模式
  在“线性加深”模式中,查看每个通道中的颜色信息,并通过减小亮度使“基色”变暗以反映混合色。如果“混合色”与“基色”上的白色混合后将不会产生变化,

  7. 变亮(Lighten)模式
  在“变亮”模式中,查看每个通道中的颜色信息,并选择“基色”或“混合色”中较亮的颜色作为“结果色”。比“混合色”暗的像素被替换,比“混合色”亮的像素保持不变。 在这种与“变暗”模式相反的模式下,较淡的颜色区域在最终的“合成色”中占主要地位。

  8. 滤色(Screen)模式
  “滤色”模式与“正片叠底”模式正好相反,它将图像的“基色”颜色与“混合色”颜色结合起来产生比两种颜色都浅的第三种颜色。
  9. 颜色减淡(Clolor Dodge)模式
  在“颜色减淡”模式中,查看每个通道中的颜色信息,并通过减小对比度使基色变亮以反映混合色。与黑色混合则不发生变化。

  10. 线性减淡(Linear Dodge)模式
  在“线性减淡”模式中,查看每个通道中的颜色信息,并通过增加亮度使基色变亮以反映混合色。

  11. 叠加(Overlay)模式
  “叠加”模式把图像的“基色”颜色与“混合色”颜色相混合产生一种中间色。“基色”内颜色比“混合色”颜色暗的颜色使“混合色”颜色倍增,比“混合色”颜色亮的颜色将使“混合色”颜色被遮盖,而图像内的高亮部分和阴影部分保持不变,因此对黑色或白色像素着色时“叠加”模式不起作用。“叠加”模式以一种非艺术逻辑的方式把放置或应用到一个层上的颜色同背景色进行混合,然而,却能得到有趣的效果。背景图像中的纯黑色或纯白色区域无法在“叠加”模式下显示层上的“叠加”着色或图像区域。背景区域上落在黑色和白色之间的亮度值同“叠加”材料的颜色混合在一起,产生最终的合成颜色。为了使背景图像看上去好像是同设计或文本一起拍摄的。

  12. 柔光(Soft Light)模式  “柔光”模式会产生一种柔光照射的效果。如果“混合色”颜色比“基色颜色的像素更亮一些,那么“结果色”将更亮;如果“混合色”颜色比“基色”颜色的像素更暗一些,那么“结果色”颜色将更暗,使图像的亮度反差增大。例如,如果在背景图像上涂了50%黑色,这是一个从黑色到白色的梯度,那着色时梯度的较暗区域变得更暗,而较亮区域呈现出更亮的色调。 其实使颜色变亮或变暗,具体取决于“混合色”。此效果与发散的聚光灯照在图像上相似。 如果“混合色”比 50% 灰色亮,则图像变亮,就像被减淡了一样。如果“混合色”比 50% 灰色暗,则图像变暗,就象被加深了一样。用纯黑色或纯白色绘画会产生明显较暗或较亮的区域,但不会产生纯黑色或纯白色。
  13. 强光(Hard Light)模式
  “强光”模式将产生一种强光照射的效果。如果“混合色”颜色“基色”颜色的像素更亮一些,那么“结果色”颜色将更亮;如果“混合色”颜色比“基色”颜色的像素更暗一些,那么“结果色”将更暗。除了根据背景中的颜色而使背景色是多重的或屏蔽的之外,这种模式实质上同“柔光”模式是一样的。它的效果要比“柔光”模式更强烈一些,同“叠加”一样,这种模式也可以在背景对象的表面模拟图案或文本,例如,如果混合色比 50% 灰色亮,则图像变亮,就像过滤后的效果。这对于向图像中添加高光非常有用。如果混合色比 50%灰色暗,则图像变暗,就像复合后的效果。这对于向图像添加暗调非常有用。用纯黑色或纯白色绘画会产生纯黑色或纯白色。
  14. 亮光(Vivid Light)模式
  通过增加或减小对比度来加深或减淡颜色,具体取决于混合色。如果混合色(光源)比 50% 灰色亮,则通过减小对比度使图像变亮。如果混合色比 50% 灰色暗,则通过增加对比度使图像变暗,

  15. 线性光(Linear Light)模式
  通过减小或增加亮度来加深或减淡颜色,具体取决于混合色。如果混合色(光源)比 50% 灰色亮,则通过增加亮度使图像变亮。如果混合色比 50% 灰色暗,则通过减小亮度使图像变暗。

  16. 点光(Pin Light)模式
  “点光”模式其实就是替换颜色,其具体取决于“混合色”。如果“混合色”比 50% 灰色亮,则替换比“混合色”暗的像素,而不改变比“混合色”亮的像素。如果“混合色”比 50% 灰色暗,则替换比“混合色”亮的像素,而不改变比“混合色”暗的像素。这对于向图像添加特殊效果非常有用。

  17. 差值(Diference)模式
  在“差值”模式中,查看每个通道中的颜色信息,“差值”模式是将从图像中“基色”颜色的亮度值减去“混合色”颜色的亮度值,如果结果为负,则取正值,产生反相效果。由于黑色的亮度值为0,白色的亮度值为255,因此用黑色着色不会产生任何影响,用白色着色则产生被着色的原始像素颜色的反相。“差值”模式创建背景颜色的相反色彩,例如,在“差值”模式下,当把蓝色应用到绿色背景中时将产生一种青绿组合色。“差值”模式适用于模拟原始设计的底片,而且尤其可用来在其背景颜色从一个区域到另一区域发生变化的图像中生成突出效果。

  18. 排除(Exclusion)模式
  “排除”模式与“差值”模式相似,但是具有高对比度和低饱和度的特点。比用“差值”模式获得的颜色要柔和、更明亮一些。建议你在处理图像时,首先选择“差值”模式,若效果不够理想,可以选择“排除”模式来试试。其中与白色混合将反转“基色”值,而与黑色混合则不发生变化。其实无论是“差值”模式还是“排除”模式都能使人物或自然景色图像产生更真实或更吸引人的图像合成。

  19. 色相(Hue)模式
  “色相”模式只用“混合色”颜色的色相值进行着色,而使饱和度和亮度值保持不变。当“基色”颜色与“混合色”颜色的色相值不同时,才能使用描绘颜色进行着色,如图30所示。但是要注意的是“色相”模式不能用于灰度模式的图像。

  20. 饱和度(Saturation)模式
  “饱和度”模式的作用方式与“色相”模式相似,它只用“混合色”颜色的饱和度值进行着色,而使色相值和亮度值保持不变。当“基色”颜色与“混合色”颜色的饱和度值不同时,才能使用描绘颜色进行着色处理,如图31所示。在无饱和度的区域上(也就是灰色区域中)用“饱和度”模式是不会产生任何效果的。

  21. 颜色(Color)模式
  “颜色”模式能够使用“混合色”颜色的饱和度值和色相值同时进行着色,而使“基色”颜色的亮度值保持不变。“颜色”模式模式可以看成是“饱合度”模式和“色相”模式的综合效果。该模式能够使灰色图像的阴影或轮廓透过着色的颜色显示出来,产生某种色彩化的效果。这样可以保留图像中的灰阶,并且对于给单色图像上色和给彩色图像着色都会非常有用。

  22. 亮度(Luminosity)模式
  “亮度”模式能够使用“混合色”颜色的亮度值进行着色,而保持“基色”颜色的饱和度和色相数值不变。其实就是用“基色”中的“色相”和“饱和度”以及“混合色”的亮度创建“结果色”。

 

三、代码实现:

其实这里的混合算法大多不复杂,特别是在有算法文档 ​​http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html。​​支持的前提下,这里我们就文档中提到的16种再加上我之前有研究过的划分算法,整理出表格。

 

Blend mode

Formula

Darken

min(Target,Blend)         

Multiply

Target * Blend         

Color Burn

1 - (1-Target) / Blend         

Linear Burn

Target + Blend - 1         

Lighten

max(Target,Blend)         

Screen

1 - (1-Target) * (1-Blend)         

Color Dodge

Target / (1-Blend)         

Linear Dodge

Target + Blend         

Overlay

(Target > 0.5) * (1 - (1-2*(Target-0.5)) * (1-Blend)) +

(Target <= 0.5) * ((2*Target) * Blend)

Soft Light

(Blend > 0.5) * (1 - (1-Target) * (1-(Blend-0.5))) +

(Blend <= 0.5) * (Target * (Blend+0.5))

Hard Light

(Blend > 0.5) * (1 - (1-Target) * (1-2*(Blend-0.5))) +

(Blend <= 0.5) * (Target * (2*Blend))

Vivid Light

(Blend > 0.5) * (1 - (1-Target) / (2*(Blend-0.5))) +

(Blend <= 0.5) * (Target / (1-2*Blend))

Linear Light

(Blend > 0.5) * (Target + 2*(Blend-0.5)) +

(Blend <= 0.5) * (Target + 2*Blend - 1)

Pin Light

(Blend > 0.5) * (max(Target,2*(Blend-0.5))) +

(Blend <= 0.5) * (min(Target,2*Blend)))

Difference

| Target - Blend |         

Exclusion

0.5 - 2*(Target-0.5)*(Blend-0.5)

 

编写具有OpenCV风格的代码:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace std;
using namespace cv;

#define EPSILON 1e-6f

#define SAFE_DIV_MIN EPSILON
#define SAFE_DIV_MAX (1.0f / SAFE_DIV_MIN)

#define CLAMP(f,min,max) ((f)<(min)?(min):(f)>(max)?(max):(f))
//! layerModelBlending algorithm flags
enum
{
DARKEN = 1, //min(Target,Blend)
MULTIPY = 2, //Target * Blend
COLOR_BURN = 3, //1 - (1-Target) / Blend
LINEAR_BRUN = 4, //Target + Blend - 1
LIGHTEN = 5, //max(Target,Blend)
SCREEN = 6, //1 - (1-Target) * (1-Blend)
COLOR_DODGE = 7, //Target / (1-Blend)
LINEAR_DODGE = 8, //Target + Blend
OVERLAY = 9, //(Target > 0.5) * (1 - (1-2*(Target-0.5)) * (1-Blend)) +(Target <= 0.5) * ((2*Target) * Blend)
SOFT_LIGHT = 10, //(Blend > 0.5) * (1 - (1-Target) * (1-(Blend-0.5))) +(Blend <= 0.5) * (Target * (Blend+0.5))
HARD_LIGHT = 11, //(Blend > 0.5) * (1 - (1-Target) * (1-2*(Blend-0.5))) +(Blend <= 0.5) * (Target * (2*Blend))
VIVID_LIGHT = 12, //(Blend > 0.5) * (1 - (1-Target) / (2*(Blend-0.5))) +(Blend <= 0.5) * (Target / (1-2*Blend))
LINEAR_LIGHT = 13, //(Blend > 0.5) * (Target + 2*(Blend-0.5)) +(Blend <= 0.5) * (Target + 2*Blend - 1)
PIN_LIGHT = 14, //(Blend > 0.5) * (max(Target,2*(Blend-0.5))) +(Blend <= 0.5) * (min(Target,2*Blend)))
DIFFERENCE = 15, //| Target - Blend |
EXCLUSION = 16, //0.5 - 2*(Target-0.5)*(Blend-0.5)
DIVIDE = 17 //Target/Blend

};

/* local function prototypes */
static inline float safe_div(float a, float b);
/* returns a / b, clamped to [-SAFE_DIV_MAX, SAFE_DIV_MAX].
* if -SAFE_DIV_MIN <= a <= SAFE_DIV_MIN, returns 0.
*/
static inline float safe_div(float a, float b)
{
float result = 0.0f;

if (fabsf(a) > SAFE_DIV_MIN)
{
result = a / b;
result = CLAMP(result, -SAFE_DIV_MAX, SAFE_DIV_MAX);
}

return result;
}
//TODO target和blend应该是同样大小
CV_EXPORTS_W void layerModelBlending(Mat target, Mat blend, Mat dst, int flag);

CV_EXPORTS_W void layerModelBlending(Mat target, Mat blend, Mat dst, int flag)
{
for (int index_row = 0; index_row < target.rows; index_row++)
for (int index_col = 0; index_col < target.cols; index_col++)
for (int index_c = 0; index_c < 3; index_c++)
switch (flag)
{
case DARKEN:
dst.at<Vec3f>(index_row, index_col)[index_c] = min(
target.at<Vec3f>(index_row, index_col)[index_c],
blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case MULTIPY:
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] *
blend.at<Vec3f>(index_row, index_col)[index_c];
break;
case COLOR_BURN:
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
safe_div((1 - target.at<Vec3f>(index_row, index_col)[index_c]),
blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case LINEAR_BRUN:
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] +
blend.at<Vec3f>(index_row, index_col)[index_c] - 1;
break;
case LIGHTEN:
dst.at<Vec3f>(index_row, index_col)[index_c] = max(
target.at<Vec3f>(index_row, index_col)[index_c],
blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case SCREEN:
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
(1 - target.at<Vec3f>(index_row, index_col)[index_c]) *
(1 - blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case COLOR_DODGE:
dst.at<Vec3f>(index_row, index_col)[index_c] = safe_div
(target.at<Vec3f>(index_row, index_col)[index_c],
1 - blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case LINEAR_DODGE:
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] +
blend.at<Vec3f>(index_row, index_col)[index_c];
break;
case OVERLAY:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
(1 - 2 * (target.at<Vec3f>(index_row, index_col)[index_c] - 0.5)) *
(1 - blend.at<Vec3f>(index_row, index_col)[index_c]);
else
dst.at<Vec3f>(index_row, index_col)[index_c] = 2 *
target.at<Vec3f>(index_row, index_col)[index_c] *
blend.at<Vec3f>(index_row, index_col)[index_c];
break;
case SOFT_LIGHT:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
(1 - target.at<Vec3f>(index_row, index_col)[index_c]) *
(1 - (blend.at<Vec3f>(index_row, index_col)[index_c] - 0.5));
else
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] *
(blend.at<Vec3f>(index_row, index_col)[index_c] + 0.5);
break;
case HARD_LIGHT:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
(1 - target.at<Vec3f>(index_row, index_col)[index_c]) *
(1 - 2 * blend.at<Vec3f>(index_row, index_col)[index_c] - 0.5);
else
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] *
(2 * blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case VIVID_LIGHT:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] = 1 -
safe_div(1 - target.at<Vec3f>(index_row, index_col)[index_c],
(2 * (blend.at<Vec3f>(index_row, index_col)[index_c] - 0.5)));
else
dst.at<Vec3f>(index_row, index_col)[index_c] =
safe_div(target.at<Vec3f>(index_row, index_col)[index_c],
(1 - 2 * blend.at<Vec3f>(index_row, index_col)[index_c]));
break;
case LINEAR_LIGHT:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] +
(2 * (blend.at<Vec3f>(index_row, index_col)[index_c] - 0.5));
else
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] +
2 * blend.at<Vec3f>(index_row, index_col)[index_c] - 1;
break;
case PIN_LIGHT:
if (target.at<Vec3f>(index_row, index_col)[index_c] > 0.5f)
dst.at<Vec3f>(index_row, index_col)[index_c] =
max(target.at<Vec3f>(index_row, index_col)[index_c],
(float)(2 * (blend.at<Vec3f>(index_row, index_col)[index_c] - 0.5)));
else
dst.at<Vec3f>(index_row, index_col)[index_c] =
min(target.at<Vec3f>(index_row, index_col)[index_c],
2 * blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case DIFFERENCE:
dst.at<Vec3f>(index_row, index_col)[index_c] =
abs(target.at<Vec3f>(index_row, index_col)[index_c] -
blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
case EXCLUSION:
dst.at<Vec3f>(index_row, index_col)[index_c] =
target.at<Vec3f>(index_row, index_col)[index_c] +
blend.at<Vec3f>(index_row, index_col)[index_c] -
2 * target.at<Vec3f>(index_row, index_col)[index_c] * blend.at<Vec3f>(index_row, index_col)[index_c];
break;
case DIVIDE:
dst.at<Vec3f>(index_row, index_col)[index_c] =
safe_div(target.at<Vec3f>(index_row, index_col)[index_c],
blend.at<Vec3f>(index_row, index_col)[index_c]);
break;
}
}

int main() {
Mat target = cv::imread("e:/template/lena.jpg");
Mat blend = cv::imread("e:/template/opencv-logo.png");
Mat dst(target.size(), CV_32FC3, Scalar::all(0));
Mat dst2(target.size(), CV_8UC3, Scalar::all(0));
if (target.empty()) {
std::cout << "Unable to load target!\n";
}
if (blend.empty()) {
std::cout << "Unable to load blend!\n";
}
resize(blend, blend, target.size());
target.convertTo(target, CV_32F, 1.0 / 255);
blend.convertTo(blend, CV_32F, 1.0 / 255);

layerModelBlending(target, blend, dst, DARKEN);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/DARKEN_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, MULTIPY);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/MULTIPY_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, COLOR_BURN);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/COLOR_BURN.jpg", dst2);

layerModelBlending(target, blend, dst, LINEAR_BRUN);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/LINEAR_BRUN.jpg", dst2);

layerModelBlending(target, blend, dst, LIGHTEN);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/LIGHTEN.jpg", dst2);

layerModelBlending(target, blend, dst, SCREEN);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/SCREEN_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, COLOR_DODGE);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/COLOR_DODGE_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, LINEAR_DODGE);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/LINEAR_DODGE_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, OVERLAY);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/OVERLAY_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, SOFT_LIGHT);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/SOFT_LIGHT_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, HARD_LIGHT);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/HARD_LIGHT_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, VIVID_LIGHT);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/VIVID_LIGHT_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, LINEAR_LIGHT);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/LINEAR_LIGHT_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, PIN_LIGHT);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/PIN_LIGHT_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, DIFFERENCE);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/DIFFERENCE_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, EXCLUSION);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/EXCLUSION_RESULT.jpg", dst2);

layerModelBlending(target, blend, dst, DIVIDE);
dst.convertTo(dst2, CV_8UC3, 255);
imwrite("E:/sandbox/layerBlendingModles/DIVIDE_RESULT.jpg", dst2);
}

 

17种结果展示如下。


基于OpenCV实现Photoshop的17种图层混合


 

四、重要的参考:

在本例代码实现过程中,主要参考了两个方面的代码:

一个是GIMP的代码:来自gimpoperationlayermode-blend.h。

函数名称

核心代码

gimp_operation_layer_mode_blend_addition


for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] + layer[c];


gimp_operation_layer_mode_blend_burn


  for  (c =  0 ; c <  3 ; c++)

            comp[c] =  1.0f  - safe_div ( 1.0f  - in[c], layer[c]);


gimp_operation_layer_mode_blend_darken_only


  for  (c =  0 ; c <  3 ; c++)

            comp[c] = MIN (in[c], layer[c]);


gimp_operation_layer_mode_blend_difference


  for  (c =  0 ; c <  3 ; c++)

            comp[c] = fabsf (in[c] - layer[c]);


gimp_operation_layer_mode_blend_divide ( const  gfloat *in,


for  (c =  0 ; c <  3 ; c++)

            comp[c] = safe_div (in[c], layer[c]);


gimp_operation_layer_mode_blend_dodge


for  (c =  0 ; c <  3 ; c++)

            comp[c] = safe_div (in[c],  1.0f  - layer[c]);


gimp_operation_layer_mode_blend_exclusion


for  (c =  0 ; c <  3 ; c++)

            comp[c] =  0.5f  -  2.0f  * (in[c] -  0.5f ) * (layer[c] -  0.5f );


gimp_operation_layer_mode_blend_grain_extract


for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] - layer[c] +  0.5f ;


gimp_operation_layer_mode_blend_grain_merge


for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] + layer[c] -  0.5f ;


gimp_operation_layer_mode_blend_hard_mix


for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] + layer[c] <  1.0f  ?  0.0f  :  1.0f ;


gimp_operation_layer_mode_blend_hardlight


for  (c =  0 ; c <  3 ; c++)

            {

              gfloat val;


               if  (layer[c] >  0.5f )

                {

                  val = ( 1.0f  - in[c]) * ( 1.0f  - (layer[c] -  0.5f ) *  2.0f );

                  val = MIN ( 1.0f  - val,  1.0f );

                }

               else

                {

                  val = in[c] * (layer[c] *  2.0f );

                  val = MIN (val,  1.0f );

                }


              comp[c] = val;

            }


gimp_operation_layer_mode_blend_hsl_color




未整理




gimp_operation_layer_mode_blend_hsv_hue

gimp_operation_layer_mode_blend_hsv_saturation

gimp_operation_layer_mode_blend_hsv_value

gimp_operation_layer_mode_blend_lch_chroma

gimp_operation_layer_mode_blend_lch_color

gimp_operation_layer_mode_blend_lch_hue

gimp_operation_layer_mode_blend_lch_lightness


if  (in[ALPHA] !=  0.0f  && layer[ALPHA] !=  0.0f )

        {

          comp[ 0 ] = layer[ 0 ];

          comp[ 1 ] = in[ 1 ];

          comp[ 2 ] = in[ 2 ];

        }


gimp_operation_layer_mode_blend_lighten_only


  for  (c =  0 ; c <  3 ; c++)

            comp[c] = MAX (in[c], layer[c]);


gimp_operation_layer_mode_blend_linear_burn


for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] + layer[c] -  1.0f ;


gimp_operation_layer_mode_blend_linear_light


for  (c =  0 ; c <  3 ; c++)

            {

              gfloat val;


               if  (layer[c] <=  0.5f )

                val = in[c] +  2.0f  * layer[c] -  1.0f ;

               else

                val = in[c] +  2.0f  * (layer[c] -  0.5f );


              comp[c] = val;

            }


gimp_operation_layer_mode_blend_luma_darken_only


  if  (dest_luminance <= src_luminance)

            {

               for  (c =  0 ; c <  3 ; c++)

                comp[c] = in[c];

            }

           else

            {

               for  (c =  0 ; c <  3 ; c++)

                comp[c] = layer[c];

            }


gimp_operation_layer_mode_blend_luma_lighten_only


  if  (dest_luminance >= src_luminance)

            {

               for  (c =  0 ; c <  3 ; c++)

                comp[c] = in[c];

            }

           else

            {

               for  (c =  0 ; c <  3 ; c++)

                comp[c] = layer[c];

            }


gimp_operation_layer_mode_blend_luminance


  if  (layer[ALPHA] !=  0.0f  && in[ALPHA] !=  0.0f )

        {

          gfloat ratio = safe_div (layer_Y_p[ 0 ], in_Y_p[ 0 ]);

          gint   c;


           for  (c =  0 ; c <  3 ; c ++)

            comp[c] = in[c] * ratio;

        }


gimp_operation_layer_mode_blend_multiply


  for  (c =  0 ; c <  3 ; c++)

            comp[c] = in[c] * layer[c];


gimp_operation_layer_mode_blend_overlay


for  (c =  0 ; c <  3 ; c++)

            {

              gfloat val;


               if  (in[c] <  0.5f )

                val =  2.0f  * in[c] * layer[c];

               else

                val =  1.0f  -  2.0f  * ( 1.0f  - layer[c]) * ( 1.0f  - in[c]);


              comp[c] = val;

            }


gimp_operation_layer_mode_blend_pin_light

未整理

一个是网站的代码:来自​​javascript:void(0)​​

 

算法名

实现

溶解

Dissolve


// Dissolve

void Dissolve(Mat& src1, Mat& src2, Mat& dst, double alpha)

{

    dst=src1;

    Mat Rand_mat(src1.size(), CV_32FC1);

    cv::randu(Rand_mat, 0,1);

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            b=Rand_mat.at<float>(index_row, index_col);

            if(b<alpha)

            {

                for(int index_c=0; index_c<3; index_c++)

               {

                   a=src2.at<Vec3f>(index_row, index_col)[index_c];

                   dst.at<Vec3f>(index_row, index_col)[index_c]=a;

               }

            }

        }

    }

}

变暗

Darken

C = MIN(A,B)

// Darken

void Darken(Mat& src1, Mat& src2, Mat& dst)

{

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=min(

                         src1.at<Vec3f>(index_row, index_col)[index_c],

                         src2.at<Vec3f>(index_row, index_col)[index_c]);

        }

    }

正片叠底

Multiply

C=A*B/255

// Multiply 正片叠底

void Multiply(Mat& src1, Mat& src2, Mat& dst)

{

    for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=

                src1.at<Vec3f>(index_row, index_col)[index_c]*

                src2.at<Vec3f>(index_row, index_col)[index_c];

        }

    }

}

颜色加深

Color_Burn

C = A-((255-A)×(255-B))/ B


// Color_Burn 颜色加深

void Color_Burn(Mat& src1, Mat& src2, Mat& dst)

{

    for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=1-

                (1-src1.at<Vec3f>(index_row, index_col)[index_c])/

                src2.at<Vec3f>(index_row, index_col)[index_c];

        }

    }

}

线性加深

Linear_Burn

C=A+B-255

// 线性增强

void(Mat& src1, Mat& src2, Mat& dst)

{

    for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=max(

                src1.at<Vec3f>(index_row, index_col)[index_c]+

                src2.at<Vec3f>(index_row, index_col)[index_c]-1, (float)0.0);

        }

    }

变亮

C = MAX(A,B)

 

滤色

Screen


// Screen

void Screen(Mat& src1, Mat& src2, Mat& dst)

{

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=1-

                         (1-src1.at<Vec3f>(index_row, index_col)[index_c])*

                         (1-src2.at<Vec3f>(index_row, index_col)[index_c]);

        }

    }

}

颜色减淡

Color_Dodge


// Color_Dodge 颜色减淡
void  Color_Dodge(Mat &  src1, Mat &  src2, Mat &  dst)
{
     for ( int  index_row = 0 ; index_row < src1.rows; index_row ++ )
    {
         for ( int  index_col = 0 ; index_col < src1.cols; index_col ++ )
        {
             for ( int  index_c = 0 ; index_c < 3 ; index_c ++ )
                dst.at < Vec3f > (index_row, index_col)[index_c] =
                          src2.at < Vec3f > (index_row, index_col)[index_c] /
                         ( 1 - src1.at < Vec3f > (index_row, index_col)[index_c]);
        }
    }
}

线性减淡(添加)

C=A+B

// Lighten

void Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

                dst.at<Vec3f>(index_row, index_col)[index_c]=max(

                         src1.at<Vec3f>(index_row, index_col)[index_c],

                         src2.at<Vec3f>(index_row, index_col)[index_c]);

        }

    }

}

浅色

 

叠加

Add_Color

// Add color

void Add_Color(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(b>0.5)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a*b;

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-2*(1-a)*(1-b);

                }

            }

        }

    }

}

柔光

Soft_Lighten

// Soft Lighten

void Soft_Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(a<=0.5)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=(2*a-1)*(b-b*b)+b;

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=(2*a-1)*(sqrt(b)-b)+b;

                }

            }

        }

    }

}

强光

Strong_Lighten

void Strong_Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(a<=0.5)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a*b;

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-2*(1-a)*(1-b);

                }

            }

        }

    }

}

亮光

Vivid_Lighten

//Vivid Lighten

void Vivid_Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(a<=0.5)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-(1-b)/(2*a);

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=b/(2*(1-a));

                }

            }

        }

    }

}

线性光

Linear_Lighten

// Linear Lighten

void Linear_Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    dst=src2+2*src1-1;

}

点光

Pin_Lighten

// Pin lighten

void Pin_Lighten(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(b<=2*a-1)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a-1;

                }

                else if(b<=2*a)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=b;

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a;

                }

            }

        }

    }

}

实色混合

Hard_mix

// Hard mix

void Hard_mix(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                if(a<1-b)

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=0.0;

                }

                else

                {

                    dst.at<Vec3f>(index_row, index_col)[index_c]=1.0;

                }

            }

        }

    }

}

差值

Difference

// Difference

void Difference(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                dst.at<Vec3f>(index_row, index_col)[index_c]=abs(a-b);

            }

        }

    }

}

排除

Exclusion

// Exclusion

void Exclusion(Mat& src1, Mat& src2, Mat& dst)

{

    float a=0;

    float b=0;

     for(int index_row=0; index_row<src1.rows; index_row++)

    {

        for(int index_col=0; index_col<src1.cols; index_col++)

        {

            for(int index_c=0; index_c<3; index_c++)

            {

                a=src1.at<Vec3f>(index_row, index_col)[index_c];

                b=src2.at<Vec3f>(index_row, index_col)[index_c];

                dst.at<Vec3f>(index_row, index_col)[index_c]=a+b-2*a*b;

            }

        }

    }

}

减去

 

划分

divide

结果色 = (基色 / 混合色) * 255

    Mat src = imread("t1.jpeg");

    src.convertTo(src,CV_32FC3,1.0/255);

    Mat gauss;

    Mat dst = src.clone();

    cv::GaussianBlur(src,gauss,Size(101,101),0);

    dst  =  src / gauss ;