表单的 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),应用于源位图的不透明度级别(SourceConstantAlpha255 = 不透明,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+ 框架都可以