AlphaBlend到底能不能进行逐像素的Alpha混合

mly_skywater 2009-10-21 01:19:06
各位大侠,上午好!
本人最近在研究alpha混合问题时遇到了一个难题难以解决,向各位求助。
我想要达到的效果:假设有两幅图像A和B,两幅图都有各自的图案(假设A中的图案是圆形的)以及背景色。我想将两幅图进行alpha混合,呈现半透明的效果,我只想将A图像中的圆形图案alpha混合到B图像中的一小块区域中去,而A图像的背景不混合,也就是说我只取了A图像的图案部分混合到B图像中。B图像的其他区域仍是原来的样子不变。

本人尝试过的解决办法:
1、使用windows GDI的函数AlphaBlend
AlphaBlend函数的前几个参数不再描述,它的最后一个参数是个结构体,如下:
BLENDFUNCTION{ BYTE BlendOp;
BYTE BlendFlags;
BYTE SourceConstantAlpha;
BYTE AlphaFormat;}
BlendOp必须为AC_SRC_OVER,BlendFlags必须为0;后两个参数的设置需要分两种情况。
情况一: AlphaFormat 设为 0 ,此时进行的alpha混合只需要设定一个alpha值,也就是第3个参数SourceConstantAlpha,这个参数为0时表示完全透明,255时表示完全不透明,0~255之间的值则为半透明效果。
但是这样做的一个问题就是设置的源图像和目标图像的混合区域都必须是矩形区域,也就是说A图像的背景也会和B图像进行Alpha混合,这是我要避免的,因为这样,A的背景色会影响B中图像的显示效果。并且混合之后B图像中能看到A图像的矩形轮廓,这个不能满足要求。
也就是说这种整个矩形区域共用一个alpha值混合的方法是行不通的。

情况二:AlphaFormat 设置为 AC_SRC_ALPHA, SourceConstantAlpha设置为255,msdn中说这时候,在混合时将采用per-pixel alpha(逐像素alpha)混合的方法进行混合,也就是说两幅图像的每个像素会根据源图像各个像素中alpha通道的值
取和目标进行混合(我的图像都是32bit的,具有alpha通道)。
这样,我对A图像各个像素的alpha值进行了初始设置,对A图像中的图案区域(假设为圆形图案)的像素点,我将它的alpha值设为127(半透明),实现代码如下
lpSurface[ x + y * lPitch ] &= 0x00FFFFFF;
lpSurface[ x + y * lPitch ] |= 0x7F000000;
而对图案之外的背景(圆外的点)中的各个像素点的alpha值设置为0。
根据msdn中描述的在这种逐像素混合的计算公式:
Src.Red = Src.Red * SourceConstantAlpha / 255.0;
Src.Green = Src.Green * SourceConstantAlpha / 255.0;
Src.Blue = Src.Blue * SourceConstantAlpha / 255.0;
Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0;
Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha
调用AlphaBlend将A图像混合到B上的过程,我设想是这样,A是源,B是目标,根据源的alpha值计算(已设定好)
对于A图像中的图案区域(圆内),因为图案区域(圆内)的所有像素点的alpha值为 127,根据公式,混合到目标区域上时,颜色应该能才生半透明的效果。 而对于背景色区域(圆外),因为我将背景色的所有像素点的alpha值设为0了,这样根据公式,背景色相当于是完全透明的了,不影响目标的颜色。
最终效果按照这个步骤理论上应该是可以达到只混合A图像中的图案,而不混合A的背景色。

但是实际的效果却和我期望的不一样,根据我上面的代码,我在 ATI 显卡(型号没看出来,不会很老)上的试验,描画出来的图像很怪异,覆盖上上面的A图像中的图案区域(圆内)出现不明的条文并遮挡住了B图像,这些条文上颜色单一,并不是A的图案,但是在没有条文的地方B都显现出来了,没条文的地方又似乎A是完全透明了,很奇怪。

2、另外尝试了BitBlt 等通过 与或 或者mask遮挡等方法
这些都不能实现真正的透明效果,这些方法只是简单的对两幅图像的颜色合并了,要么就是去掉某幅图像背景色,然后将它覆盖在另外一副图像上,所以都不会有透明效果。想要透明效果必须操作alpha。


对msdn中AlphaBlend函数及BLENDFUNCTION结构体的的描述看了很多很多遍,感觉使用方法没出错,到底问题出在哪,各位csdn的大侠们帮忙看看,出点主意,谢谢了。
函数的参数SourceConstantAlpha为0~255间的整数,Src.Alpha也应该是这么个范围,为什么公式中可以有
Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue,这其中的怎么可以有 1- Src.Alpha了,不明白了。

如果我的描述不清,你也可以参考之前别人写的一篇帖子。

他的描述如下:
楼主DelphiBoy2003(努力提升功力中)2005-09-05 15:35:21 在 Delphi / 语言基础/算法/系统设计 提问

我现在有三副图片,其中一副为背景图,另外两幅图是一个需要透明的图片和一个掩码图片,这两幅图片进行透明贴图操作,并且需要把得到的透明贴图使用 AlphaBlend函数贴到背景图片上,我举例吧,我的一副图片是一个圆形,并且另外一副图片是这个圆形的掩码图片,背景图是一个风景图片,我现在想的是把这个圆形的贴图使用Alpha运算贴到背景图上,我写的代码无论如何只能实现没有透明前的圆形(也就是包含了边界的矩形区域的图片)Alpha的贴到背景图片上,并不能只贴一个圆形到背景图片上!

3 楼Idle_(阿呆)回复于 2005-09-06 12:43:16 得分 5

去看一下 Graphics.TransparentStretchBlt的实现你就知道怎么做了Top
4 楼Linux2001(闭关开发中)回复于 2005-09-09 16:02:41 得分 40

我发一个Demo给你,把邮箱告诉我

参考连接
http://topic.csdn.net/t/20050905/15/4250543.html

这是原贴,可是这里面的4楼我找不到联系方式了,有人能通过昵称之类的找到他的空间吗/
...全文
1374 33 打赏 收藏 转发到动态 举报
写回复
用AI写文章
33 条回复
切换为时间正序
请发表友善的回复…
发表回复
xqhrs232 2012-10-29
  • 打赏
  • 举报
回复
觉得应该是支持!!!因为他支持24位带ALPHA通道的BMP图片的混合,而BMP图片的每个像素的ALPHA值是不一样的。所以应该支持像素级的混合!!!
idcsdn 2009-11-02
  • 打赏
  • 举报
回复
不错
mly_skywater 2009-10-28
  • 打赏
  • 举报
回复
[Quote=引用 27 楼 laviewpbt 的回复:]
A图像:
B图像

混合后图像


是这个意思不,是的,完全可以用alphablend.

[/Quote]
谢谢你回来!
恩,如你图形所描绘的,就是要那种效果,只是3副图像的背景可能不一样,而我只需要其中一幅图的背景色,再想问问你,你这个如果要实现的话用alphablend应该怎么做了,怎么样做到不让背景色混合了,比方说你话的这3个图中的椭圆的背景色为什么没有和方块混合了。我现在唯一想到的方法就是判断出椭圆的区域,在这个区域内的才进行alpha混合,否则只用方框的图像而不混合。实在想不出来用 alphablend 可以实现。

mly_skywater 2009-10-28
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 delphiguy 的回复:]
你要做的其实是先做一个临时的alphablend,再用它做带keycolor的抠像效果,写回原图像。

在你的例子中:
“我想要达到的效果:假设有两幅图像A和B,两幅图都有各自的图案(假设A中的图案是圆形的)以及背景色。我想将两幅图进行alpha混合,呈现半透明的效果,我只想将A图像中的圆形图案alpha混合到B图像中的一小块区域中去,而A图像的背景不混合,”

步骤:
A和B做alphablend,生成图像C
生成A的两个mask图像,一个AndMask(背景色为全0,非背景色为全1),一个XorMask(背景色为全1,非背景色为全0)
把AndMask BitBlt到C,使用SRCAND 操作
把XorMask BitBlt到B,使用SRCAND 操作
把C BitBlt到B,使用SRCINVERT 操作(SRCPAINT也可以)
B就是最终效果了。

这样做适用于任意形状的图案,不只是圆形。

[/Quote]
你的思想我明白了,我想了下,按照你的做法应该能达到牛人“laviewpbt(人要靠自己(33184777))”画的那些图的效果。就是不知道速度会怎么样,我明天试试,你的想法真好,真的很感谢你!
lambochan 2009-10-27
  • 打赏
  • 举报
回复
colorSrc = GetPixel( hdcSrc, x, y ); // #1
colorDes = GetPixel( hdcDest, x, y ); // #2
m_pDraw->AlphaMixed( colorSrc, colorDes, res, 0.5 );
SetPixel( hdcDest, x, y, res ); // #3


这#1/#2/#3本来就非常慢....3句加起来就非一般的慢..
zhouzhipen 2009-10-27
  • 打赏
  • 举报
回复
movd mm3,ialpha;//这是alpha值
你就不能把这个加一个判断,当在不同的地方就改变这个值.
我上面这片代码怎么也比你写的C++版的快.(上面的函数在测试时都能在2ms内将1024*1024的两个图形混合)

laviewpbt 2009-10-27
  • 打赏
  • 举报
回复
A图像:
B图像

混合后图像


是这个意思不,是的,完全可以用alphablend.
  • 打赏
  • 举报
回复
你要做的其实是先做一个临时的alphablend,再用它做带keycolor的抠像效果,写回原图像。

在你的例子中:
“我想要达到的效果:假设有两幅图像A和B,两幅图都有各自的图案(假设A中的图案是圆形的)以及背景色。我想将两幅图进行alpha混合,呈现半透明的效果,我只想将A图像中的圆形图案alpha混合到B图像中的一小块区域中去,而A图像的背景不混合,”

步骤:
A和B做alphablend,生成图像C
生成A的两个mask图像,一个AndMask(背景色为全0,非背景色为全1),一个XorMask(背景色为全1,非背景色为全0)
把AndMask BitBlt到C,使用SRCAND 操作
把XorMask BitBlt到B,使用SRCAND 操作
把C BitBlt到B,使用SRCINVERT 操作(SRCPAINT也可以)
B就是最终效果了。

这样做适用于任意形状的图案,不只是圆形。
mly_skywater 2009-10-26
  • 打赏
  • 举报
回复
[Quote=引用 23 楼 zhouzhipen 的回复:]
记得给分
[/Quote]
你给的这个满足不了我的要求啊
在我程序中我用逐像素点的alpha混合的代码如下:
主要是圆周外和内的原图像上的点的alpha值不一样,而你这个代码所有像素点的alpha值是一样的。
我写的代码如下:
for( int y = m_iAlphaDesY; y < m_iAlphaDesY + m_iAlphaDesHeight; y++ ){
for( int x = m_iAlphaDesX; x < m_iAlphaDesX + m_iAlphaDesWidth; x++ ){
dDist = DIST( x, y, centerX, centerY ); //求2点之间的距离
if( dDist - r < 0.0000001 ){
// r为圆的半径,只有在圆内的点才进行混合
colorSrc = GetPixel( hdcSrc, x, y );
colorDes = GetPixel( hdcDest, x, y );
m_pDraw->AlphaMixed( colorSrc, colorDes, res, 0.5 );
SetPixel( hdcDest, x, y, res );
}
}
}
HRESULT AlphaMixed ( COLORREF color1, COLORREF color2, COLORREF &res, double alphaValue )
{
HRESULT hrt = S_OK;
unsigned short color1_r = 0, color1_g = 0, color1_b = 0, color2_r = 0, color2_g = 0, color2_b = 0;

if( alphaValue < 0 || alphaValue > 1.0 ){
return( E_FAIL );
}

color1_r = SPLITCOLOR_R( color1 );
color1_g = SPLITCOLOR_G( color1 );
color1_b = SPLITCOLOR_B( color1 );

color2_r = SPLITCOLOR_R( color2 );
color2_g = SPLITCOLOR_G( color2 );
color2_b = SPLITCOLOR_B( color2 );

res = FSV84D_RGB( alphaValue * color1_r + ( 1.0 - alphaValue ) * color2_r, alphaValue * color1_g + ( 1.0 - alphaValue ) * color2_g, alphaValue * color1_b + ( 1.0 - alphaValue ) * color2_b ); // FSV84D_RGB 为RGB宏

return( hrt );
}
我写的这个代码速度比较慢,谁能帮忙优化下,用到的MMX等汇编指令函数的话,如何使用,最好能详细的写下来,要不我估计看不懂,问题解决了我在另一个版块发的一个同样的帖子的分数(100分)也都可以给你,谢谢啦。
另外一个帖子链接为:
http://topic.csdn.net/u/20091021/13/88832508-e01a-405f-acff-91f965439da2.html
zhouzhipen 2009-10-26
  • 打赏
  • 举报
回复
记得给分
zhouzhipen 2009-10-26
  • 打赏
  • 举报
回复

void CGSurface::AlphaBlt(int x,int y,LPCGSURFACE sour,LPRECT prc,int alpha,COLORREF Key_Color)
{
RECT rect={0,0,sour->m_Desc.dwWidth,sour->m_Desc.dwHeight};
if(prc==NULL)
prc=▭
_RESTRICT_RECT(x,y,prc,this);

if(alpha<=0)
{
HBltFast(x,y,sour,prc,Key_Color);
return;
}
else if(alpha>255)
return;
BeginDraw();
sour->BeginDraw();
int srcw=(prc->right-prc->left);
int srch=(prc->bottom-prc->top);
int sourline=sour->m_Desc.lPitch-srcw*4;
int destline=m_Desc.lPitch-srcw*4;
LPBYTE pSour=((LPBYTE)(sour->m_GraphBuffer+prc->top*sour->m_Pitch+prc->left));
LPBYTE pDest=((LPBYTE)(m_GraphBuffer+y*m_Pitch+x));

int ialpha=(256-alpha)<<24;

for(int j=0;j<srch;j++)
{
__asm
{
mov ebx,pDest;
mov ecx,pSour;
mov eax,Key_Color;
xor edx,edx;
cmp edx,srcw;
jc STLOOP;
jmp ENDLOOP;
STLOOP:
cmp [ecx],eax;
je KEYOUT;
pxor mm2,mm2;
movd mm0,dword ptr [ebx];
movd mm1,dword ptr [ecx];
punpcklbw mm0,mm2;
punpcklbw mm1,mm2;
movd mm3,ialpha;
punpcklbw mm3,mm2;
punpckhwd mm3,mm3;
punpckhdq mm3,mm3;
movq mm4,mm0;
movq mm5,mm1;
psubusw mm4,mm1;
psubusw mm5,mm0;
pmullw mm4,mm3;
pmullw mm5,mm3;
psrlw mm4,8;
psrlw mm5,8;
paddusw mm0,mm5;
psubusw mm0,mm4;
packuswb mm0,mm0;
movd dword ptr [ebx],mm0 //保存结果
KEYOUT:
add ebx,4;
add ecx,4;
inc edx;
cmp edx,srcw;
jc STLOOP;
jmp ENDLOOP;
ENDLOOP:
add ebx,destline;
add ecx,sourline;
mov pSour,ecx;
mov pDest,ebx;
}
}
__asm emms;

sour->EndDraw();
EndDraw();
}


自己改改,这是我的一个图形库中的一函数源码.这个函数绝对符合你的要求.不但可以区域混合,还可以有关键色.
图形库你可以到这去把这个下载下来,内有图形库的头文件和引用文件.
http://download.csdn.net/source/1467303
mly_skywater 2009-10-23
  • 打赏
  • 举报
回复
谁能给出逐点alpha混合的MMX快速算法,我分都给他!!!
mly_skywater 2009-10-22
  • 打赏
  • 举报
回复
基本放弃了通过调用AlphaBlend函数实现alpha混合的方法,因为如果按照 laviewpbt 所说的ARGB格式的图像还需要预处理的话,和逐点alpha混合的效果估计差不多。发现网上都在说 MMX 指令优化alpha混合的文章,这方面有了解的朋友可以进来说说。
laviewpbt 2009-10-22
  • 打赏
  • 举报
回复
这是我老版本的Imageshop用得输出函数,你可以参考(VB的代码):




Public Function RenderGdi(ByVal DestDC As Long, _
Optional ByVal DestX As Long, _
Optional ByVal DestY As Long, _
Optional ByVal DestWidth As Long, _
Optional ByVal DestHeight As Long, _
Optional ByVal SrcX As Long, _
Optional ByVal SrcY As Long, _
Optional ByVal SrcWidth As Long, _
Optional ByVal SrcHeight As Long, _
Optional ByVal NeedPreMultipluy As Boolean = True) As Boolean

If m_Handle = 0 Then Exit Function
Dim Blend As Long
Blend = AC_SRC_OVER Or (255 * &H10000)
Blend = Blend Or (AC_SRC_ALPHA * &H1000000)

If NeedPreMultipluy = True Then '如果原始图像的格式是ARGB的,则需要预乘
Dim i As Long, j As Long
Dim k As Long, PixelCount As Long
Dim DataArr(0 To 3) As Byte, pDataArr(0 To 0) As Long
Dim OldArrPtr As Long, OldpArrPtr As Long
Dim DataArrC(0 To 3) As Byte, pDataArrC(0 To 0) As Long
Dim OldArrPtrC As Long, OldpArrPtrC As Long
Dim Alpha As Long
Dim PARGB As New C32DIB

PARGB.InitializeDIB SrcWidth, SrcHeight
MakePoint VarPtrArray(DataArr), VarPtrArray(pDataArr), OldArrPtr, OldpArrPtr
MakePoint VarPtrArray(DataArrC), VarPtrArray(pDataArrC), OldArrPtrC, OldpArrPtrC
pDataArrC(0) = PARGB.Pointer
For i = 0 To SrcHeight - 1
pDataArr(0) = m_Pointer + SrcX * 4 + i * m_Stride
For j = 1 To SrcWidth
Alpha = DataArr(3) 'Alpha
If Alpha <> 255 Then
DataArrC(2) = DataArr(2) * Alpha \ 255 'R
DataArrC(1) = DataArr(1) * Alpha \ 255 'G
DataArrC(0) = DataArr(0) * Alpha \ 255 'B
Else
DataArrC(2) = DataArr(2)
DataArrC(1) = DataArr(1)
DataArrC(0) = DataArr(0)
End If
DataArrC(3) = Alpha
pDataArr(0) = pDataArr(0) + 4
pDataArrC(0) = pDataArrC(0) + 4
Next
Next
FreePoint VarPtrArray(DataArr), VarPtrArray(pDataArr), OldArrPtr, OldpArrPtr
FreePoint VarPtrArray(DataArrC), VarPtrArray(pDataArrC), OldArrPtrC, OldpArrPtrC
AlphaBlend DestDC, DestX, DestY, DestWidth, DestHeight, PARGB.hDC, 0, 0, SrcWidth, SrcHeight, Blend
PARGB.DestroyDIB
Set PARGB = Nothing
Else
AlphaBlend DestDC, DestX, DestY, DestWidth, DestHeight, m_Hdc, 0, 0, SrcWidth, SrcHeight, Blend
End If
End Function


我的图像格式在内存的是以ARGB保存的,所以输出的时候都预乘。

mly_skywater 2009-10-22
  • 打赏
  • 举报
回复
菜鸟一个,呵呵,你也帮了不少忙了,还是谢谢你。
laviewpbt 2009-10-22
  • 打赏
  • 举报
回复
看了楼主上面帖子的代码,感觉楼主对GDI的了解还很肤浅,这个问题我不想在回答了。
mly_skywater 2009-10-22
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 dirdirdir3 的回复:]
当然可以,参考一下我做的这个,也是逐点用mask来指定透明度的:
http://download.csdn.net/source/1700479

另外大小不一样的话,可以先strechblt一下就可以了,包括mask也可以做同样的工作.............................
[/Quote]
dirdirdir3 你好!根据你的提示我试了一下,遇到了一个问题。简单描述一下:
在将源图像逐像素的alpha混合和到目标图像上的某个区域之前,因为源矩形和目标矩形大小不同,我先将源图像 StretchBlt 到了另外一个内存DC上,设置内存DC的大小就是目标矩形的大小,但是就在这一步我遇到了问题。
const RECT rc = { 0, 0, iAlphaDesWidth, iAlphaDesHeight};
HBRUSH brush = CreateSolidBrush( RGB( 0x7F,0x7F,0x7F ) );
HDC memdc = CreateCompatibleDC( hdcSrc );
FillRect( memdc , &rc, brush );
//HBITMAP hbm = CreateCompatibleBitmap( memdc , iAlphaDesWidth, iAlphaDesHeight);
//HBITMAP holdbm = (HBITMAP)SelectObject( maskdc2, hbm );
SetStretchBltMode( maskdc2, COLORONCOLOR );
StretchBlt( memdc , 0, 0, m_iAlphaDesWidth, m_iAlphaDesHeight, hdcSrc, 360, 0, 1200, 1200, SRCPAINT );
这些代码的最后一步 StretchBlt 返回值总是为0,也就是说从源图像中取出一个矩形区域缩放到内存DC中失败了,我注释掉的代码打开的话返回值为1,即使这样,我在使用内存DC的过程中发现它的内容还是只有brush的颜色,源图像中的矩形区域的颜色还是没有blt过来。感觉是内存DC的使用错误,但是不知道少了什么步骤导致失败。
请各位指教。谢谢!
mly_skywater 2009-10-21
  • 打赏
  • 举报
回复
感觉你和msdn上说的有出入,呵呵
laviewpbt 2009-10-21
  • 打赏
  • 举报
回复
PARGB 是 ARGB预乘后的结果
ARGB颜色分量分别为:100(A),45,56,120,则对应的PARGB数据为
100(A),45*100/255,56*100/255,120*100/255
mly_skywater 2009-10-21
  • 打赏
  • 举报
回复
你说的PARGB我明白了,但是我看msdn中这么说的:
If the source has both the SourceConstantAlpha (that is, it is not 0xFF) and per-pixel alpha, the source is pre-multiplied by the SourceConstantAlpha and then the blend is based on the per-pixel alpha. The following tables show this. Note that SourceConstantAlpha is divided by 255 because it has a value that ranges from 0 to 255.

Src.Red = Src.Red * SourceConstantAlpha / 255.0;
Src.Green = Src.Green * SourceConstantAlpha / 255.0;
Src.Blue = Src.Blue * SourceConstantAlpha / 255.0;
Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0;
Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha
这些计算中的前4个按照你说的,必须由我去计算完成之后再去调用AlphaBlend函数就能正确了,是这样吗?
这个貌似计算量也不小。而且如果我将SourceConstantAlpha 设置为255的话不就不用算了吗,直接就是PARGB格式的了。我看msdn上就是这个意思,如果要使用逐点混合的话,SourceConstantAlpha 必须设置为255。
我的理解对吗?
加载更多回复(12)

19,468

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 图形处理/算法
社区管理员
  • 图形处理/算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧