0xAA55 发表于 2016-5-21 16:15:20

【VB】使用GDI实现整张位图颜色相加 AdditionBlt


其中实例窗口中用户每次拖动鼠标,程序都会在鼠标经过的区域不断用颜色相加的方式绘制一张如下位图。

这张位图来源于DXSDK8中的Media文件夹中的particle.bmp

所谓的整张位图颜色相加,与透明混合不同,是『目标颜色 = (源 + 目标) > 1 ? 1 : (源 + 目标)』。GDI的AlphaBlend函数本身是被设计用来进行透明混合的,它本身对每个像素进行如下的计算:

如果源图有透明通道:
Dest = Dest * (1 - Src.Alpha * SCA) + Src * SCA
如果源图没有透明通道:
Dest = Dest * (1 - SCA) + Src * SCA

也就是说,本身透明混合的计算里面,源图应该要乘以自己的透明通道值的,但是AlphaBlend忽略了这样的计算,也就是如果你的Src.Alpha为0,那么它就完全进行颜色的加法了——然而并不是,当你Src.Alpha为0时它认为这个像素是全透明的。。但Src.Alpha为1的时候是可以的(相当于0.5 % 不透明度,十分接近0了)
所以我们只需要将源图的Alpha通道设置为1,就可以用它做加法计算了?虽然是这样,但是它不会做溢出处理,如果你的源颜色值加上目标颜色值大于255,它就会只保留低8位,造成回滚——两个比较亮的颜色相加后实际成为了一个比较黑的颜色。这种溢出如果被我们自己处理的话,就能被解决。

因为从二进制加法的角度解释这个就是,每个颜色通道都是8bit,当它们最高位都是1,然后相加以后,就会进位,进的位会被丢弃所以就会出现回卷现象。而我们如何才能判断哪些像素在高亮的地方发生了进位呢?我的方法是,将两张需要做加法混合的图,亮度都减少一半。这相当于它们每个通道的颜色值除以2,或者右移1位。经过这样的处理以后,再做加法混合,颜色通道的第7bit——最高位,就是原先应该被丢弃掉的第8bit,也就是进位了。这个时候用位域运算,用BitBlt将一个全图全通道都是0x80的纯色图做SRCAND就可以提取出所有的进位了的部分了。
将两张图减少亮度然后相加,其实可以直接一步搞定——直接用AlphaBlend将两张图进行透明混合,透明度为50%。
提取了进位后,再用AlphaBlend将其亮度降低——用一张纯黑的图以1/255的比例进行透明混合,相当于它的每个颜色都减去1,那么这些溢出过的颜色就会从0x80变成0x7F,然后再和0x80筛选出来的图做SRCPAINT(或运算)就能将所有溢出过的像素Clamp到255亮度而不会发生回卷——丢弃最高位的行为。这样得到的图再拿去和之前直接做加法混合的图做SRCPAINT混合,就能得到带亮度限定、溢出部分全亮处理的两图叠加图了。
但是因为AlphaBlend不会对不透明度为0的像素做处理,然后GDI的图像通常不带Alpha通道,因此需要手动处理,建立一张32bit真彩色位图(Win7用CreateCompatibleBitmap就可以建立默认位数32的位图,但XP下你会得到24bit、16bit的位图,此时应该建立DIBSection,然而这样的话它估计就是纯CPU运算了,达不到光栅显卡加速的效果——然而现在大家用的显卡都是图形卡,应该是没有光栅加速效果的吧,但做透明混合的时候应该也是多少有些帮助的),然后手动将所有像素的不透明通道设为0.5%(也就是1,最大值255的),再用AlphaBlend就可以做加法混合了。但是这样一来,每次经过处理以后,原先被加亮绘图覆盖的部分将会稍微变黑——因为被AlphaBlend经过不透明通道为1的混合处理以后,根据计算式Dest = Dest * 0.95 + Src,颜色值大于127的部分会被减去1.这是这种处理方式的缺陷啦。做少量的加法混合图的时候,这种现象应该是几乎不会引起人的注意的。
如果要做到完美的处理,就只能自己操作Bit了——这样还能保证,它是一定不会进行显卡加速的!(当然是坏消息,有显卡加速才是更好的做法,所以我才会用这些API)

模块 modAdditionBlt:
Option Explicit

Private Declare Function Rectangle Lib "gdi32" (ByVal hDC As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function GetStockObject Lib "gdi32" (ByVal nIndex As Long) As Long
Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
Private Declare Function SetROP2 Lib "gdi32" (ByVal hDC As Long, ByVal nDrawMode As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

Private Const PS_SOLID = 0
Private Const R2_MASKPEN = 9   'DPa
Private Const GRAY_BRUSH = 2
Private Const BLACK_BRUSH = 4
Private Const NULL_PEN = 8

Type AdditionBitmap_t
    Width As Long
    Height As Long
    OrigPic As Bit32_Picture
    Temp1 As Bit32_Picture
    Temp2 As Bit32_Picture
End Type

Function AdditionBitmap_Create(ByVal hDC As Long, ByVal bmWidth As Long, ByVal bmHeight As Long, ByVal hSrcDC As Long) As AdditionBitmap_t
AdditionBitmap_Create.Width = bmWidth
AdditionBitmap_Create.Height = bmHeight
AdditionBitmap_Create.OrigPic = Bit32_Create(hDC, bmWidth, bmHeight) '图像资源
AdditionBitmap_Create.Temp1 = Bit32_Create(hDC, bmWidth, bmHeight) '临时图像1
AdditionBitmap_Create.Temp2 = Bit32_Create(hDC, bmWidth, bmHeight) '临时图像2
BitBlt AdditionBitmap_Create.OrigPic.hDC, 0, 0, bmWidth, bmHeight, hSrcDC, 0, 0, vbSrcCopy '复制图像资源,强行适应格式
Bit32_SetAlpha AdditionBitmap_Create.OrigPic, 1 '设置Alpha通道值全为1(%0.5不透明度,但不预乘Alpha值。设为0就不显示了只好设为1)

'临时图像1,高位(溢出)检测用
'临时图像2,用于合并所有的图

SetROP2 AdditionBitmap_Create.Temp1.hDC, R2_MASKPEN 'And模式
SelectObject AdditionBitmap_Create.Temp1.hDC, GetStockObject(NULL_PEN)
SelectObject AdditionBitmap_Create.Temp1.hDC, GetStockObject(GRAY_BRUSH) '颜色是&H808080
SelectObject AdditionBitmap_Create.Temp2.hDC, GetStockObject(BLACK_BRUSH)
End Function

Sub AdditionBitmap_Delete(AdditionBitmap As AdditionBitmap_t)
Bit32_Delete AdditionBitmap.OrigPic
Bit32_Delete AdditionBitmap.Temp1
Bit32_Delete AdditionBitmap.Temp2
End Sub

Sub AdditionBlt(ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, SrcBitmap As AdditionBitmap_t, ByVal SrcX As Long, ByVal SrcY As Long)
'直接用AlphaBlend做图像加法会有饱和部分,由于它默认不处理,会导致回卷。因此要手动提取饱和部分,将其设为全1,再合并

'提取饱和部分
BitBlt SrcBitmap.Temp1.hDC, 0, 0, nWidth, nHeight, hDestDC, X, Y, vbSrcCopy
AlphaBlend SrcBitmap.Temp1.hDC, 0, 0, nWidth, nHeight, SrcBitmap.OrigPic.hDC, SrcX, SrcY, nWidth, nHeight, &H800000 '混合为半亮图
Rectangle SrcBitmap.Temp1.hDC, 0, 0, nWidth + 1, nHeight + 1 '取得每通道位面7的部分(Rectangle的宽高要加1)
Rectangle SrcBitmap.Temp2.hDC, 0, 0, nWidth + 1, nHeight + 1 '清空临时图片2
AlphaBlend SrcBitmap.Temp2.hDC, 0, 0, nWidth, nHeight, SrcBitmap.Temp1.hDC, 0, 0, nWidth, nHeight, &HFE0000 '半亮图每通道亮度-1
BitBlt SrcBitmap.Temp2.hDC, 0, 0, nWidth, nHeight, SrcBitmap.Temp1.hDC, 0, 0, vbSrcPaint '合并高位,完成二值化处理
'SrcBitmap.Temp2: 半亮图位面7每通道二值化(饱和部分)

'合成全亮图
AlphaBlend hDestDC, X, Y, nWidth, nHeight, SrcBitmap.OrigPic.hDC, SrcX, SrcY, nWidth, nHeight, &H1FF0000 '完全加法
'就是这个完全加法会导致被遮盖的图发生灰化,但目前没办法解决。。除非源图Alpha设为0时它也做渲染处理
BitBlt hDestDC, X, Y, nWidth, nHeight, SrcBitmap.Temp2.hDC, 0, 0, vbSrcPaint '合并饱和部分
End Sub模块 Bit32:Option Explicit

Private Type BITMAPINFOHEADER '40 bytes
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Type BLENDFUNCTION
    BlendOp As Byte
    BlendFlags As Byte
    SCA As Byte
    AlphaFormat As Byte
End Type

Type ARGB
    B As Byte
    G As Byte
    R As Byte
    A As Byte
End Type

Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function GetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFOHEADER, ByVal wUsage As Long) As Long
Private Declare Function SetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFOHEADER, ByVal wUsage As Long) As Long
Private Declare Function CreateDIBSection Lib "gdi32" (ByVal hDC As Long, pBitmapInfo As Any, ByVal un As Long, ppBits As Long, ByVal handle As Long, ByVal dw As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Declare Function AlphaBlend Lib "msimg32" (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Private Const BITSPIXEL = 12         'Number of bits per pixel

Const AC_SRC_OVER As Long = 0
Const AC_SRC_ALPHA As Long = 1

Type Bit32_Picture
    hDC As Long
    hBmp As Long '只能用于上面定义的hDC
    pBits As Long '-1表示DDB
    Width As Long
    Height As Long
End Type

Function Bit32_Create(ByVal hDC As Long, ByVal bmWidth As Long, ByVal bmHeight As Long) As Bit32_Picture
Dim hNewDC As Long, hNewBmp As Long, pBits As Long
hNewDC = CreateCompatibleDC(hDC)

'如果用CreateCompatibleBitmap创建的不是32bit位图,就创建DIB,否则创建DDB
If GetDeviceCaps(hNewDC, BITSPIXEL) <> 32 Then
    Dim BMIF As BITMAPINFOHEADER
    With BMIF
      .biSize = 40
      .biWidth = bmWidth
      .biHeight = bmHeight
      .biPlanes = 1
      .biBitCount = 32
      .biSizeImage = bmWidth * bmHeight * 4
    End With
    hNewBmp = CreateDIBSection(hNewDC, BMIF, 0, pBits, 0, 0)
    SelectObject hNewDC, hNewBmp
    DeleteObject hNewBmp
Else
    '创建DDB
    hNewBmp = CreateCompatibleBitmap(hDC, bmWidth, bmHeight)
    pBits = -1
    SelectObject hNewDC, hNewBmp
    DeleteObject hNewBmp
End If
Bit32_Create.hDC = hNewDC
Bit32_Create.hBmp = hNewBmp
Bit32_Create.pBits = pBits
Bit32_Create.Width = bmWidth
Bit32_Create.Height = bmHeight
End Function

Sub Bit32_Delete(Bit32 As Bit32_Picture)
If Bit32.hDC Then DeleteDC Bit32.hDC
Bit32.hDC = 0
End Sub

'设置整张图的Alpha透明通道
Sub Bit32_SetAlpha(Bit32 As Bit32_Picture, Optional ByVal Alpha As Byte = &HFF&, Optional ByVal SetBits As Boolean = False)
Dim Colors() As ARGB
ReDim Colors(Bit32.Width * Bit32.Height - 1)

'如果是DIB则直接操作pBits,否则用GetDIBits、SetDIBits操作位图数据
If Bit32.pBits = -1 Then
    Dim BMIF As BITMAPINFOHEADER
    With BMIF
      .biSize = 40
      .biWidth = Bit32.Width
      .biHeight = Bit32.Height
      .biPlanes = 1
      .biBitCount = 32
      .biSizeImage = Bit32.Width * Bit32.Height * 4
    End With
    GetDIBits Bit32.hDC, Bit32.hBmp, 0, Bit32.Height, Colors(0), BMIF, 0
Else
    CopyMemory Colors(0), ByVal Bit32.pBits, Bit32.Width * Bit32.Height * 4
End If

'设置透明通道
Dim I&
For I = 0 To UBound(Colors)
    Colors(I).A = Alpha
Next
'如果需要预乘透明度值,则进行处理
If SetBits Then
    'Alpha = 255 - Alpha
    For I = 0 To UBound(Colors)
      Colors(I).R = CLng(Colors(I).R) * Alpha / 255
      Colors(I).G = CLng(Colors(I).G) * Alpha / 255
      Colors(I).B = CLng(Colors(I).B) * Alpha / 255
    Next
End If

'提交更改
If Bit32.pBits = -1 Then
    SetDIBits Bit32.hDC, Bit32.hBmp, 0, Bit32.Height, Colors(0), BMIF, 0
Else
    CopyMemory ByVal Bit32.pBits, Colors(0), Bit32.Width * Bit32.Height * 4
End If
End SubBIN:
名称: AdditionBlt_Bin.7z
大小: 11947 字节
SHA256: 95CD011E880F51D01FBA91BFD1FE4CD59C092D509D8BD6F1F5A313184AD2A92D

源码:**** Hidden Message *****

zcjhy 发表于 2017-7-1 23:16:31

真的不错的效果

二十六 发表于 2017-10-18 10:57:58

学习一下

qxinfo 发表于 2017-10-22 22:03:02

】使用GDI实现整张位图颜色相加 AdditionBlt [修改]

阿呆在上海 发表于 2017-10-23 18:37:09

好好学习,图像问题很难

Phalcon 发表于 2018-2-11 15:55:21

是调用GDI的函数

0xAA55 发表于 2018-2-11 16:16:00

Phalcon 发表于 2018-2-11 15:55
是调用GDI的函数

对。并且是想要避开自己进行颜色计算的。

0xAA55 发表于 2018-5-6 00:59:33

有优化大概是错觉……

ky52879 发表于 2018-5-28 09:57:31

看看源码,想学

bigwind 发表于 2018-8-2 20:43:31

SHA256: 95CD011E880F51D01FBA91BFD1FE4CD59C092D509D8BD6F1F5A313184AD2A92D

系统消息 发表于 2019-11-1 16:00:19

还是用 _mm_adds_epu8 直接一次性批量计算16个字节无符号8位饱和加法最高效,VB6可以通过ShellCode来做这个优化,或者用VC写个dll来调用。

0xAA55 发表于 2019-11-6 15:21:30

系统消息 发表于 2019-11-1 16:00
还是用 _mm_adds_epu8 直接一次性批量计算16个字节无符号8位饱和加法最高效,VB6可以通过ShellCode来做这个 ...

考虑到DWM开启Aero环境下、GDI接口可能有部分是GPU负责的(封装得类似于低效的GPU驱动的可能性是有的),暂且考虑了如此的方式,通过API来做这些事,而非自己写算法。

虽说,流式SIMD进行整张位图处理的方式也十分像GPU的行为,并且也是一种高效的CPU渲染的方式。只是,Windows在实现GDI的时候,应该已经用了类似的指令集。

系统消息 发表于 2019-11-6 20:30:07

0xAA55 发表于 2019-11-6 15:21
考虑到DWM开启Aero环境下、GDI接口可能有部分是GPU负责的(封装得类似于低效的GPU驱动的可能性是有的), ...

主要是GDI没有提供对线性减淡混合的实现,还有就是DIB位图不会有GPU加速吧,我现在写GDI程序一般都是DIB位图操作。

hxin123456 发表于 2020-4-3 22:31:28

看看学习下

大宝 发表于 2020-7-8 10:23:27

本帖最后由 china_shy_wzb 于 2020-7-20 13:59 编辑

怎样用VB6编辑位图颜色

东东哥 发表于 2021-10-1 16:09:39

叫你隐藏,抓出来。

xiongsx 发表于 2021-11-27 20:33:44

王王王王王王王王王王王王王王王王王王王王王

xiawan 发表于 2022-5-9 16:03:32

感谢楼主分享~~~
页: [1]
查看完整版本: 【VB】使用GDI实现整张位图颜色相加 AdditionBlt