【PS算法理论探讨二】 Photoshop中图层样式之 投影样式 算法原理初探讨。
接下来几篇文章我们将稍微简单的探索下PS中多种图层混合模式的算法内部原理,因为毕竟没有这方面的官方资料,所以很多方面也只是本人自己的探索和实践,有可能和实际的情况有着较大的差异。
在PS的实践中,图层样式的存在使得一个简单的图形蜕变为一个丰富的样式成为可能,而在PS的各个版本中,图层样式的选项也越来越丰富, 功能也越来越强大。作为一个成功的图形和图形编辑软件,图层样式功能是否缺失也可以看成其是否具有强大生命力的一个典型标志,比如作为图像开源界的扛把子 GIMP 就没有这个功能。而平时我们能看到的商业软件也鲜有这个功能。

在我使用的CS6版本的PS中,提供了斜面和浮雕、描边、内阴影、内发光、光泽、颜色叠加、渐变叠加、图案叠加、外发光、投影等10中图层样式,在我后续的文章中将分别讲述除了 外发光和内发光 之外的其他8种样式的原理和实现。
本文将简单讲述投影样式的原理,投影样式的可控参数界面如下所示:

参数包含了混合模式、不透明度、角度、距离、扩展、大小‘等高线、消除锯齿、杂色等。我们先从大的方向开始讲起。
在PS中,如果我们打开一幅JPG图像(一般为RGB格式的),我们会发现PS为该图像所其的名字为背景层,而且层右侧有一个锁的符号,如下所示:

如果此时我们双击这个层,出现的是新建图层的界面,而不是图层样式的旋向,如上图所示。
但是,如果我们打开的是一副带透明通道的32位PNG图像,此时系统默认就是用图层0为该图像命名,而且后侧没有锁的符号。

此时双击图层符号,则打开了图层样式对话框。
通过这个现象可以做个简单的猜测,图层样式需要Alpha通道,而实际的研究也表明,大部分的图层样式(除颜色叠加、渐变叠加、图案叠加,我局的应该把他们从样式中开除)都是对Alpha通道的数据进行一定处理后,再配合某种颜色和原图进行一定程度的融合。
完美甚至可以沿用另外一种流行的说法,图层样式其内在实际上是按照一定的规则虚拟了1个或几个图层,然后通过不同的图层位置(位于上部或下部)、混合样式、不透明度等和原图进行混合。这个也是所有的样式里的混合模式、不透明度的概念源头所在。
再次回到这个投影样式吧。 在PS里随意的弄下这个效果,可以直观的感觉到这个样式的作用是根据所选参数在当前层下部虚拟一个阴影层。
那么我的实现思路核心如下:
第一步: 按照指定的角度将原图的Alpha信息偏移一定的角度,偏移后无效的区域Alpha设置为0。

原始图像 原始图像的Alpha通道信息 按照指定的角度偏离后的Alpha信息(角度30, 距离20)
简单的代码如下所示:
float SinV = -sinf(Angle / 180.0 * 3.1415926f);
float CosV = cosf(Angle / 180.0 * 3.1415926f);
int Left = (int)(Distance * CosV + 0.499999f);
int Top = (int)(Distance * SinV + 0.499999f);
// 计算Alpha通道的偏移信息
for (int Y = 0; Y < Height; Y++)
{
int NewY = Y + Top;
if ((NewY < 0) || (NewY >= Height))
{
memset(ShiftA + Y * Width, 0, Width);
}
else
{
unsigned char *LinePD = ShiftA + Y * Width;
for (int X = 0; X < Width; X++)
{
int NewX = X + Left;
if ((NewX < 0) || (NewX >= Width))
{
LinePD[X] = 0;
}
else
{
int Index = NewY * Stride + NewX * 4 + 3;
LinePD[X] = Src[Index];
}
}
}
}
界面中的角度和距离共同决定了这个Alpha通道偏离的程度。
对面后面的大小和扩展参数,我们结合网络中的一些参考资料,通过本人的实践,基本上可以确定是使用的如下算法。
首先我们把大小设置为10,然后把扩展设置为100%,对于上面的图,可达到如下效果:

大小为10,扩展为100%时的结果 大小为0时的结果
可以看到,当大小为10,扩展100%时,阴影部分变的更为粗大,通过测试,我们发现这个实际上应该是对前述偏移后的Alpha选区进行了一定程度的圆形最大值算法,我们是是圆形,我们可以比较下同样半径的圆形和矩形最大值的结果区别:

半径为10的矩形最大值 半径为10的圆形最大值
很明显的可以看到,矩形最值不能保留原来光滑的圆角,而圆形可以。
因此,我们推测扩展就是对选区进行圆形的最大值算法,而最大值的半径和大小以及扩展的数据有关,根据PS界面扩展后面的% 百分比可以认定他为大小的 百分比。
而大小参数,明显可以看到,随着大小的变大,阴影越来越模糊,因此,可以猜测这个为对Alpha进行模糊。不过我测试所,似乎并不是高斯模糊,不晓得实际为何种模糊。
// 第二步对这个Alpha进行下堵窒,算法上就是圆形的最大值算法
int ChokeSize = (Size * Choke + 49) / 100;
if (ChokeSize != 0) // 堵窒
{
Status = IM_MaxFilter_Round_Gray(ShiftA, ShiftA, Width, Height, Width, ChokeSize);
if (Status != IM_STATUS_OK) goto FreeMemory;
}
// 第三步对Alpha进行羽化了,高斯模糊(但是PS的不晓得属于那种模糊)
if ((Size != 0) && (Size != ChokeSize))
{
Status = IM_GaussBlur(ShiftA, ShiftA, Width, Height, Width, Size - ChokeSize);
if (Status != IM_STATUS_OK) goto FreeMemory;
}
那么下面还有一个关键的东西,就是那个等高线,这个东西网络上把他说的好神奇,各路大神都有发表感言。我看啊,都是假神,那个东西其实就是如他表面所表现出来的东西,就是一个曲线调整,而且和PS本身的曲线也是一个意思,只不过他调整的不是图像里的RGB,而是这里的Alpha,通过动态调整这个Alpha获得不同的结果。
// 第四步对选区进行等高线算法,实际上就是一个查表
for (int Y = 0; Y < Height * Width; Y++)
{
ShiftA[Y] = Table[ShiftA[Y]];
}
那么最后一步,就是根据不透明度、混合模式以及用户提供的背景色来创建一个新的图层,这个图层位于当前层下方,进行图层混合了。如果是一个单独的图层,由于这个图层下面没有其他图层,混合样式在这里其实是起不到作用的(除了那个另类的溶解),这个时候一个简单的混合代码如下所示:
for (int Y = 0; Y < Height; Y++)
{
unsigned char *LinePD = Dest + Y * Stride;
unsigned char *LinePS = Src + Y * Stride;
unsigned char *LinePA = ShiftA + Y * Width;
for (int X = 0; X < Width; X++)
{
int B1 = BackColor_B, G1 = BackColor_G, R1 = BackColor_R, A1 = LinePA[X];
int B2 = LinePS[0], G2 = LinePS[1], R2 = LinePS[2], A2 = LinePS[3];
int NewA1 = A1 * Opacity;
int BlendAlpha = IM_Div255(A2 * NewA1);
int Alpha = A2 * 255 + NewA1 - BlendAlpha;
if (Alpha != 0)
{
LinePD[0] = (B1 * NewA1 + B2 * A2 * 255 - BlendAlpha * B1) / Alpha;
LinePD[1] = (G1 * NewA1 + G2 * A2 * 255 - BlendAlpha * G1) / Alpha;
LinePD[2] = (R1 * NewA1 + R2 * A2 * 255 - BlendAlpha * R1) / Alpha;
}
else
{
LinePD[0] = LinePS[0];
LinePD[1] = LinePS[1];
LinePD[2] = LinePS[2];
}
LinePD[3] = IM_Div255(Alpha);
LinePS += 4;
LinePD += 4;
}
}
注意这里的混合的Alpha需要改变。
至于界面里的消除锯齿应该是针对曲线的,这个就是在曲线插值时加上抗锯齿功能,那个什么杂色之类的无所谓,就是在Alpha信息里加上一些随机噪音。没啥好难的。
当然,经过一些其他测试,发现PS里的投影还有一些更为复杂的逻辑,和本文的讲述不一致,但是本文的效果也在一定程度上能局部复原结果,对于一些普通的应用是足以完成任务了
提供一个链接工大家测试:https://files.cnblogs.com/files/Imageshop/DropShadow.rar
如果想时刻关注本人的最新文章,也可关注公众号:

【PS算法理论探讨二】 Photoshop中图层样式之 投影样式 算法原理初探讨。的更多相关文章
- 装载:对CSS中的Position、Float属性的一些深入探讨
对CSS中的Position.Float属性的一些深入探讨 对CSS中的Position.Float属性的一些深入探讨 对于Position.Float我们在平时使用上可以说是使用频率非常高的两个 ...
- 【转】shell命令中>/dev/null 2>&1的实现原理
异步执行 exec("/alidata/server/php/bin/php /nas/wxdoctor/index.php App/Common/WordsPic/user_id/&quo ...
- (二十)WebGIS中图层树功能的设计和实现
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 在GIS的桌面工具中,比如arcgis desktop或者S ...
- (十二) WebGIS中矢量图层的设计
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在前几章中我们已经了解了什么是矢量查询.屏幕坐标与地理坐标之 ...
- ps中图层混合模式、多图层叠加、不透明度、填充、图层样式详解
图像领域中,通过进行一下想法的时候,都要通过用ps看下是不是合理,而ps中图层是必用的一个功能,下面详解一下图层有关的叠加原理. 基本顺序是图层从下往上继续, 先计算图层的填充,再计算样式.最后计算不 ...
- SLAM中的优化理论(二)- 非线性最小二乘
本篇博客为系列博客第二篇,主要介绍非线性最小二乘相关内容,线性最小二乘介绍请参见SLAM中的优化理论(一)-- 线性最小二乘.本篇博客期望通过下降法和信任区域法引出高斯牛顿和LM两种常用的非线性优化方 ...
- [Java 泥水匠] Java Components 之二:算法篇之项目实践中的位运算符(有你不懂的哦)
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节. 交流QQ群:[编程之美 365234583] ...
- 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点.感觉也是生物智能的一个体现,和遗传算法.神经网络有异曲同工之妙.只不过当时没有实际需求学习,所以没去研究.最近有一个这样 ...
- [转载] 散列表(Hash Table)从理论到实用(中)
转载自:白话算法(6) 散列表(Hash Table)从理论到实用(中) 不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好 ...
随机推荐
- Atcoder M-SOLUTIONS Programming Contest C - Best-of-(2n-1)(无穷级数求和+组合恒等式)
Atcoder 题面传送门 & 洛谷题面传送门 无穷级数求和的简单题,稍微写写吧,正好也算帮我回忆下组合数这一块的内容. 首先我们不妨假设 A 赢,B 赢的情况就直接镜像一下即可.我们枚举 B ...
- C++ and OO Num. Comp. Sci. Eng. - Part 3.
2. Expressions and Statements 声明是将一个种类型的变量引入程序的语句. 作用域 作用域又一对花括号限定,在所有花括号之外的为全局作用域. 在作用域内声明的变量为局部变量. ...
- SUNTANS 及 FVCOM 对流扩散方程求解简介[TBC]
最近接到一个任务,就是解决FVCOM中对流扩散计算不守衡问题.导师认为是其求解时候水平和垂向计算分开求解所导致的,目前我也没搞清到底有什么问题,反正就是让把SUNTANS的对流扩散计算挪到FVCOM中 ...
- datamash 命令行下的快速计算工具
github地址:https://github.com/agordon/datamash
- python—模拟生成双色球号
双色球规则:"双色球"每注投注号码由6个红色球号码和1个蓝色球号码组成.红色球号码从1--33中不重复选择:蓝色球号码从1--16中选择. # -*- coding:UTF-8 - ...
- MySQL深层理解,执行流程
MySQL是一个关系型数据库,关联的数据保存在不同的表中,增加了数据操作的灵活性. 执行流程 MySQL是一个单进程服务,每一个请求用线程来响应, 流程: 1,客户请求,服务器开辟一个线程响应用户. ...
- A Child's History of England.15
And indeed it did. For, the great army landing from the great fleet, near Exeter, went forward, layi ...
- day31 协程
day31 协程 一.死锁与递归锁 所谓死锁:是指两个或者两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产 ...
- 容器之分类与各种测试(三)——slist的用法
slist和forward_list的不同之处在于其所在的库 使用slist需要包含 #include<ext\list> 而使用forward_list则需要包含 #include< ...
- C++之数组转换
题目如下: 这道题经过好久的思考也没找到能一次性输入两组数的方法,只能一次性处理一组数,所以就把代码放上来,欢迎交流留言一起讨论可以放两组数的方法~(QQ 841587906) 1 #include ...