[福利]國內首篇利用freetype的跨平臺truetype字體真正輪廓(非位圖)獲取(帶完整qt工程代碼)-秒殺GetGlyphOutline

前言

最近在做一款激光打標控制的產品,我的思路是將所有的圖元矢量化,但是當做到文字矢量化的時候,真是讓我想破了腦袋,後來搜索得知了GetGlyphOutline,就是一個WINAPI,衆所周知,WINAPI大都需要一個HDC,但是我使用的Qt,這樣就出現了幾個問題:

  • Qt中的控件有些是直接渲染的,很難直接獲取控件句柄-
  • 不跨平臺,以後這一塊還是要重寫
  • VC的各種變量類型寫在Qt裏面,調試起來真是一團糟

做了些實驗以後,發現這種方法實在是緣木求魚,然後輕易地搜索到了freeType這一款優秀的字體引擎,下面我們就開始對freetype的探索。

所需基本知識點

trueType字體的一些基本概念

  • 字體(font):不同字符圖像的集合

  • 字體外觀(font face):一個外觀對應一個(.ttf)文件,但是一個字體家族可能佔用多個字體文件,因爲它包括多種外觀,比如字體族Arial,它包括兩種外觀,於是就有 arial.ttf對應Arial Regular外觀,ariali.ttf對應Arial Italic外觀,我們習慣把Arial Regular也稱爲一種字體,實際上它只是一種字體外觀

  • 字體文件的基本結構:一般裏面有一個或多個字符圖(charmap),不同的字符圖一般標識不同的平臺,所以在一種平臺上一般只有一種字符圖,這個字符圖可以宏觀簡單的理解爲一個key-value,key-字符索引一般就是字符對應編碼的編碼值,value(字符構成)在truetype一般是對其矢量圖形的描述,我們只要將字符編碼對應的矢量圖形描述得到,就可以進行隨心所欲的處理了

trueType字體的基本構成

首現說一下字體輪廓,拿我自己解析出來的一個例子來說吧(這是俺的大名:)):
解析出來的源字體
渲染出來的矢量還是不錯的!
輪廓線就是字體輪廓中一條條的封閉曲線:
標註輪廓線的元
紅色箭頭標註的就是字體輪廓線,共有兩條;每條輪廓線又由其他直線或曲線組成:

  • 直線
  • 二次Bezier曲線
  • freetype官方說明字體曲線可能包含有(三次Bezier曲線),但經過我的實驗以及其他官方資料,truetype字體並不含有三次Bezier曲線

besier曲線定義

由於此處只用到一次貝塞爾(有界直線)和二次貝塞爾曲線
給定點P0、P1,線性bezier曲線只是一條兩點之間的直線。這條線由下式給出,且其等同於線性插值。
一次bezier曲線表達式
二次方bezier曲線的路徑由給定點P0、P1、P2的函數B(t)追蹤:
二次bezier曲線表達式
按照上述插值規則,如果t分割的足夠密,就可以得到平滑的曲線

freetype對trueType的解析

解析基本步驟

Created with Raphaël 2.1.2開始freetype字體初始化設置字符編碼方式獲取字符對應的編碼值查charmap表獲取編碼值的索引根據索引獲取輪廓描述對獲取到的輪廓數據進行補償相關渲染和處理結束

freetype字體庫初始化(省略了變量聲明)

int  error = FT_Init_FreeType( &library );
    if(error)
    {
        printf("load freetype errror!");
    }
    error = FT_New_Face(library,fontFilePath.toStdString().c_str() ,0,&face);
    if ( error == FT_Err_Unknown_File_Format )
        {
        printf("FT_Err_Unknown_File_Format");
    }
    else if(error)
    {
        printf(" another error code means that the font file could not  or simply that it is broken...");
    }
    error = FT_Set_Pixel_Sizes(face, /* handle to face object */
                                      0, /* pixel_width */
                                   8 );/* pixel_height */
    if(error)
    {
        printf("char size set error");
    }

設置字體編碼方式

只需一句代碼

FT_Select_Charmap(face,FT_ENCODING_UNICODE);

獲取字符編碼值

wchar_t charX= L'元';

對字體輪廓進行解析

這裏我們需要先對FT_Outline這個freetype內置結構體做一個說明:
FT_Outline

  • n_points:輪廓中的點數
  • n_contours 輪廓中輪廓線數
  • points 點座標數組
  • contours 輪廓線端點索引數組
  • tags 點標記數組

注意:下方全程高能
這裏,points是一個FT_Vector記錄數組的指針,用來存儲每個輪廓點的向量座標。它表示爲一個象素1/64,也叫做26.6固定浮點格式。
contours是一組點索引,用來劃定輪廓的輪廓線。例如,第一個輪廓線總是從0點開始,以contours[0]點結束。第二個輪廓線從contours[0]+1點開始,以contours[1]結束,等等。
注意,每條輪廓線都是封閉的,n_points應該和contours[n_controus-1]+1相同。最後,tags是一組字節,用來存放每個輪廓的點標記。
從這兒,大家可能就會感覺以下的步驟很麻煩了!是的,至少會需要兩層循環,這還不算完,最難處理的是下面這一堆規定:

輪廓內部點規則描述

  • 每條弧由一系列起點、終點和控制點描述,輪廓的每個點有一個特定的標記,表示它用來描述一個線段還是一條弧。這個標記可以有以下值:
  • FT_Curve_Tag_On 當點在曲線上,這對應線段和弧的起點和終點。其他標記叫做“Off”點,即它不在輪廓線上,但是作爲Bezier弧的控制點。
  • FT_Curve_Tag_Conic 一個Off點,控制一個conic Bezier弧
  • FT_Curve_Tag_Cubic 一個Off點,控制一個cubic Bezier弧
  • 下面的規則應用於將輪廓點分解成線段和弧 z 兩個相鄰的“on”點表示一條線段;
  • 弧z 一個conic Off(二次bezier曲線控制點)在兩個on點之間表示一個conic Bezier(二次bezier)弧,off點是控制點,on點是起點和終點;
  • 兩個相鄰的cubic off(三次bezier曲線控制點)點在兩個on點之間表示一個cubic Bezier(三次bezier)弧,它必須有兩個cubic控制點和兩個on
    點。
  • 兩個相鄰的conic off(二次bezier曲線控制點)強制在它們正中間創建一個虛擬的on點(座標爲兩者中點)。這大大方便定義連續的conic弧。TrueType規範就是這麼定義的。

輪廓端點規則描述

  • 如果輪廓的首尾點均爲FT_Curve_Tag_On,則不需做任何處理
  • 如果輪廓首點爲FT_Curve_Tag_Conic,尾點爲FT_Curve_Tag_On,就將尾點作爲第一條bezier曲線的首點
  • 如果輪廓首點爲FT_Curve_Tag_On,尾點爲FT_Curve_Tag_Conic,就將首點作爲最後一條bezier曲線的尾點
  • 如果首尾點均爲FT_Curve_Tag_Conic,則取兩者平均值分別作爲第一條bezier曲線的首點和最後一條曲線的尾點。

規則總結

這一串規則看起來非常複雜晦澀,其實總結一下就是,每個輪廓點就是是一個’圓環’的點集,當出現相鄰的bezier控制點後,就補償一箇中值。最後,我們’拆環’就可以了

解析算法描述

Created with Raphaël 2.1.2獲取outline中的各個值單個輪廓線首尾索引值獲取首端點補償中點連續的控制點的中值補償尾端點補償拆分成一段段獨立bezier曲線結束

單個輪廓線首尾索引值獲取

Created with Raphaël 2.1.2單個輪廓線首尾索引值獲取第幾條輪廓線是否是第0條 ?索引值 0~outline->contours[0]處理完畢索引值 outline->contours[contourIndex -1] +1~outline->contours[contourIndex]yesno

首尾端點補償

按照上述規則即可,具體看附件工程

中值補償

也是按照上述規則

很多東西看代碼比較直接,附上大家最想要的完整工程,記得改一下pro文件裏面的lib目錄路徑:
戳我下載源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章