1. 程式人生 > >Alpha顏色混合的魔法 上篇

Alpha顏色混合的魔法 上篇

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

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

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

正文: 
  為了便於討論,這裡只處理32bit的ARGB顏色;
  程式碼使用C++,編譯器:VC2005

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

A: 一些顏色和圖片的資料定義:

#define asm __asm

typedef unsigned 
char TUInt8; // [0..255]struct TARGB32      //32 bit color{
    TUInt8  b,g,r,a;          
//a is alpha
};

struct TPicRegion  //一塊顏色資料區的描述,便於引數傳遞
{
    TARGB32
*    pdata;         //顏色資料首地址
long        byte_width;    //一行資料的物理寬度(位元組寬度);
                
//abs(byte_width)有可能大於等於width*sizeof(TARGB32);
long        width;         
//畫素寬度
long        height;        //畫素高度};

//那麼訪問一個點的函式可以寫為:
inline TARGB32& Pixels(const TPicRegion& pic,constlong x,constlong y)
{
    
return ( (TARGB32*)((TUInt8*)pic.pdata+pic.byte_width*
y) )[x];
}

B: 混合兩幅圖片
  這裡簡單的按照50%的比例混合兩幅圖片;演算法也很簡單,就是將顏色分量直接
相加,然後取平均值;函式如下:

void PicBlend_half(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
=(DstColor.b + SrcColor.b)/2
;
            DstColor.g
=(DstColor.g + SrcColor.g)/2
;
            DstColor.r
=(DstColor.r + SrcColor.r)/2
;
            DstColor.a
=(DstColor.a + SrcColor.a)/2
;
        }
    }
}

函式效果:

           

             混合前源圖片0                             混合前源圖片1

                        

                          PicBlend_half混合後的結果圖片

C.按比例混合兩幅圖片
  我們來增強PicBlend_half的混合能力,允許指定兩幅圖片的混合比例(引數Alpha);
  Alpha屬於[0..255],當Alpha=127的時候與PicBlend_half等價(小的誤差不算:)
  Alpha顏色混合公式:Dst=( Src0*(255-Alpha) + Src1*Alpha ) / 255;

void PicBlend_Alpha(const TPicRegion& picDst,const TPicRegion& picSrc,TUInt8 Alpha)
{
    
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
=(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
;
        }
    }
}

 
提示: 利用兩幅圖片然後不斷的調整Alpha混合引數就可以得到漂亮的
動畫效果(過渡/切換螢幕等);

函式效果:
           

           
          Alpha=64時混合後的結果圖片             Alpha=192時混合後的結果圖片


D.帶關鍵色的圖片合成
  圖片中用一種特殊的顏色來代表圖片透明的部分,這個關鍵色一般會選擇圖片中實際沒
有用到的顏色;程式在顯示圖片的時候跳過這些特殊畫素,從而形成透明效果;
  GIF的透明、很多遊戲中的透明貼圖都應用了這種原理;
  比如一幅帶有透明關鍵色的圖片:
              

  該圖片中的關鍵色顏色為純紅色,R=255;G=0;B=0;

  函式實現:

void PicBlend_KeyColor(const TPicRegion& picDst,const TPicRegion& picSrc,const TARGB32& KeyColor)
{
    
long width =
min(picDst.width ,picSrc.width );
    
long height=
min(picDst.height,picSrc.height);
    unsigned 
long KeyColorValue=(*(unsigned long*)&KeyColor) &0x00FFFFFF
;
    
for (long y=0;y<height;++
y)
    {
        
for (long x=0;x<width;++
x)
        {
            TARGB32  SrcColor
=
Pixels(picSrc,x,y);
            
if ( ( (*(unsigned long*)&SrcColor) &0x00FFFFFF )!=
KeyColorValue )
                Pixels(picDst,x,y)
=
SrcColor;
        }
    }
}

函式效果:

          
            PicBlend_KeyColor函式效果圖

提示:有時為了方便也可以將一個顏色範圍內的顏色都作為透明關鍵色;

一個支援換裝人物系統簡單示例:
                  
              身體           頭           髮型 

                        

          按 底、身體、頭、髮型 的順序混合後的效果圖


E.帶Alpha通道的圖片的混合
  PicBlend_KeyColor的實現方式有一些缺點,比如美工做圖片的時候需要"摳邊"
(將透明區域和不透明區域分離),增加了工作量;合成的圖片在“精靈”的邊界區域
有鋸齒感(如果有縮放的話,鋸齒感會更強);我們需要一種更加自由的定義方式,
每個顏色增加一個專門的通道Alpha通道來描述該畫素的透明資訊;
  Alpha屬於[0..255],
  帶Alpha通道的顏色混合公式:Dst=( Dst*(255-Src.Alpha) + Src*Src.Alpha ) / 255;
  (提示:  當Alpha==0時,  公式化簡為: Dst=Dst; //Src完全透明
          當Alpha==255時,公式化簡為: Dst=Src; //Src完全不透明  )

void PicBlend(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=
SrcColor.a;

            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(32bit ARGB顏色)  其中透明通道展示

                    
     PicBlend混合後的結果圖片(注意精靈的輪廓線,與背景完美的融合在了一起)


F.顏色混合方案:加
  有時候,直接把兩幅圖片顏色值相加也能得到很不錯的效果;
比如在實現一些光照特效、太陽引起的鏡頭光暈等效果的時候就很不錯;
(顏色相加時可能會超出255的值域,需要把結果飽和到255)

    inline long border_color_up(long color)
    {
        
if (color>=255
)
            
return255
;
        
else
return color;
    }
void PicBlend_Add(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_up(DstColor.b +
 SrcColor.b);
            DstColor.g
=border_color_up(DstColor.g +
 SrcColor.g);
            DstColor.r
=border_color_up(DstColor.r +
 SrcColor.r);
            DstColor.a
=border_color_up(DstColor.a +
 SrcColor.a);
        }
    }
}

函式效果:

                
                混合前源圖片0                  混合前源圖片1

                  
                      PicBlend_Add混合後的結果圖