首页  编辑  

图像透明算法

Tags: /超级猛料/Picture.图形图像编程/图形处理算法/   Date Created:

如何通过算法设置出象photoshop里一幅图象在另一幅图象上如何透明地显示,

并且可以设置透明度,如50%的透明度。并不是单幅图象的透明性。

1 引言

在游戏、图像处理等程序中,经常会看到图像的淡入、淡出、两个或多个图像重叠显示、三维物体的光影效果等,这些特殊效果使用到的技术就是图像的混合技术,也叫图像的alpha混合。alpha混合是像素融合算法之一,就是按照"alpha"混合向量的值来混合源像素和目标像素,具体的说就是将源像素和目标像素加权相加的组合(混合)两个图像(源图像和目标图像)。源像素的权值通常被称为alpha值,目标像素的权值是(1,1是最大的颜色值。alpha混合是按颜色通道定义,而不是按位定义的。

返回目录

2 alpha混合的实现

我们定义alpha为0时表示源像素完全透明,为1时表示完全不透明。假定我们用的是24位色或32位色位图,alpha混合的概念运算式为:

Dst.Red    = Src.Red   * alpha + (1-alpha) * Dst.Red  ;

Dst.Green  = Src.Green * alpha + (1-alpha) * Dst.Green;

Dst.Blue   = Src.Blue  * alpha + (1-alpha) * Dst.Blue ;

Dst.Alpha  = Src.Alpha * alpha + (1-alpha) * Dst.Alpha;

根据这个公式我们可以编制alpha混合图像的函数:

// 24位位图的alpha混合. 要求源位图和目标位图大小相等,且像素格式都为24位

procedure AlphaBlend24(BitmapDest, BitmapSrc:TBitmap; Alpha:Byte);

var

 pDest, pSrc : PByteArray;

 X,Y :Integer;

 Beta:Integer;

begin

 Beta := 255 - Alpha;

 for Y := 0 to BitmapDest.Height - 1 do

 begin

   pDest := BitmapDest.ScanLine[Y];

   pSrc  := BitmapSrc.ScanLine[Y];

   for X := 0 to BitmapDest.Width - 1 do

   begin

     pDest[0]:= (pSrc[0] * Alpha + pDest[0] * Beta) div 255;

     pDest[1]:= (pSrc[1] * Alpha + pDest[1] * Beta) div 255;

     pDest[2]:= (pSrc[2] * Alpha + pDest[2] * Beta) div 255;

     Inc(Longword(pDest) ,3); // 指向下一个像素

     Inc(Longword(pSrc ) ,3);

   end;

 end;

end;

注意: 代码中没有使用浮点数,只是将0到1的alpha值用0到255代替,这是Windows程序的通常做法。以上的代码只实现了24位位图的alpha混合,其它像素类型的alpha混合代码是相似的,所以这里不再给出。

返回目录

3 Windows API的AlphaBlend使用

由于图像的alpha混合技术使用非常广泛,所有图像处理引擎都支持这项操作,如OpenGL和DirectX等。但是这些图像处理引擎都是用来处理复杂图像,而且使用比较复杂,如果我们的程序不是用来处理复杂图像而只需要简单的alpha混合功能,那么使用上述图形引擎就显得没有必要了。实际上在Windows 98和Windows 2000中已经提供了实现alpha混合的API,就是AlphaBlend函数,这个函数是Windows 98和Windows 2000中GDI新加的功能,AlphaBlend函数声明如下:

BOOL AlphaBlend(HDC hdcDest, int nXOriginDest, int nYOriginDest,

int nWidthDest,  int nHeightDest,HDC hdcSrc, int nXOriginSrc, int nYOriginSrc,

int nWidthSrc, int nHeightSrc,BLENDFUNCTION blendFunction);

该API函数中使用了一个结构

typedef struct _BLENDFUNCTION

{

   BYTE   BlendOp;

   BYTE   BlendFlags;

   BYTE   SourceConstantAlpha;

   BYTE   AlphaFormat;

} BLENDFUNCTION;

AlphaBlend函数的原型看起来和StretchBlt函数非常像,头5个参数指明了目标设备上下文和以逻辑坐标定义的矩形。同样,随后的5个参数指明了源设备上下文和以逻辑坐标定义的源表面的矩形。对源设备上下文也有限制,即源矩形必须在源设备上下文内,源设备上下文不能有剪切和旋转。注意,用到源设备上下文使得AlphaBlend函数不能直接使用DIB(即设备无关位图)。最后一个参数blendFunction是一个BLENDFUNCTION结构。BLENDFUNCTION结构控制源和目标位图的混合方式,它的BlendOp字段指明了源混合操作,但只支持AC_SRC_OVER,即根据源alpha值把源图像叠加到目标图像上。OpenGL的alpha混合还支持其他的方式,如常量颜色源。下一个字段BlendFalgs必须是0,也是为以后的应用保留的。最后一个字段AlphaFormat有两个选择:0表示常量alpha值,AC_SRC_ALPHA表示每个像素有各自的alpha通道。如果AlphaFormat字段为0,源位图中的所有像素使用同样的常量alpha值,即SourceConstantAlpha字段中的值,该值实际上是0和255,而不是0和1。这里0表示完全透明,255表示完全不透明。目标像素以255-SourceConstantAlpha值作为alpha值。如果AlphaFormat字段的值是AC_SRC_ALPHA,源设备表面的每个像素必须有各自的alpha通道。即,必须是32-bpp的物理设备上下文,或是选中了32-bpp DDB和DIB段的内存设备上下文。这些情况下,每个源像素有4个8位通道:红、绿、蓝和alpha。每个像素的alpha通道和SourceConstantAlpha字段一起用于把源和目标混合起来。实际用于计算的运算式如下:

Tmp.Red   = Src.Red   * SourceConstantAlpha / 255;

Tmp.Green = Src.Green * SourceConstantAlpha / 255;

Tmp.Blue  = Src.Blue  * SourceConstantAlpha / 255;

Tmp.Alpha = Src.Alpha * SourceConstantAlpha / 255;

Beta      = 255 - Tmp.alpha;

Dst.Red   = Tmp.Red   + Round((Beta * Dst.Red  )/255);

Dst.Green = Tmp.Green + Round((Beta * Dst.Green)/255);

Dst.Blue  = Tmp.Blue  + Round((Beta * Dst.Blue )/255);

Dst.Alpha = Tmp.Alpha + Round((Beta * Dst.Alpha)/255);

返回目录

4 程序实例

下面演示AlphaBlend函数的使用,在Delphi 7中新建一工程,在Form1上放置三个Image控件和一个TrackBar控件,再加上一个Label控件和两个RadioButton控件。Image1中调入一个24位位图,Iamge2中调入另外一个24位位图,TrackBar1的Min属性设为0,Max属性设为255. 程序的主要代码如下:

procedure TForm1.FormCreate(Sender: TObject);

begin

 Image3.Width := Image1.Width;

 Image3.Height := Image1.Height;

 TrackBar1Change(Self);

end;

procedure TForm1.TrackBar1Change(Sender: TObject);

begin

 Label1.Caption := IntToStr(TrackBar1.Position);

 if RadioButton1.Checked then

   AlphaDraw1

 else

   AlphaDraw2;

end;

// 使用AlphaBlend API进行alpha混合

procedure TForm1.AlphaDraw1;

var

 Bmp: TBitmap;

 BF: BLENDFUNCTION;

begin

 Bmp := TBitmap.Create;

 Bmp.Assign(Image1.Picture.Bitmap);

 BF.BlendOp := AC_SRC_OVER;

 BF.BlendFlags := 0;

 BF.SourceConstantAlpha := TrackBar1.Position;

 BF.AlphaFormat := 0;

 Windows.AlphaBlend(Bmp.Canvas.Handle, 0,0,Bmp.Width, Bmp.Height,

                   Image2.Picture.Bitmap.Canvas.Handle, 0,0,Bmp.Width, Bmp.Height,BF);

 Image3.Canvas.Draw(0,0,Bmp);

 Bmp.Free;

end;

// 使用自编的AlphaBlend24函数进行alpha混合

procedure TForm1.AlphaDraw2;

var

 Bmp: TBitmap;

begin

 Bmp := TBitmap.Create;

 Bmp.Assign(Image1.Picture.Bitmap);

 AlphaBlend24(Bmp, Image2.Picture.Bitmap, TrackBar1.Position);

 Image3.Canvas.Draw(0,0,Bmp);

 Bmp.Free;

end;

运行时从左到右拖动TrackBar,最下面图像就由第一幅图逐渐变为第二幅图。本程序还演示了自编alpha混合函数的使用,效果与AlphaBlend相同。本示例程序在Delphi 7 和 Windows 2000中文版中运行通过。

返回目录

5 参考文献

[1]《Windows 图形编程》(美)Feng Yuan 著 机械工业出版社,2002

[2]《Windows 程序设计》(第5版)(美)Charles Petzold 著 北京大学出版社,1999

BCB(:)) (2001-1-1 15:15:00)  得0分

Tbitmap有关的属性:

 1.TansparentColor: 透明色;

 2.PixelFormat:指明每一象点颜色所占的二进位数;

               pf16bit:  点2字节;

               pf24bit:  3字节;

               pf32bit:  4字节;

 3.ScanLine[行号];每一行象素缓冲的首址,即数组

           的首址,每一象点占连续字节;

有了上面的3个属性,就可以写出合成2幅图象的算法;

 算法:

       for 第0行 到 最后一行

           for 第0列 TO 最后一列

             begin

                   1.取第一幅一个象点c1;

                   2.取第二幅一个象点c2;

                   3.如果c1是透明色,c3:=c2;

                     如果c2是透明色,c3:=c1;

                   4.送c3

             end

nononono(null,null) (2001-1-1 20:01:00)  得0分

我有个效率不高(速度很慢),但效果绝对好的算法:

for 第0行 到 最后一行

   for 第0列 TO 最后一列

     begin

           1.取第一幅一个像点c1的R色 c1R;

           2.取第二幅一个像点c2的R色 c2R;

           3.新像点的R色 cR := (c1R*K+c2R*(1-K));

           4.取第一幅一个像点c1的G色 c1G;

           5.取第二幅一个像点c2的G色 c2G;

           6.新像点的G色 cG := (c1G*K+c2G*(1-K));

           7.取第一幅一个像点c1的B色 c1B;

           8.取第二幅一个像点c2的B色 c2B;

           9.新像点的B色 cB := (c1B*K+c2B*(1-K));

         10.新像点的RGB色 cRGB := RGB(cR,cG,cB);

     end

 

其中 K 是2个图合并时,第一幅图的权重。改变这个 K 值可以得到一个渐变的效果。

jz_x(北风) (2001-1-2 15:04:00)  得0分

可将下面代码转换为DELPHI代码就可:

         CDC              memDC;

   CBitmap        &cBitmap=m_bmpDraw;

   CBitmap*        pOldMemBmp = NULL;

   COLORREF        col,colMask;

   CRect        cRect;

   int        x, y;

   CRgn        wndRgn, rgnTemp;

   GetWindowRect(&cRect);

   CPoint ptOrg=cRect.TopLeft();

   BITMAP bmInfo;

   cBitmap.GetObject(sizeof(bmInfo),&bmInfo);

   CRect rcNewWnd=CRect(ptOrg,CSize(bmInfo.bmWidth,bmInfo.bmHeight));

   memDC.CreateCompatibleDC(pDC);

   pOldMemBmp = memDC.SelectObject(&cBitmap);

   colMask=memDC.GetPixel(0,0);

   wndRgn.CreateRectRgn(0, 0, rcNewWnd.Width(), rcNewWnd.Height());

   for(x=0; x<=rcNewWnd.Width(); x++)

   {

       for(y=0; y<=rcNewWnd.Height(); y++)

       {

           col = memDC.GetPixel(x, y);

           if(col == colMask)

           {

               rgnTemp.CreateRectRgn(x, y, x+1, y+1);

               wndRgn.CombineRgn(&wndRgn, &rgnTemp, RGN_XOR);

               rgnTemp.DeleteObject();    

           }

       }

   }

   if (pOldMemBmp) memDC.SelectObject(pOldMemBmp);

   SetWindowRgn((HRGN)wndRgn, TRUE);

   MoveWindow(rcNewWnd);  

lingweitao(涛生) (2001-1-2 16:48:00)  得0分

呵呵!看来这些人没明白你的意思呀!我曾经研究过这个问题,不过我是用VC做的,不知道用DELPHI怎么做,不过我可以把算法告诉你:取得两幅为图在每一点的像素值,分离出R,G,B的值,生成位图相应像素的R,G,B由下面公式决定:R=kR1+(1-k)R2  G=kG1+(1-k)G2  B=kB1+(1-k)B2  k为透明度,范围是0%-100% ,R1,R2,G1,G2,B1,B2为混合的两幅位图分离出来的R,G,B值,R,G,B为混合后的相应像素的R,G,B值。  

APIer(APIer) (2001-1-2 17:01:00)  得0分

使用DDraw吧,这是一个简单的Alpha特效啊。

nononono的方法是可行的,但是使用了太多的乘法,还要用到浮点,在16位位图游戏编程中,大家通常使用这样的方法来进行Alpha混合:

1先分色并移位

2进行混色:(混色深度为32,足够了)

 DestColor= (RscColor-DestColor)*Alpha_Depth〉〉5+DestColor;(R.G.B分别计算)

3最后合色

欢迎到我的业余游戏制作主页看看,上面有相关的文章:http://calfsoft.51.net

我的Email:APIer@cmmail.com  

nononono(null,null) (2001-1-7 12:39:00)  得0分

akuan,这样试试:

分离RGB3色用"位与"、"移位"运算的方法;

透明的比例按 32、16分级。

如:如果是按32级,

A图的权重 K = n/32,则B图的权重 = (32-n)/32,

可以得到这样的算法:

     cR := (c1R*n+c2R*(32-n));

     再对cR右移4位。

     

这样的算法要快很多。