天天看點

Windows桌面應用程式(1-2-4-7th) DPI和裝置無關的像素

要使用Windows圖形進行有效程式設計,您必須了解兩個相關的概念:

  • 每英寸點數(DPI)
  • 裝置無關像素(DIP)。

我們從DPI開始。這将需要短暫的繞行排版。在印刷術中,類型的大小以稱為點的機關來測量。一點等于1/72英寸。

1點=1/72英寸 1 點 = 1 / 72 英 寸

注意 這是桌面出版的定義。曆史上,一個點的具體措施已經變化。

例如,12點字型被設計成适合1/6”(12/72)行的文本。顯然,這并不意味着字型中的每個字元恰好是1/6”高。實際上,某些字元可能會比1/6”更高,例如,在許多字型中,字元Å比字型标稱高度要高,為了正确顯示,字型之間需要一些額外的空間,這個空間是稱為領先。

下圖顯示了一個72磅字型。實線在文本周圍顯示1”高的邊界框,虛線稱為基線,字型中的大部分字元位于基線上,字型的高度包括基線上方的部分(上升)和基線以下的部分(下降)。在這裡顯示的字型中,上升56點,下降16點。

Windows桌面應用程式(1-2-4-7th) DPI和裝置無關的像素

顯示72點字型的插圖。

但是,當涉及到計算機顯示器時,測量文字大小是有問題的,因為像素的大小不盡相同。像素的大小取決于兩個因素:顯示器分辨率和顯示器的實體尺寸。是以,實體英寸不是一個有用的度量,因為實體英寸和像素之間沒有固定的關系。相反,字型是以邏輯機關來衡量的。72點字型被定義為一個邏輯英寸高。邏輯英寸然後被轉換為像素。多年來,Windows使用以下轉換:一個邏輯英寸等于96像素。使用這個縮放因子,72點字型被渲染成96像素高。12點字型是16像素高。

12點=12/72邏輯英寸=1/6邏輯英寸=96/6像素=16像素 12 點 = 12 / 72 邏 輯 英 寸 = 1 / 6 邏 輯 英 寸 = 96 / 6 像 素 = 16 像 素

這個比例因子被描述為每英寸96點(DPI)。術語圓點來源于印刷,其中油墨的實體點放在紙上。對于計算機顯示器來說,每邏輯英寸說96像素更準确,但術語DPI已經停滞。

由于實際像素大小不同,在一台螢幕上可讀的文本在其他螢幕上可能太小。另外,人們有不同的喜好——有些人喜歡較大的文字。是以,Windows使使用者可以更改DPI設定。例如,如果使用者将顯示設定為144 DPI,則72點字型的高度為144像素。标準DPI設定為100%(96 DPI),125%(120 DPI)和150%(144 DPI)。使用者也可以應用自定義設定。從Windows 7開始,DPI是按使用者設定的。

DWM縮放

如果一個程式沒有考慮DPI,在高DPI設定下可能會出現以下缺陷:

  • 剪裁的UI元素。
  • 不正确的布局。
  • 像素化的位圖和圖示。
  • 不正确的滑鼠坐标,這可能影響命中測試,拖放等等。

為了確定較舊的程式在高DPI設定下工作,DWM實作了有用的回退。如果某個程式未标記為DPI,則DWM将縮放整個UI以比對DPI設定。例如,在144 DPI時,UI縮放了150%,包括文本,圖形,控件和視窗大小。如果程式建立一個500×500視窗,視窗實際上顯示為750×750像素,視窗内容将相應地縮放。

這種行為意味着較舊的程式在高DPI設定下“正常工作”。但是,縮放也會導緻模糊的外觀,因為縮放是在視窗繪制之後應用的。

DPI感覺應用程式

為了避免DWM縮放,程式可以将自己标記為DPI感覺。這告訴DWM不要執行任何自動DPI縮放。所有新的應用程式都應設計為DPI感覺型,因為DPI感覺能夠在更高的DPI設定下改善UI的外觀。

一個程式通過其應用程式清單聲明自己的DPI。清單是一個簡單的描述DLL或應用程式的XML檔案。清單通常嵌入在可執行檔案中,盡管它可以作為單獨的檔案提供。 清單包含諸如DLL依賴關系,請求的特權級别以及該程式設計的Windows版本等資訊。

要聲明您的程式是支援DPI的,請在清單中包含以下資訊。

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
    <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
            <dpiAware>true</dpiAware>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>
           

此處顯示的清單僅僅是部厘清單,但Visual Studio連結程式會自動為您生成其他清單。要在項目中包含部厘清單,請在Visual Studio中執行以下步驟。

  1. 在項目菜單上,單擊屬性。
  2. 在左側窗格中,展開配置屬性,展開清單工具,然後單擊輸入和輸出。
  3. 在其他清單檔案文本框中,鍵入清單檔案的名稱,然後單擊确定。

通過将您的程式标記為支援DPI,您告訴DWM不要縮放您的應用程式視窗。現在,如果您建立500×500的視窗,無論使用者的DPI設定如何,視窗都将占用500×500像素。

GDI和DPI

GDI繪圖以像素為機關進行測量。這意味着如果您的程式被标記為支援DPI,并且您要求GDI繪制一個200×100的矩形,則得到的矩形将在螢幕上寬200像素,高100像素。但是,GDI字型大小被縮放到目前的DPI設定。換句話說,如果建立72磅字型,則96 DPI時的字型大小為96像素,144 DPI時為144像素。這是使用GDI以144 DPI呈現的72點字型。

Windows桌面應用程式(1-2-4-7th) DPI和裝置無關的像素

在GDI中顯示DPI字型縮放的圖表。

如果您的應用程式支援DPI并使用GDI進行繪制,則縮放所有繪圖坐标以比對DPI。

Direct2D和DPI

Direct2D自動執行縮放以比對DPI設定。在Direct2D中,坐标是以獨立于裝置的像素(DIP)為機關測量的。DIP被定義為邏輯英寸的1/96。在Direct2D中,所有繪圖操作都在DIP中指定,然後按比例縮放到目前的DPI設定。

DPI設定 DIP尺寸
96 1 像素
120 1.25 像素
144 1.5 像素

例如,如果使用者的DPI設定為144 DPI,并且要求Direct2D繪制200×100的矩形,則該矩形将為300×150個實體像素。此外,DirectWrite測量DIP中的字型大小,而不是點數。要建立12點字型,請指定16個DIP(12點=1/6邏輯英寸=96/6 DIP)。在螢幕上繪制文本時,Direct2D将DIP轉換為實體像素。該系統的好處是無論目前的DPI設定如何,測量機關對于文本和繪圖都是一緻的。

小心一點:滑鼠和視窗坐标仍然以實體像素給出,而不是DIP。例如,如果處理WM_LBUTTONDOWN消息滑鼠向下的位置以實體像素給出。要在該位置繪制一個點,必須将像素坐标轉換為DIP。

将實體像素轉換為DIP

從實體像素到DIP的轉換使用以下公式。

DIPs=像素/(DPI/96.0) D I P s = 像 素 / ( D P I / 96.0 )

要獲得DPI設定,請調用ID2D1Factory::GetDesktopDpi方法。DPI傳回為兩個浮點值,一個用于x軸,另一個用于y軸。從理論上講,這些價值可能不同。為每個軸計算一個單獨的比例因子。

float g_DPIScaleX=;
float g_DPIScaleY=;
void InitializeDPIScale(ID2D1Factory *pFactory){
    FLOAT dpiX,dpiY;
    pFactory->GetDesktopDpi(&dpiX,&dpiY);
    g_DPIScaleX=dpiX/;
    g_DPIScaleY=dpiY/;
}
template<typename T>
float PixelsToDipsX(T x){
    return static_cast<float>(x)/g_DPIScaleX;
}
template<typename T>
float PixelsToDipsY(T y){
    return static_cast<float>(y)/g_DPIScaleY;
}
           

如果您不使用Direct2D,則可以使用另一種方法擷取DPI設定:

void InitializeDPIScale(HWND hwnd){
    HDC hdc=GetDC(hwnd);
    g_DPIScaleX=GetDeviceCaps(hdc,LOGPIXELSX)/;
    g_DPIScaleY=GetDeviceCaps(hdc,LOGPIXELSY)/;
    ReleaseDC(hwnd,hdc);
}
           

調整渲染目标的大小

如果視窗大小發生變化,則必須調整渲染目标的大小以比對。在大多數情況下,您還需要更新布局并重新繪制視窗。以下代碼顯示了這些步驟。

void MainWindow::Resize(){
    if(pRenderTarget!=NULL){
        RECT rc;
        GetClientRect(m_hwnd,&rc);
        D2D1_SIZE_U size=D2D1::SizeU(rc.right,rc.bottom);
        pRenderTarget->Resize(size);
        CalculateLayout();
        InvalidateRect(m_hwnd,NULL,FALSE);
    }
}
           

GetClientRect函數以實體像素(不是DIP)擷取客戶區的新大小。 ID2D1HwndRenderTarget::Resize方法更新渲染目标的大小,也以像素指定。InvalidateRect函數通過将整個客戶區添加到視窗的更新區域來強制重繪。(請參閱第1單元的”繪制視窗“)

随着視窗的增長或縮小,您通常需要重新計算繪制的對象的位置。例如,在圓形程式中,必須更新半徑和中心點:

void MainWindow::CalculateLayout(){
    if(pRenderTarget!=NULL){
        D2D1_SIZE_F size=pRenderTarget->GetSize();
        const float x=size.width/;
        const float y=size.height/;
        const float radius=min(x,y);
        ellipse=D2D1::Ellipse(D2D1::Point2F(x,y),radius,radius);
    }
}
           

ID2D1RenderTarget::GetSize方法傳回DIP(非像素)渲染目标的大小,這是計算布局的合适機關。有一個密切相關的方法,ID2D1RenderTarget::GetPixelSize,傳回實體像素的大小。對于HWND呈現目标,此值與GetClientRect傳回的大小相比對。但請記住,繪圖是在DIP中執行的,而不是像素。

下一個

在Direct2D中使用顔色

原文連結:DPI and Device-Independent Pixels