【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

       如果想時刻關注本人的最新文章,也可關注公衆號:

                                           

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章