字符编码方式有ASCII,GBK和Unicode等,ASCII编码方式用 1 byte来表示一个字符,ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。
GBK码用两个字节表示一个汉字。
Unicode码把全世界的字符与编码统一地对应起来,一个Unicode编码值唯一地对应着世界上的一个字符,例如Unicode编码值4E25,在世界各地都表示着汉字“严”。用多少个字节表示一个字符呢?它是不确定的,根据Unicode码的存储方式的不同而不同,有UTF-8,Unicode big endian等存储方式。
点阵显示方式主要用来显示ASCII码字符的点阵和汉字库点阵。当然,freetype最终也是通过字体的点阵来显示,只是freetype获得点阵需要一个过程,而ASCII和汉字库点阵是现成的,根据ASCII码或GBK码在点阵数组或汉字库中直接获取字体的点阵。
ASCII码对应的字符最多就256个字符,可以用一个unsigned char类型点阵数组fontdata_8x16[]来实现所有ASCII码字符的点阵形状,每一个字符的点阵16个元素构成。即一个ASCII点阵在显示屏上的总像素数就是8*16=128个,是固定的,点阵的字体的大小,长*宽就是 8*16像素。对fontdata_8x16数组,当然不是由我们自己来一个一个字符地去实现它的点阵,那工程量将是非常大的,别人早实现好了,可到 linux 3.4.2内核目录 drivers/video/console/font_8x16.c拷贝一份。
下面是fontdata_8x16[]数组的部分内容:
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 0 0x00 '^@' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
0x99, /* 10011001 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0x7e, /* 01111110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
......
/* 65 0x41 'A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x10, /* 00010000 */
0x38, /* 00111000 */
0x6c, /* 01101100 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xfe, /* 11111110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
......
}
字符点阵的首地址索引值即为字符的ASCII码值,即可根据字符的ASCII码值得到字符点阵首地址,首地址为:fontdata_8x16[ASCII码值*16]的地址。如字符'^A',ASCII码值为1,索引值也为1,点阵第一行为fontdata_8x16[1*16];字符‘A’的ASCII码值为65,索引值也为65,点阵第一行为fontdata_8x16[65*16],也即字符‘A’由fontdata_8x16[1040],fontdata_8x16[1041]......fontdata_8x16[1055] 共16个元素共同表示。
我们怎么通过数组元素的内容把字符在LCD上显示出来呢?
以‘A’字符为例,由于这些元素中的那些为1的位描绘了'A'的字形,每一位对应这一个像素,我们把所有位0的位用同一种颜色的像素显示,把所有为1的位用另一种颜色的像素显示,这样就在LCD上把'A'字符显示出来了。重点是能把每一个0或1,在LCD正确的位置上用不同的颜色描绘出来。
汉字库点阵与ASCII字符点阵原理一样,把所有汉字的字形点阵都预先设计好,获得要显示的汉字的点阵的内存首地址,把内存的内容转换为RGB值后正确地写到framebuffer正确的对应位置上即可。只是这个汉字的点阵不是用数组来实现,用文件的形式来保存所有汉字的点阵,这个文件叫汉字库点阵文件,常用的一种实现是HZK16,这个汉字库文件实现的汉字点阵是16*16像素。根据GBK码在汉字库中检索汉字及汉字显示,在细节上与ASCII码有些区别,也不难,具体看我代码注释。
用这种固定的点阵来显示字符的缺点是字体大小也是固定的,字体的物理大小只与像素的物理尺寸相关了。freetype可以改变这个情况,它能实现变化的字体大小,字体旋转,字体颜色等功能,灵活地显示字体。
GBK码用两个字节表示一个汉字。
Unicode码把全世界的字符与编码统一地对应起来,一个Unicode编码值唯一地对应着世界上的一个字符,例如Unicode编码值4E25,在世界各地都表示着汉字“严”。用多少个字节表示一个字符呢?它是不确定的,根据Unicode码的存储方式的不同而不同,有UTF-8,Unicode big endian等存储方式。
点阵显示方式主要用来显示ASCII码字符的点阵和汉字库点阵。当然,freetype最终也是通过字体的点阵来显示,只是freetype获得点阵需要一个过程,而ASCII和汉字库点阵是现成的,根据ASCII码或GBK码在点阵数组或汉字库中直接获取字体的点阵。
ASCII码对应的字符最多就256个字符,可以用一个unsigned char类型点阵数组fontdata_8x16[]来实现所有ASCII码字符的点阵形状,每一个字符的点阵16个元素构成。即一个ASCII点阵在显示屏上的总像素数就是8*16=128个,是固定的,点阵的字体的大小,长*宽就是 8*16像素。对fontdata_8x16数组,当然不是由我们自己来一个一个字符地去实现它的点阵,那工程量将是非常大的,别人早实现好了,可到 linux 3.4.2内核目录 drivers/video/console/font_8x16.c拷贝一份。
下面是fontdata_8x16[]数组的部分内容:
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 0 0x00 '^@' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
0x99, /* 10011001 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0x7e, /* 01111110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
......
/* 65 0x41 'A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x10, /* 00010000 */
0x38, /* 00111000 */
0x6c, /* 01101100 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xfe, /* 11111110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
......
}
字符点阵的首地址索引值即为字符的ASCII码值,即可根据字符的ASCII码值得到字符点阵首地址,首地址为:fontdata_8x16[ASCII码值*16]的地址。如字符'^A',ASCII码值为1,索引值也为1,点阵第一行为fontdata_8x16[1*16];字符‘A’的ASCII码值为65,索引值也为65,点阵第一行为fontdata_8x16[65*16],也即字符‘A’由fontdata_8x16[1040],fontdata_8x16[1041]......fontdata_8x16[1055] 共16个元素共同表示。
我们怎么通过数组元素的内容把字符在LCD上显示出来呢?
以‘A’字符为例,由于这些元素中的那些为1的位描绘了'A'的字形,每一位对应这一个像素,我们把所有位0的位用同一种颜色的像素显示,把所有为1的位用另一种颜色的像素显示,这样就在LCD上把'A'字符显示出来了。重点是能把每一个0或1,在LCD正确的位置上用不同的颜色描绘出来。
汉字库点阵与ASCII字符点阵原理一样,把所有汉字的字形点阵都预先设计好,获得要显示的汉字的点阵的内存首地址,把内存的内容转换为RGB值后正确地写到framebuffer正确的对应位置上即可。只是这个汉字的点阵不是用数组来实现,用文件的形式来保存所有汉字的点阵,这个文件叫汉字库点阵文件,常用的一种实现是HZK16,这个汉字库文件实现的汉字点阵是16*16像素。根据GBK码在汉字库中检索汉字及汉字显示,在细节上与ASCII码有些区别,也不难,具体看我代码注释。
下面是一个ASCII码点阵和汉字库点阵的实例show_font.c:
#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> #define FONTDATAMAX 4096 static const unsigned char fontdata_8x16[FONTDATAMAX] = { /* 0 0x00 '^@' */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ /* 1 0x01 '^A' */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x7e, /* 01111110 */ 0x81, /* 10000001 */ 0xa5, /* 10100101 */ 0x81, /* 10000001 */ 0x81, /* 10000001 */ 0xbd, /* 10111101 */ 0x99, /* 10011001 */ 0x81, /* 10000001 */ 0x81, /* 10000001 */ 0x7e, /* 01111110 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ 0x00, /* 00000000 */ ...... } int 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; int fd_hzk16; struct stat hzk_stat;//文件stat结构体 unsigned char *hzkmem;//把文件映射到hzkmem /* color : 0x00RRGGBB */ void lcd_put_pixel(int x, int y, unsigned int color) { unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width; //找到对应的像素在内存中的位置,后面向其(*pen_8)写颜色值。 //通过pen_8来操作fbmem,实现LCD显示 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;//8位像素的话,颜色值直接赋给这个内存中即可。 break; } case 16: { /* color : 0x00RRGGBB ,unsigned int 32位color的格式*/ /* 565 */ red = (color >> 16) & 0xff; //右移16位后剩下高16位,再&0xff,又剩下低8位。即取出32位color的16-23位 green = (color >> 8) & 0xff;//取出32color的8-15位 blue = (color >> 0) & 0xff;//取出32color的0-7位 color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3); //取出对应的组成565 *pen_16 = color; break; } case 32: { *pen_32 = color;//32位像素也直接写即可。 break; } default: { printf("can't surport %dbpp\n", var.bits_per_pixel); break; } } } void lcd_put_ascii(int x, int y, unsigned char c) { unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16]; //从fontdata_8x16[FONTDATAMAX]数组中获得点阵起始位置 int i, b; unsigned char byte; for (i = 0; i < 16; i++)//点阵有16行 { byte = dots[i]; for (b = 7; b >= 0; b--)//点阵有8列 { if (byte & (1<<b))//判断点阵中的各个点是否为1 { /* show */ lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */ } else { /* hide */ lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */ } } } } void lcd_put_chinese(int x, int y, unsigned char *str) { unsigned int area = str[0] - 0xA1; unsigned int where = str[1] - 0xA1; unsigned char *dots = hzkmem + (area * 94 + where)*32; unsigned char byte; int i, j, b; for (i = 0; i < 16; i++)//16行 for (j = 0; j < 2; j++) { byte = dots[i*2 + j]; for (b = 7; b >=0; b--) { if (byte & (1<<b)) { /* show */ lcd_put_pixel(x+j*8+7-b, y+i, 0xffffff); /* 白 */ } else { /* hide */ lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */ } } } } int main(int argc, char **argv) { unsigned char str[] = "中";//汉字库起始位置+偏移值=点阵实际位置, //这里的汉字库点阵是16*16的,意思是每行是16 bit,即2 Byte,总共16列。 //即一个汉字点阵的长度是2 byte *16= 32 Byte. //偏移值就是(area * 94 + where)*32 ,一个区(area)有94个汉字,这是固定的。 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; //分辨率 * 每像素位数 /8 =xxx字节。一行多少字节。即line_width是行宽,是以字节为单位的。 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; } fd_hzk16 = open("HZK16", O_RDONLY); if (fd_hzk16 < 0) { printf("can't open HZK16\n"); return -1; } if(fstat(fd_hzk16, &hzk_stat)) { printf("can't get fstat\n"); return -1; } hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARED, fd_hzk16, 0); if (hzkmem == (unsigned char *)-1) { printf("can't mmap for hzk16\n"); return -1; } /* 清屏: 全部设为黑色 */ memset(fbmem, 0, screen_size); lcd_put_ascii(var.xres/2, var.yres/2, 'A'); printf("chinese code: %02x %02x\n", str[0], str[1]); lcd_put_chinese(var.xres/2 + 8, var.yres/2, str); return 0; }点阵显示的重点是根据ASCII码值或GBK码值,在ASCII字符点阵数据或汉字库文件中检索字符点阵,把保存点阵的内存的内容,用颜色值表示,然后又怎么正确的写到framebuffer对应位置,以使字符能再lcd上正确地显示出来。
用这种固定的点阵来显示字符的缺点是字体大小也是固定的,字体的物理大小只与像素的物理尺寸相关了。freetype可以改变这个情况,它能实现变化的字体大小,字体旋转,字体颜色等功能,灵活地显示字体。