【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/LayerStyle.rar  

     

       如果想时刻关注本人的最新文章,也可关注公众号:

                                           

 

posted @ 2021-12-24 11:51  Imageshop  阅读(1448)  评论(0编辑  收藏  举报