[C#] 在高DPI的螢幕上,有些應用程式內的字會模糊的原因?以及解決方法!

開啟Visual Studio C#的Windows Form應用程式來設計,一定曾經注意到雖然在VS裡面設計Form裡面的字是清晰的,結果Compiler之後跑出來的字是模糊的!以下圖幫各位回憶一下。



這最主要原因是DPI太高的關係,可能會想說DPI高的話解析度就高呀,為什麼會DPI越高越模糊!先來聊聊一些單位的定義。

DPI:DPI是dots per inch,即每英吋內的點數量,也叫做螢幕密度,通常一個點dot就是一個像素pixel,所以越大DPI的螢幕,可以使用更多像素來顯示一張圖,因此能可以顯示的更精緻。
Pt/point:文字大小的單位是pt(point)。那要先定義一下pt這個單位,72 pt為一英寸,但這是對於全型字來說,例如漢字(下圖);如果是英文半型字,就要看各自的寬度了。
現在就來回答問題吧!為什麼在高DPI的螢幕會顯示出這麼模糊的文字呢?魔鬼就藏在單位轉換裡面。

程式有分為DPI感知與非DPI感知的應用程式,端看當初是否有加入此功能,在早期的作業系統下螢幕還沒這麼高級,大多為96 DPI,導致許多程式就沒考慮到現在的高DPI螢幕例如現在的120、144、192等,因此大部分舊的應用程式都為非感知DPI的類型。到後來Microsoft發展出「DPI Virtualization」,此技術運作模式大概是這樣的,當應用程式沒有先聲明他能支援多少DPI時,Windows會先將他放進沙盒裡,在這裡面沙盒內是先將應用程式渲染成96 DPI解析度下的畫面,然後看實際上的解析度多少再進行縮放,也因為如此,非感知DPI的應用程式在高DPI的螢幕下會出現模糊的情況。

現在先來進行單位換算:
96 DPI: 1.33 dots = 1 pt
120 DPI: 1.67 dots = 1 pt
144 DPI: 2 dots = 1 pt
192 DPI: 2.67 dots = 1 pt

從上面數據的意思是,假設現在要顯示一個字,其大小為1 pt,在各種解析度的螢幕上需要使用來顯示點數,例如96 DPI需要用1.33個像素,其兩倍解析度的192 DPI就需要其兩倍的點數2.67來表示,可以看的出來吧?當一張圖的解析度為96 DPI,你需要放大兩倍才能在192 DPI的螢幕上顯示出一樣大小的畫面。但是圖片本身解析度是不會變高的!所以如果將其放大(點陣圖放大)的話必定會模糊。用講的很難想像,讓我用圖來說明。

應用程式內的字「測試」在96 DPI環境下設定為48 pt,下方圖(a)為在96 DPI螢幕上執行的結果,可以看到清楚的「測試」兩個字,並且為48 pt。但若要在192 DPI螢幕上運作的話,必須如圖(b),先將應用程式在沙盒中渲染完成,因為96 DPI環境為192 DPI環境的1/2,所以應用程式的長與寬也會縮小為1/2 (2x變為x, 2y變為y),其中當然包括字型當然跟著應用程式縮放,因此若直接把沙盒內的字體顯示出來的話,會只有24 pt(48 pt的一半)的大小。但應用程式就是要顯示48 pt的字,怎麼辦呢?那就只能直接把在沙盒內的渲染結果放大兩倍達到48 pt的結果,最後變成圖(d),可以來回看看圖(a)和圖(d)觀察是否變模糊。
到這裡已經知道為何高DPI的螢幕會影響到字體的顯示了,接下來就要說說如何讓C#設計出來的視窗字體不會模糊,先上程式碼再解釋

        [STAThread]
        static void Main()
        {
            if (Environment.OSVersion.Version.Major >= 6)
                SetProcessDPIAware();
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern bool SetProcessDPIAware();
原理很簡單,在Main()的附近加上如上紅色的程式,使程式為DPI感知,大功告成,結果如下圖。

留言

這個網誌中的熱門文章

[Hyper-V] 讓 Windows 可以吃到超過 16TB 的硬碟!