天天看點

bitmap畫文字 居中_文字引擎-FreeType2 文字進階渲染:變換&居中&字間距

全球圖形學領域教育的領先者、自研引擎的倡導者、底層技術研究領域的技術公開者,東漢書院在緻力于使得更多人群具備核心級競争力的道路上,将帶給小夥伴們更多的公開技術教學和視訊,感謝一路以來有你的支援。我們正在用實際行動來幫助小夥伴們建構一套成體系的圖形學知識架構,你在我們這裡獲得的不止于那些毫無意義的代碼,我們這裡更多的是代碼背後的故事,以及精準、透徹的了解。我們不會扔給人們一本書或者給個思路讓人們去自學,我們是親自來設計出好的課程,讓人們明白到底背後還有哪些細節。

這裡插播一個引擎大賽的消息,感興趣的同學可以看一眼,這也是東漢書院的立項使命:東漢書院:自研引擎大賽​zhuanlan.zhihu.com

bitmap畫文字 居中_文字引擎-FreeType2 文字進階渲染:變換&居中&字間距

原文連結FreeType Tutorial / II​www.freetype.org

bitmap畫文字 居中_文字引擎-FreeType2 文字進階渲染:變換&居中&字間距

5. Advanced Text Rendering: Transformation and Centering and Kerning

We are now going to modify our code in order to be able to easily transform the rendered string, for example, to rotate it. First, some minor improvements.

我們現在就來修改我們的代碼使得它能夠更容易的變換我們被渲染出來的字元串,比如旋轉它。首先是一些小的改進。

a. Packing and Translating Glyphs

We start by packing the information related to a single glyph image into a single structure instead of parallel arrays.

typedef struct TGlyph_

{

FT_UInt index;

FT_Vector pos;

FT_Glyph image;

} TGlyph, *PGlyph;

We also translate each glyph image directly after it is loaded to its position on the baseline at load time. As we will see, this has several advantages. Here is our new glyph sequence loader.

在加載的時候,我們同樣的要把每一個文字圖像在它加載出來之後直接平移到它基線上的某個位置上去。我們将會看到,這麼做有很多優勢。下面是我們新版本的文字加載器的代碼:

FT_GlyphSlot slot = face->glyph;

FT_UInt glyph_index;

FT_Bool use_kerning;

FT_UInt previous;

int pen_x, pen_y, n;

TGlyph glyphs[MAX_GLYPHS];

PGlyph glyph;

FT_UInt num_glyphs;

... initialize library ...

... create face object ...

... set character size ...

pen_x = 0;

pen_y = 0;

num_glyphs = 0;

use_kerning = FT_HAS_KERNING( face );

previous = 0;

glyph = glyphs;

for ( n = 0; n < num_chars; n++ )

{

glyph->index = FT_Get_Char_Index( face, text[n] );

if ( use_kerning && previous && glyph->index )

{

FT_Vector delta;

FT_Get_Kerning( face, previous, glyph->index,

FT_KERNING_MODE_DEFAULT, &delta );

pen_x += delta.x >> 6;

}

glyph->pos.x = pen_x;

glyph->pos.y = pen_y;

error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );

if ( error ) continue;

error = FT_Get_Glyph( face->glyph, &glyph->image );

if ( error ) continue;

FT_Glyph_Transform( glyph->image, 0, &glyph->pos );

pen_x += slot->advance.x >> 6;

previous = glyph->index;

glyph++;

}

num_glyphs = glyph - glyphs;

Note that translating glyphs now has several advantages. The first one is that we don't need to translate the glyph bbox when we compute the string's bounding box.

注意,現在平移文字有很多優勢。第一個優勢就是當我們計算字元串的包圍盒的時候我們不需要去平移文字的包圍盒。

void compute_string_bbox( FT_BBox *abbox )

{

FT_BBox bbox;

bbox.xMin = bbox.yMin = 32000;

bbox.xMax = bbox.yMax = -32000;

for ( n = 0; n < num_glyphs; n++ )

{

FT_BBox glyph_bbox;

FT_Glyph_Get_CBox( glyphs[n], ft_glyph_bbox_pixels,

&glyph_bbox );

if (glyph_bbox.xMin < bbox.xMin)

bbox.xMin = glyph_bbox.xMin;

if (glyph_bbox.yMin < bbox.yMin)

bbox.yMin = glyph_bbox.yMin;

if (glyph_bbox.xMax > bbox.xMax)

bbox.xMax = glyph_bbox.xMax;

if (glyph_bbox.yMax > bbox.yMax)

bbox.yMax = glyph_bbox.yMax;

}

if ( bbox.xMin > bbox.xMax )

{

bbox.xMin = 0;

bbox.yMin = 0;

bbox.xMax = 0;

bbox.yMax = 0;

}

*abbox = bbox;

}

With the above modifications, the compute_string_bbox function can now compute the bounding box of a transformed glyph string, which allows further code simplications.

有了上面的修改之後,compute_string_bbox函數現在就可以計算平移後的文字字元串的包圍盒了,這麼做了之後,後續的代碼就會變得簡單很多。

FT_BBox bbox;

FT_Matrix matrix;

FT_Vector delta;

... load glyph sequence ...

... set up `matrix' and `delta' ...

for ( n = 0; n < num_glyphs; n++ )

FT_Glyph_Transform( glyphs[n].image, &matrix, &delta );

compute_string_bbox( &bbox );

b. Rendering a Transformed Glyph Sequence

However, directly transforming the glyphs in our sequence is not a good idea if we want to reuse them in order to draw the text string with various angles or transformations. It is better to perform the affine transformation just before the glyph is rendered.

然而,如果你希望重用這些文字,以便于在繪制字元串的時候能夠對它們的角度進行變換的話,那麼直接變換我們的文字就不會是一個比較好的解決方案。其實最好是在文字即将被渲染之前去執行仿射變換。

FT_Vector start;

FT_Matrix matrix;

FT_Glyph image;

FT_Vector pen;

FT_BBox bbox;

compute_string_bbox( &string_bbox );

string_width = (string_bbox.xMax - string_bbox.xMin) / 64;

string_height = (string_bbox.yMax - string_bbox.yMin) / 64;

start.x = ( ( my_target_width - string_width ) / 2 ) * 64;

start.y = ( ( my_target_height - string_height ) / 2 ) * 64;

matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );

matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );

matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );

matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );

pen = start;

for ( n = 0; n < num_glyphs; n++ )

{

error = FT_Glyph_Copy( glyphs[n].image, &image );

if ( error ) continue;

FT_Glyph_Transform( image, &matrix, &pen );

FT_Glyph_Get_CBox( image, ft_glyph_bbox_pixels, &bbox );

if ( bbox.xMax <= 0 || bbox.xMin >= my_target_width ||

bbox.yMax <= 0 || bbox.yMin >= my_target_height )

continue;

error = FT_Glyph_To_Bitmap(

&image,

FT_RENDER_MODE_NORMAL,

0,

1 );

if ( !error )

{

FT_BitmapGlyph bit = (FT_BitmapGlyph)image;

my_draw_bitmap( bit->bitmap,

bit->left,

my_target_height - bit->top );

pen.x += image.advance.x >> 10;

pen.y += image.advance.y >> 10;

FT_Done_Glyph( image );

}

}

There are a few changes compared to the original version of this code.We keep the original glyph images untouched; instead, we transform a copy.

We perform clipping computations in order to avoid rendering and drawing glyphs that are not within our target surface.

We always destroy the copy when calling FT_Glyph_To_Bitmap in order to get rid of the transformed scalable image. Note that the image is not destroyed if the function returns an error code (which is why FT_Done_Glyph is only called within the compound statement).

The translation of the glyph sequence to the start pen position is integrated into the call to FT_Glyph_Transform instead of FT_Glyph_To_Bitmap.

相比最原始的代碼的版本,上面的代碼有這樣的一些變化:我們儲存了原始的文字圖像,我們變換的是原始的文字圖像的一份拷貝。

我們執行了剪裁的操作,如此一來我們就可以不用去繪制那些在我們視野之外的文字。

我們總是會在調用FT_Glyph_To_Bitmap的時候去銷毀文字的拷貝資料以避免平移後的可縮放圖像。注意,如果這個函數傳回錯誤的話,這個圖像是不會被銷毀的(這也是為什麼會出現FT_Done_Glyph的原因)。

從筆觸的起始位置到文字序列的平移操作使用FT_Glyph_Transform函數而不是FT_Glyph_To_Bitmap函數。

It is possible to call this function several times to render the string with different angles, or even change the way start is computed in order to move it to different place.

我們可以調用這個函數多次來把字元串渲染成不同角度的模樣,或者甚至為了把字元串放到不同的位置上去,我們可以改變筆觸起始位置的計算方式。

This code is the basis of the FreeType 2 demonstration program named

這段代碼是FreeType2文檔裡面ftstring.c實作的基本思路。它能夠使得第一部分代碼非常容易的被擴充成為一個在不修改第二部分代碼的情況下,執行進階文字排版或者word-wrapping的代碼。

Note, however, that a normal implementation would use a glyph cache in order to reduce memory needs. For example, let us assume that our text string is ‘FreeType’. We would store three identical glyph images in our table for the letter ‘e’, which isn't optimal (especially when you consider longer lines of text, or even whole pages).

注意,一般性的實作會使用文字緩沖區減少記憶體開銷。比如,我們假設我們要渲染的字元串是“FreeType”。我們可能需要存儲三個同樣‘e’的文字圖像到我們的表裡面去,這樣做實際上是沒有經過什麼優化的。

A FreeType demo program that shows how glyph caching can be implemented is

ftview.c這個FreeType的demo裡展示了如何實作文字的緩沖機制。通常情況下,‘ftview’這個程式是FreeType的開發團隊來檢查FreeType的加載、解碼和文字渲染是否能正常工作的一個程式。

Another very useful demo program is

另一個非常有用的樣本程式是ftdiff.c,這個程式展示了FreeType中的各種渲染和hinting模式的結果。特别是,它也展示了如何去做子像素的擺放-在這個教程中,我們假設所有的代碼都使用的是整型的坐标。

我們核心關注和讨論的領域是引擎的底層技術以及商業化方面的資訊,可能并不适合初級入門的同學。官方相關資訊主要包括了對市面上的引擎的各種視角的分析以及宏觀方面的技術讨論,相關領域感興趣的同學可以關注東漢書院以及圖形之心公衆号。

隻言片語,無法描繪出整套圖形學領域的方方面面,隻有成體系的知識結構,才能夠充分了解和掌握一門科學,這是藝術。我們已經為你準備好各式各樣的内容了,東漢書院,等你來玩。