库的移植和使用---例子:JPEG的解码和编码

目录

开源库移植步骤

注:以下操作均以 JPEG图片操作 为例

[1]:下载库的源码包

  • 从对应库的官网下载库的源码包,可直接搜索”lib关键字“,如”libJPEG

  • 下载时,最好选择全英文网站进行下载,优先选择代码沟通网站【github、sourceforge、git】,一般可以选择以”.org“结尾的网址(xxx.sourceforge.org)

  • 根据不同的系统平台,选择不同的压缩形式的源码包

    例:Windows平台---> xxx.zip Linux平台---> xxx.tar.gz

image

网址为:JPEG库源码包下载

[2]:解压,且阅读“README(自述文件)",了解对应库的使用规则

  • 此时是为了阅读“README”,所以可以直接在 Windows下直接解压源码包

  • 阅读顺序一般为:

    1. install.txt: 学习库的移植和安装步骤
    2. libjpeg.txt: 学习库的使用方法和步骤
    3. example.c: 在了解库的基本使用方法后,可以依照给出的库使用例子,进行模仿使用该库

image

[3]:打开源码中的install.txt的文本,学习库的移植和安装步骤

  • 经过阅读文本可知:(重要且背诵)

    移植libjpeg的步骤分为三步:配置(./configure) + 编译(make) + 安装(make install)。
    image

[4]:把下载好的源码包jpegsrc.v9f.tar.gz发送到linux系统的家目录下进行解压

image

  • 注意不可以在共享文件夹进行解压,因为共享文件夹依然属于Windows平台环境

[5] :“配置” (./configure)

切换到解压后的jpeg-9f的文件夹内,进行“配置”操作,且依照实际需要修改configure的参数(数据存放路径 + 指定平台)

  • 若是直接使用 ./configure ,系统会按照默认参数进行配置,不一定符合我们实际所需,所以需要使用指令对configure的参数进行修改
  • 默认参数指的是:编辑器使用---gcc 、 数据存放路径---/usr/local 等
  • 可以使用 "./configure --help" 查看所有可改的选项
  • 我们一般需要修改 数据存放路径 和 指定目标平台 两个参数。代码如下:

修改路径: --prefix = [绝对路径]

指定目标平台:--host = arm-linux (不用加gcc,因为此时修改的是平台;且一般修改平台后,编辑器也会随之修改)

image

  • 配置成功后,会得到一个“Makefile”脚本文件,为下一步“编译”做准备

image

[6]: “编译” (make)

配置成功之后,会得到一个makefile脚本文件,此时可以完成移植的第二步:*编译*,在命令行输入指令:make ,该指令会自动执行makefile

image

  • “编译”过程中不可以有错误(出现error),如果出错,则需要重新进行“配置”

  • 如果出现下图情况,是因为没有安装“make”工具所导致的,只需安装一下解决

image

image

[7]:“安装” (make install)

**编译通过之后,则可以完成libjpeg库的*安装*,此时在命令行输入指令: make install **

image

  • “安装”过程中也不可以有错误(出现error),如果出错,则需要重新进行“配置”
  • "安装"生成的文件会被放置到"配置"过程中指定的路径下

即 头文件----> [指定路径]/include

库文件----> [指定路径]/lib

[8]:安装完成后,拷贝用户指定的安装路径中生成的libjpeg库的头文件和库文件,方便后续设计程序使用

image

[9]:把include文件夹和lib文件夹与自己的工程文件放在同一个路径,方便后期的工程维护!

image

库的使用

阅读libjpeg.txt相关信息,了解并学习JPEG图片的解码与编码过程

image

解码步骤

为了可以把一张jpg图片显示在LCD上,所以需要把jpg图片进行解压,解压之后就可以得到图片内部的像素点的颜色分量,就可以把像素点的颜色分量向LCD的像素点写入。就需要掌握jpg图片的解压流程(背下来)。借助习题来更好的理解解压流程:

习题:设计程序实现在LCD上的任意位置显示一张任意大小的jpg图片,注意不要越界。

[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联

  • 解码对象一般命名为 cinfo,且解码对象是一个结构体变量
  • 调用“jpeg_create_decompress(&cinfo)"完成对解码对象的初始化

image

代码展示:

/*[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联*/

    //创建解码对象,其是一个结构体变量 
    struct jpeg_decompress_struct cinfo;
    //创建错误处理对象
    struct jpeg_error_mgr jerr;

    //将错误处理对象与解码对象相关联
    cinfo.err = jpeg_std_error(&jerr);

[2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!

  • 需要把打开的文件的文件指针和解码对象进行绑定

image

代码展示:

/*[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图片进行解码

image

代码展示:

/*[3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码*/

    (void) jpeg_read_header(&cinfo, TRUE);

[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!

  • 使用默认参数的情况,即对jpg图片的操作不涉及缩放图片大小等操作

image

代码展示:

/*[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!*/

    /* 在该习题要求中,并不涉及图片缩放等问题,所以我们可以省略该步骤
   * jpeg_read_header(), 
   */

[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!

  • 图像宽: output_width
  • 图像宽: output_height
  • 图像色深:output_components

​ 注意:此处的色深数据是以“字节”为单位存储

image

代码展示:

/*[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!*/
    //我们只需要调用该函数,将图像信息放入解码对象中,无需注意其的返回值
    (void) jpeg_start_decompress(&cinfo);

[6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中

  • 注意:转换算法需要用户自己依照要求设计

  • 该题目需要任意位置显示,所以在“写入”时需要考虑图片的初始位置

  • 由于该题目的图片大小也是未知的,所以在程序中必须调用解码对象中的图片信息,而不能将条件”写死“

image

  • (void) jpeg_read_scanlines(&cinfo, &buffer, 1) 函数返回的是实际读取的行的数量

  • cinfo.output_scanline 该变量记录的是扫描行的数量,初始值为0,调用一次"jpeg_read_scanlines()"函数,数值加1

  • JPEG存储的方式与BMP不同,JPEG的存储方式是大端存储。即 像素点的颜色顺序位 RGB; 存储的图像行数据 是从上到下

  • 申请缓冲区大小时,不应该申请超过图像一行数据大小的空间

image

image

代码展示:

*[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) )

image

image

代码展示:

/*[7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件)*/

    //解码完成
    (void) jpeg_finish_decompress(&cinfo);
    //释放解码对象
    jpeg_destroy_decompress(&cinfo);
    //关闭打开的图像文件
    fclose(infile);


    return 1;

[8]: 将所有头文件和库文件与main.c 一同完成编译

image

错误情况分析:

  1. image

  2. image

代码完整展示

/*******************************************************************
*
*	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;
}
posted @ 2024-05-14 08:04  飞子的唠唠叨  阅读(226)  评论(0编辑  收藏  举报