所谓区域就是指,用CreateRectRgn等API建立起来的东东。
现在,PictureBox中有一幅图形,假如我想把其中红色的部分转为区域,该怎么办呢?红色的区域可能非常复杂,可能内外交错,也可能有内外圈。
总之,一般来说,不能用简单的多边形去一点点勾出(至少,大部分活(“懒”)人是不会去干的:)请问该怎么做呢?
现在,PictureBox中有一幅图形,假如我想把其中红色的部分转为区域,该怎么办呢?红色的区域可能非常复杂,可能内外交错,也可能有内外圈。
总之,一般来说,不能用简单的多边形去一点点勾出(至少,大部分活(“懒”)人是不会去干的:)请问该怎么做呢?
当用于图片透[镂]空操作时,是连续区域时还还办,互不相邻的就麻烦了。ExtFloodfill可以在对某一点及其周围与之相颜色相同的连片区域涂成某种颜色。
Windows提供了一个API函数SetWindowRgn,凭着这个函数,我们可以把窗口设置为任意形状。问题在于,我们如何来获取所需的区域形状。一般情况下,我们可以使用CreateEllipticRgn创建椭圆区域,CreateRectRgn创建矩形区域,CreateRoundRectRgn创建圆角矩形区域。(口干,喝一口水,继续……)如果我们需要不规则的形状呢?那就可以使用CreatePolygonRgn。可是这个函数需要的参数之一是包含整个不规则区域轮廓坐标点的数组,对于一个稍微复杂一点的形状就可能需要几百个坐标点,要获取和改动这些坐标点都是相当麻烦的。
有没有更为方便的方法呢?答案是肯定的。(不然我在这儿瞎搞什么?)
原理是用一张图片作为窗体的背景,图片中有一种颜色是我们不需要的,称为透明色。然后编程一行行地扫描图片,将透明色的点删去,而把有用的像素点合并成一块区域,如此便得到所需的形状了。
但是行扫描的速度奇慢,我最初实现的程序起码用了5分钟才显示出窗体。咎其原因是我们选错了兵刃。一开始我使用GetPixel来获取每一点的颜色,这样每取一个点都需要通过设备上下文hdc从图片中读取信息,这就是造成龟速的罪魁祸首了。
正确的方法是使用GetBitmapBits函数。它可以将位图中每一点颜色信息一下子读到一个数组中,以后只要扫描这个数组就行了,这将极大的提高运行速度。Public Declare Function GetObjectAPI Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Public Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long
Public Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Public Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long
Public Const RGN_OR = 2
Public Declare Function GetBitmapBits Lib "gdi32" (ByVal hBitmap As Long, ByVal dwCount As Long, lpBits As Any) As Long
Public Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
Dim bmByte() As BytePublic Sub SetAutoRgn(hForm As Form, Optional transColor As Byte = vbNull)
Dim X As Long, Y As Long
Dim Rgn1 As Long, Rgn2 As Long
Dim SPos As Long, EPos As Long
Dim bm As BITMAP
Dim hbm As Long
Dim Wid As Long, Hgt As Long
'获取窗体背景图片尺寸
hbm = hForm.Picture
GetObjectAPI hbm, Len(bm), bm
Wid = bm.bmWidth
Hgt = bm.bmHeight
'改变窗体尺寸以符合背景图片大小
hForm.Height = Hgt * Screen.TwipsPerPixelY
hForm.Width = Wid * Screen.TwipsPerPixelX
ReDim bmByte(1 To Wid, 1 To Hgt)
GetBitmapBits hbm, Wid * Hgt, bmByte(1, 1) '获取图像数组
'如果没有传入transColor参数,则用第一个像素作为透明色
If transColor = vbNull Then transColor = bmByte(1, 1)
Rgn1 = CreateRectRgn(0, 0, 0, 0)
For Y = 1 To Hgt '逐行扫描
X = 0
Do
X = X + 1
While (bmByte(X, Y) = transColor) And (X < Wid)
X = X + 1 '跳过是透明色的点
Wend
SPos = X
While (bmByte(X, Y) <> transColor) And (X < Wid)
X = X + 1 '跳过不是透明色的点
Wend
EPos = X - 1
'这一段是合并区域
If SPos <= EPos Then
Rgn2 = CreateRectRgn(SPos - 1, Y - 1, EPos, Y)
CombineRgn Rgn1, Rgn1, Rgn2, RGN_OR
DeleteObject Rgn2
End If
Loop Until X >= Wid
Next Y
SetWindowRgn hForm.hwnd, Rgn1, True '设定窗体形状区域
DeleteObject Rgn1
End Sub 以上这一大段程序列出了所有必需的API函数申明。自定义过程SetAutoRgn有两个参数,hForm是将要设置形状的窗体名称,transColor是透明颜色值。关于获取窗体背景图片尺寸的代码,相信各位一看就明白,我不再罗嗦。
我想需要解释的是像素格式的概念。大家可能已经注意到程序中二维数组bmByte()的类型是Byte,而在后面的循环体中,我每次取数组的一项就代表一个像素点,这意味着窗体的背景图片只能是8位色深256色的图片(比如GIF格式)。如果是16位色深的图片(JPG格式),那么数组的行宽得翻一倍,并且两个数组项才代表一个像素,要不你就把bmByte()的类型改为Integer。同理,24位色深的像素点要用3个Byte表示,32位的要用4个Byte或者一个Long。transColor的值在不同的像素格式下的值也是不同的。比如在8位情况下,它的取值范围为0-255,代表颜色在图片调色板中的位置。好在程序默认以图片左上角第一个像素为透明色,这样你也就不必深究transColor的具体表示方法了。更为详细的关于像素格式的资料可以在MSDN的光盘中找到,限于篇幅,这里就不展开了。
好,理解了程序,我们可以试着来运行一下了,大家可以先看看程序运行的效果(见图二)。在我的古董MMX200上,把图一这样一张图片变成图二那样的窗体,大约耗时0.4秒左右,这么一点延迟应该是可以忍受的。如果你不放心,可以这样来调用以了解精确的运行时间。Private Sub Form_Load()
Dim t As Single
t = Timer
If Me.Picture <> 0 Then Call SetAutoRgn(Me)
MsgBox Timer - t
End Sub
建这样的区域一个重要的目的就像上面写的,建异型窗体。
或许有人会说可以用一个现成的API,但事实上俺的程序是For 98专用的,那我原来首先想到的也是读象素(以下为大致过程):Public Function MakeRegion(pic As PictureBox) As Long
Dim x As Long, y As Long, StartLineX As Long
Dim FullRegion As Long, LineRegion As Long, KeyColor As Long
Dim InLine As Boolean, InFirstRegion As Boolean
Dim hdc As Long, PicWidth As Long, PicHeight As Long
hdc = pic.hdc
PicWidth = pic.ScaleWidth
PicHeight = pic.ScaleHeight
InFirstRegion = True: InLine = False
x = y = StartLineX = 0
KeyColor = GetPixel(hdc, 0, 0)
For y = 0 To PicHeight - 1
For x = 0 To PicWidth - 1
If GetPixel(hdc, x, y) = KeyColor Or x = PicWidth Then
If InLine Then
InLine = False
LineRegion = CreateRectRgn(StartLineX, y, x, y + 1)
If InFirstRegion Then
FullRegion = LineRegion
InFirstRegion = False
Else
CombineRgn FullRegion, FullRegion, LineRegion, RGN_OR
DeleteObject LineRegion
End If
End If
Else
If Not InLine Then
InLine = True
StartLineX = x
End If
End If
Next
Next
MakeRegion = FullRegion
End Function但问题也明摆着——低下的效率,呵呵现在俺再试试wxj_lake(蔚蓝的风)的办法,也希望大家看了有所启发,共同进步!诶,难得看到这么好的中文资料啦,谢谢rainstormmaster(暴风雨 v2.0) 老大一个字——顶!收藏了