目錄
一、使用freetype 顯示一個文字
二、使用 freetype 顯示一行文字
1. 了解笛卡爾坐標系
2. 每個字符的大小可能不同
3. 怎么在指定位置顯示一行文字
4. freetype 的幾個重要數據結構
4.1、FT_Library結構體
4.2、FT_Face結構體
4.3、FT_GlyphSlot結構體
4.4、FT_Glyph結構體
4.5、FT_BBox結構體
5.讀懂顯示一行字體的代碼
draw函數
第一步
第二步:計算外框
第三步:調整坐標
第四步: 轉換、加載位圖、繪制
第五步:上機實驗
一、使用freetype 顯示一個文字
之前學習Framebuffer的時候,把它和Framebuffer寫在了一起,可以看看我寫的這個文章:Framebuffer應用編程
二、使用 freetype 顯示一行文字
1. 了解笛卡爾坐標系
在 LCD 的坐標系中,原點在屏幕的左上角。對于笛卡爾坐標系,原點在左下角。freetype 使用笛卡爾坐標系,在顯示時需要轉換為 LCD 坐標系。
V:縱向總像素個數
從圖可知,X 方向坐標值是一樣的。
在 Y 方向坐標值需要換算,假設 LCD 的高度是 V。
在 LCD 坐標系中坐標是(x, y),那么它在笛卡爾坐標系中的坐標值為(x, V-y)。
反過來也是一樣的,在笛卡爾坐標系中坐標是(x, y),那么它在 LCD 坐標系中坐標值為(x, V-y)。
2. 每個字符的大小可能不同
在使用?FT_Set_Pixel_Sizes?函數設置字體大小時,這只是“期望值”。比如“百問網 www.100ask.net”,如果把“.”顯示得跟其他漢字一樣大,不好看。
所以在顯示一行文字時,后面文字的位置會受到前面文字的影響。
例:
幸好,freetype 幫我們考慮到了這些影響。
對于?freetype?字體的尺寸(freetype?Metrics),需要參考圖:
在顯示一行文字時,這些文字會基于同一個基線來繪制位圖:baseline。
在?baseline?上,每一個字符都有它的原點(origin),比如上圖中?baseline左邊的黑色圓點就是字母“g”的原點。當前?origin?加上?advance?就可以得到下一個字符的?origin,比如上圖中?baseline?右邊的黑色圓點。在顯示一行中多個文件字時,后一個文字的原點依賴于前一個文字的原點及?advance。
有圖可得:
origin + advance = origin(下一個字的)
字符的位圖是有可能越過?baseline?的,比如上圖中字母“g”在?baseline下方還有像。
上圖中紅色方框內就是字母“g”所點據的位圖,它的四個角落不一定與原點
重合。
上圖中那些?xMin 、 xMax 、 yMin 、 yMax?如 何 獲 得 ? 可 以 使 用
FT_Glyph_Get_CBox?函數獲得一個字體的這些參數,將會保存在一個 FT_BBox結構體中,以后想計算一行文字的外框時要用到圖這些信息:
更多結構體后面會詳細說明!!
3. 怎么在指定位置顯示一行文字
要顯示一行文字時,每一個字符都有自己外框:xMin、xMax、yMin、yMax。把這些字符的?xMin、yMin?中的最小值取出來,把這些字符的?xMax、yMax?中的最大值取出來,就可以確定這行文字的外框了。
要想在指定位置(x, y)顯示一行文字,步驟如圖所示:
第1步 先指定第 1 個字符的原點 pen 坐標為(0, 0),計算出它的外框
第2步 再計算右邊字符的原點,也計算出它的外框
把所有字符都處理完后就可以得到一行文字的整體外框:假設外框左上角坐標為(x’, y’)。
第3步 想在(x, y)處顯示這行文字,調整一下 pen 坐標即可。怎么調整?
pen 為(0, 0)時對應左上角(x’, y’);那么左上角為(x, y)時就可以算出pen 為(x-x’, y-y’)。
4. freetype 的幾個重要數據結構
4.1、FT_Library結構體
對應 freetype 庫,使用 freetype 之前要先調用以下代碼:
FT_Library library; /* 對應 freetype 庫 */
error = FT_Init_FreeType( &library ); /* 初始化 freetype 庫 */
4.2、FT_Face結構體
它對應一個矢量字體文件,在源碼中使用?FT_New_Face?函數打開字體文件后,就可以得到一個?FT_Face結構體face。
為什么稱之為 face?
估計是文字都是寫在二維平面上的吧,正對著人臉?不用管原因了,總之認為它對應一個字體文件就可以。
代碼如下:
error = FT_New_Face(library, font_file, 0, &face ); /* 加載字體文件 */
4.3、FT_GlyphSlot結構體
插槽?用來保存字符的處理結果:比如轉換后的 glyph、位圖,如圖:
上一個說的FT_Face結構體里面成員有一個FT_GlyphSlot結構體,里面保存的是字符的處理結果(含有變換后的glyph、位圖、advance等信息)
一個 face 中有很多字符,生成一個字符的點陣位圖時,位圖保存在哪里?
保存在插槽中:face->glyph
生成第 1 個字符位圖時,它保存在 face->glyph 中;生成第 2 個字符位圖時,也會保存在 face->glyph 中,會覆蓋第 1 個字符的位圖。
所以我們生成后要及時取走,防止覆蓋!
使用當中我們通常會定義一個變量來保存:
FT_GlyphSlot slot = face->glyph; /?插槽: 字體的處理結果保存在這里?/
4.4、FT_Glyph結構體
字體文件中保存有字符的原始關鍵點信息,使用 freetype 的函數可以放大、縮小、旋轉,這些新的關鍵點保存在插槽中(注意:位圖也是保存在插槽中)。
新的關鍵點使用?FT_Glyph?來表示,可以使用這樣的代碼從 slot 中獲得glyph:
error = FT_Get_Glyph(slot , &glyph);
4.5、FT_BBox結構體
FT_BBox?結構體定義如下,它表示一個字符的外框,即新?glyph?的外框:
可以使用以下代碼從 glyph 中獲得這些信息:
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &bbox );
5.讀懂顯示一行字體的代碼
學習了上面的一堆基礎知識,我們就懂了步驟:
第1步 先指定第 1 個字符的原點 pen 坐標為(0, 0),計算出它的外框
第2步 再計算右邊字符的原點,也計算出它的外框
把所有字符都處理完后就可以得到一行文字的整體外框:假設外框左上角坐標為(x’, y’)。
第3步 想在(x, y)處顯示這行文字,調整一下 pen 坐標即可。怎么調整?
pen 為(0, 0)時對應左上角(x’, y’);那么左上角為(x, y)時就可以算出pen 為(x-x’, y-y’)。
第4步 轉換、加載位圖、繪制一個字符,計算下一個字符origin,繼續繪制下一個字符…依次循環
完整的代碼如下:
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_Hint fd_fb;
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;/* color : 0x00RRGGBB */
void lcd_put_pixel(int x, int y, unsigned int color)
{unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;unsigned short *pen_16; unsigned int *pen_32; unsigned int red, green, blue; pen_16 = (unsigned short *)pen_8;pen_32 = (unsigned int *)pen_8;switch (var.bits_per_pixel){case 8:{*pen_8 = color;break;}case 16:{/* 565 */red = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue = (color >> 0) & 0xff;color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);*pen_16 = color;break;}case 32:{*pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}/*********************************************************************** 函數名稱: draw_bitmap* 功能描述: 根據bitmap位圖,在LCD指定位置顯示漢字* 輸入參數: x坐標,y坐標,位圖指針* 輸出參數: 無* 返 回 值: 無* 修改日期 版本號 修改人 修改內容* -----------------------------------------------* 2020/05/12 V1.0 zh(angenao) 創建***********************************************************************/ /* 在LCD上繪制: 使用LCD坐標 */
void
draw_bitmap( FT_Bitmap* bitmap,FT_Int x,FT_Int y)
{FT_Int i, j, p, q;FT_Int x_max = x + bitmap->width;FT_Int y_max = y + bitmap->rows;//printf("x = %d, y = %d\n", x, y);for ( j = y, q = 0; j < y_max; j++, q++ ){for ( i = x, p = 0; i < x_max; i++, p++ ){if ( i < 0 || j < 0 ||i >= var.xres || j >= var.yres )continue;//image[j][i] |= bitmap->buffer[q * bitmap->width + p];lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);}}
}/* 計算一行文字的外框 */
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{/* FT_Face face : 對應一個字體文件 */int i; //用于for循環計算次數int error; //用于保存函數返回值FT_BBox bbox; //初始外框FT_BBox glyph_bbox;//用來保存新加載的外框(從glyph得到外框: bbox)FT_Vector pen; //定義原點FT_Glyph glyph; //用于保存字體文件FT_GlyphSlot slot = face->glyph;/* 插槽: 字體的處理結果保存在這里 *//* 初始化 */bbox.xMin = bbox.yMin = 32000;bbox.xMax = bbox.yMax = -32000;/* 指定原點為(0, 0) */pen.x = 0;pen.y = 0;/* 計算每個字符的bounding box *//* 先translate, 再load char, 就可以得到它的外框了 */for (i = 0; i < wcslen(wstr); i++){/* 轉換:transformation */FT_Set_Transform(face, 0, &pen);/* 加載位圖: 一個字一個字得去加載位圖 會保存在face里面 */error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);if (error){printf("FT_Load_Char error\n");return -1;}/* 在face結構體里面取出新的關鍵點保存在變量glyph里(從 slot 中獲得glyph) */error = FT_Get_Glyph(face->glyph, &glyph);if (error){printf("FT_Get_Glyph error!\n");return -1;}/* 從glyph得到外框: bbox,然后保存在變量glyph_bbox中 */FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &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;/* 計算下一個字符的原點: increment pen position */pen.x += slot->advance.x;pen.y += slot->advance.y;}/* return string bbox */*abbox = bbox;//保存結果
}/* 調整原點并繪制 */
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{int i;int error;FT_BBox bbox;FT_Vector pen;FT_Glyph glyph;FT_GlyphSlot slot = face->glyph;/* 把LCD坐標轉換為笛卡爾坐標 */int x = lcd_x;int y = var.yres - lcd_y;/* 計算外框 */compute_string_bbox(face, wstr, &bbox);/* 反推原點 */pen.x = (x - bbox.xMin) * 64; /* 單位: 1/64像素 */pen.y = (y - bbox.yMax) * 64; /* 單位: 1/64像素 *//* 處理每個字符 */for (i = 0; i < wcslen(wstr); i++){/* 轉換:transformation */FT_Set_Transform(face, 0, &pen);/* 加載位圖: load glyph image into the slot (erase previous one) */error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);if (error){printf("FT_Load_Char error\n");return -1;}/* 在LCD上繪制: 使用LCD坐標 */draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top);/* 計算下一個字符的原點: increment pen position */pen.x += slot->advance.x;pen.y += slot->advance.y;}return 0;
}int main(int argc, char **argv)
{wchar_t *wstr = L"百問網www.100ask.net";FT_Library library;FT_Face face;int error;FT_BBox bbox;int font_size = 24;int lcd_x, lcd_y;if (argc < 4){printf("Usage : %s <font_file> <lcd_x> <lcd_y> [font_size]\n", argv[0]);return -1;}lcd_x = strtoul(argv[2], NULL, 0);//將輸入的X坐標轉換成想要的形式 lcd_y = strtoul(argv[3], NULL, 0);//將輸入的Y坐標轉換成想要的形式 if (argc == 5)font_size = strtoul(argv[4], NULL, 0);//將輸入的字體大小轉換成想要的形式 fd_fb = open("/dev/fb0", O_RDWR);//打開設備節點if (fd_fb < 0){printf("can't open /dev/fb0\n");return -1;}if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)){printf("can't get var\n");return -1;}if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)){printf("can't get fix\n");return -1;}line_width = var.xres * var.bits_per_pixel / 8;pixel_width = var.bits_per_pixel / 8;screen_size = var.xres * var.yres * var.bits_per_pixel / 8;fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);if (fbmem == (unsigned char *)-1){printf("can't mmap\n");return -1;}/* 清屏: 全部設為黑色 */memset(fbmem, 0, screen_size);error = FT_Init_FreeType( &library ); /* 初始化 freetype 庫 */error = FT_New_Face( library, argv[1], 0, &face ); /* 加載字體文件 */FT_Set_Pixel_Sizes(face, font_size, 0); /* 設置字體大小 */display_string(face, wstr, lcd_x, lcd_y); /* */return 0;
}
draw函數
void draw_bitmap( FT_Bitmap* bitmap, FT_Int x, FT_Int y)
{FT_Int i, j, p, q;FT_Int x_max = x + bitmap->width;FT_Int y_max = y + bitmap->rows;//printf("x = %d, y = %d\n", x, y);for ( j = y, q = 0; j < y_max; j++, q++ ){for ( i = x, p = 0; i < x_max; i++, p++ ){if ( i < 0 || j < 0 ||i >= var.xres || j >= var.yres )continue;//image[j][i] |= bitmap->buffer[q * bitmap->width + p];lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);}}
}
第一步
第二步:計算外框
/* 計算一行文字的外框 */
int compute_string_bbox(FT_Face face, wchar_t *wstr, FT_BBox *abbox)
{/* FT_Face face : 對應一個字體文件 */int i; //用于for循環計算次數int error; //用于保存函數返回值FT_BBox bbox; //初始外框FT_BBox glyph_bbox;//用來保存新加載的外框(從glyph得到外框: bbox)FT_Vector pen; //定義原點FT_Glyph glyph; //用于保存字體文件FT_GlyphSlot slot = face->glyph;/* 插槽: 字體的處理結果保存在這里 *//* 初始化 */bbox.xMin = bbox.yMin = 32000;bbox.xMax = bbox.yMax = -32000;/* 指定原點為(0, 0) */pen.x = 0;pen.y = 0;/* 計算每個字符的bounding box *//* 先translate, 再load char, 就可以得到它的外框了 */for (i = 0; i < wcslen(wstr); i++){/* 轉換:transformation */FT_Set_Transform(face, 0, &pen);/* 加載位圖: 一個字一個字得去加載位圖 會保存在face里面 */error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);if (error){printf("FT_Load_Char error\n");return -1;}/* 在face結構體里面取出新的關鍵點保存在變量glyph里(從 slot 中獲得glyph) */error = FT_Get_Glyph(face->glyph, &glyph);if (error){printf("FT_Get_Glyph error!\n");return -1;}/* 從glyph得到外框: bbox,然后保存在變量glyph_bbox中 */FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &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;/* 計算下一個字符的原點: increment pen position */pen.x += slot->advance.x;pen.y += slot->advance.y;}/* return string bbox */*abbox = bbox;//保存結果
}
第三步:調整坐標
第四步: 轉換、加載位圖、繪制
/* 調整原點并繪制 */
int display_string(FT_Face face, wchar_t *wstr, int lcd_x, int lcd_y)
{int i;int error;FT_BBox bbox;FT_Vector pen;FT_Glyph glyph;FT_GlyphSlot slot = face->glyph;/* 把LCD坐標轉換為笛卡爾坐標 */int x = lcd_x;int y = var.yres - lcd_y;/* 計算外框 */compute_string_bbox(face, wstr, &bbox);/* 反推原點 */pen.x = (x - bbox.xMin) * 64; /* 單位: 1/64像素 */pen.y = (y - bbox.yMax) * 64; /* 單位: 1/64像素 *//* 處理每個字符 */for (i = 0; i < wcslen(wstr); i++){/* 轉換:transformation */FT_Set_Transform(face, 0, &pen);/* 加載位圖: load glyph image into the slot (erase previous one) */error = FT_Load_Char(face, wstr[i], FT_LOAD_RENDER);if (error){printf("FT_Load_Char error\n");return -1;}/* 在LCD上繪制: 使用LCD坐標 */draw_bitmap( &slot->bitmap,slot->bitmap_left,var.yres - slot->bitmap_top);/* 計算下一個字符的原點: increment pen position */pen.x += slot->advance.x;pen.y += slot->advance.y;}return 0;
}