ASCII字符点阵和汉字库点阵显示

    字符编码方式有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码有些区别,也不难,具体看我代码注释。

    下面是一个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可以改变这个情况,它能实现变化的字体大小,字体旋转,字体颜色等功能,灵活地显示字体。

猜你喜欢

转载自blog.csdn.net/qq_22863733/article/details/79723823