在 LCD 上显示 jpeg 图像-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

在 LCD 上显示 jpeg 图像

在这里插入图片描述

JPEG 简介

JPEG(Joint Photographic Experts Group)是第一个国际数字图像压缩标准,由国际标准组织制定

它是应用最广的图像压缩标准

JPEG 提供有损压缩,压缩比高于其他传统压缩算法

虽然有损压缩,但损失部分不易被人眼察觉,利用了人眼对高频信息不敏感的特点

JPEG 压缩文件的后缀名通常是 .jpg 或 .jpeg

libjpeg 简介

JPEG 压缩标准使用算法压缩原始图像数据,生成 .jpg 或 .jpeg 文件

为在 LCD 上显示 .jpg 或 .jpeg 图像,需要解压缩以获得原始 RGB 数据

解压缩过程需要使用特定的算法

自编解压算法复杂,可使用现成的库,如 libjpeg

libjpeg 是用 C 语言编写的库,包含 JPEG 解码、编码及其他功能

libjpeg 是开源的,可以获取其源代码

libjpeg 移植

下载源码包

  • 下载地址: http://www.ijg.org/files/

  • 以 v9b 为例

编译源码

  • 将压缩包文件拷贝到 Ubuntu 系统用户家目录下

  • 文件解压

    • tar -xzf jpegsrc.v9b.tar.gz
  • 在家目录下的 tools 文件夹中创建一个名为 jpeg 的文件夹,该目录作为 libjpeg 库的安装目

  • 进入到 libjpeg 源码目录 jpeg-9b 中

    • 配置工程

      • 对交叉编译工具的环境进行初始化

        • source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
      • ./configure --host=arm-poky-linux-gnueabi --prefix=/home/dt/tools/jpeg/

    • 编译工程

      • make
    • 安装

      • make install

安装目录下的文件夹介绍

  • 安装目录下的文件夹

  • bin 目录下包含一些测试工具

  • include 目录下包含头文件

  • lib 目录下包含动态链接库文件

移植到开发板

  • 将 bin 目录下的所有测试工具拷贝到开发板 Linux 系统/usr/bin 目录

  • 将 lib
    目录下的所有库文件拷贝到开发板 Linux 系统/usr/lib 目录

    • 拷贝 lib 目录下的库文件时,需要注意符号链接的问题,不能破坏原有的符号链接;可以将 lib 目录下
      的所有文件打包成压缩包的形式,进入到 lib 目录,执行命令

      • tar -czf lib.tar.gz ./*
    • 将 lib.tar.gz 压缩文件解压到开发板 Linux 系统/usr/lib 目录下

      • tar -xzf lib.tar.gz -C /usr/lib
  • 解压成功之后,接着执行 libjpeg 提供的测试工具,看看移植是否成功,成功打印出这些信息就表示我们的移植成功了

    • djpeg --help

libjpeg 使用说明

错误处理

  • 使用 libjpeg 库函数时可能会产生错误,因此需要预先做好错误处理

  • libjpeg 实现了默认错误处理函数,当出现内存不足或文件格式不对等错误时,默认函数会调用 exit() 结束进程

  • 可以通过 libjpeg 提供的接口注册自定义错误处理函数,以修改错误处理方式

  • 错误处理对象使用 struct jpeg_error_mgr 结构体描述

  • error_exit 函数指针指向错误处理函数,jpeg_std_error() 函数将 libjpeg 错误处理设置为默认方式

    • //初始化错误处理对象、并将其与解压对象绑定
      cinfo.err = jpeg_std_error(&jerr);
  • 若要修改默认错误处理函数

    • void my_error_exit(struct jpeg_decompress_struct cinfo)
      {
      /
      … */
      }
      cinfo.err.error_exit = my_error_exit;

创建解码对象

  • 使用 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 文件的头信息

  • 调用 jpeg_read_header(&cinfo, TRUE) 是解码前必须执行的约定操作,用于读取 JPEG 文件头部信息

  • 读取头部信息后,JPEG 图像的宽度、高度、颜色通道数和颜色空间等信息会赋值给 cinfo 对象的相应成员变量

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

    • /* Known color spaces. /
      typedef enum {
      JCS_UNKNOWN, /
      error/unspecified /
      JCS_GRAYSCALE, /
      monochrome /
      JCS_RGB, /
      red/green/blue, standard RGB (sRGB) /
      JCS_YCbCr, /
      Y/Cb/Cr (also known as YUV), standard YCC /
      JCS_CMYK, /
      C/M/Y/K /
      JCS_YCCK, /
      Y/Cb/Cr/K /
      JCS_BG_RGB, /
      big gamut red/green/blue, bg-sRGB /
      JCS_BG_YCC /
      big gamut Y/Cb/Cr, bg-sYCC */
      } J_COLOR_SPACE;

设置解码处理参数

  • 在解码前,可以通过修改 cinfo 对象的成员变量来设置解码参数,这些参数在调用 jpeg_read_header() 后会被设置为默认值

  • 两个重要的解码参数包括

    • 输出的颜色空间(cinfo.out_color_space):默认值为 JCS_RGB

    • 图像缩放操作(cinfo.scale_num 和 cinfo.scale_denom):用于设置解码图像的大小比例,支持的比例有 1/1、1/2、1/4 和 1/8,默认值为 1/1

      • 例如,要将输出图像设置为原图的 1/2 大小,可以设置
        cinfo.scale_num = 1 ;
        cinfo.scale_denom = 2;

开始解码

  • 设置完参数后,可以调用 jpeg_start_decompress(&cinfo) 开始解码

  • 解压缩完成后,解压后的图像信息会填充到 cinfo 结构中

  • 填充的图像信息包括

    • 输出图像宽度:cinfo.output_width

    • 输出图像高度:cinfo.output_height

    • 每个像素的颜色通道数:cinfo.output_components(如灰度为1,RGB888为3)

读取数据

  • 解码后的数据是按行读取的,存储顺序为从左到右、从上到下,每个像素点的颜色或灰度通道数据依次存储

  • 对于24-bit RGB图像,每行数据的存储模式为B,G,R,B,G,R,…

  • libjpeg默认解码得到的图像数据格式为BGR888

  • 定义BGR888颜色类型:

    • typedef struct bgr888_color {
      unsigned char red;
      unsigned char green;
      unsigned char blue;
      } attribute ((packed)) bgr888_t;
  • 每次读取一行数据,计算每行数据需要的空间大小:RGB图像为宽度×3,灰度图为宽度×1

  • 分配行缓冲区:

    • bgr888_t *line_buf = malloc(cinfo.output_width * cinfo.output_components);
  • 可以调用jpeg_read_scanlines()读取数据,一次读一行

    • jpeg_read_scanlines(&cinfo, &buf, 1);
  • 使用循环读取所有行数据

    • 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);

struct jpeg_decompress_struct cinfo;

struct jpeg_error_mgr jerr;

  • 使用 libjpeg 库解码 JPEG 数据时,核心数据结构是 struct jpeg_decompress_struct,它记录 JPEG 数据的详细信息及解码后输出数据的信息

  • 还需要定义一个 struct jpeg_error_mgr 结构体变量,用于处理错误

  • 示例代码中定义了 JPEG 解码对象 cinfo 和错误处理对象 jerr

libjpeg 应用编程

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <jpeglib.h>

typedef struct bgr888_color {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} __attribute__ ((packed)) bgr888_t;

static int width;                       //LCD X分辨率
static int height;                      //LCD Y分辨率
static unsigned short *screen_base = NULL;        //映射后的显存基地址
static unsigned long line_length;       //LCD一行的长度(字节为单位)
static unsigned int bpp;    //像素深度bpp

static int show_jpeg_image(const char *path)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *jpeg_file = NULL;
    bgr888_t *jpeg_line_buf = NULL;     //行缓冲区:用于存储从jpeg文件中解压出来的一行图像数据
    unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到LCD显存的一行数据
    unsigned int min_h, min_w;
    unsigned int valid_bytes;
    int i;

    //绑定默认错误处理函数
    cinfo.err = jpeg_std_error(&jerr);//初始化错误处理对象、并将其与解压对象绑定 

    //打开.jpeg/.jpg图像文件
    jpeg_file = fopen(path, "r");   //只读方式打开
    if (NULL == jpeg_file) {
        perror("fopen error");
        return -1;
    }

    //创建JPEG解码对象
    jpeg_create_decompress(&cinfo);

    //指定图像文件
    jpeg_stdio_src(&cinfo, jpeg_file);

    //读取图像信息
    jpeg_read_header(&cinfo, TRUE);
    printf("jpeg图像大小: %d*%d\n", cinfo.image_width, cinfo.image_height);

    //设置解码参数
    cinfo.out_color_space = JCS_RGB;//默认就是JCS_RGB
    //cinfo.scale_num = 1;
    //cinfo.scale_denom = 2;

    //开始解码图像
    jpeg_start_decompress(&cinfo);

    //为缓冲区分配内存空间
    jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
    fb_line_buf = malloc(line_length);

    //判断图像和LCD屏那个的分辨率更低
    if (cinfo.output_width > width)
        min_w = width;
    else
        min_w = cinfo.output_width;

    if (cinfo.output_height > height)
        min_h = height;
    else
        min_h = cinfo.output_height;

    //读取数据
    valid_bytes = min_w * bpp / 8;//一行的有效字节数 表示真正写入到LCD显存的一行数据的大小
    while (cinfo.output_scanline < min_h) {

        jpeg_read_scanlines(&cinfo, (unsigned char **)&jpeg_line_buf, 1);//每次读取一行数据

        //将读取到的BGR888数据转为RGB565
        for (i = 0; i < min_w; i++)
            fb_line_buf[i] = ((jpeg_line_buf[i].red & 0xF8) << 8) |
                    ((jpeg_line_buf[i].green & 0xFC) << 3) |
                    ((jpeg_line_buf[i].blue & 0xF8) >> 3);

        memcpy(screen_base, fb_line_buf, valid_bytes);
        screen_base += width;//+width  定位到LCD下一行显存地址的起点
    }

    //解码完成
    jpeg_finish_decompress(&cinfo); //完成解码
    jpeg_destroy_decompress(&cinfo);//销毁JPEG解码对象、释放资源

    //关闭文件、释放内存
    fclose(jpeg_file);
    free(fb_line_buf);
    free(jpeg_line_buf);
    return 0;
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 传参校验 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <jpeg_file>\n", argv[0]);
        exit(-1);
    }

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    line_length = fb_fix.line_length;
    bpp = fb_var.bits_per_pixel;
    screen_size = line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 显示BMP图片 */
    memset(screen_base, 0xFF, screen_size);
    show_jpeg_image(argv[1]);

    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(EXIT_SUCCESS);    //退出进程
}

大致与libjpeg 使用说明相同

在 while 循环中,每次通过 jpeg_read_scanlines() 读取一行数据

jpeg_read_scanlines() 的第二个参数是一个 unsigned char ** 类型指针

读取到的数据需要转换为 RGB565 格式,因为开发板的 LCD 显示设备是 RGB565 格式

总结

libjpeg功能:libjpeg不仅能解码JPEG,还能编码和实现其他JPEG功能

libjpeg API数量:libjpeg的API不多,可通过浏览头文件jpeglib.h了解函数作用

函数名和注释:函数的命名和注释信息能帮助理解其功能

  • 30
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木不迷茫(˵¯͒¯͒˵)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值