計算機字體渲染的學問

前言:

哥們的項目中用到了樹莓派Zero,就是那個搭載着約十年前單核芯片的單板計算機。性能制約令人痛苦,幸運的是它也具有GPU,於是使用了OpenGL ES、OpenVG等硬件加速框架。其中為了渲染矢量字體,接觸了FreeType字體庫。

讀完FreeType文檔之後,發現習以為常的字體里也有大學問,今天咱們來聊聊字體

字體對比

字體是什麼?

正如上圖,同樣的內容,不一樣的視覺表現形式。計算機並不需要字體來完成它的工作:查找、儲存、編輯修改,但只要是通過屏幕、打印機這些視覺介質把文本呈現給人類看,就必須有字體

無論是開機BIOS打印的簡陋字符,還是日常使用的微軟雅黑、蘋方等非常漂亮的字體,甚至咱們手寫的帶有個人特色的一系列文本都是字體。這麼算下來,字體就是文本的視覺表現形式

歐柳顏趙,他們的字體有人模仿能以假亂真,但是計算機字體必須做到一模一樣。網頁縮放文本毫不失真,可截圖放大凈是馬賽克。咱們從原理上找找原因。

講個故事:

還記得小學我最喜歡的雜誌叫做《電腦愛好者》,到現在家裡還有幾十本留了下來,其中有一期介紹了清華大學做的一個網站能生成個人的字體。就是在格子紙按照要求的順序寫漢字英文數字,再拍照上傳,就能獲得字體文件。正好我爸他是個硬筆書法愛好者,就讓他寫了兩千多個常用字(遠達不到GB2312)上傳生成,最後字體真的做了出來。

保存有那個字體的電腦早已廢棄不用,而且被我折騰成了Linux,雖然希望渺茫,下次回家還是要去找一找那個字體。

字體的表示原理

一般的字型表示方式有兩種:位圖(bitmap)、矢量輪廓(vectorial outline)。

位圖

漢字取模

搞嵌入式的朋友對位圖應該不陌生,在12864LCD屏幕上顯示文本要先在電腦上取模,再把獲得的數據寫到源文件里。取模的時候要選擇字體的尺寸,16*16、24*24就代表每個字佔多少個像素。16*16=256,每個字256個像素,也就要256個bit來確定每一個像素上是空白(0)還是有顏色(1)。用位圖來描述字型,其實就是對畫面的直接表述,用的時候方便快速,直接輸出到顯示即可。

如圖,這樣的文字很有顆粒感,當然也可以製作分辨率超高的位圖字體,只不過佔用空間就有點大了。除此以外,字號是基本固定的,16*16的字體在12864上大小正常,放到4K屏幕就會非常小,即便像素4合1合成了更大像素,顆粒感依然非常強。要想多種字號,就要保存每個字號的字體,所以目前位圖在PC手機上應用較少。

矢量

矢量字型要求在渲染的過程中進行數學運算才能得出字型。但是隨着算力提升,計算機、智能手機一般都是矢量字體,Windows下的TrueType就是最常見的,結尾為ttf的文件就是TrueType矢量字體文件。

具體來說,矢量表述也用到了,但這些點並不用來顯示,而是作為矢量描述的一部分。連續兩點之間使用數學方法描述出字體的輪廓(如下圖),一段段輪廓首尾相接形成封閉字型。

線段

字體描述中,輪廓是一段段直線曲線組成,,直的叫「線段」(line segment),曲線則是由貝塞爾曲線(Bézier arc),每條線的兩端成為端點(on point,圖中黑色實心),直線段是由兩個on point描述的。

至於貝塞爾曲線,除了端點還需要一個或多個控制點(off point,圖中空心點),一個控制點兩個端點組成二次貝塞爾曲線,如果再加一個控制點就形成三次貝塞爾曲線。貝塞爾曲線在設計領域的廣泛應用也是因為如此優雅簡單的描述方式。

不過,當遇到複雜的字符,比如某些漢字,描述會異常複雜,也就需要大量控制點和端點。而且,這些點也要放在劃分好的點位上,不能隨意放置。在設計字體時,往往會先設定一個方形區域,稱為EM square。這個EM區域會被劃分成許多的單元(unit),單元的數量越多,可以認為設計字體時可選的點位也越多,字型也就可以更精細。一般來說TrueType字體會有2048個unit。

對比一下

位圖,對字型圖像的直接描述,不適合縮放。由於不需要縮放演算,用起來比較快。

矢量,對字型的輪廓描述,放縮無失真,有算力要求。

字符排版要素

應用廣泛的矢量輪廓表述在實際應用中會遇到不少問題,比如屏幕是由像素組成,矢量輪廓計算再精確也逃不過最後要顯示到分立的像素上去。再者,漢字較為方正,換做其他文字可能會出現需要處理的地方。看完FreeType文檔後,不由感謝相關工作者的付出,平常被忽略的細節里隱藏着無數的工作成果。

example

以上圖中的example為例,字母p非常的瀟洒,甚至都延申到了a。文字是斜的,完全分不出字間距,amp三個字連在了一起。這已經非常像人類用手書寫的文本了,但問題是,計算機是怎麼做到的。

下面是計算機渲染文本時常用的一部分步驟。

字距微調(Kerning)

讓我們簡化一下情況,討論比較常見的字符。

按照常見的從左到右書寫方式,字符會一個挨着一個地排列,如下圖:

字符排列

圖中帶箭頭線段的長度被稱為進寬度(advance width),它們的寬度各不相同。在同一行上,下一個字體開始的位置要根據前面文字的advance width來確定,但很多時候並不是單純的相加,比如BRAVO:

nokern

kern

對比兩個BRAVO,發現即使文章里的A與V都是第二種情況,在非斜體的情況下豎直方向上沒有留出通路。這種樣式更符合書寫和閱讀習慣。這樣的調整被成為字距微調(kerning)。其實現原理基本就是預置一些規則表比如kern和GPOS表,當連續文本出現這些組合的時候,就按照設定調整字距。其中GPOS還能根據上下文內容來進一步優化,比如當前字符在句子中的位置。

放縮中的柵格對齊(grid-fitting,hinting)

矢量字體在渲染前必須放縮到需要的大小,再經過計算轉換成像素描述的位圖以便顯示。按照之前說的矢量輪廓的組成方法:端點與控制點,把所有的點位置等比放大進行計算,就是需要的結果。所以,這就要求各個字符放大的中心點要一致,不然放大後字符參差不齊。

在轉換成位圖時,因為分辨率達不到無窮大,就會出現一些問題。比如H字母應該是對稱的,但如果非常不巧,一些控制點或者端點在縮放後落不到顯示器的像素柵格上,就可能會出現H兩邊粗細不同,E的三橫長短不一這種情況。柵格對齊(grid-fitting或hinting),就會稍微變化一下字型比例,將所有的點放置到像素柵格上去,保證渲染顯示不出問題。

除此以外,柵格對齊還能優化縮放後閱讀觀感,比如小寫的m在一些低分辨率屏幕上如果縮小太多,它下部會變得非常密集影響美觀和辨識度,這時hinting就會把字符橫向拉長一些,下部的間距由此變大,更加美觀。

總結

最近搞得東西資料總是很少,官方英文文檔動輒幾百頁,昏天黑地的讀下來可以說是痛並快樂着。

字體與顯示方面知識繁多,不少內容是第一次接觸,覺得值得記錄,本文內容不過是九牛一毛。

帶有括號的名詞是自己起的名字,請以括號內英文為準。

技術新人,水平一般,文章如有錯誤請一定指出,如有其他意見也請不吝賜教。

參考:

FreeType官方文檔://www.freetype.org/freetype2/docs/glyphs/index.html

OpenVG1.1官方文檔://www.khronos.org/registry/OpenVG/specs/openvg-1.1.pdf

更多嵌入式相關內容,請來公眾號,找我聊聊天吧:

公眾號


歡迎轉載,轉載請註明作者與原文鏈接。

作者:胡小安

原文地址://www.cnblogs.com/huxiaoan/p/15147360.html

Tags: