@@ -42,8 +42,10 @@ Attribute VB_Exposed = False
4242'PhotoDemon Splash Screen
4343'Copyright 2001-2020 by Tanner Helland
4444'Created: 15/April/01
45- 'Last updated: 10/June/20
46- 'Last update: rewrite as a proper layered window with alpha effects
45+ 'Last updated: 18/December/20
46+ 'Last update: remove GDI+ font code from splash rendering; GDI+ font creation functions have
47+ ' unpredictable perf impacts, and it's reliably faster to use GDI on 24-bpp surfaces
48+ ' (then produce a 32-bpp copy ourselves)
4749'
4850'Unless otherwise noted, all source code in this file is shared under a simplified BSD license.
4951' Full license details are available in the LICENSE.md file, or at https://photodemon.org/license/
@@ -57,20 +59,6 @@ Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, ByRef l
5759Private Declare Function SetWindowLong Lib "user32 " Alias "SetWindowLongA " (ByVal hWnd As Long , ByVal nIndex As Long , ByVal dwNewLong As Long ) As Long
5860Private Declare Function UpdateLayeredWindow Lib "user32 " (ByVal hWnd As Long , ByVal hdcDst As Long , ByVal pptDst As Long , ByVal psize As Long , ByVal hdcSrc As Long , ByVal pptSrc As Long , ByVal crKey As Long , ByVal pblend As Long , ByVal dwFlags As Long ) As Long
5961
60- 'Unfortunately, GDI doesn't render text onto 32-bpp surfaces correctly (the alpha channel gets ignored).
61- ' We need to use a more convoluted GDI+ approach, and because I don't have a safe wrapper class for
62- ' GDI+ font rendering, everything gets called manually.
63- 'Private Declare Function GdipCreateFont Lib "gdiplus" (ByVal srcFontFamily As Long, ByVal srcFontSize As Single, ByVal srcFontStyle As Long, ByVal srcMeasurementUnit As Long, ByRef dstCreatedFont As Long) As GP_Result
64- 'Private Declare Function GdipCreateFontFamilyFromName Lib "gdiplus" (ByVal ptrToSrcFontName As Long, ByVal srcFontCollection As Long, ByRef dstFontFamily As Long) As GP_Result
65- Private Declare Function GdipCreateFontFromDC Lib "gdiplus " (ByVal srcDC As Long , ByRef dstCreatedFont As Long ) As GP_Result
66- Private Declare Function GdipCreateStringFormat Lib "gdiplus " (ByVal formatAttributes As Long , ByVal srcLanguage As Long , ByRef dstStringFormat As Long ) As GP_Result
67- Private Declare Function GdipDeleteFont Lib "gdiplus " (ByVal srcFont As Long ) As GP_Result
68- 'Private Declare Function GdipDeleteFontFamily Lib "gdiplus" (ByVal srcFontFamily As Long) As GP_Result
69- Private Declare Function GdipDeleteStringFormat Lib "gdiplus " (ByVal srcStringFormat As Long ) As GP_Result
70- Private Declare Function GdipDrawString Lib "gdiplus " (ByVal dstGraphics As Long , ByVal srcStringPtr As Long , ByVal strLength As Long , ByVal gdipFontHandle As Long , ByRef layoutRect As RectF , ByVal gdipStringFormat As Long , ByVal gdipBrush As Long ) As GP_Result
71- Private Declare Function GdipSetStringFormatAlign Lib "gdiplus " (ByVal dstStringFormat As Long , ByVal newAlignment As Long ) As GP_Result
72- Private Declare Function GdipSetTextRenderingHint Lib "gdiplus " (ByVal dstGraphics As Long , ByVal newRenderHintMode As Long ) As GP_Result
73-
7462'A logo, drop shadow and screen backdrop are used to generate the splash. These DIBs are released once m_splashDIB (below)
7563' has been successfully assembled.
7664Private m_logoDIB As pdDIB , m_shadowDIB As pdDIB
@@ -162,92 +150,55 @@ Public Sub PrepareRestOfSplash()
162150 Dim logoFontName As String
163151 If OS.IsVistaOrLater Then logoFontName = "Segoe UI" Else logoFontName = "Tahoma"
164152
165- 'Next, we need to create various GDI+ font objects. These may fail (particularly on
166- ' non-standard configs like Wine), so abandon font rendering if anything goes badly.
167- Dim fontOK As Boolean , gpFontHandle As Long
168-
169- 'YIKES! For ages I have used the normal GDI+ pathway of GdipCreateFontFamily > GdipCreateFont,
170- ' but after experiencing random startup stutters on Win 10, I profiled and tracked down the
171- ' CreateFontFamily call as the culprit. It occasionally takes hundreds of ms, with occasional
172- ' lurches above ONE SECOND for that single function call! I have no idea when this changed,
173- ' but I've got a reliable workaround - turns out it's much much MUCH faster (e.g. < 10 ms vs
174- ' 500+ ms) to initialize a GDI font, attach it to a GDI DC, then indirectly create a GDI+ font
175- ' from said DC. Go figure.
176-
177- 'Original code here in case I ever need to revert this approach:
178- 'Dim gpFontFamily As Long
179- 'fontOK = (GdipCreateFontFamilyFromName(StrPtr(logoFontName), 0&, gpFontFamily) = GP_OK)
180- 'Const GP_FONTBOLD As Long = 1
181- 'If fontOK Then fontOK = (GdipCreateFont(gpFontFamily, logoFontSize, GP_FONTBOLD, GP_U_Point, gpFontHandle) = GP_OK)
153+ 'Font color varies by build version.
154+ ' (As a convenience, non-production builds are tagged RED; normal builds, BLUE.)
155+ Dim logoFontColor As Long
156+ If (PD_BUILD_QUALITY <> PD_PRODUCTION) Then logoFontColor = RGB(255 , 50 , 50 ) Else logoFontColor = RGB(50 , 127 , 255 )
182157
183- 'New approach: use pdFont to create a font, because it's ~50x faster!
158+ 'Create a GDI font with the desired settings
184159 Dim tmpFont As pdFont
185160 Set tmpFont = New pdFont
186161 tmpFont.SetFontFace logoFontName
187162 tmpFont.SetFontSize logoFontSize
188163 tmpFont.SetFontBold True
189164 tmpFont.CreateFontObject
190- tmpFont.AttachToDC m_splashDIB.GetDIBDC
191- fontOK = (GdipCreateFontFromDC(m_splashDIB.GetDIBDC, gpFontHandle) = GP_OK)
192165
193- 'Kill the temporary GDI font copy
166+ 'Assemble the current version and description strings
167+ Dim versionString As String
168+ versionString = Trim$(g_Language.TranslateMessage("version %1" , Updates.GetPhotoDemonVersion()))
169+
170+ 'Create a dummy 24-bpp DIB and paint the version to it. (This is required for reliable
171+ ' antialiasing behavior; GDI can't render text to 32-bpp surfaces if antialiasing is active,
172+ ' so we need to paitn to a 24-bpp surface, then upsample to 32-bpp and fill in alpha manually.)
173+ Dim fntWidth As Long , fntHeight As Long
174+ fntWidth = tmpFont.GetWidthOfString(versionString)
175+ fntHeight = tmpFont.GetHeightOfString(versionString)
176+
177+ 'Create a temporary 24-bpp target for the text. (We'll use white-on-black text to
178+ ' simplify the process of manually upsampling to 32-bpp.)
179+ Dim tmpDIB As pdDIB
180+ Set tmpDIB = New pdDIB
181+ tmpDIB.CreateBlank fntWidth, fntHeight, 24 , vbBlack
182+
183+ 'Paint the version string
184+ tmpFont.SetFontColor vbWhite
185+ tmpFont.AttachToDC tmpDIB.GetDIBDC
186+ tmpFont.FastRenderText 0 , 0 , versionString
194187 tmpFont.ReleaseFromDC
195188 Set tmpFont = Nothing
196189
197- 'Next, we need a text formatter. Minimal options are used, but we do need right-alignment
198- Dim gpStringFormat As Long
199- If fontOK Then fontOK = (GdipCreateStringFormat(0 &, 0 &, gpStringFormat) = GP_OK)
200-
201- Const GP_STRINGALIGNFAR As Long = 2
202- If fontOK Then fontOK = (GdipSetStringFormatAlign(gpStringFormat, GP_STRINGALIGNFAR) = GP_OK)
203-
204- 'Everything else uses prebuilt PD classes
205- Dim cSurface As pd2DSurface , cBrush As pd2DBrush
190+ 'Convert the temporary DIB to 32-bpp, using the grayscale channel as the alpha guide
191+ tmpDIB.ConvertTo32bpp
192+ Dim grayArray() As Byte
193+ ReDim grayArray(0 To fntWidth - 1 , 0 To fntHeight - 1 ) As Byte
194+ DIBs.GetDIBGrayscaleMap tmpDIB, grayArray, False
206195
207- 'If we created everything correctly, render version text
208- If fontOK And (m_splashDIB.GetDIBDC <> 0 ) Then
209-
210- 'Next, we need a layout rect
211- Dim fontRect As RectF
212- fontRect.Top = pdLogoBottom + Interface.FixDPI(8 )
213- fontRect.Left = 0
214- fontRect.Width = pdLogoRight
215- fontRect.Height = fontRect.Top + 100
216-
217- 'Assemble the current version and description strings
218- Dim versionString As String
219- versionString = Trim$(g_Language.TranslateMessage("version %1" , Updates.GetPhotoDemonVersion()))
220-
221- 'Wrap a GDI+ surface around the destination DIB
222- Set cSurface = New pd2DSurface
223- cSurface.WrapSurfaceAroundPDDIB m_splashDIB
224-
225- 'Activate grayscale antialiasing + hinting
226- Const GP_TextRenderingHintAntiAliasGridFit As Long = 3
227- GdipSetTextRenderingHint cSurface.GetHandle, GP_TextRenderingHintAntiAliasGridFit
228-
229- 'Next, font color.
230- ' (As a convenience, non-production builds are tagged RED; normal builds, BLUE.)
231- Dim logoFontColor As Long
232- If (PD_BUILD_QUALITY <> PD_PRODUCTION) Then logoFontColor = RGB(255 , 50 , 50 ) Else logoFontColor = RGB(50 , 127 , 255 )
233-
234- 'Text gets painted by a stock GDI+ brush
235- Set cBrush = New pd2DBrush
236- cBrush.SetBrushColor logoFontColor
237-
238- 'Render the finished text!
239- GdipDrawString cSurface.GetHandle, StrPtr(versionString), Len(versionString), gpFontHandle, fontRect, gpStringFormat, cBrush.GetHandle
240-
241- End If
242-
243- 'Free various font and string objects
244- Set cBrush = Nothing
245- Set cSurface = Nothing
246- If (gpStringFormat <> 0 ) Then GdipDeleteStringFormat gpStringFormat
247- If (gpFontHandle <> 0 ) Then GdipDeleteFont gpFontHandle
196+ tmpDIB.CreateBlank fntWidth, fntHeight, 32 , logoFontColor, 255
197+ DIBs.ApplyTransparencyTable tmpDIB, grayArray
198+ tmpDIB.SetAlphaPremultiplication True
248199
249- 'A GDI+ font family object is no longer allocated; see above comments for details
250- 'If (gpFontFamily <> 0) Then GdipDeleteFontFamily gpFontFamily
200+ 'Paint the final 32-bpp version image onto the splash screen
201+ tmpDIB.AlphaBlendToDC m_splashDIB.GetDIBDC, dstX:=pdLogoRight - fntWidth, dstY:=pdLogoBottom + Interface.FixDPI( 8 )
251202
252203 'We now have a back buffer with everything the splash screen requires
253204 ' (except the progress bar, which will be drawn later). Create a front buffer
0 commit comments