Form's TransparencyKey leaves ghastly colored edging [closed]
When the TransparencyKey
is used to make a transparent Form and a PNG with transparent pixels is placed as the background image, the PNG image has an unsightly colored edge along most of its image outline (notably the color used to set the forms transparency key too... In my case Magenta).
I have tried for hours too come up with a solution but can't find one.
What did almost work was to use multiple Forms with different TransparencyKey
colors and match the Forms pixel by pixel if the pixel was not matched by color or position in one of the other Forms it was excluded if there was a match it was written pixel by pixel on a new Form.
It wasn't perfect but came very close. However this method took literally 2.5 hours which is way too long for a small logo to be processed.
How can I fix this issue?
Solution 1:
This code is a translation (with minor interpretations) of the code found here:
Windows Form Transparent Background Image.
Originally from the Microsoft samples code-base (at least it was, before they killed it).
When a Form is rendered transparent, setting its TransparencyKey
to the same Color used as the BackGroundColor
and then draw a semi-transparent Bitmap on the transparent surface of the Form, the anti-aliased parts of the Bitmap are not blended with whatever there is behind the Form.
The Color used as TransparencyKey
may affect the rendering result, but the semi-transparent pixels (especially the pixels near the edges of the Bitmap) will always be visible on the different backgrounds, since there's no blending.
To solve the problem, we can build a 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.
To create a Layered Form, we can set the WS_EX_LAYERED
extended style overriding the Form's CreateParams property:
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+: The WS_EX_LAYERED
style is supported for top-level windows and child windows. Previous Windows versions support this style only for top-level windows.
To draw a Bitmap that can blend with the background, we select a Bitmap into the Window Device Context, then call UpdateLayeredWindow, specifying the type of rendering using a BLENDFUNCTION
structure.
This structure allows to define (BlendOp
) how the source and destination Bitmaps are blended (actually, the only possible operation is Source Over, AC_SRC_OVER
), the level of opacity applied to the source Bitmap (SourceConstantAlpha
: 255
= opaque, 0
= fully transparent) and how the Colors of the source and destination bitmaps are interpreted (AlphaFormat
).
Here, we want to blend a source Bitmap that has an Alpha Channel (per-pixel alpha), so it's semi-transparent: we specify AC_SRC_ALPHA
as the AlphaFormat
(see the Docs about how Color blending is interpreted based on the Color type of the source bItmap).
That's all.
To build a Layered Form, add a new Form to a Project, change the Constructor as shown here, add the CreateParams
override, the WndProc
override if the Form can be moved dragging it and the SelectBitmap()
method call, which activates the alpha blending of the source Bitmap (passed in the constructor) and the Form's DC.
Also, add the NativeMethods
support class to the Project:
► The Form can be created as usual, in this case passing a Bitmap object to its constructor:
(the Bitmap format must be a 32bit ARGB
- a PNG
with alpha channel will do)
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
support 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
You can download a Sample Project from Google Drive.
Built with .Net Framework 4.7.2 - any other Framework, 4.5.2+, will do