本文說明實體像素與裝置獨立像素 (DIP) 之間的差異,以及在 Win2D 中處理 DPI (每英吋點數) 的方式。
Win2D 的設計方式是讓許多應用程式可以忽略此差異,因為它提供明智的預設行為,在 DPI 低和高的裝置上執行時,都能做出正確的動作。 如果您的應用程式有更特殊的需求,或您對「明智的預設」意義有不同的意見,請繼續閱讀冗長的詳細資料...
什麼是 DPI?
DPI 代表「每英吋點數」。 這是對輸出顯示器 (例如電腦監視器或手機螢幕) 其像素密度的概略測量。 DPI 越高,這些構成顯示器的點就越多越細小。
DPI 只是概略測量,因為並非所有顯示器硬體都足以信賴、可報告準確的資訊。 有些電腦監視器完全不會向作業系統報告 DPI,或者使用者可能已設定其系統使用與實際硬體不同的 DPI 來轉譯 (例如變更 UI 文字元素的大小)。 應用程式可以使用 DPI 來選擇繪製事物的大小,但不應該依賴它做為顯示大小的確切實體測量。
96 的 DPI 值會被視為中性預設值。
什麼是像素?
像素是單一個彩色點。 電腦圖形中的影像是由以二維網格線排列的許多像素所組成。 您可以將像素視為建造所有影像的原子。
像素的實體大小可能因顯示器而有所不同。 當電腦連接到大型但低解析度的監視器或外部顯示器時,像素可能會相當大,但在具有 1080p 螢幕的手機上,卻只顯示了幾英寸,像素很小。
在 Win2D 中,每當您看到使用整數資料類型指定位置或大小的 API 時 (或包含整數的 BitmapSize 等結構),這表示 API 是以像素單位運作。
大部分的 Win2D API 都使用 DIP,而不是像素。
什麼是 DIP?
DIP 代表「裝置獨立像素」。 這是一個虛擬化單位,可能等於、大於或小於實體像素。
像素與 DIP 之間的比率取決於 DPI:
pixels = dips * dpi / 96
當 DPI 為 96 時,像素和 DIP 相同。 使用較高的 DPI 時,單一 DIP 可能會對應到一個以上的像素 (或在 DPI 不是 96 確切倍數的常見情況下,則是部分像素)。
大部分 Windows 執行階段 API,包括 Win2D,都使用 DIP,而不是像素。 這有一個優點,讓圖形保持大致相同的實體大小,無論應用程式在哪個顯示器上執行。 舉例來說,如果應用程式指定按鈕寬度為 100 DIP,則當在手機或 4k 監視器等高 DPI 裝置上執行時,此按鈕會自動縮放為寬度超過 100 個像素,因此它仍然是使用者可按一下的合理大小。 另一方面,如果按鈕大小是以像素指定,在這類高 DPI 顯示器上看起來就會非常小,因此應用程式必須執行更多工作,針對每種螢幕以不同的方式調整版面配置。
在 Win2D 中,每當您看到使用浮點資料類型指定位置或大小的 API 時 (或包含浮點值的 Vector2 或 Size 等結構),這表示 API 是以 DIP 運作。
若要在 DIP 與像素之間轉換,請使用方法 ConvertDipsToPixels(Single, CanvasDpiRounding) 和 ConvertPixelsToDips(Int32)。
具有 DPI 的 Win2D 資源
包含點陣圖影像的所有 Win2D 資源,也有相關聯的 DPI 屬性:
CanvasBitmapCanvasRenderTargetCanvasSwapChainCanvasControlCanvasVirtualControlCanvasAnimatedControlCanvasImageSource
所有其他資源類型都與 DPI 無關。 例如,單一 CanvasDevice 執行個體可用來繪製許多不同 DPI 的控制項或轉譯目標,因此裝置本身沒有 DPI。
同樣地, CanvasCommandList 沒有 DPI,因為它包含向量繪圖指示,而不是點陣圖影像。 DPI 只會在點陣化過程中發揮作用,也就是將命令清單繪製到轉譯目標或控制項 (其確實具有 DPI) 的時候。
控制項 DPI
Win2D 控制項 (CanvasControl、CanvasVirtualControl 和 CanvasAnimatedControl) 會自動使用與應用程式執行所在的顯示器相同的 DPI。 這符合 XAML、CoreWindow 和其他 Windows 執行階段 API 所使用的座標系統。
如果 DPI 變更 (舉例來說,如果應用程式移至不同的顯示器),控制項會引發 CreateResources 事件並傳遞 DpiChanged 的 CanvasCreateResourcesReason。 應用程式應該藉由重新建立相依於控制項 DPI 的任何資源 (例如 rendertargets) 來回應此事件。
Rendertarget DPI
可以繪製到專案的項目 (不僅包括 CanvasRenderTarget,還包括類似轉譯目標的類型 CanvasSwapChain 和 CanvasImageSource) 也有自己的 DPI,但與這些類型的控制項不同,這些類型不會直接連接到顯示器,因此 Win2D 無法自動判斷 DPI 應該是什麼。 如果您要繪製到稍後將複製到畫面的轉譯目標,您可能想要讓轉譯目標使用與畫面相同的 DPI,但如果您的繪製有其他用途 (例如產生上傳至網站的影像),則預設的 96 DPI 會更合適。
為了簡化這兩種使用模式,Win2D 提供兩種類型的建構函式多載:
CanvasRenderTarget(ICanvasResourceCreator, width, height, dpi)
CanvasRenderTarget(ICanvasResourceCreatorWithDpi, width, height)
ICanvasResourceCreator 介面可由 CanvasDevice 以及 Win2D 控制項所實作。 由於裝置本身沒有任何特定的 DPI,因此從裝置建立轉譯目標時,您必須明確指定 DPI。
舉例來說,若要建立預設 DPI 轉譯目標,其中 DIP 和像素將一律相同:
const float defaultDpi = 96;
var rtWithFixedDpi = new CanvasRenderTarget(canvasDevice, width, height, defaultDpi);
ICanvasResourceCreatorWithDpi 藉由新增 DPI 屬性來延伸 ICanvasResourceCreator。 此介面是由 Win2D 控制項所實作,並可讓您輕鬆地建立轉譯目標,其會自動繼承與建立來源控制項相同的 DPI:
var rtWithSameDpiAsDisplay = new CanvasRenderTarget(canvasControl, width, height);
點陣圖 DPI
CanvasBitmap 與轉譯目標不同的是,不會自動從控制項繼承 DPI。 建立和載入點陣圖的方法,包括多載以明確指定 DPI,但如果您省略此部分,則不論目前顯示器的設定為何,點陣圖 DPI 都會預設為 96。
點陣圖與其他類型不同的原因在於,它們是輸入資料的來源,而不是要繪製的輸出。 因此,對於點陣圖而言,重要的不是輸出目標位置的 DPI,而是來源影像的 DPI,這與目前顯示器的設定完全無關。
如果您載入 100x100 預設 DPI 的點陣圖,然後將它繪製到轉譯目標上,則點陣圖會從 96 DPI 的 100 DIP (也就是 100 像素) 縮放至目的地轉譯目標 DPI 的 100 DIP (如果它是高 DPI 轉譯目標,則像素數可能較大)。 產生的影像大小一律為 100 DIP (因此不會有令人不快的版面配置意外),但如果低 DPI 來源的點陣圖放大到較高 DPI 的目的地,可能會有一些模糊問題。
為了在高 DPI 能有最好的清晰度,某些應用程式可能想要提供多組不同解析度的點陣圖影像,並在載入時選取最符合目的地控制項 DPI 的版本。 其他應用程式可能傾向只提供高 DPI 的點陣圖,並在較低 DPI 顯示器上執行時讓 Win2D 縮小這些點陣圖 (縮小看起來通常比放大的表現更好)。 不論是哪一種情況,都可以將點陣圖 DPI 指定為 LoadAsync(ICanvasResourceCreator, String, Single) 的參數。
請注意,某些點陣圖檔案格式包含自己的 DPI 中繼資料,但 Win2D 會忽略此項目,因為它的設定通常不正確。 不過,載入點陣圖時必須明確指定 DPI。
CanvasDrawingSession DPI
CanvasDrawingSession 對於他要繪製到的任何目標,不管是控制項、轉譯目標、交換鏈結等等,都會繼承其 DPI。
根據預設,所有繪製作業都會以 DIP 為單位來運作。 如果您想要以像素為單位來運作,可以透過 Units 屬性變更。
效果 DPI
影像效果管線會從繪製效果的任何目標 CanvasDrawingSession 繼承其 DPI。 在內部,效果處理一律以像素為單位來運作。 參數值 (例如大小或位置等) 是以 DIP 為單位來指定,但這些單位會在進行任何實際影像操作之前轉換成像素。
如果使用點陣圖當做效果的來源影像,但其 DPI 與目標繪圖工作階段不同的時候,會自動在點陣圖與效果之間插入內部 DpiCompensationEffect。 這會縮放點陣圖來符合目標的 DPI,也就是是您通常想要的結果。 如果這不是您想要的結果,也可以插入自己的 DpiCompensationEffect 執行個體來自訂行為。
注意
如果實作自訂效果,請考慮套用對等 DPI 處理配置,以確保搭配內建 Win2D 效果使用時的行為一致。
Composition API
Microsoft.Graphics.Canvas.Composition API 的運作層級低於 Win2D XAML 控制項,因此不會嘗試代表您自動處理 DPI。 您可自己決定想要以哪種單位來運作,並在組合視覺化樹狀結構中設定要達成該目標所需的任何轉換。
Windows.UI.Composition API,像是 CreateDrawingSurface,一律以像素為單位來指定大小。 使用 Win2D 繪製到組合介面時,您可以在呼叫 CreateDrawingSession(CompositionDrawingSurface, Rect, Single) 時,指定想要使用的任何 DPI。 所有透過傳回 CanvasDrawingSession 而執行的繪圖,都會對應放大或縮小。
如何測試 DPI 處理
要測試您的應用程式是否能做出正確的動作來回應變更的顯示器 DPI,最簡單的方式就是在 Windows 10 或 Windows 11 上執行,然後於應用程式正在執行的時候變更顯示器設定:
- 以滑鼠右鍵按下桌面背景,然後選擇 [顯示設定]
- 移動 [變更文字、應用程式與其他項目的大小] 標籤的滑桿
- 按一下 [套用] 按鈕
- 選擇 [稍後登出]
如果您沒有 Windows 10 或 Windows 11,您也可以使用 Windows 模擬器進行測試。 在 Visual Studio 工具列中,將 [本機電腦] 設定變更為 [模擬器],然後使用 [變更解析度] 圖示來切換模擬的顯示器:
- 100% (DPI = 96)
- 140% (DPI = 134.4)
- 180% (DPI = 172.8)