库的移植和使用---例子:JPEG的解码和编码
- 开源库移植步骤
- 库的使用
- 解码步骤
- 习题:设计程序实现在LCD上的任意位置显示一张任意大小的jpg图片,注意不要越界。
- [1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联
- [2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!
- [3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码
- [4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!
- [5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!
- [6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中
- [7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件-----fclose(infile) )
- [8]: 将所有头文件和库文件与main.c 一同完成编译
- 代码完整展示
- 解码步骤
开源库移植步骤
注:以下操作均以 JPEG图片操作 为例
[1]:下载库的源码包
-
从对应库的官网下载库的源码包,可直接搜索”lib关键字“,如”libJPEG“
-
下载时,最好选择全英文网站进行下载,优先选择代码沟通网站【github、sourceforge、git】,一般可以选择以”.org“结尾的网址(xxx.sourceforge.org)
-
根据不同的系统平台,选择不同的压缩形式的源码包
例:Windows平台---> xxx.zip Linux平台---> xxx.tar.gz
网址为:JPEG库源码包下载
[2]:解压,且阅读“README(自述文件)",了解对应库的使用规则
-
此时是为了阅读“README”,所以可以直接在 Windows下直接解压源码包
-
阅读顺序一般为:
- install.txt: 学习库的移植和安装步骤
- libjpeg.txt: 学习库的使用方法和步骤
- example.c: 在了解库的基本使用方法后,可以依照给出的库使用例子,进行模仿使用该库
[3]:打开源码中的install.txt的文本,学习库的移植和安装步骤
-
经过阅读文本可知:(重要且背诵)
移植libjpeg的步骤分为三步:配置(./configure) + 编译(make) + 安装(make install)。
[4]:把下载好的源码包jpegsrc.v9f.tar.gz发送到linux系统的家目录下进行解压
- 注意不可以在共享文件夹进行解压,因为共享文件夹依然属于Windows平台环境
[5] :“配置” (./configure)
切换到解压后的jpeg-9f的文件夹内,进行“配置”操作,且依照实际需要修改configure的参数(数据存放路径 + 指定平台)
- 若是直接使用 ./configure ,系统会按照默认参数进行配置,不一定符合我们实际所需,所以需要使用指令对configure的参数进行修改
- 默认参数指的是:编辑器使用---gcc 、 数据存放路径---/usr/local 等
- 可以使用 "./configure --help" 查看所有可改的选项
- 我们一般需要修改 数据存放路径 和 指定目标平台 两个参数。代码如下:
修改路径: --prefix = [绝对路径]
指定目标平台:--host = arm-linux (不用加gcc,因为此时修改的是平台;且一般修改平台后,编辑器也会随之修改)
- 配置成功后,会得到一个“Makefile”脚本文件,为下一步“编译”做准备
[6]: “编译” (make)
配置成功之后,会得到一个makefile脚本文件,此时可以完成移植的第二步:*编译*,在命令行输入指令:make ,该指令会自动执行makefile
-
“编译”过程中不可以有错误(出现error),如果出错,则需要重新进行“配置”
-
如果出现下图情况,是因为没有安装“make”工具所导致的,只需安装一下解决
[7]:“安装” (make install)
**编译通过之后,则可以完成libjpeg库的*安装*,此时在命令行输入指令: make install **
- “安装”过程中也不可以有错误(出现error),如果出错,则需要重新进行“配置”
- "安装"生成的文件会被放置到"配置"过程中指定的路径下
即 头文件----> [指定路径]/include
库文件----> [指定路径]/lib
[8]:安装完成后,拷贝用户指定的安装路径中生成的libjpeg库的头文件和库文件,方便后续设计程序使用
[9]:把include文件夹和lib文件夹与自己的工程文件放在同一个路径,方便后期的工程维护!
库的使用
阅读libjpeg.txt相关信息,了解并学习JPEG图片的解码与编码过程
解码步骤
为了可以把一张jpg图片显示在LCD上,所以需要把jpg图片进行解压,解压之后就可以得到图片内部的像素点的颜色分量,就可以把像素点的颜色分量向LCD的像素点写入。就需要掌握jpg图片的解压流程(背下来)。借助习题来更好的理解解压流程:
习题:设计程序实现在LCD上的任意位置显示一张任意大小的jpg图片,注意不要越界。
[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联
- 解码对象一般命名为 cinfo,且解码对象是一个结构体变量
- 调用“jpeg_create_decompress(&cinfo)"完成对解码对象的初始化
代码展示:
/*[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联*/
//创建解码对象,其是一个结构体变量
struct jpeg_decompress_struct cinfo;
//创建错误处理对象
struct jpeg_error_mgr jerr;
//将错误处理对象与解码对象相关联
cinfo.err = jpeg_std_error(&jerr);
[2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!
- 需要把打开的文件的文件指针和解码对象进行绑定
代码展示:
/*[2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!*/
FILE * infile; //接收打开文件的文件指针
unsigned char * buffer; //输出行缓冲区
int row_stride; //buffer一行的像素点数量,即图片的宽度
// 以二进制方式打开图片,并进行错误处理
if ((infile = fopen(filename, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", filename);
return 0;
}
//把打开的文件的文件指针和解码对象进行绑定
jpeg_stdio_src(&cinfo, infile);
[3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码
代码展示:
/*[3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码*/
(void) jpeg_read_header(&cinfo, TRUE);
[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!
- 使用默认参数的情况,即对jpg图片的操作不涉及缩放图片大小等操作
代码展示:
/*[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!*/
/* 在该习题要求中,并不涉及图片缩放等问题,所以我们可以省略该步骤
* jpeg_read_header(),
*/
[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!
- 图像宽: output_width
- 图像宽: output_height
- 图像色深:output_components
注意:此处的色深数据是以“字节”为单位存储的
代码展示:
/*[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!*/
//我们只需要调用该函数,将图像信息放入解码对象中,无需注意其的返回值
(void) jpeg_start_decompress(&cinfo);
[6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中
-
注意:转换算法需要用户自己依照要求设计。
-
该题目需要任意位置显示,所以在“写入”时需要考虑图片的初始位置
-
由于该题目的图片大小也是未知的,所以在程序中必须调用解码对象中的图片信息,而不能将条件”写死“
-
(void) jpeg_read_scanlines(&cinfo, &buffer, 1) 函数返回的是实际读取的行的数量
-
cinfo.output_scanline 该变量记录的是扫描行的数量,初始值为0,调用一次"jpeg_read_scanlines()"函数,数值加1
-
JPEG存储的方式与BMP不同,JPEG的存储方式是大端存储。即 像素点的颜色顺序位 RGB; 存储的图像行数据 是从上到下
-
申请缓冲区大小时,不应该申请超过图像一行数据大小的空间
代码展示:
*[6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中*/
//计算图像一行的大小
row_stride = cinfo.output_width * cinfo.output_components;
//为自定义缓冲区申请堆内存,注意申请的内存空间大小应为图像一行的大小
buffer = calloc(1,row_stride);
//定义一个int类型变量,用于存放颜色分量数据
int data = 0;
/*定义一个循环,用于循环写入一行的图像数据;
使用解码对象当前扫描行数与图像的高比较结果作为循环条件,当两者相等,即图像数据写入完后退出循环*/
while (cinfo.output_scanline < cinfo.output_height)
{
/*调用jpeg_read_scanlines函数,读取解码对象中的图像一行数据,并存放进自定义缓冲区中
且cinfo.output_scanline会随着调用该函数而增加1,保证while循环能够正常退出*/
(void) jpeg_read_scanlines(&cinfo, &buffer, 1); //从上到下,从左到右 RGB RGB RGB RGB
//将缓冲区中存储的数据逐一写入LCD的内存映射空间中
for (int i = 0; i < cinfo.output_width; ++i) //012 345
{
/*由于图片没有透明度,所以一个像素点大小为3byte,而data为int类型变量,所以需要
借助"|=" 使得颜色分量顺序存储正确;又因为JEPG存储颜色分量顺序为RGB,所以进行下面算法*/
data |= buffer[3*i]<<16; //R
data |= buffer[3*i+1]<<8; //G
data |= buffer[3*i+2]; //B
/*把像素点写入到LCD的指定位置。其中800*start_y + start_x控制的是用户自定义的图片显示初始位置;
800*(cinfo.output_scanline-1)控制的是写入图像数据的行数切换;+ i控制的是写入图像数据的列数切换*/
lcd_mp[800*start_y + start_x + 800*(cinfo.output_scanline-1) + i] = data;
//最后需将data内部清零,避免对下一次循环的颜色分量写入造成影响
data = 0;
}
}
[7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件-----fclose(infile) )
代码展示:
/*[7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件)*/
//解码完成
(void) jpeg_finish_decompress(&cinfo);
//释放解码对象
jpeg_destroy_decompress(&cinfo);
//关闭打开的图像文件
fclose(infile);
return 1;
[8]: 将所有头文件和库文件与main.c 一同完成编译
错误情况分析:
代码完整展示
/*******************************************************************
*
* file name: main.c
* author : 790557054@qq.com
* date : 2024/05/13
* function : 该案例是掌握JPEG的解码过程
* note : None
*
* CopyRight (c) 2023-2024 790557054@qq.com All Right Reseverd
*
* *****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include "jpeglib.h"
/********************************************************************
*
* name : read_JPEG_file
* function : 实现
* argument : 完成libjpeg库的移植,实现在LCD上的任意位置
显示一张任意大小的jpg图片,并且对可能越界的情况做错误处理。
* @filename :需要解码的jpg图片
@start_x :图片显示初始位置的横坐标
@start_y :图片显示初始位置的纵坐标
@lcd_mp :LCD屏内存映射空间的地址
*
* retval : 调用成功返回1,调用失败返回0
* author : 790557054@qq.com
* date : 2024/05/13
* note : 学习JPEG的解码过程,以及JPEG存储颜色分量的方式
*
* *****************************************************************/
int read_JPEG_file (char * filename,int start_x,int start_y,int * lcd_mp)
{
/*[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联*/
//创建解码对象,其是一个结构体变量
struct jpeg_decompress_struct cinfo;
//创建错误处理对象
struct jpeg_error_mgr jerr;
//将错误处理对象与解码对象相关联
cinfo.err = jpeg_std_error(&jerr);
//对解码对象进行初始化
jpeg_create_decompress(&cinfo);
/*[2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!*/
FILE * infile; //接收打开文件的文件指针
unsigned char * buffer; //输出行缓冲区
int row_stride; //buffer一行的像素点数量,即图片的宽度
// 以二进制方式打开图片,并进行错误处理
if ((infile = fopen(filename, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", filename);
return 0;
}
//把打开的文件的文件指针和解码对象进行绑定
jpeg_stdio_src(&cinfo, infile);
/*[3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码*/
(void) jpeg_read_header(&cinfo, TRUE);
/*[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!*/
/* 在该习题要求中,并不涉及图片缩放等问题,所以我们可以省略该步骤
* jpeg_read_header(),
*/
/*[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!*/
//我们只需要调用该函数,将图像信息放入解码对象中,无需注意其的返回值
(void) jpeg_start_decompress(&cinfo);
/*[6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中*/
//计算图像一行的大小
row_stride = cinfo.output_width * cinfo.output_components;
//为自定义缓冲区申请堆内存,注意申请的内存空间大小应为图像一行的大小
buffer = calloc(1,row_stride);
//定义一个int类型变量,用于存放颜色分量数据
int data = 0;
/*定义一个循环,用于循环写入一行的图像数据;
使用解码对象当前扫描行数与图像的高比较结果作为循环条件,当两者相等,即图像数据写入完后退出循环*/
while (cinfo.output_scanline < cinfo.output_height)
{
/*调用jpeg_read_scanlines函数,读取解码对象中的图像一行数据,并存放进自定义缓冲区中
且cinfo.output_scanline会随着调用该函数而增加1,保证while循环能够正常退出*/
(void) jpeg_read_scanlines(&cinfo, &buffer, 1); //从上到下,从左到右 RGB RGB RGB RGB
//将缓冲区中存储的数据逐一写入LCD的内存映射空间中
for (int i = 0; i < cinfo.output_width; ++i) //012 345
{
/*由于图片没有透明度,所以一个像素点大小为3byte,而data为int类型变量,所以需要
借助"|=" 使得颜色分量顺序存储正确;又因为JEPG存储颜色分量顺序为RGB,所以进行下面算法*/
data |= buffer[3*i]<<16; //R
data |= buffer[3*i+1]<<8; //G
data |= buffer[3*i+2]; //B
/*把像素点写入到LCD的指定位置。其中800*start_y + start_x控制的是用户自定义的图片显示初始位置;
800*(cinfo.output_scanline-1)控制的是写入图像数据的行数切换;+ i控制的是写入图像数据的列数切换*/
lcd_mp[800*start_y + start_x + 800*(cinfo.output_scanline-1) + i] = data;
//最后需将data内部清零,避免对下一次循环的颜色分量写入造成影响
data = 0;
}
}
/*[7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件)*/
//解码完成
(void) jpeg_finish_decompress(&cinfo);
//释放解码对象
jpeg_destroy_decompress(&cinfo);
//关闭打开的图像文件
fclose(infile);
return 1;
}
int main(int argc, char const *argv[])
{
//1.打开LCD open
int lcd_fd = open("/dev/fb0",O_RDWR);
//2.对LCD进行内存映射 mmap
int *lcd_mp = (int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
//3.读取图片初始显示位置
int start_x = 0, start_y = 0;
printf("Please input start_x :\n");
scanf("%d", &start_x);
printf("Please input start_y :\n");
scanf("%d", &start_y);
//3.显示一张jpg
read_JPEG_file (argv[1],start_x,start_y,lcd_mp);
return 0;
}