正点原子linux应用编程——提高篇2

本篇主要针对的是LCD相关的应用程序。

FrameBuffer应用编程

FrameBuffer介绍

帧缓冲(framebuffer)是Linux系统中的一种显示驱动接口,它将显示设备(如LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不必关心物理显存的位置等具体细节,这些都由Framebuffer设备驱动来完成。

FrameBuffer设备对应的设备文件为/dev/fbX(X为数字,0、1、2、3等),Linux下可支持多个FrameBuffer设备,最多可达32个,分别为/dev/fb0到/dev/fb31,开发板出厂系统中,/dev/fb0设备节点便是LCD屏

LCD基础

这个在驱动教程学过了,就直接跳过了。

LCD应用编程介绍

应用程序通过对LCD设备节点/dev/fb0(假设 LCD对应的设备节点是/dev/fb0)进行I/O操作即可实现对LCD的显示控制,实质就相当于读写了LCD的显存,而显存是LCD的显示缓冲区,LCD 硬件会从显存中读取数据显示到LCD液晶面板上。

操作/dev/fbX的基本步骤如下:

  1. 首先打开/dev/fbX设备文件。
  2. 使用ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。
  3. 通过存储映射I/O方式将屏幕的显示缓冲区映射到用户空间(mmap)。
  4. 映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。
  5. 完成显示后,调用munmap()取消映射、并调用close()关闭设备文件。

使用ioctl()获取屏幕参数

通过ioctl()函数来获取屏幕参数信息,对于Framebuffer设备来说,常用的request包括FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO。

FBIOGET_VSCREENINFO:表示获取FrameBuffer设备的可变参数信息,可变参数信息使用struct fb_var_screeninfo结构体来描述,所以此时ioctl()需要有第三个参数,它是一个struct fb_var_screeninfo *指针,指向struct fb_var_screeninfo类型对象,调用ioctl()会将LCD屏的可变参数信息保存在struct fb_var_screeninfo类型对象中,如下所示:

struct fb_var_screeninfo fb_var;
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);

FBIOPUT_VSCREENINFO:表示设置FrameBuffer设备的可变参数信息,既然是可变参数,那说明应用层可对其进行修改、重新配置,当然前提条件是底层驱动支持这些参数的动态调整,同样此时ioctl()需要有第三个参数,也是一个struct fb_var_screeninfo *指针,指向struct fb_var_screeninfo类型对
象,表示用struct fb_var_screeninfo对象中填充的数据设置LCD,如下所示:

struct fb_var_screeninfo fb_var = {0};
/* 对 fb_var 进行数据填充 */
......
......
/* 设置可变参数信息 */
ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);

FBIOGET_FSCREENINFO:表示获取FrameBuffer设备的固定参数信息,既然是固定参数,那就意味着应用程序不可修改。固定参数信息使用struct fb_fix_screeninfo结构体来描述,所以此时ioctl()需要有第三个参数,它是一个struct fb_fix_screeninfo *指针,指向struct fb_fix_screeninfo类型对象,调用ioctl()会将LCD的固定参数信息保存在struct fb_fix_screeninfo对象中,如下所示:

struct fb_fix_screeninfo fb_fix;
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

以上三个宏定义以及两个数据结构题均定义在头文件中。

struct fb_var_screeninfo以及struct fb_bitfield和struct fb_fix_screeninfo结构体都去看教程复习,内容比较多,用的时候用那么几个也就好了。

  • 获取LCD屏幕的参数信息示例

定义好fb_fix_screeninfo和fb_var_screeninfo结构体fb_fix和fb_var;之后open打开LCD设备文件,通过ioctl的FBIOGET_VSCREENINFO以及FBIOGET_FSCREENINFO获取参数信息,之后就把感兴趣的参数printf出来就可以了。

使用mmap()将显示缓冲区映射到用户空间

因为显示屏一般数据量会很大,所以一般均采用存储映射I/O的方式,也就是通过mmap()将显示缓冲区映射到用户空间,然后应用程序就可以直接丢显示缓冲区进行读写。

LCD应用编程——基本操作

完成了画点、画线、画矩形等基本LCD操作。

首先需要定义一个argb8888格式转成rgb565格式的函数,可通过宏定义方式完成,就是把原来的32位数据,取出19-24位>>8作为r,12-18位>>5作为g,3-8位>>3作为b然后拼一起就是了。

lcd_draw_point函数,完成画点,其中对传入的x和y先进性判断,不超过LCD的分辨率width和height,之后调用sreen_base画点,坐标就是y * width + x。

lcd_draw_line函数,完成画线,先校验x和y,然后通过y*width+x定位到起始点,通过传入的dir判断方向,由此计算end,之后for循环调用lcd_draw_point画点。

lcd_draw_rectangle函数,完成画矩形,直接计算出x_end和y_end,然后调用四次lcd_draw_line。

lcd_fill函数,完成填充,在里面校验四个顶点的坐标,之后通过start_y*width定位到起始点,之后两层for循环调用screen_base即可。

在main函数中,首先open打开LCD设备文件,然后ioctl获取参数信息,计算出screen_size,width以及height;之后通过mmap映射到screen_base(screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);然后在后面调用lcd的一系列函数画图形就可以了。

LCD应用编程——显示BMP图片

BMP图像介绍

BMP(全称Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP图像文件所占用的空间很大,但是没有失真、并且解析BMP图像简单。

BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit以及32bit,典型的BMP图像文件由四部分组成:

  1. BMP文件头(BMP file header),它包含BMP文件的格式、大小、位图数据的偏移量等信息;
  2. 位图信息头(bitmap information),它包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息;
  3. 调色板(color palette),这部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;
  4. 位图数据(bitmap data),也就是图像数据。

对于bmp格式的具体介绍,教程中有,比较长,这里就不再赘述,之后万一用到了再去看看。

LCD上显示BMP图像

首先需要定义bmp_file_header结构体保存bmp文件头数据;定义bmp_info_header结构体保存位图信息头数据。

show_bmp_image函数,来显示BMP图像,首先open打开设备文件,然后read读取BMP文件头,并通过memcmp来确认是否为BMP文件;之后read获取位图信息头;之后就可以把获取的位图信息数据通过printf打印出来;然后将读写位置通过lseek和读取到的file_h.offset移动到图像数据开始处,malloc申请一个buf来暂存一行数据;在校验数据之后,判断是正向还是倒向位图,根据方向来read数据。

main函数中,open打开LCD设备文件,ioctl获取参数,然后mmap把显示缓冲区映射到进程地址空间screen_base,然后memset先初始化为0xFF,之后调用show_bmp_image显示图像,最后需要munmap取消映射并close文件。

LCD上显示jpeg图像

JPEG简介

JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。JPEG压缩文件通常以.jpg或.jpeg作为文件后缀名。

libjpeg简介

libjpeg是一个完全用C语言编写的函数库,包含了JPEG解码(解压缩)、JPEG编码(创建压缩)和其他的JPEG功能的实现。可以使用libjpeg库对.jpg或.jpeg压缩文件进行解压或者生成.jpg或.jpeg压缩文件。

libjpeg移植

这个就直接看教程对着做就好了。

libjpeg使用

首先,使用libjpeg库需要在应用程序中包含它的头文件jpeglib.h,该头文件包含了一些结构体数据结构以及API接口的申明。

用libjpeg库解码jpeg数据的时候,最重要的一个数据结构为struct jpeg_decompress_struct结构体,该数据结构记录着jpeg数据的详细信息,也保存着解码之后输出数据的详细信息。除此之外,还需要定义一个用于处理错误的对象,错误处理对象是一个struct jpeg_error_mgr结构体变量。如下所示:

struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;

错误处理

当错误发生时,譬如如果内存不足、文件格式不对等,则会调用libjpeg实现的默认错误处理函数,默认错误处理函数将会调用exit()结束束整个进程;当然,可以修改错误处理的方式,libjpeg提供了接口让用户可以注册一个自定义错误处理函数。

结构体的话可以看教程,里面的error_exit函数指针便指向了错误处理函数。使用libjpeg库函数jpeg_std_error()会将libjpeg错误处理设
置为默认处理方式。

创建解码对象

要使用libjpeg解码jpeg数据,这步是必须要做的。如下所示:

jpeg_create_decompress(&cinfo);

在创建解码对象之后,如果解码结束或者解码出错时,需要调用jpeg_destroy_decompress销毁/释放解码对象,否则将会内存泄漏。

设置数据源

设置需要进行解码的jpeg文件,使用jpeg_stdio_src()函数设置数据源:

FILE *jpeg_file = NULL;
//打开.jpeg/.jpg 图像文件
jpeg_file = fopen("./image.jpg", "r"); //只读方式打开
if (NULL == jpeg_file) {
perror("fopen error");
return -1;
}
//指定图像文件
jpeg_stdio_src(&cinfo, jpeg_file);

读取jpeg文件头信息

和创建解码对象一样,是必须要调用的。如下所示:

jpeg_read_header(&cinfo, TRUE);

调用jpeg_read_header()后,可以得到jpeg图像的一些信息,譬如jpeg图像的宽度、高度、颜色通道数以及colorspace等,这些信息会赋值给cinfo对象中的相应成员变量,如下所示:

cinfo.image_width //jpeg 图像宽度
cinfo.image_height //jpeg 图像高度
cinfo.num_components //颜色通道数
cinfo.jpeg_color_space //jpeg 图像的颜色空间

设置解码处理函数

这些参数都有一个默认值,调用jpeg_read_header()函数后,这些参数被设置成相应的默认值。直接对cinfo对象的成员变量修改即可。

开始解码

经过设置后即可解码,调用jpeg_start_decompress()函数:

jpeg_start_decompress(&cinfo);

在完成解压缩操作后,会将解压后的图像信息填充至cinfo结构中。如果希望在调用jpeg_start_decompress()之前就获得参数,可以通过调用jpeg_calc_output_dimensions()的方法实现。

读取数据

读取数据是按照行读取的,解码后的数据按照从左到右、从上到下顺序存储,每个像素点对应各颜色或灰度。

libjpeg默认解码得到的图像数据是BGR888格式,即R颜色在低8位、而B颜色在高8位。

每次读取一行数据,计算每行数据需要的空间大小,以RGB图像举例就是:

bgr888_t *line_buf = malloc(cinfo.output_width * cinfo.output_components);

当然除了malloc分配,也可以使用libjpeg的内存管理器分配。

分配好缓冲,可以调用jpeg_read_scanlines()读取数据,该函数可指定一次读的行数,但目前只能支持一次只读1行,如下:

jpeg_read_scanlines(&cinfo, &buf, 1

需要循环读取每一行,cinfo.output_scanline就表示接下来要读的索引,所以可以如下方式读取jpeg数据:

while(cinfo.output_scanline < cinfo.output_height)
{ 
	jpeg_read_scanlines(&cinfo, buffer, 1);
	//do something 
}

结束解码

解码完毕之后调用jpeg_finish_decompress()函数:

jpeg_finish_decompress(&cinfo);

释放解码对象

当解码完成之后,需要调用jpeg_destroy_decompress()函数销毁/释放解码对象:

jpeg_destroy_decompress(&cinfo);

libjpeg应用编程

首先需要定义bgr888的结构体来存储像素的色彩值。

老样子,fb_fix_screeninfo和fb_var_screeninfo的两个结构体变量fb_fix和fb_var存储LCD屏幕参数信息,同时定义static unsigned short指针screen_base来存放映射的I/O地址。

  • 编写show_jpeg_image函数

需要libjpeg相关的jpeg_decompress_struct结构体变量cinfo,jpeg_error_mgr结构体变量jerr,FILE指针jpeg_file,自定义的bgr888_t的结构体指针jpeg_line_buf(行缓冲,存储jpeg解压出来的一行数据),unsigned short指针变量fb_line_buf,还有的就是LCD相关参数的存储变量。

jpeg_std_error绑定默认错误处理函数,然后通过fopen打开jpeg文件,调用jpeg_create_decompress创建JPEG解码对象,然后jpeg_read_header读取图像信息,通过cinfo的成员变量设置解码参数(cinfo.out_color_space设置为JCS_RGB),然后jpeg_start_decompress开始解码,malloc分配空间给到jpeg_line_buf以及fb_line_buf。

之后判断图像和LCD的分辨率并取更小值,然后读取数据,设置valid_bytes作为一行的有效字节数后,在while中判断cinfo.output_scanline

以上读取完成后,调用jpeg_finish_decompress完成解码,再调用jpeg_destroy_decompress释放解码对象。

  • main函数

main函数中,open打开framebuffer设备文件,通过ioctl获取LCD的参数信息,mmap进行映射,然后memset把screen_base全部初始化为0xFF后,调用show_jpeg_image显示图像。

总结

libjpeg除了JPEG解码功能外,还可以实现JPEG编码以及其它一些JPEG功能,如果有需求就去看一下源码学一下就可以了。

LCD上显示png图片

PNG简介

PNG(便携式网络图形格式PortableNetwork Graphic Format,简称PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF文件,同时增加一些GIF文件所不具备的特性。PNG使用从LZ77派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。

具有如下的特点:无损压缩、体积小、索引彩色模式、更优化的网络传输显示、支持透明效果。

libpng简介

可以使用libpng库对png文件进行解码,跟libjpeg一样,它也是一套免费、开源的C语
言函数库,支持对png图像文件解码、编码等功能。

zlib移植

zlib是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的C语言函数库。libpng依赖于zlib库,所以要想移植libpng先得移植zlib库才可以。

之后的移植步骤看教程就可以了。

libpng移植

这一部分同样看教程操作就可以了。

libpng使用

主要学习如何解码png文件。

libpng的数据结构

首先,使用libpng库需要包含它的头文件。png.h头文件中包含了API、数据结构的申明,libpng中有两个很重要的数据结构体:png_struct和png_info。

使用libpng之前,需要创建一个png_struct对象并对其进行初始化操作,该对象由libpng库内部使用,调用libpng库函数时,通常需要把这个对象作为参数传入。

png_info数据结构体描述了png图像的信息,可调用API来访问png_info,get方法png_get_XXX和set方法png_set_XXX。

创建和初始化png_struct

使用png_create_read_struct()函数创建一
个png_struct对象、并完成初始化操作,read 表示我们需要创建的是一个用于png解码的png_struct对象;同理可以使用png_create_write_struct()创建一个用于png编码的png_struct对象。

png_create_read_struct()函数原型如下所示:

png_structp png_create_read_struct(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, 
png_error_ptr warn_fn);

返回值是一个png_structp指针,指向一个png_struct对象;如果创建失败会返回NULL。

第一个参数user_png_ver指的是libpng的版本信息,通常将其设置为PNG_LIBPNG_VER_STRING。

可以指定自定义的错误处理函数和自定义的警告处理函数,通过参数error_fn指向自定义的错误处理函数、通过参数warn_fn指向自定义的警告处理函数,而参数error_ptr表示传递给这些函数所使用的数据结构的指针;当然也可将它们设置为NULL,表示使用libpng默认的错误处理函数以及警告函数。

创建和初始化png_info

调用png_create_info_struct()函数创建一个png_info对象,其函数原型如下所示:

png_infop png_create_info_struct(png_const_structrp png_ptr);

返回一个png_infop指针,指向一个png_info对象;如果创建失败,则会返回NULL。

有一个参数,需要传入一个png_struct对象的指针,内部会将它们之间建立关联。销毁png_struct时,也可将png_info销毁。

设置错误返回点

默认的错误处理函数会执行一个跳转动作,跳转到程序中的某一个位置,这个位置称为错误返回点。

goto语句就可以来执行跳转,但goto只能在一个函数内部跳转,不能跨函数。Linux下,可以使用库函数setjmp和longjmp进行跨函数跳转。

setjmp 函数用于设置跳转点,也就是跳转位置;longjmp执行跳转,那么它会跳转到setjmp函数所设置的跳转点,来看看这两个函数的原型:

#include 

int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

env参数,是一个jmp_buf类型的参数,jmp_buf 是一种特殊类型,当调用setjmp()时,它会把当前进程环境的各种信息保存到env参数中,而调用longjmp()也必须指定相同的参数,这样才可跳转到setjmp()所设置的跳转点。

可通过setjmp()的返回值,来区分setjmp()是第几次调用,初次调用返回值为0,后续为longjmp()调用中参数val的值。

libpng默认就是使用setjmp和longjmp的组合完成发生错误的跳转,所以需要setjmp设置错误返回点,如下所示:

/* 设置错误返回点 */
if (setjmp(png_jmpbuf(png_ptr))) {
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	return -1;
}

指定数据源

通常可以使用多种方式来指定数据源,譬如文件输入流、内存中的数据流等,教程中以文件输入流为例。

可通过调用png_init_io()可以指定数据源,该数据源以文件输入流的方式提供,函数原型如下:

png_init_io(png_structrp png_ptr, png_FILE_p fp);

第一个参数是png_ptr,指向png_struct对象;而第二个参数fp则是一个png_FILE_p类型指针,其实就是标准I/O中的FILE *指针。

读取png图像数据并解码

从png文件中读取数据并进行解码,将解码后的图像数据存放在内存中,待用户读取。关于这一步的操作,libpng提供了两种方式去处理:high-level接口处理和low-level接口处理。high-level只是对low-level方式进行了一个封装,使用high-level接口非常方便只需一个函数即可,但缺点是灵活性不高、被限定
了;而low-level接口恰好相反,灵活性高、但需要用户调用多个API。

  • high-level接口

一般在用户内存空间足够大,可一次性存放整个png数据,并且数据输出格式先定位libpng预定义的数据转换格式时,可采用high-level接口。

正点原子linux应用编程——提高篇2_第1张图片

high-level接口只需要使用一个函数png_read_png(),调用该函数将一次性把整个png文件的图像数据解码出来、将解码后的数据存放在内存中,如下所示:

png_read_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, png_voidp params);

第一个参数png_ptr为指向png_struct对象的指针,第二个参数info_ptr为指向png_info对象的指针;而第三个参数transforms为整型参数,取值为libpng预定义的数据转换类型,可以使用or(C语言的或 | 运算符)组合多个转换类型。

  • low-level接口

首先调用png_read_info()获取png图像信息:

png_read_info(png_ptr, info_ptr);

然后调用libpng的API来查询读取到png_info中的信息:

unsigned int width = png_get_image_width(png_ptr, info_ptr); //获取 png 图像的宽度
unsigned int height = png_get_image_height(png_ptr, info_ptr); //获取 png 图像的高度
unsigned char depth = png_get_bit_depth(png_ptr, info_ptr); //获取 png 图像的位深度
unsigned char color_type = png_get_color_type(png_ptr, info_ptr); //获取 png 图像的颜色类型

之后调用libpng库的API,也就是一系列png_set_xxx函数来设置解码输出的参数:

unsigned char depth = png_get_bit_depth(png_ptr, info_ptr);
unsigned char color_type = png_get_color_type(png_ptr, info_ptr);
if (16 == depth)
	png_set_strip_16(png_ptr); //将 16 位深度转为 8 位深度
if (8 > depth)
	png_set_expand(png_ptr); //如果位深小于 8,则扩展为 24-bit RGB
if (PNG_COLOR_TYPE_GRAY_ALPHA == color_type)
	png_set_gray_to_rgb(png_ptr); //如果是灰度图,则转为 RGB

当然也可以自定义转换函数,通过调用png_set_read_user_transform_fn()函数向libpng注册一个自定义转换函数,还可以通过png_set_user_transform_info()函数告诉libpng自定义转换函数的用户自定义数据结构和输出数据的详细信息。

之后设置完格式,需要调用png_read_update_info()函数更新信息:

png_read_update_info(png_ptr, info_ptr);

更新完成,就可以进行解码,调用png_read_image()函数可以一次性把整个png文件的图像数据解码出来、并将解码后的数据存放在用户提供的内存区域中:

png_read_image(png_ptr, row_pointers);

参数png_ptr指向png_struct对象;第二个参数row_pointers是一个png_bytepp类型的指针变量,也就是unsigned char **,是一个指针数组。这需要先预定义好row_pointers的空间(可通过png_malloc完成)。

当然也可以通过png_read_rows()来解码1行或多行数据。

最后读取解码完成后,调用png_read_end()结束:

png_read_end(png_ptr, info_ptr);

读取解码后数据

对于low-level,直接从缓冲区获取数据就可以了。

对于high-level,可通过调用png_get_rows()获取指向每一行数据缓冲区的指针数组,如下所示:

png_bytepp row_pointers = NULL;
row_pointers = png_get_rows(png_ptr, info_ptr);//获取到指向每一行数据缓冲区的指针数组

结束销毁对象

调用png_destroy_read_struct()销毁png_struct对象,该函数原型如下所示:

void png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr);

使用方法如下:

png_destroy_read_struct(png_ptr, info_ptr, NULL);

libpng应用编程

首先要定义好LCD屏幕的两个结构体变量,以及映射地址。

  • show_png_image函数

定义好png相关的结构体指针,png_structp结构体变量png_ptr,png_infop结构体变量info_ptr,之后就是FILE指针以及LCD相关的参数变量。

fopen打开png文件后,调用png_create_read_struct分配和初始化png_ptr,调用png_create_info_struct分配和初始化info_ptr;通过setjmp方法设置错误点,如果错误就调用png_destroy_read_struct;调用png_init_io指定数据源后,调用png_read_png读取png文件;之后判断RGB888以及设置判断图像和LCD分辨率谁更小并完成min_h和min_w设置。

解码已经完成,通过png_get_rows来读取数据,存到row_pointers中,然后在for循环中,RGB888转为RGB565并memcpy拷贝到base中完成LCD显示。最后png_destroy_read_struct释放内存。

  • main函数

open打开LCD设备文件,然后ioctl获取参数,mmap映射到screen_base,memset设置为0xFF后调用show_png_image完成显示。

LCD横屏切换竖屏

LCD屏正向放置情况下(以 800*480分辨率为例,它的左上角就是坐标(0, 0)、左下角坐标是(0, 480-1)、右上角坐标是(800-1, 0)、右下角坐标是(800-1, 480-1)。

像素点的排列顺序是从左到右、从上到下,对LCD上不同像素点进行操作时,需要找到该像素点对应的显存地址,同样也是基于这种标准来的;假设显存基地址为(unsigned
char *)base,那么定位一个(x, y)坐标像素点对应的地址的公式为:base + (y * width + x) * pix_bytes,其中pix_bytes 表示一个像素点使用pix_bytes个字节来描述。示意图如下:

正点原子linux应用编程——提高篇2_第2张图片

应用程序中将LCD屏修改为竖屏显示原理上非常简单,譬如在应用程序中将左下角作为起点(0, 0),那么左上角对应就是(480-1, 0)、右下角对应就是(0, 800-1)、右上角对应就是(480-1, 800-1),如下图所示:

正点原子linux应用编程——提高篇2_第3张图片

在上图中竖屏方式下,应用程序的(x, y)坐标点对应的显存地址可通过如下公式进行计算:

base + ((height - 1- x) * width + y)) * pix_bytes;

编写示例代码

这里与之前驱动LCD的代码几乎是一样的,就是坐标的计算,即for循环的内容修改了一下,就是参考上面的那个公式。

LCD显示字符

原始方法:取模显示字符

对字符取模,然后在LCD屏上打点显示字符。

可以通过一些字符取模软件获取到字符的子模;所谓子模,其实就是一个二维数组,用于表示字符点阵中,哪些小方块应该要填充颜色、哪些小方块不填充颜色。会使用一个二维数组来表示这个字符点阵:

unsigned char arr[86][8];

可以通过字符取模的软件,获取字符的子模,然后在LCD屏上显示字符。

  • 编写应用程序

LCD相关的两个结构体以及显存地址定义好之后,通过软件获取字符子模并通过unsigned char ch_char[86][8]来保存一下。同样的定义一个argb8888转rgb565的宏定义完成格式转换。

编写lcd_draw_character函数,里面定义好屏幕的参数,然后先计算二维数组的columns,传入的是字符宽度w,columns=w/8同时如果w有剩余还要columns++;之后限制一下x和y不要超过屏幕,就可以计算坐标,end_x=x+w-1; end_y=y+h-1;同样的限制一下这两个坐标之后,计算有效的h和w,h=end_y-y+1; w=end_x-x+1; 之后就可以通过y*width+x定位到起点之后,通过两层for循环给screen_base进行打点(*(ch+y*cloumns+j)&(0x1<<(x%8))为真就打点)。

main函数中,就是open打开LCD设备文件后,ioctl获取屏幕参数并通过mmap完成存储映射,之后memset把屏幕全部置0xFF,然后调用lcd_draw_character显示就可以了。

freetype简介

FreeType是一个完全免费(开源)的软件字体引擎库,设计小巧、高效、高度可定制且可移植,它提供了统一的接口来访问多种不同格式的字体文件。它提供了一个简单、易于使用且统一的接口来访问字体文件的内容,从而大大简化了这些任务。

freetype移植

这里还是跟着教程做一下就可以了。

freetype使用

  • 字形(glyph)

字符图像就叫做字形,一个字符能够有多种不同的字形,可以理解为字形就是字符的一种书写风格。

  • 字形索引

在字体文件中,通过字形索引找到对应的字形,而字形索引是由字符编码转换而来的,譬如ASCII编码、GB2312编码、BIG5编码、GBK编码以及国际标准字符集使用的Unicode编码等。

  • 像素点(pixel)、点(point)以及dpi

像素点就是LCD里面的常用概念不多做介绍;点(point)是一种简单的物理单位,在数字印刷中,一个点(point)等于1/72英寸(1英寸等于25.4毫米)。dpi(dots per inch)表示每英寸的像素点数。三者换算关系如下:

像素点数 = 点数 * dpi / 72
  • 字形布局

可分为水平布局和垂直布局。

  • 基准线、原点

原点、基准线可以用于定位字形,水平布局和垂直布局使用不同的约束来放置字形。

  • 字形宽度和高度

width描述了字形轮廓的最左边到最右边的距离;而height描述了字形轮廓的最上边到最下边的距离。同一种书写风格,不同字符所对应的字形,它们的宽高是不一定相等的。

  • bearingX和bearingY

bearingX表示从垂直基线到字形轮廓最左边的距离。对于水平布局来说,字形在垂直基线的右侧,所以bearingX是一个正数;而对于垂直布局来说,字形在垂直基线上居中放置,所以字形轮廓的最左边通常是在垂直基线的左侧,所以bearingX是一个负数。

bearingY则表示从水平基线到字形轮廓最上边的距离。对于垂直布局来说,bearingY是一个正数,字形处于水平基线的下方;而对于水平布局来说,如果字形轮廓的最上边在水平基线的上方,则bearingY是一个正数、相反则是一个负数。

  • xMin/xMax、yMin/yMax

xMin表示字形轮廓最左边的位置,xMax则表示字形轮廓最右边的位置;yMin表示字形轮廓最下边的位置,yMax则表示字形轮廓最上边的位置。

  • advance

advance表示步进宽度,相邻两个原点位置的距离(字间距)。如果是水平布局,则表示相邻的两个原点在水平方向上的距(advanceX),也就是相邻两条垂直基线之间的距离;同理,如果是垂直布局,则表示相邻的两个原点在垂直方向上的距离(advanceY),也就是相邻两条水平基线之间的距离。

正点原子linux应用编程——提高篇2_第4张图片

正点原子linux应用编程——提高篇2_第5张图片

  • 字符显示对齐

通过水平基线和垂直基线进行对齐。

正点原子linux应用编程——提高篇2_第6张图片

对于水平布局来说,相邻两条垂直基线的距离就是字间距或者叫步进宽度;从一个字形的原点加上一个步进宽度就到了下一个字形的原点。

当在屏幕上画字形的时候,首先要定位到字形的左上角位置,从左上角开始,依次从左到右、从上到下,字形显示的宽度就是字形的宽度width、字符显示的高度就是字形的高度height。通过bearingY和bearingX便可确定字形左上角位置。譬如将(100, 100)这个位置作为原点,那么,字符显示位置的左上角便是(100+bearingX, 100-bearingY)。

初始化FreeType库

在使用FreeType库函数之前,需要对FreeType库进行初始化操作,使用FT_Init_FreeType()函数完成初始化操作。在调用该函数之前,还需要定义一个FT_Library类型变量,调用FT_Init_FreeType()函数时
将该变量的指针作为参数传递进去。如下所示:

FT_Library library;
FT_Error error;
error = FT_Init_FreeType(&library);
if (error)
	fprintf(stderr, "Error: failed to initialize FreeType library object\n");

FT_Init_FreeType创建了一个FreeType库对象,并将library作为库对象的句柄;调用成功返回0;失败将返回一个非零值错误码。

加载face对象

通过调用FT_New_Face()函数创建一个新的face对象,其实就是加载字体文件。一个face对象描述了一个特定的字体样式和风格。调用FT_New_Face()函数前,还需要定义一个FT_Face类型变量。

FT_New_Face()函数原型如下所示:

FT_Error FT_New_Face(FT_Library library, const char *filepathname,
		FT_Long face_index, FT_Face *aface);

函数参数以及返回值说明如下:

  • library:一个FreeType库对象的句柄,face 对象从中建立;
  • filepathname:字库文件路径名(一个标准的C字符串);
  • face_index:某些字体格式允许把几个字体face嵌入到同一个文件中,这个索引指示想加载的face,其实就是一个下标,通常把它设置为0即可!
  • aface:一个指向新建face对象的指针,当失败时其值被设置为NULL。
  • 返回值:调用成功返回0;失败将返回一个非零值的错误码。

设置字体大小

设置字体的大小有两种方式:FT_Set_Char_Size()和FT_Set_Pixel_Sizes()。

调用FT_Set_Pixel_Sizes()函数设置字体的宽度和高度,以像素为单位,使用示例如下所示:

FT_Set_Pixel_Sizes(face, 50, 50);

第一个参数传入face句柄;第二个参数和第三个参数分别指示字体的宽度和高度,以像素为单位。可以讲高度或宽度设置为0,意味着改参数与另一个参数保持相等。

调用FT_Set_Char_Size()函数设置字体大小,示例如下所示,假设在一个300x300dpi 的设备上把字体大小设置为16pt:

error = FT_Set_Char_Size(
	face,//face 对象的句柄
	16*64, //以 1/64 点为单位的字体宽度
	16*64, //以 1/64 点为单位的字体高度
	300, //水平方向上每英寸的像素点数
	300); //垂直方向上每英寸的像素点数

这里要注意,字体的宽度和高度并不是以像素为单位,而是以1/64点(point)为单位表示;宽度高度参数与FT_Set_Pixel_Sizes一样;dpi设为0则表示使用默认值72dpi。

加载字形图像

示例代码

与之前一样,定义LCD相关的两个结构体以及映射地址和屏幕尺寸,定义argb8888转rgb565的宏,定义好LCD设备文件节点。

定义FT_Library以及FT_Face变量。

编写fb_dev_init函数,里面open打开LCD设备文件,然后ioctl获取参数,mmap映射,然后memset把屏幕设置为0xFF。

编写freetype_init函数,先定义FT_Error、FT_Vector以及FT_Matrix变量error、pen和matrix;调用FT_Init_FreeType函数初始化FreeType库,然后调用FT_New_Face加载face对象;之后要通过pen.x和pen.y设置好原点,之后FT_Set_Transform和FT_Set_Pixel_Sizes进行设置。

之后就是lcd_draw_character函数,这里需要定义FT_GlyphSlot变量slot,传入face->glyph来获取;之后在for循环中通过FT_Load_Char加载字形,之后就是一系列坐标计算,最终调用screen_base数组来进行上色。

main函数中,调用fb_dev_init初始化LCD,再调用freetype_init初始化FreeType库,之后调用lcd_draw_character写字就可以了。

总结

这一篇笔记就是针对LCD的各种显示,总结在一起。

LCD可以显示bmp图片、jpeg图片(移植libjpeg)、png图片(移植libpng)、显示字符(移植FreeType)。具体的操作就看上面的笔记总结就可以了。

你可能感兴趣的:(linux学习,linux,笔记,学习)