表单的 TransparencyKey 留下可怕的彩色边缘
Form's TransparencyKey leaves ghastly colored edging
当使用 TransparencyKey
制作透明表格并将具有透明像素的 PNG 作为背景图像时,PNG 图像的大部分图像轮廓(特别是颜色也用于设置表单透明度键...在我的例子中是洋红色)。
我也试了几个小时想出一个解决方案,但找不到。
几乎起作用的是使用具有不同 TransparencyKey
颜色的多个表单,如果像素与其他表单之一的颜色或位置不匹配,则逐个像素地匹配表单,如果存在则被排除是在新表格上逐像素写入的匹配项。
它并不完美,但已经非常接近了。然而,这种方法实际上花费了 2.5 小时,这对于要处理的小徽标来说太长了。
我该如何解决这个问题?
此代码是对此处找到的代码的翻译(略有解释):
.
最初来自 Microsoft 样本代码库(至少在他们杀死它之前是这样)。
当表单呈现透明时,将其 TransparencyKey
设置为与 BackGroundColor
[=88= 相同的颜色] 然后在窗体的透明表面上绘制一个半透明的位图,位图的抗锯齿部分不会与窗体后面的任何东西混合。
用作TransparencyKey
的Color可能会影响渲染结果,但是半透明像素(尤其是靠近Bitmap边缘的像素)在不同的背景上总是可见的,因为没有混合。
为了解决这个问题,我们可以建立一个Layered Window:
The system automatically composes and repaints layered windows and the
windows of underlying applications. As a result, layered windows are
rendered smoothly, without the flickering typical of complex window
regions. In addition, layered windows can be partially translucent,
that is, alpha-blended.
要创建分层表单,我们可以设置 WS_EX_LAYERED
extended style overriding the Form's CreateParams 属性:
Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
parms.ExStyle = parms.ExStyle Or WS_EX_LAYERED
Return parms
End Get
End Property
Windows 8+:WS_EX_LAYERED
样式支持顶级 windows 和子 windows。以前的 Windows 版本仅支持顶级 windows.
的这种样式
要绘制一个可以与背景融为一体的位图,我们select一个位图到Window设备上下文中,然后调用UpdateLayeredWindow, specifying the type of rendering using a BLENDFUNCTION
结构。
此结构允许定义 (BlendOp
) 源位图和目标位图如何混合(实际上,唯一可能的操作是 Source Over,AC_SRC_OVER
),应用于源位图的不透明度级别(SourceConstantAlpha
:255
= 不透明,0
= 完全透明) 以及如何解释源位图和目标位图的颜色 (AlphaFormat
)。
在这里,我们要混合具有 Alpha 通道(每像素 alpha)的源位图,因此它是半透明的:我们将 AC_SRC_ALPHA
指定为AlphaFormat
(请参阅有关如何根据源位图的颜色类型解释颜色混合的文档)。
就是这样。
要构建分层表单,请将新表单添加到项目中,如图所示更改构造函数,添加 CreateParams
覆盖,如果可以拖动表单则添加 WndProc
覆盖和 SelectBitmap()
方法调用,它激活源位图(在构造函数中传递)和窗体的 DC 的 alpha 混合。
此外,将 NativeMethods
支持 class 添加到项目中:
► 可以像往常一样创建 Form,在本例中将 Bitmap 对象传递给它的构造函数:
(位图格式必须是 32 位 ARGB
- 带 alpha 通道的 PNG
即可)
Dim layeredForm As New PerPixelAlphaLayeredForm(bitmap)
layeredForm.Show()
Public Class PerPixelAlphaLayeredForm
Public Sub New()
Me.New(Nothing)
End Sub
Public Sub New(bitmap As Bitmap)
InitializeComponent()
Me.LayerBitmap = bitmap
End Sub
Private ReadOnly Property LayerBitmap As Bitmap
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
If Me.LayerBitmap IsNot Nothing Then
Me.ClientSize = Me.LayerBitmap.Size
Dim screenSize = Screen.FromHandle(Me.Handle).Bounds.Size
Me.Location = New Point((screenSize.Width - Me.Width) \ 2, (screenSize.Height - Me.Height) \ 2)
SelectBitmap(Me.LayerBitmap)
' Or, call the SelectBitmapFadeOut() method
' Task.Run(Function() SelectBitmapFadeOut(Me.LayerBitmap))
End If
Me.TopMost = True
End Sub
Private Sub SelectBitmap(bitmap As Bitmap)
NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, 255)
End Sub
Private Async Function SelectBitmapFadeOut(bitmap As Bitmap) As Task
Dim fadeProgress As Integer = 255
For i = fadeProgress To 1 Step -1
BeginInvoke(New MethodInvoker(Sub() NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, fadeProgress)))
fadeProgress -= 1
Await Task.Delay(10)
Next
End Function
Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
If Not DesignMode Then parms.ExStyle = parms.ExStyle Or NativeMethods.WS_EX_LAYERED
Return parms
End Get
End Property
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = NativeMethods.WM_NCHITTEST Then
m.Result = New IntPtr(NativeMethods.HTCAPTION)
Else
MyBase.WndProc(m)
End If
End Sub
End Class
NativeMethods
支持class:
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Friend Class NativeMethods
Public Const HTCAPTION As Integer = &H2
Public Const WM_PAINT = &HF
Public Const WM_NCHITTEST As Integer = &H84
Public Const WS_EX_LAYERED As Integer = &H80000
Public Const AC_SRC_OVER As Byte = 0
Public Const AC_SRC_ALPHA As Byte = 1
<Flags>
Friend Enum ULWFlags
ULW_COLORKEY = &H1
ULW_ALPHA = &H2
ULW_OPAQUE = &H4
ULW_EX_NORESIZE = &H8
End Enum
<StructLayout(LayoutKind.Sequential)>
Friend Structure POINT
Public x As Integer
Public y As Integer
Public Sub New(X As Integer, Y As Integer)
Me.x = X
Me.y = Y
End Sub
End Structure
<StructLayout(LayoutKind.Sequential)>
Friend Structure SIZE
Public cx As Integer
Public cy As Integer
Public Sub New(cX As Integer, cY As Integer)
Me.cx = cX
Me.cy = cY
End Sub
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)>
Friend Structure ARGB
Public Blue As Byte
Public Green As Byte
Public Red As Byte
Public Alpha As Byte
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)>
Friend Structure BLENDFUNCTION
Public BlendOp As Byte
Public BlendFlags As Byte
Public SourceConstantAlpha As Byte
Public AlphaFormat As Byte
End Structure
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function UpdateLayeredWindow(hWnd As IntPtr, hdcDst As IntPtr, ByRef pptDst As POINT,
ByRef psize As SIZE, hdcSrc As IntPtr, ByRef pprSrc As POINT, crKey As Integer,
ByRef pblend As BLENDFUNCTION, dwFlags As ULWFlags) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SetLayeredWindowAttributes(hWnd As IntPtr, crKey As Integer,
bAlpha As Byte, dwFlags As ULWFlags) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function ReleaseDC(hWnd As IntPtr, hDC As IntPtr) As Integer
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function CreateCompatibleDC(hDC As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function GetDC(hWnd As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteDC(hdc As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SelectObject(hDC As IntPtr, hObject As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteObject(hObject As IntPtr) As Boolean
End Function
Public Shared Sub SelectBitmapToLayeredWindow(form As Form, bitmap As Bitmap, opacity As Integer)
If bitmap.PixelFormat <> PixelFormat.Format32bppArgb Then
Throw New ApplicationException("The bitmap must be 32bpp with alpha-channel.")
End If
Dim screenDc As IntPtr = GetDC(IntPtr.Zero)
Dim sourceDc As IntPtr = CreateCompatibleDC(screenDc)
Dim hBitmap As IntPtr = IntPtr.Zero
Dim hOldBitmap As IntPtr = IntPtr.Zero
Try
' Get handle to the New bitmap and select it into the current device context.
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0))
hOldBitmap = SelectObject(sourceDc, hBitmap)
Dim windowLocation As New POINT(form.Left, form.Top)
Dim windowSize As New SIZE(bitmap.Width, bitmap.Height)
Dim sourceLocation As New POINT(0, 0)
Dim blend As New BLENDFUNCTION() With {
.BlendOp = AC_SRC_OVER,
.BlendFlags = 0,
.SourceConstantAlpha = CType(opacity, Byte),
.AlphaFormat = AC_SRC_ALPHA
}
' Update the window.
' Handle => Handle to the layered window
' screenDc => Handle to the screen DC
' windowLocation => Screen position of the layered window
' windowSize => SIZE of the layered window
' sourceDc => Handle to the layered window surface DC
' sourceLocation => Location of the layer in the DC
' 0 => Color key of the layered window
' blend => Transparency of the layered window
' ULW_ALPHA => Use blend as the blend function
UpdateLayeredWindow(form.Handle, screenDc, windowLocation, windowSize,
sourceDc, sourceLocation, 0, blend, ULWFlags.ULW_ALPHA)
Finally
' Release device context.
ReleaseDC(IntPtr.Zero, screenDc)
If hBitmap <> IntPtr.Zero Then
SelectObject(sourceDc, hOldBitmap)
DeleteObject(hBitmap)
End If
DeleteDC(sourceDc)
End Try
End Sub
End Class
您可以从 Google 驱动器下载 Sample Project。
使用 .Net Framework 4.7.2 构建 - 任何其他 4.5.2+ 框架都可以
当使用 TransparencyKey
制作透明表格并将具有透明像素的 PNG 作为背景图像时,PNG 图像的大部分图像轮廓(特别是颜色也用于设置表单透明度键...在我的例子中是洋红色)。
我也试了几个小时想出一个解决方案,但找不到。
几乎起作用的是使用具有不同 TransparencyKey
颜色的多个表单,如果像素与其他表单之一的颜色或位置不匹配,则逐个像素地匹配表单,如果存在则被排除是在新表格上逐像素写入的匹配项。
它并不完美,但已经非常接近了。然而,这种方法实际上花费了 2.5 小时,这对于要处理的小徽标来说太长了。
我该如何解决这个问题?
此代码是对此处找到的代码的翻译(略有解释):
最初来自 Microsoft 样本代码库(至少在他们杀死它之前是这样)。
当表单呈现透明时,将其 TransparencyKey
设置为与 BackGroundColor
[=88= 相同的颜色] 然后在窗体的透明表面上绘制一个半透明的位图,位图的抗锯齿部分不会与窗体后面的任何东西混合。
用作TransparencyKey
的Color可能会影响渲染结果,但是半透明像素(尤其是靠近Bitmap边缘的像素)在不同的背景上总是可见的,因为没有混合。
为了解决这个问题,我们可以建立一个Layered Window:
The system automatically composes and repaints layered windows and the windows of underlying applications. As a result, layered windows are rendered smoothly, without the flickering typical of complex window regions. In addition, layered windows can be partially translucent, that is, alpha-blended.
要创建分层表单,我们可以设置 WS_EX_LAYERED
extended style overriding the Form's CreateParams 属性:
Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
parms.ExStyle = parms.ExStyle Or WS_EX_LAYERED
Return parms
End Get
End Property
Windows 8+:WS_EX_LAYERED
样式支持顶级 windows 和子 windows。以前的 Windows 版本仅支持顶级 windows.
要绘制一个可以与背景融为一体的位图,我们select一个位图到Window设备上下文中,然后调用UpdateLayeredWindow, specifying the type of rendering using a BLENDFUNCTION
结构。
此结构允许定义 (BlendOp
) 源位图和目标位图如何混合(实际上,唯一可能的操作是 Source Over,AC_SRC_OVER
),应用于源位图的不透明度级别(SourceConstantAlpha
:255
= 不透明,0
= 完全透明) 以及如何解释源位图和目标位图的颜色 (AlphaFormat
)。
在这里,我们要混合具有 Alpha 通道(每像素 alpha)的源位图,因此它是半透明的:我们将 AC_SRC_ALPHA
指定为AlphaFormat
(请参阅有关如何根据源位图的颜色类型解释颜色混合的文档)。
就是这样。
要构建分层表单,请将新表单添加到项目中,如图所示更改构造函数,添加 CreateParams
覆盖,如果可以拖动表单则添加 WndProc
覆盖和 SelectBitmap()
方法调用,它激活源位图(在构造函数中传递)和窗体的 DC 的 alpha 混合。
此外,将 NativeMethods
支持 class 添加到项目中:
► 可以像往常一样创建 Form,在本例中将 Bitmap 对象传递给它的构造函数:
(位图格式必须是 32 位 ARGB
- 带 alpha 通道的 PNG
即可)
Dim layeredForm As New PerPixelAlphaLayeredForm(bitmap)
layeredForm.Show()
Public Class PerPixelAlphaLayeredForm
Public Sub New()
Me.New(Nothing)
End Sub
Public Sub New(bitmap As Bitmap)
InitializeComponent()
Me.LayerBitmap = bitmap
End Sub
Private ReadOnly Property LayerBitmap As Bitmap
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
If Me.LayerBitmap IsNot Nothing Then
Me.ClientSize = Me.LayerBitmap.Size
Dim screenSize = Screen.FromHandle(Me.Handle).Bounds.Size
Me.Location = New Point((screenSize.Width - Me.Width) \ 2, (screenSize.Height - Me.Height) \ 2)
SelectBitmap(Me.LayerBitmap)
' Or, call the SelectBitmapFadeOut() method
' Task.Run(Function() SelectBitmapFadeOut(Me.LayerBitmap))
End If
Me.TopMost = True
End Sub
Private Sub SelectBitmap(bitmap As Bitmap)
NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, 255)
End Sub
Private Async Function SelectBitmapFadeOut(bitmap As Bitmap) As Task
Dim fadeProgress As Integer = 255
For i = fadeProgress To 1 Step -1
BeginInvoke(New MethodInvoker(Sub() NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, fadeProgress)))
fadeProgress -= 1
Await Task.Delay(10)
Next
End Function
Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
If Not DesignMode Then parms.ExStyle = parms.ExStyle Or NativeMethods.WS_EX_LAYERED
Return parms
End Get
End Property
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = NativeMethods.WM_NCHITTEST Then
m.Result = New IntPtr(NativeMethods.HTCAPTION)
Else
MyBase.WndProc(m)
End If
End Sub
End Class
NativeMethods
支持class:
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Friend Class NativeMethods
Public Const HTCAPTION As Integer = &H2
Public Const WM_PAINT = &HF
Public Const WM_NCHITTEST As Integer = &H84
Public Const WS_EX_LAYERED As Integer = &H80000
Public Const AC_SRC_OVER As Byte = 0
Public Const AC_SRC_ALPHA As Byte = 1
<Flags>
Friend Enum ULWFlags
ULW_COLORKEY = &H1
ULW_ALPHA = &H2
ULW_OPAQUE = &H4
ULW_EX_NORESIZE = &H8
End Enum
<StructLayout(LayoutKind.Sequential)>
Friend Structure POINT
Public x As Integer
Public y As Integer
Public Sub New(X As Integer, Y As Integer)
Me.x = X
Me.y = Y
End Sub
End Structure
<StructLayout(LayoutKind.Sequential)>
Friend Structure SIZE
Public cx As Integer
Public cy As Integer
Public Sub New(cX As Integer, cY As Integer)
Me.cx = cX
Me.cy = cY
End Sub
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)>
Friend Structure ARGB
Public Blue As Byte
Public Green As Byte
Public Red As Byte
Public Alpha As Byte
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)>
Friend Structure BLENDFUNCTION
Public BlendOp As Byte
Public BlendFlags As Byte
Public SourceConstantAlpha As Byte
Public AlphaFormat As Byte
End Structure
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function UpdateLayeredWindow(hWnd As IntPtr, hdcDst As IntPtr, ByRef pptDst As POINT,
ByRef psize As SIZE, hdcSrc As IntPtr, ByRef pprSrc As POINT, crKey As Integer,
ByRef pblend As BLENDFUNCTION, dwFlags As ULWFlags) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SetLayeredWindowAttributes(hWnd As IntPtr, crKey As Integer,
bAlpha As Byte, dwFlags As ULWFlags) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function ReleaseDC(hWnd As IntPtr, hDC As IntPtr) As Integer
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function CreateCompatibleDC(hDC As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function GetDC(hWnd As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteDC(hdc As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SelectObject(hDC As IntPtr, hObject As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteObject(hObject As IntPtr) As Boolean
End Function
Public Shared Sub SelectBitmapToLayeredWindow(form As Form, bitmap As Bitmap, opacity As Integer)
If bitmap.PixelFormat <> PixelFormat.Format32bppArgb Then
Throw New ApplicationException("The bitmap must be 32bpp with alpha-channel.")
End If
Dim screenDc As IntPtr = GetDC(IntPtr.Zero)
Dim sourceDc As IntPtr = CreateCompatibleDC(screenDc)
Dim hBitmap As IntPtr = IntPtr.Zero
Dim hOldBitmap As IntPtr = IntPtr.Zero
Try
' Get handle to the New bitmap and select it into the current device context.
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0))
hOldBitmap = SelectObject(sourceDc, hBitmap)
Dim windowLocation As New POINT(form.Left, form.Top)
Dim windowSize As New SIZE(bitmap.Width, bitmap.Height)
Dim sourceLocation As New POINT(0, 0)
Dim blend As New BLENDFUNCTION() With {
.BlendOp = AC_SRC_OVER,
.BlendFlags = 0,
.SourceConstantAlpha = CType(opacity, Byte),
.AlphaFormat = AC_SRC_ALPHA
}
' Update the window.
' Handle => Handle to the layered window
' screenDc => Handle to the screen DC
' windowLocation => Screen position of the layered window
' windowSize => SIZE of the layered window
' sourceDc => Handle to the layered window surface DC
' sourceLocation => Location of the layer in the DC
' 0 => Color key of the layered window
' blend => Transparency of the layered window
' ULW_ALPHA => Use blend as the blend function
UpdateLayeredWindow(form.Handle, screenDc, windowLocation, windowSize,
sourceDc, sourceLocation, 0, blend, ULWFlags.ULW_ALPHA)
Finally
' Release device context.
ReleaseDC(IntPtr.Zero, screenDc)
If hBitmap <> IntPtr.Zero Then
SelectObject(sourceDc, hOldBitmap)
DeleteObject(hBitmap)
End If
DeleteDC(sourceDc)
End Try
End Sub
End Class
您可以从 Google 驱动器下载 Sample Project。
使用 .Net Framework 4.7.2 构建 - 任何其他 4.5.2+ 框架都可以