D2DIPsToPixels, D2PixelsToDIP function

Requires: Direct2D.lg32

Purpose

Converts physical pixels to Direct2D's DIP coordinate system (and vice cersa) for the current render target or Me.

Syntax

Dip! = D2PixelsToDIP(Pixels!)
Pixels! = D2DIPsToPixel(Dip!)

Pixels!, Dip!: float expression

Description

On high-resolution displays the number of pixels do not match the number of DIPs (device independent pixels <=> 1/96 inch) as it does on a regular 96 dots-per-inch display. This not a problem with a dpi-unaware program; Windows makes sure the program's coordinate system matches the 96 dots-per-inch coordinate system and there is no need to convert physical pixels to DIPs. When the program is dpi-aware the program uses the display's full Dpi as a coordinate system. Mouse input is provided in the display's Dpi. D2PixelsToDIP helps in converting the physical pixel value to Direct2D's DIP coordinate system, while D2DIPsToPixel performs the reverse conversion.

Example

'

' Samples\Direct2D\D2PixelsToDIP.g32

' (dpi-aware)

'

$Library "direct2d"

$Library "gfawinx"

DpiAwareness()

Global Object Win1RT

OpenW 1, 0, 0, 320, 260, ~15

FormScaleForDpi Me

Set Win1RT = D2GetRT()

Do

Sleep

Until Me Is Nothing

 

Sub Win_1_Paint

D2BeginDraw Win1RT

D2Clear D2C_Yellow

D2Text 50, 50, "Move mouse over window"

D2EndDraw

EndSub

 

Sub Win_1_ReSize

D2ResizeRT Win1RT, _X, _Y

 

Sub Win_1_MouseMove(Button&, Shift&, x!, y!)    ' x,y in ScaleWidth/Height-units

x! = x! * ScaleMX, y! = y! * ScaleMY          ' to pixels

D2BeginDraw Win1RT

D2Plot D2PixelsToDIP(x!), D2PixelsToDIP(y!)   ' pixels to D2-DIP

D2EndDraw

EndSub

 

Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)

Local Int dpi

If Mess% == WM_DPICHANGED

If FormScaleForDpi(Me, wParam%, lParam%' scale (resize) form and it's Ocx-es

' Here: Update other Dpi dependent resources (toolbar, bitmaps, Direct2D)

dpi = LoWord(wParam%)             ' new Dpi for form

D2SetDpiRT Win1RT, dpi            ' update D2 rendertarget's dpi

EndIf

retval% = 0 : ValidRet? = True    ' tell OS we handled it

EndIf

EndSub

 

Function FormScaleForDpi(ByVal frm As Form, Optional newdpi As Int, Optional lParam As Int) As Bool

Local i As Int, c As Control, rc As RECT, prevDpi As Int, newrc As Pointer To RECT, _

tb As Tab, w As Int, h As Int

With frm

' Set initial newdpi for the form and the scale factors for a LoadForm

If .WhatsThisHelpID == 0

If Exist(":{" + .Name)                    ' a Form-Editor form

.WhatsThisHelpID = WinDpi(0)            ' initial dpi is system newdpi

.ScaleWidth = .ScaleWidth * 96 / .WhatsThisHelpID   ' set initial scale factors

.ScaleHeight = .ScaleHeight * 96 / .WhatsThisHelpID

Else                                      ' created by OpenW or Form command

.WhatsThisHelpID = 96                   ' coord space is 96

EndIf

EndIf

prevDpi = .WhatsThisHelpID

newdpi = LoWord(newdpi)

If newdpi == 0 Then newdpi = WinDpi(.hWnd)

Exit Func If newdpi == prevDpi              ' don't continue if equal

' Size the form, except if it is a child window

If !(GetWindowLong(.hWnd, GWL_STYLE) %& WS_CHILD)

If lParam                                 ' size to the OS suggested size

Pointer newrc = lParam                  ' ptr to RECT in lParam

SizeW .hWnd, newrc.Right - newrc.Left, newrc.Bottom - newrc.Top

Else                                      ' scale based on the current client size

GetClientSize .hWnd, w, h               ' can't use _X _Y, they are for ME only

AdjustW .hWnd, Scale(w, newdpi, prevDpi), Scale(h, newdpi, prevDpi)

EndIf

EndIf

.FontSize = .FontSize * newdpi / prevDpi

.ScaleWidth = .ScaleWidth * prevDpi / newdpi

.ScaleHeight = .ScaleHeight * prevDpi / newdpi

' Scale position and size of the Ocx windowed controls

For Each c In frm.Controls

If !(TypeOf(c) Is ImageList || TypeOf(c) Is Timer || _

TypeOf(c) Is CommDlg || TypeOf(c) Is TrayIcon' not the windowless OCX-es

' First set fontsize, but not all OCX-es have a Font property

If !(TypeOf(c) Is ProgressBar || TypeOf(c) Is Slider || TypeOf(c) Is Image || TypeOf(c) Is RichEdit)

c.fontsize = c.fontsize * newdpi / prevDpi

ElseIf TypeOf(c) Is RichEdit            ' Requires LoadRichEdit50W!

SendMessage c.hwnd, WM_DPICHANGED, MakeLong(newdpi, newdpi), 0

EndIf

' Set new position and size for OCX-controls

GetWinPos(c.hwnd, rc)                   ' relative to parent

~SetWindowPos(c.hwnd, 0, _

Scale(rc.Left, newdpi, prevDpi), Scale(rc.Top, newdpi, prevDpi), _

Scale(rc.Right - rc.Left, newdpi, prevDpi), _

Scale(rc.Bottom - rc.Top, newdpi, prevDpi),  SWP_NOZORDER) ' new pos and size

If TypeOf(c) Is Form                    ' Scale OCX-es of a child Form Ocx

c.WhatsThisHelpID = prevDpi

~FormScaleForDpi(c, newdpi)

ElseIf TypeOf(c) Is TabStrip            ' Scale TabStrip's attached forms

For Each tb In c.Tabs

If TypeOf(tb.Ocx) Is Form

tb.Ocx.WhatsThisHelpID = prevDpi

~FormScaleForDpi(tb.Ocx, newdpi)

EndIf

Next

EndIf

EndIf

Next

.WhatsThisHelpID = newdpi     ' store new newdpi

EndWith

FormScaleForDpi = True

EndFunc

Remarks

By default, D2PixelsToDIP calculates the DIPs for the current render target. The app has to make sure the render target for the window is properly activated (D2GetRT, D2SetRT, or D2BeginDraw). However, if D2PixelsToDIP is executed without setting the current render target, it uses the window's DPI for Me. This is possible because the render target's DPI value is in sync with the window's dpi when the WM_DPICHANGE is properly handled. In a dpi-aware application WM_DPICHANGE is sent when the DPI for the window has changed. The program responds by setting the render target's DPI to the new DPI of the window.

Each time D2PixelsToDIP executes it requests the Dpi values from the render target (D2GetDpiRT) or WindDpi(Me) and uses the Dpi value in the formula: pixels * (Dpi/96). To improve performance the application may keep track of the render target's Dpi itself - using D2GetDpi - and execute the formula directly in the code.

See Also

D2GetRT, D2SetRT, D2BeginDraw, D2GetSizeRT, D2AdjustW, D2SetDpiRT

{Created by Sjouke Hamstra; Last updated: 03/03/2022 by James Gaite}