邱明冉
編者按:每個(gè)操作系統(tǒng)在設(shè)計(jì)之初,都會考慮如何在終端上顯示文字,而且都不約而同地采用了字體這一方式來顯示豐富的文字形態(tài)。字體對于操作系統(tǒng)的重要性不言而喻,從本期開始,我們將探討系統(tǒng)中字體的顯示和渲染原理,以及對字體的優(yōu)化和應(yīng)用。
一位日常使用Linux系統(tǒng)的同學(xué)說:“Mac的字體顯示對高分屏有優(yōu)化,Linux的字體顯示對所有屏幕都有優(yōu)化?!弊鳛橐粋€(gè)需要整天面對屏幕上密密麻麻文字的學(xué)生,聽到這句話,筆者心動了。但是筆者裝好雙系統(tǒng)之后,并沒有感受到Linux的字體顯示比Windows 10的顯示有進(jìn)步。為了刨根究底,我們需要從計(jì)算機(jī)是如何顯示文字的開始探索……
● 像素——顯示的基本單元
在討論計(jì)算機(jī)如何顯示字體之前,先介紹一下VGA是如何進(jìn)行顯示的。
以640*480@60Hz的VGA顯示規(guī)格為例,電子槍不斷地重復(fù)著從左上角掃描到右下角,再回到左上角的過程,為了能夠顯示640*480像素的有效區(qū)域,電子槍的掃描范圍相當(dāng)于800*525像素,多出來的“無效掃描”就是在電子槍掃描無法顯示的邊框處和返回行首列過程中發(fā)生的(如圖1)。
于是,為了讓連接VGA的顯示器能夠以640*480@60Hz工作,必須設(shè)計(jì)一個(gè)電路,使得開發(fā)板能夠在正確的時(shí)候(當(dāng)電子槍掃描到800*525中有效的640*480時(shí))通過VGA傳遞當(dāng)前要打印的像素的顏色。這個(gè)電路還可以檢測當(dāng)前掃描到的像素坐標(biāo)。
VGA顯示器接收到正確的垂直同步信號和RGB值才能正常工作。既然可以控制每一個(gè)像素的顏色,那么也就可以在屏幕上顯示文字了(如圖2)。
● 從像素到文字
在計(jì)算機(jī)科學(xué)中,抽象是一個(gè)重要的理念。如果每次顯示文字的時(shí)候都要在像素點(diǎn)層次工作,那就太煩瑣了。于是計(jì)算機(jī)科學(xué)家們想到,將整個(gè)640*480像素的屏幕劃分成一個(gè)個(gè)8像素寬、16像素高的切片,每個(gè)切片顯示一個(gè)ASCII字符。預(yù)先定義好每個(gè)ASCII字符在這8*16像素內(nèi)如何顯示,要顯示哪個(gè)字符的時(shí)候,就加載定義好的顯示方式來顯示即可。
這種抽象出來的方法可以應(yīng)用到多個(gè)方面,在游戲場景制作時(shí)也可以這樣分解。Unity引擎將游戲畫面分解成一塊塊“地磚”,設(shè)計(jì)師只需要預(yù)先設(shè)計(jì)幾種地磚,通過鋪地磚就可以快速完成游戲場景設(shè)計(jì)(如圖3)。
用現(xiàn)在的編程思維來講,就是預(yù)先定義好一個(gè)一維數(shù)組character,每個(gè)字符就是數(shù)組的一個(gè)元素。數(shù)組元素的大小為8*16=128位,每一位表示這8*16個(gè)像素中某一個(gè)像素是黑還是白,那么“A”的顯示方式和存儲方式就如圖4所示。
在電路的具體實(shí)現(xiàn)上,開發(fā)板將這些信息保存到ROM(只讀內(nèi)存)中,供VGA使用。這種字體,稱為點(diǎn)陣字體(Bitmap Font,直譯為“位圖字體”)。在后來的DOS系統(tǒng)中,顯示模式分為圖形模式和文本模式,圖形模式下通過直接寫顯存可以對每一個(gè)像素的顏色進(jìn)行控制,而文本模式下通過寫顯存只能控制哪一個(gè)位置輸出哪個(gè)字符以及字符的顏色。
由于計(jì)算機(jī)主板上的ROM容量較小又比較寶貴,一般只存儲最基本的128個(gè)ASCII碼字符,在這些ASCII字符之外的文字和符號(如漢字和特殊符號)要想顯示出來,就必須采用其他方法,如將點(diǎn)陣信息儲存在硬件插卡或磁盤文件中,形成字庫。
在中文DOS系統(tǒng)中,不僅要解決字庫的加載,還要解決如何在屏幕上顯示這些文字。經(jīng)歷過漢卡之后,曾經(jīng)的UCDOS、CCDOS等優(yōu)秀的漢字系統(tǒng),最終采用“直接寫屏”(調(diào)用BIOS的10H中斷在圖形模式下直接操作顯存地址)技術(shù)以圖形方式在屏幕上“畫”出漢字。在純粹的西文DOS環(huán)境中,要想顯示出漢字是一個(gè)不小的挑戰(zhàn)。字庫文件最后成了一致看好的最終解決方案。
點(diǎn)陣字體大小固定,但是支持整數(shù)倍放大。然而這樣放大后就像位圖一樣,會有明顯的鋸齒。所以,為了滿足不同大小的需要,一種字體需要提供好幾個(gè)版本。圖5是8*16的字體放大成16*32的大?。ㄗ螅?,與本來就是16*32大小字體的比較(右),可以看出,用放大方法得到的字體,有明顯的鋸齒,并且最初的不對稱也被放大。
即使是同一種字體,在不同大小下看起來也像是不同字體一樣。例如,當(dāng)字體較大時(shí),襯線體可以顯示出自己的襯線,而字體較小時(shí),因?yàn)橄袼攸c(diǎn)數(shù)量的限制,這種細(xì)節(jié)只能被舍棄。我們在命令提示符下查看同一點(diǎn)陣字體不同大小時(shí)的差別。8*16大小的Terminal字體,可以看到明顯的襯線以及字體橫細(xì)豎粗的特點(diǎn)(如上頁圖6)。
而6*12大小的Terminal字體,和8*16長寬比相等,但是沒有襯腳,筆畫粗細(xì)均勻,看上去和前者完全不是一種字體(如圖7)。
8*18大小的Terminal字體也跟8*16大小的Terminal字體完全不同(如圖8)。
● 從文字到像素
隨著圖形界面的普及,用戶對一種字體提供多種字號的需求不斷增長。點(diǎn)陣字體的設(shè)計(jì)效率不如人意,尤其是對非字母語言的文字而言。于是,設(shè)計(jì)師想到用數(shù)學(xué)方程來描述筆畫,把字符分隔成若干關(guān)鍵點(diǎn),用光滑的曲線連接,通過計(jì)算就可以得出同一種字體在各種不同的字號下應(yīng)該如何顯示。這就是矢量字體(Vector Font,也稱輪廓字體)。
Photoshop的鋼筆工具可以繪制貝塞爾曲線,這種曲線依賴于數(shù)學(xué)方程,不受畫布像素的限制,顯得非常平滑(如圖9)。
但是當(dāng)使用它在畫布上繪圖并填充時(shí),就會發(fā)現(xiàn)繪制區(qū)域的圖形依然受到畫布像素的制約,每一個(gè)像素只能有一種顏色,平滑的邊緣會變成鋸齒狀(如圖10)。同理,通過數(shù)學(xué)函數(shù)設(shè)計(jì)的矢量字體,當(dāng)最終呈現(xiàn)在顯示器上的時(shí)候,也會受到顯示器像素的制約,在字號較小的時(shí)候,顯示的效果可能會很差。
例如,字母“e”的矢量設(shè)計(jì)圖(如圖11左側(cè)),它如何在顯示器上顯示呢?顯示器上的每個(gè)像素就是一個(gè)小格子,我們可以用初等數(shù)學(xué)中常用的二值化進(jìn)行處理:如果一個(gè)像素一半以上面積被字體覆蓋,那么這個(gè)像素就用來顯示字體,否則這個(gè)像素不顯示字體。圖11右側(cè)就是轉(zhuǎn)換過程。
當(dāng)字體較小時(shí),為了避免像素太少引起矢量字體的嚴(yán)重失真,Windows的做法是對小號字體直接使用原始的點(diǎn)陣字體,不進(jìn)行矢量渲染。不過微軟對Windows 10系統(tǒng)默認(rèn)的微軟雅黑字體的處理是例外,全字號使用ClearType亞像素渲染技術(shù),所以有人覺得雅黑字體顯示效果較好。
● 抗鋸齒技術(shù)
上面所說的非黑即白填充法只能渲染出粗糙的字體效果。在Windows XP之前的Windows系統(tǒng),微軟就采用這樣的做法來顯示矢量字體。要改進(jìn)顯示效果,可以根據(jù)理想的字型在此像素所覆蓋的面積比例,賦予像素不同的灰度,靠近字體邊緣的地方表現(xiàn)為深灰到淺灰不等的顏色(如上頁圖11),人眼會將它轉(zhuǎn)換為字體的輪廓。這種通過過渡色減輕字體邊緣鋸齒感的技術(shù),就是抗鋸齒技術(shù)(如上頁圖12)。
字體抗鋸齒在日常使用中效果如何呢?這里以圖形軟件開發(fā)常用的Qt框架為例來演示。這是窗口中的一個(gè)按鈕,按鈕文字字號是50,圖13和圖14展示了抗鋸齒的效果。
或者我們也可以用14號字,截圖后放大觀察區(qū)別(如圖15、圖16)。從圖15中可以看到,它使用了紅、藍(lán)、棕等顏色而不是灰色作過渡色(詳見后文“亞像素”部分)。
抗鋸齒在字體的日常使用感受中影響還是很大的,尤其是在字號較小的情況下。開啟了抗鋸齒之后,不但字形得到了更好的還原,顯示效果在大部分情況下也有明顯提高。目前的系統(tǒng)也都默認(rèn)開啟了字體抗鋸齒(如圖17)。
● 字體微調(diào)
有時(shí)候在小字號下,開啟了抗鋸齒,字體糊成一團(tuán),不易辨別,顯示效果反而變差了。為了解決抗鋸齒導(dǎo)致的字體邊緣模糊問題,人們想到通過輕微調(diào)整字形,使其盡可能與像素點(diǎn)貼合,以避免大量使用過渡像素導(dǎo)致觀感模糊,從而生成清晰易讀的文本。這就是字體微調(diào)(hinting,直譯為“提示”)。
圖18中維基百科的圖片很好地詮釋了“字體微調(diào)”。上下兩行中上一行沒有字體微調(diào),下一行有字體微調(diào)。
微軟十分重視字體微調(diào),認(rèn)為顯示字體的時(shí)候應(yīng)該針對顯示屏做一定的優(yōu)化,使之適應(yīng)較低精度的像素分布,獲得清晰效果,方便用戶閱讀。而蘋果認(rèn)為,在顯示屏上所顯示的字體應(yīng)該和印刷出來的成品效果接近,即使這樣會導(dǎo)致字體看上去有些模糊。二者字體渲染理念的不同也就產(chǎn)生了不同的效果(如圖19)。
字體微調(diào)使文字清晰易讀的同時(shí),一定程度上扭曲了字體,甚至可能導(dǎo)致比較復(fù)雜的文字會顯示錯(cuò)誤的字形。把Windows 10桌面(屏幕縮放100%)下默認(rèn)大小的字體放大來看,可以看到為了使筆畫對比度提升、顯示更清晰,字體微調(diào)甚至將“真”字里面的三橫減去了一橫(如圖20)。
在關(guān)閉字體微調(diào)效果的Linux XFCE桌面上同樣的文字,筆畫沒丟,但顯示效果比較模糊,讀起來眼睛很累(如圖21)。
上面兩圖的比較是在不同系統(tǒng)不同字體上進(jìn)行的,沒有控制變量,不夠嚴(yán)謹(jǐn)。我們來看看Linux XFCE桌面下Noto Serif Regular字體(大小為10號)開啟微調(diào)的效果(如圖22、圖23),可以看到大寫的E、H、R等字母明顯更銳利了,小寫g下面的圈從3*2的長方形變成了3*3的正方形,這也是字體微調(diào)之后與原字形產(chǎn)生的差別。
除了對字形進(jìn)行微調(diào),有時(shí)還會對字距進(jìn)行微調(diào),以達(dá)到更自然的顯示效果(如圖24)。
● 亞像素渲染
在液晶顯示屏上,一個(gè)像素是由紅、綠、藍(lán)三個(gè)長條形亞像素構(gòu)成的,LCD可以單獨(dú)控制每一個(gè)子像素,這個(gè)特性正好可以用來提高字體精度。白色字體只有離得非常近才能觀察到白色是由RGB三種顏色混合而成的。
亞像素渲染正是利用了LCD顯示屏像素的特點(diǎn),將字體水平方向的精細(xì)度提高到原先的三倍(如圖25),在字號較小的情況下尤其有效。
圖26形象展示了亞像素渲染是如何提高水平精度的,圖左側(cè)把每個(gè)像素分解成RGB三個(gè)亞像素,實(shí)際上肉眼只能看出圖右側(cè)的效果。
采用亞像素渲染后,如果僅僅是在計(jì)算機(jī)屏幕上放大(如使用XFCE桌面的ALT+滾輪進(jìn)行全局縮放,或者Snipaste、QQ截圖等截圖工具自帶的放大鏡),看到的只是字體輪廓花花綠綠的整塊整塊大小的像素,而且一定是“白底黑字左橙右青,黑底白字左青右橙”。只有用微距相機(jī)對準(zhǔn)屏幕拍攝,然后放大查看照片,才能發(fā)現(xiàn)字體輪廓附近像素的RGB亞像素亮暗不同(如圖27)。
亞像素渲染加入了顏色信息,圖28左側(cè)輪廓邊緣亞像素分別是R(亮)G(一般)B(暗),所以左側(cè)像素整體表現(xiàn)出的顏色是大部分紅+小部分綠=偏紅的黃色。
除了水平RGB亞像素渲染,Linux還支持其他種類的亞像素渲染(當(dāng)然也可以關(guān)閉亞像素渲染),以適應(yīng)各種不同的顯示設(shè)備。字體渲染的高度可定制性,就是同學(xué)口中“Linux的字體顯示對所有屏幕都有優(yōu)化”的含義吧。而筆者使用的屏幕完全符合微軟“不甚高端”的預(yù)期,已經(jīng)在Windows 10字體渲染策略下取得了較好的顯示效果,所以才沒有感受到Linux在字體渲染上的優(yōu)勢……