Alpha顏色混合的魔法 下篇

                        Alpha顏色混合的魔法 下篇
                  
[email protected]   2007.10.14

摘要:本系列文章介紹了一種在圖像處理、2D遊戲、3D遊戲中經常使用的圖片混合模型:Alpha顏色混合;
它就像神奇的魔法一樣,在電腦屏幕上給我們展現出一個個絢麗多彩的世界!

全文 分爲: 上篇 各種Alpha顏色混合方式
           下篇 其他一些顏色混合方案、補充

tag:Alpha,Blend,透明,顏色混合,顏色混合公式

正文: 
  爲了便於討論,這裏只處理32bit的ARGB顏色;
  代碼使用C++,編譯器:VC2005
  (一些數據定義等代碼請參見《Alpha顏色混合的魔法 上篇》)

  (文章中的效果圖片都是用給出的例子代碼實際生成)

A: 利用Alpha顏色混合給圖片上蒙一層顏色
 
void PicBlendColor(const TPicRegion& picDst,const TARGB32&  SrcColor)
{
    unsigned long Alpha=SrcColor.a;
    for (long y=0;y<picDst.height;++y)
    {
        for (long x=0;x<picDst.width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            DstColor.b=(DstColor.b*(255-Alpha) + SrcColor.b*Alpha)/255;
            DstColor.g=(DstColor.g*(255-Alpha) + SrcColor.g*Alpha)/255;
            DstColor.r=(DstColor.r*(255-Alpha) + SrcColor.r*Alpha)/255;
            DstColor.a=(DstColor.a*(255-Alpha) + SrcColor.a*Alpha)/255;
        }
    }
}
 
  函數效果:
        
                      源圖片  
            

         
       混合顏色(B=0,G=0,R=255,A=127)後的圖片   


         
   在多處混合3個不同顏色(B=0,G=0,R=255,A=127)、
          (B=0,G=255,R=0,A=127)、(B=255,G=0,R=0,A=127)後的圖片   


B.用乘以一個顏色來調節圖片顏色通道
 
void PicBlend_Mul(const TPicRegion& picDst,const TARGB32& MulColor)
{
    for (long y=0;y<picDst.height;++y)
    {
        for (long x=0;x<picDst.width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            DstColor.b=(DstColor.b*MulColor.b)/255;
            DstColor.g=(DstColor.g*MulColor.g)/255;
            DstColor.r=(DstColor.r*MulColor.r)/255;
            DstColor.a=(DstColor.a*MulColor.a)/255;
        }
    }
}

   函數效果:
      
                    源圖片  

      
   乘以顏色(B=255,G=255,R=127,A=255)後的圖片
      (相當於將紅色通道的顏色值減半)  

C.Alpha通道的逆表示法的優勢
 
  前面帶Alpha通道的圖片混合時使用的混合公式:
      Dst=( Dst*(255-Src.Alpha) + Src*Src.Alpha ) / 255;
  爲了優化運算,我們可以將圖片格式改爲Alpha的逆表示法;
       將顏色中的顏色值R保存爲R*Alpha/255;
       將顏色中的顏色值G保存爲G*Alpha/255;
       將顏色中的顏色值B保存爲B*Alpha/255;
       將顏色中的Alpha通道值保存爲RAlpha(RAlpha=255-Alpha);
     那麼混合的時候可以簡化爲Dst=(Dst*Src.RAlpha)/255 + Src;

D.多個帶Alpha通道的圖片的繪製順序的問題
  圖片按透明屬性可以分爲3種:所有像素完全不透明、部分像素完全透
明另一部分完全不透明、帶Alpha通道的圖片(透明度爲0到255);做過3D
渲染的人應該都知道,在繪製紋理的時候,帶Alpha通道的紋理需要最後
繪製,並且需要排好序才能正確繪製,而用其它兩種就不需要排序;因爲
可以使用深度緩衝區就能保證按任何繪製順序都能正確的渲染圖像(請自
己想想怎麼實現:)  (補充: 實際上,就是排好序的帶Alpha通道圖片繪製
也不能保證就正確(排序只是是情況稍好些);最簡單的一種情況:3維空間
中的3張帶Alpha通道圖片相互壓住;這時排序繪製也得到錯誤的渲染;遊
戲中經常可以看到這種貼圖錯誤;(一種解決辦法是將相互壓住的部分分裂
成多個紋理))
 
  我們來看看3個像素的Alpha混合的情況:
  三個顏色爲 C1,C2,C3,先是C2混合到C1,得到C12,然後C3再混合到C12得到C123;
  由Alpha混合公式有:
    C12=(C1*(255-C2.A)+C2*C2.A)/255;
    C123=(C12*(255-C3.A)+C3*C3.A)/255;
        =( C1*(255-C2.A)(255-C3.A) + C2*C2.A*255 + C3.C3.A*255 - C2*C2.A*C3.A ) / (255*255)
  由該公式可以看看出,C1先混合C2和先混合C3將得到不同的混合結果(C2/C3的不對稱性);
  所以使用Alpha混合時要得到正確的混合結果必須保證圖片顏色值的混合順序;
 
  那麼我想完成這樣的一個功能是否就不能完成呢?我想“將需要依次繪製的兩張
帶Alpha通道圖片(兩次繪製),預先合成爲一張圖片,而保持繪製它的時候和以前
的兩次繪製得到的效果一致”
  這個要求實際上要求更改3個顏色的繪製順序,而得到相同的混合效果;這個功能是
能夠完成的,只是需要一個新的混合公式(前面已經證明Alpha混合公式做不到);
我們來推導一下這個有用的公式:
  三個顏色爲 C1,C2,C3,假設用新公式混合C2,C3得到C23,然後C23再用Alpha混合到
C1從而得到C123';
  所以有C123'=(C1*(255-C23.A)+C23*C23.A)/255;
  由於要求C123'等於前面的C123,所以有:
  (C1*(255-C23.A)+C23*C23.A)/255
   = ( C1*(255-C2.A)(255-C3.A) + C2*C2.A*255 + C3.C3.A*255 - C2*C2.A*C3.A ) / (255*255)
  即:C1*(255-C23.A)*255=C1*(255-C2.A)*(255-C3.A)            ----(1)
          C23*C23.A*255=C2*C2.A*255 + C3.C3.A*255 - C2*C2.A*C3.A ----(2)
  由(1)有:
C23.A=(C2.A+C3.A) - (C2.A*C3.A/255)    ----(3)
  由(2)有:
C23=(C2*C2.A*255 + C3.C3.A*255 - C2*C2.A*C3.A)/(C23.A*255) ----(4)
  (3),(4)就是我們需要的預處理混合公式;
  (提示: (4)中當C23.A=0的時候C23可以爲任意值)
  (提示: 更多顏色的預先混合公式用類似的推導也很容易得到;用浮點顏色有利於提高精度,
       用Alpha通道的逆表示法在這裏也有很多優勢)
 
 
  (不知道誰能想到不排序也能得到正確的Alpha混合方式的快速方法,或者添加
新的混合參數通道,使圖片不排序也能正確混合。)

(顏色之間還能進行很多其他類型的混合運算,下面做一些簡單示例)

E. 顏色的最大值、最小值混合

void PicBlend_Max(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=max(DstColor.b , SrcColor.b);
            DstColor.g=max(DstColor.g , SrcColor.g);
            DstColor.r=max(DstColor.r , SrcColor.r);
            DstColor.a=max(DstColor.a , SrcColor.a);
        }
    }
}
   函數效果:
         
                 源圖片0                             源圖片1
  

                            
                            (源圖片0-源圖片1)效果   

void PicBlend_Min(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=min(DstColor.b , SrcColor.b);
            DstColor.g=min(DstColor.g , SrcColor.g);
            DstColor.r=min(DstColor.r , SrcColor.r);
            DstColor.a=min(DstColor.a , SrcColor.a);
        }
    }
}
   函數效果:
         
                 源圖片0                             源圖片1     

                      
                                  混合後效果

F. 顏色的相減和顏色距離
    inline long border_color_down(long color)
    {
        if (color<0)
            return 0;
        else
            return color;
    }
void PicBlend_Sub(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=border_color_down(DstColor.b - SrcColor.b);
            DstColor.g=border_color_down(DstColor.g - SrcColor.g);
            DstColor.r=border_color_down(DstColor.r - SrcColor.r);
            DstColor.a=border_color_down(DstColor.a - SrcColor.a);
        }
    }
}
   函數效果:
     
          
                   源圖片0                             源圖片1       
  
          
            (源圖片0-源圖片1)效果                  (源圖片1-源圖片0)效果      

 (提示:比如用減去圖片的亮度來得到物體的假陰影(需要考慮方向或傾斜))

void PicBlend_Distance(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=abs(DstColor.b - SrcColor.b);
            DstColor.g=abs(DstColor.g - SrcColor.g);
            DstColor.r=abs(DstColor.r - SrcColor.r);
            DstColor.a=abs(DstColor.a - SrcColor.a);
        }
    }
}
   函數效果:
          
                   源圖片0                             源圖片1       

  
                        
                                    混合效果  

 (提示:比如用連續幀之間的顏色距離圖,以得到視野中運動物體的信息(背景會被減掉))

G. 顏色除法
    inline long color_div(long color,long colordiv)
    {
        if (colordiv==0)
            return 255;
        else
            return border_color_up(color*255/colordiv);
    }
void PicBlend_Div(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=color_div(DstColor.b , SrcColor.b);
            DstColor.g=color_div(DstColor.g , SrcColor.g);
            DstColor.r=color_div(DstColor.r , SrcColor.r);
            DstColor.a=color_div(DstColor.a , SrcColor.a);
        }
    }
}
   函數效果:
          
                   源圖片0                             源圖片1            
          
            
          (源圖片0 div 源圖片1)效果             (源圖片1 div 源圖片0)效果     

   
H. 把顏色亮度當作爲混合係數的混合效果
    inline unsigned long gray(const TARGB32& Color)
    {
        return (Color.r+Color.g+Color.b)/3;
    }

void PicBlend_MixByGray(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);
            unsigned long Alpha=(gray(SrcColor)+255-gray(DstColor))>>1;

            DstColor.b=(DstColor.b*(255-Alpha) + SrcColor.b*Alpha)/255;
            DstColor.g=(DstColor.g*(255-Alpha) + SrcColor.g*Alpha)/255;
            DstColor.r=(DstColor.r*(255-Alpha) + SrcColor.r*Alpha)/255;
            DstColor.a=(DstColor.a*(255-Alpha) + SrcColor.a*Alpha)/255;
        }
    }
}

   函數效果:
          
                   源圖片0                             源圖片1                
          
                        
                                  混合後效果


    inline unsigned char mix_color(unsigned long a,unsigned long b)
    {
        return a+b-((a*b) >>7 );
    }

void PicBlend_MixByColor(const TPicRegion& picDst,const TPicRegion& picSrc)
{
    long width =min(picDst.width ,picSrc.width );
    long height=min(picDst.height,picSrc.height);
    for (long y=0;y<height;++y)
    {
        for (long x=0;x<width;++x)
        {
            TARGB32& DstColor=Pixels(picDst,x,y);
            TARGB32  SrcColor=Pixels(picSrc,x,y);

            DstColor.b=mix_color(DstColor.b,SrcColor.b);
            DstColor.g=mix_color(DstColor.g,SrcColor.g);
            DstColor.r=mix_color(DstColor.r,SrcColor.r);
            DstColor.a=mix_color(DstColor.a,SrcColor.a);
        }
    }
}

   函數效果:
          
                   源圖片0                             源圖片1                
          
                       
                                   混合後效果

提示: 還有其他很多顏色之間的混合運算,你也可組合各種運算用到顏色的混合中,
也許你會得到一種新的有趣或有用的算法呢:) 


(顏色混合系列原來計劃寫3篇文章,後來將速度優化部分取消了;考慮主
要有:例子函數很多,只能選擇其中的幾個來優化;很多優化的效果和優
化側重點可能和實際要處理的數據有關;我的blog中已經有很多優化的具
體文章,本系列文章就不再重複這"體力活"了:)


 

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