混合 颜色

Alpha颜色混合的魔法 上篇 “法术篇”
                      
HouSisong@GMail.com   2007.08.14

摘要:本系列文章介绍了一种在图像处理、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,const long x,const long  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
)
            
return 255
;
        
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混合后的结果图


摘要:本系列文章介绍了一种在图像处理、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中已经有很多优化的具
体文章,本系列文章就不再重复这"体力活"了:)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值