借助libjpeg库,libjpeg这个库主要用于处理jpeg数据,比如将RGB压缩成JPEG,或者将JPEG解压为RGB。
libjpeg处理的图片一般是矩形,矩形里是一行行数据,一行数据为”scanline”。每行数据是一个个像素,像素的值由component单元组成,RGB有三个component,灰度值有一个component。图片信息相当于一个二维数组,压缩是把二维数组压缩为一个jpeg图片,解压是把jpeg图片恢复为二维数组。
jpg图片的提取步骤:
1.分配和初始化一个decompression(解压结构体)对象 cInfo和错误处理结构体对象jerr
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
jpeg_decompress_struct
是libjpeg库中用于管理JPEG解压缩过程的主要结构体。它包含了所有解码JPEG图像所需的信息,包括解码参数、图像信息、输入输出数据指针、解码状态等。这个结构体可以看作是libjpeg解码器的主控制结构。
jpeg_error_mgr
结构体用于管理libjpeg库的错误处理。它定义了如何报告和处理JPEG解码过程中可能出现的各种错误。libjpeg库通过这个结构体来配置错误处理行为,比如设置错误输出、错误恢复选项等。
2.设置出错处理函数并初始化编解码结构对象
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
在libjpeg库中,实现了默认错误处理函数,当错误发生时,比如如果内存不足(非常可能发生,后面会介绍)等,则默认错误处理函数将会调用exit函数结束整个进程
3.指定源文件
infile = fopen(filename, "rb");
if (!infile) {
fprintf(stderr, "Can't open %s\n", filename);
exit(1);
}
jpeg_stdio_src(&cinfo, infile);
在libjpeg库中仅仅提供了文件作为输入数据的接口
4.用jpeg_read_header获得jpg图片信息
调用jpeg_read_header(&cinfo, TRUE)读取jpeg文件头信息,这个和初始化解码对象一样,是必须要调用的,是约定。
5.设置解压参数,比如放大、缩小
插入图片的不缩放时可以不必设置,不设置的话通常就是JCS_RGB,RGB888的格式,除此之外,还可以设置缩放大小等,scale_num,scale_denom:因为实际的显示设备千变万化,我们可能需要根据实际情况对输出数据进行一些缩放才能够显示。libjpeg支持对输出数据进行缩放(scale),这个变量就是用来设置缩放的参数。
6.启动解压:jpeg_start_decompress(…)
经过前面的参数设置,接着调用jpeg_start_decompress(&cinfo);开始解码。
7.循环调用jpeg_read_scanlines(…)
width = cinfo.output_width;
*height = cinfo.output_height;
fprintf(stderr, "QR size=[%d*%d] \n", *width,*height);
JSAMPROW row_pointer[1]; // 指向一行像素数据的指针
*buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
JPOOL_IMAGE,
(*width)*cinfo.output_components,
*height); // 分配内存
计算输出行缓冲区大小,分配足够的内存来存储解码后的图像数据,即用buffer来存储。
对解码出来的数据做处理,逐行取出并放入buffer中。
while (cinfo.output_scanline < cinfo.output_height) {
fprintf(stderr, "Info : jpeg_read_scanlines %d/%d +++\n",cinfo.output_scanline,cinfo.output_height);
row_pointer[0] = (*buffer)[cinfo.output_scanline];
(void) jpeg_read_scanlines(&cinfo, row_pointer, 1);
fprintf(stderr, "Info : jpeg_read_scanlines %d/%d ---\n",cinfo.output_scanline,cinfo.output_height);
}
cinfo.output_scanline
的值通常在调用jpeg_read_scanlines
函数后由libjpeg库内部更新。每当jpeg_read_scanlines
成功读取一行数据后,libjpeg库会自动增加cinfo.output_scanline
的值,以准备读取下一行数据。
8.释放decompress结构体并关闭打开的文件
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
总体代码如下:
// 解码JPEG图像的函数
void decode_jpeg(const char *filename, JSAMPARRAY *buffer, int *width, int *height) {
FILE *infile;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
// 设置错误处理
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
// 打开输入文件
infile = fopen(filename, "rb");
if (!infile) {
fprintf(stderr, "Can't open %s\n", filename);
exit(1);
}
jpeg_stdio_src(&cinfo, infile);
// 读取文件头
(void) jpeg_read_header(&cinfo, TRUE);
// 开始解码
jpeg_start_decompress(&cinfo);
// 计算输出行缓冲区大小
*width = cinfo.output_width;
*height = cinfo.output_height;
fprintf(stderr, "QR size=[%d*%d] \n", *width,*height);
JSAMPROW row_pointer[1]; // 指向一行像素数据的指针
*buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
JPOOL_IMAGE,
(*width)*cinfo.output_components,
*height); // 分配内存
// 解码每一行
while (cinfo.output_scanline < cinfo.output_height) {
fprintf(stderr, "Info : jpeg_read_scanlines %d/%d +++\n",cinfo.output_scanline,cinfo.output_height);
row_pointer[0] = (*buffer)[cinfo.output_scanline];
(void) jpeg_read_scanlines(&cinfo, row_pointer, 1);
fprintf(stderr, "Info : jpeg_read_scanlines %d/%d ---\n",cinfo.output_scanline,cinfo.output_height);
}
fprintf(stderr, "QR Decode finished\n");
// 完成解码
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
}
把解码后的图片插入到目标图片上
1获取源图像和目标图像的像素位置
const JSAMPLE *src_pixel = &source[y][x * 3];
uint32_t *dst_pixel = &target[(y + y_offset) * dst_w + x + x_offset];
src_pixel
是一个指向JSAMPLE
类型的常量指针,它现在指向了source
数组中某个像素的红色分量的地址。通过递增这个指针,可以访问到同一像素的绿色和蓝色分量。此处的source数组就是上面存储解压信息的buffer。
target
数组被假设为是以像素为单位的一维数组,而每个像素占用了4个字节(一个32位整数),用于存储ARGB或RGBA格式的颜色值
source
数组是以颜色分量为单位的(即R、G、B、A分别存储,每个分量占用1个字节),那么在计算像素位置时确实需要将x
乘以4.
2假设源图像是RGB格式,目标图片也是RGB
则这里进行简单的粘贴复制即可。
整体代码如下:
void insert_image(uint32_t *target, const JSAMPARRAY source, int src_w, int src_h, int dst_w, int dst_h, int x_offset, int y_offset) {
for (int y = 0; y < src_h; ++y) {
for (int x = 0; x < src_w; ++x) {
// 获取源图像和目标图像的像素位置
const JSAMPLE *src_pixel = &source[y][x * 3];
uint32_t *dst_pixel = &target[(y + y_offset) * dst_w + x + x_offset];
// 假设源图像是RGB格式,这里进行简单的颜色复制
//*dst_pixel = (0xFF << 24) | (*src_pixel << 16) | (*(src_pixel + 1) << 8) | *(src_pixel + 2);
*dst_pixel = (*src_pixel << 16) | (*(src_pixel + 1) << 8) | *(src_pixel + 2);
}
}
}