找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 11043|回复: 17

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

[复制链接]
发表于 2016-5-21 16:15:20 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
20160520121640.png
其中实例窗口中用户每次拖动鼠标,程序都会在鼠标经过的区域不断用颜色相加的方式绘制一张如下位图。
particle.png
这张位图来源于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:
  1. Option Explicit

  2. 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
  3. Private Declare Function GetStockObject Lib "gdi32" (ByVal nIndex As Long) As Long
  4. Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
  5. Private Declare Function SetROP2 Lib "gdi32" (ByVal hDC As Long, ByVal nDrawMode As Long) As Long
  6. Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
  7. Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

  8. Private Const PS_SOLID = 0
  9. Private Const R2_MASKPEN = 9     '  DPa
  10. Private Const GRAY_BRUSH = 2
  11. Private Const BLACK_BRUSH = 4
  12. Private Const NULL_PEN = 8

  13. Type AdditionBitmap_t
  14.     Width As Long
  15.     Height As Long
  16.     OrigPic As Bit32_Picture
  17.     Temp1 As Bit32_Picture
  18.     Temp2 As Bit32_Picture
  19. End Type

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

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

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

  35. Sub AdditionBitmap_Delete(AdditionBitmap As AdditionBitmap_t)
  36. Bit32_Delete AdditionBitmap.OrigPic
  37. Bit32_Delete AdditionBitmap.Temp1
  38. Bit32_Delete AdditionBitmap.Temp2
  39. End Sub

  40. 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)
  41. '直接用AlphaBlend做图像加法会有饱和部分,由于它默认不处理,会导致回卷。因此要手动提取饱和部分,将其设为全1,再合并

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

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

  2. Private Type BITMAPINFOHEADER '40 bytes
  3.     biSize As Long
  4.     biWidth As Long
  5.     biHeight As Long
  6.     biPlanes As Integer
  7.     biBitCount As Integer
  8.     biCompression As Long
  9.     biSizeImage As Long
  10.     biXPelsPerMeter As Long
  11.     biYPelsPerMeter As Long
  12.     biClrUsed As Long
  13.     biClrImportant As Long
  14. End Type

  15. Type BLENDFUNCTION
  16.     BlendOp As Byte
  17.     BlendFlags As Byte
  18.     SCA As Byte
  19.     AlphaFormat As Byte
  20. End Type

  21. Type ARGB
  22.     B As Byte
  23.     G As Byte
  24.     R As Byte
  25.     A As Byte
  26. End Type

  27. Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
  28. Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
  29. 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
  30. 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
  31. 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
  32. Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
  33. Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
  34. Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
  35. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  36. 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
  37. 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
  38. Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
  39. Private Const BITSPIXEL = 12         '  Number of bits per pixel

  40. Const AC_SRC_OVER As Long = 0
  41. Const AC_SRC_ALPHA As Long = 1

  42. Type Bit32_Picture
  43.     hDC As Long
  44.     hBmp As Long '只能用于上面定义的hDC
  45.     pBits As Long '-1表示DDB
  46.     Width As Long
  47.     Height As Long
  48. End Type

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

  52. '如果用CreateCompatibleBitmap创建的不是32bit位图,就创建DIB,否则创建DDB
  53. If GetDeviceCaps(hNewDC, BITSPIXEL) <> 32 Then
  54.     Dim BMIF As BITMAPINFOHEADER
  55.     With BMIF
  56.         .biSize = 40
  57.         .biWidth = bmWidth
  58.         .biHeight = bmHeight
  59.         .biPlanes = 1
  60.         .biBitCount = 32
  61.         .biSizeImage = bmWidth * bmHeight * 4
  62.     End With
  63.     hNewBmp = CreateDIBSection(hNewDC, BMIF, 0, pBits, 0, 0)
  64.     SelectObject hNewDC, hNewBmp
  65.     DeleteObject hNewBmp
  66. Else
  67.     '创建DDB
  68.     hNewBmp = CreateCompatibleBitmap(hDC, bmWidth, bmHeight)
  69.     pBits = -1
  70.     SelectObject hNewDC, hNewBmp
  71.     DeleteObject hNewBmp
  72. End If
  73. Bit32_Create.hDC = hNewDC
  74. Bit32_Create.hBmp = hNewBmp
  75. Bit32_Create.pBits = pBits
  76. Bit32_Create.Width = bmWidth
  77. Bit32_Create.Height = bmHeight
  78. End Function

  79. Sub Bit32_Delete(Bit32 As Bit32_Picture)
  80. If Bit32.hDC Then DeleteDC Bit32.hDC
  81. Bit32.hDC = 0
  82. End Sub

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

  87. '如果是DIB则直接操作pBits,否则用GetDIBits、SetDIBits操作位图数据
  88. If Bit32.pBits = -1 Then
  89.     Dim BMIF As BITMAPINFOHEADER
  90.     With BMIF
  91.         .biSize = 40
  92.         .biWidth = Bit32.Width
  93.         .biHeight = Bit32.Height
  94.         .biPlanes = 1
  95.         .biBitCount = 32
  96.         .biSizeImage = Bit32.Width * Bit32.Height * 4
  97.     End With
  98.     GetDIBits Bit32.hDC, Bit32.hBmp, 0, Bit32.Height, Colors(0), BMIF, 0
  99. Else
  100.     CopyMemory Colors(0), ByVal Bit32.pBits, Bit32.Width * Bit32.Height * 4
  101. End If

  102. '设置透明通道
  103. Dim I&
  104. For I = 0 To UBound(Colors)
  105.     Colors(I).A = Alpha
  106. Next
  107. '如果需要预乘透明度值,则进行处理
  108. If SetBits Then
  109.     'Alpha = 255 - Alpha
  110.     For I = 0 To UBound(Colors)
  111.         Colors(I).R = CLng(Colors(I).R) * Alpha / 255
  112.         Colors(I).G = CLng(Colors(I).G) * Alpha / 255
  113.         Colors(I).B = CLng(Colors(I).B) * Alpha / 255
  114.     Next
  115. End If

  116. '提交更改
  117. If Bit32.pBits = -1 Then
  118.     SetDIBits Bit32.hDC, Bit32.hBmp, 0, Bit32.Height, Colors(0), BMIF, 0
  119. Else
  120.     CopyMemory ByVal Bit32.pBits, Colors(0), Bit32.Width * Bit32.Height * 4
  121. End If
  122. End Sub
复制代码
BIN: AdditionBlt_Bin.7z (11.67 KB, 下载次数: 31)
名称: AdditionBlt_Bin.7z
大小: 11947 字节
SHA256: 95CD011E880F51D01FBA91BFD1FE4CD59C092D509D8BD6F1F5A313184AD2A92D

源码:
游客,如果您要查看本帖隐藏内容请回复

本帖被以下淘专辑推荐:

回复

使用道具 举报

发表于 2017-7-1 23:16:31 | 显示全部楼层
真的不错的效果
回复 赞! 靠!

使用道具 举报

发表于 2017-10-18 10:57:58 | 显示全部楼层
学习一下
回复

使用道具 举报

发表于 2017-10-22 22:03:02 | 显示全部楼层
】使用GDI实现整张位图颜色相加 AdditionBlt [修改]
回复 赞! 靠!

使用道具 举报

发表于 2017-10-23 18:37:09 | 显示全部楼层
好好学习,图像问题很难
回复 赞! 靠!

使用道具 举报

发表于 2018-2-11 15:55:21 | 显示全部楼层
是调用GDI的函数
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2018-2-11 16:16:00 | 显示全部楼层
Phalcon 发表于 2018-2-11 15:55
是调用GDI的函数

对。并且是想要避开自己进行颜色计算的。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2018-5-6 00:59:33 | 显示全部楼层
有优化大概是错觉……
回复 赞! 靠!

使用道具 举报

发表于 2018-5-28 09:57:31 | 显示全部楼层
看看源码,想学
回复 赞! 靠!

使用道具 举报

发表于 2018-8-2 20:43:31 | 显示全部楼层
SHA256: 95CD011E880F51D01FBA91BFD1FE4CD59C092D509D8BD6F1F5A313184AD2A92D
回复 赞! 靠!

使用道具 举报

发表于 2019-11-1 16:00:19 | 显示全部楼层
还是用 _mm_adds_epu8 直接一次性批量计算16个字节无符号8位饱和加法最高效,VB6可以通过ShellCode来做这个优化,或者用VC写个dll来调用。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 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位图操作。
回复 赞! 靠!

使用道具 举报

发表于 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 | 显示全部楼层
叫你隐藏,抓出来。
回复 赞! 靠!

使用道具 举报

发表于 2021-11-27 20:33:44 | 显示全部楼层
王王王王王王王王王王王王王王王王王王王王王
回复 赞! 靠!

使用道具 举报

发表于 2022-5-9 16:03:32 | 显示全部楼层
感谢楼主分享~~~
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-12 08:39 , Processed in 0.050011 second(s), 34 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表