libjpeg:实现jpeg内存解压缩塈转换色彩空间/压缩分辨率
2016-01-23 16:31
393 查看
前一篇博客《libjpeg:实现jpeg内存压缩暨error_exit错误异常处理和个性化参数设置》实现了jpeg图像的内存压缩,本文来讨论jpeg图像内存解压缩的过程以及让libjpeg在解压缩时就将图像转灰度或其他色彩空间。
先贴出完整代码,再分段说明。
jpeg_mem.h
jpeg_mem.cpp
图像像素数据保存在类型为
成员对象
因为
在上面的代码中用到了我之前一篇博客(《C++11实现模板化(通用化)RAII机制》)中实现的
对图像解码时出现的处理方式参见前一篇博客《libjpeg:实现jpeg内存压缩暨error_exit错误异常处理和个性化参数设置》。
先贴出完整代码,再分段说明。
jpeg_mem.h
/* 图像矩阵基本参数 */ typedef struct _image_matrix_pram{ int32_t width; // 图像宽度 int32_t height; // 图像高度 uint8_t channels; // 通道数 J_COLOR_SPACE color_space; // 图像数据的色彩空间 uint8_t align; // 内存对齐方式 0为不对齐,>0为以2的n次幂对齐 std::vector <uint8_t> pixels; // 图像数据 }image_matrix_pram,*image_matrix_pram_ptr; /* 处理压缩解压缩后内存数据的回调函数 */ using mem_callback_fun=std::function<void(const uint8_t*,unsigned long)>; /* 定制压缩解压缩参数 */ using jpeg_custom_fun=std::function<void(j_common_ptr)>; /* jpeg图像处理异常类 */ class jpeg_mem_exception:public std::logic_error{ public: // 继承基类构造函数 using std::logic_error::logic_error; }; /* 图像解压缩接口类 */ struct jpeg_decompress_interface{ // 图像缓冲区 std::vector<JSAMPROW> buffer; // 设置自定义输出参数的函数对象 jpeg_custom_fun custom_output=[](j_common_ptr){}; // 虚函数用于初始化内存填充解压缩后图像信息数据 virtual void start_output(const jpeg_decompress_struct&cinfo)=0; // 虚函数用于将解压缩后的数据写入图像内存区 virtual void put_pixel_rows(JDIMENSION num_scanlines)=0; virtual ~jpeg_decompress_interface()=default; }; /* 默认的图像解压缩接口实现 */ struct jpeg_decompress_default:public jpeg_decompress_interface{ /* 解压缩后的图像基本信息 */ image_matrix_pram img; // 当前处理的目标图像像素行数 JDIMENSION next_line; virtual void start_output(const jpeg_decompress_struct&cinfo){ // 填充图像基本信息结构 img.width=cinfo.output_width; img.height=cinfo.output_height; img.color_space=cinfo.out_color_space; img.channels=cinfo.output_components; // 分配像素数据存储区 img.pixels=std::vector<uint8_t>(img.width*img.height*img.channels); // buffer只保存一行像素的目标数据指针 buffer=std::vector<JSAMPROW>(1); next_line=0; // 初始化buffer指向第一像素存储地址 buffer[next_line]=img.pixels.data(); } virtual void put_pixel_rows(JDIMENSION num_scanlines){ // buffer指向下一行要像素存储地址 buffer[0]=img.pixels.data()+(++next_line)*img.width*img.channels; } virtual ~jpeg_decompress_default()=default; };
jpeg_mem.cpp
/* 自定义jpeg图像压缩/解压缩过程中错误退出函数 */ METHODDEF(void) jpeg_mem_error_exit (j_common_ptr cinfo) { // 调用 format_message 生成错误信息 char err_msg[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) (cinfo,err_msg); // 抛出c++异常 throw jpeg_mem_exception(err_msg); } /* 将jpeg格式的内存数据块jpeg_data解压缩 * 图像行数据存储的方式都由decompress_instance定义 * 出错抛出 jpeg_mem_exception */ void load_jpeg_mem(uint8_t *jpeg_data,size_t size, jpeg_decompress_interface &decompress_instance) { if(nullptr==jpeg_data||0==size) throw jpeg_mem_exception("empty image data"); // 定义一个压缩对象 jpeg_decompress_struct cinfo; //用于错误信息 jpeg_error_mgr jerr; // 错误输出绑定到压缩对象 cinfo.err = jpeg_std_error(&jerr); // 设置自定义的错误处理函数 jerr.error_exit = jpeg_mem_error_exit; // RAII对象在函数结束时释放资源 gdface::raii buffer_guard([&](){ jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); }); // 初始化压缩对象 jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, jpeg_data, (unsigned long)size); // 设置内存输出缓冲区 (void) jpeg_read_header(&cinfo, true); decompress_instance.custom_output((j_common_ptr)&cinfo); // 执行自定义参数设置函数 (void) jpeg_start_decompress(&cinfo); // 输出通道数必须是1/3/4 if (cinfo.output_components != 1 && cinfo.output_components != 3 && cinfo.output_components != 4) { throw jpeg_mem_exception( "load_jpeg_mem(): Failed to load JPEG data cause by output_components error"); } decompress_instance.start_output(cinfo); JDIMENSION num_scanlines; JDIMENSION max_lines; while (cinfo.output_scanline < cinfo.output_height) { num_scanlines = jpeg_read_scanlines(&cinfo, decompress_instance.buffer.data(), (JDIMENSION)decompress_instance.buffer.size()); max_lines=std::min((cinfo.output_height-cinfo.output_scanline),(JDIMENSION)decompress_instance.buffer.size()); // 如果取到的行数小于预期的行数,则图像数据不完整抛出异常 if (num_scanlines<max_lines) throw jpeg_mem_exception("load_jpeg_mem(): Incomplete data"); decompress_instance.put_pixel_rows(num_scanlines); } }
image_matrix_pram
image_matrix_pram用于描述图像矩阵(非压缩状态)的基本信息
图像像素数据保存在类型为
std::vector <uint8_t>的向量对象中。
align为每行像素数据的内存对齐方式,如:为2时,以2的2次幂,就是4字节对齐,默认为0。
color_space为图像的色彩空间,枚举类型
J_COLOR_SPACE在
jpeglib.h中定义,一般RGB图像是
JCS_RGB,灰度图像是
JCS_GRAYSCALE。
/* 图像矩阵基本参数 */ typedef struct _image_matrix_pram{ int32_t width; // 图像宽度 int32_t height; // 图像高度 uint8_t channels; // 通道数 J_COLOR_SPACE color_space; // 图像数据的色彩空间 uint8_t align; // 内存对齐方式 0为不对齐,>0为以2的n次幂对齐 std::vector <uint8_t> pixels; // 图像数据 }image_matrix_pram,*image_matrix_pram_ptr;
jpeg_decompress_interface
为适应不同的解压缩需求,定义了jpeg_decompress_interface接口类,调用
load_jpeg_mem对图像数据解压时必须提供一个类型为
jpeg_decompress_interface的对象做入口参数,该接口主要
start_output和
put_pixel_rows两个函数,用于图像数据初始化和存储。
buffer对象是行像素解压缩数据的存储缓冲区,保存每行像素数据缓冲区的地址,libjpeg每次最多能解压缩的像素行数由
buffer的元素个数决定。
start_output根据传入参数
jpeg_decompress_struct中提供的图像基本信息,对图像存储区进行初始化。
put_pixel_rows则对负责将解压缩到缓冲区(buffer)的每行(row)像素存储到图像存储区中。
/* 图像解压缩接口类 */ struct jpeg_decompress_interface{ // 图像缓冲区 std::vector<JSAMPROW> buffer; // 设置自定义输出参数的函数对象 jpeg_custom_fun custom_output=[](j_common_ptr){}; // 虚函数用于初始化内存填充解压缩后图像信息数据 virtual void start_output(const jpeg_decompress_struct&cinfo)=0; // 虚函数用于将解压缩后的数据写入图像内存区 virtual void put_pixel_rows(JDIMENSION num_scanlines)=0; virtual ~jpeg_decompress_interface()=default; };
jpeg_decompress_default:jpeg_decompress_interface的默认实现
一般情况下,像素的每个通道数据都是连续存储的,所以针对这种常用的图像矩阵存储方式,提供了jpeg_decompress_interface接口的默认实现
jpeg_decompress_default。
jpeg_decompress_default每次只提供一行像素的缓冲区指针,由此控制libjpeg每次只解压缩一行数据。
成员对象
img保存解压缩后的结果数据,当图像成功解压缩后,
img中就存储了解压缩后图像的所有完整信息。
next_line成员指向当前要解压缩的像素行数
start_output中根据
jpeg_decompress_struct提供的图像宽/高/通道数计算出图像矩阵需要的存储区并分配相应的内存(
img.pixels)。
buffer中只有一个指针类型的元素,指向
img.pixels每一行像素的地址。这样jpeglib在解压缩出来的一行数据直接写入了
img.pixels
因为
buffer指针直接指向了图像存储区(
img.pixels)每行像素的对应位置,所以
put_pixel_rows不需要有复制数据的动作,只需要将
next_line加1,并根据
next_line将
buffer中的指针指向下一行像素的地址就可以了。
/* 默认的图像解压缩接口实现 */ struct jpeg_decompress_default:public jpeg_decompress_interface{ /* 解压缩后的图像基本信息 */ image_matrix_pram img; // 当前处理的目标图像像素行数 JDIMENSION next_line; virtual void start_output(const jpeg_decompress_struct&cinfo){ // 填充图像基本信息结构 img.width=cinfo.output_width; img.height=cinfo.output_height; img.color_space=cinfo.out_color_space; img.channels=cinfo.output_components; // 分配像素数据存储区 img.pixels=std::vector<uint8_t>(img.width*img.height*img.channels); // buffer只保存一行像素的目标数据指针 buffer=std::vector<JSAMPROW>(1); next_line=0; // 初始化buffer指向第一像素存储地址 buffer[next_line]=img.pixels.data(); } virtual void put_pixel_rows(JDIMENSION num_scanlines){ // buffer指向下一行要像素存储地址 buffer[0]=img.pixels.data()+(++next_line)*img.width*img.channels; } virtual ~jpeg_decompress_default()=default; };
load_jpeg_mem
load_jpeg_mem函数根据
decompress_instance参数提供的数据存储方式对长度为
size的
jpeg图像数据
jpeg_data进行解压缩,最后解压缩的结果如何处理由
decompress_instance对象定义,
load_jpeg_mem函数本身并不关心。
/* 将jpeg格式的内存数据块jpeg_data解压缩 * 图像行数据存储的方式都由decompress_instance定义 * 出错抛出 jpeg_mem_exception */ void load_jpeg_mem(uint8_t *jpeg_data,size_t size, jpeg_decompress_interface &decompress_instance) { if(nullptr==jpeg_data||0==size) throw jpeg_mem_exception("empty image data"); // 定义一个压缩对象 jpeg_decompress_struct cinfo; //用于错误信息 jpeg_error_mgr jerr; // 错误输出绑定到压缩对象 cinfo.err = jpeg_std_error(&jerr); // 设置自定义的错误处理函数 jerr.error_exit = jpeg_mem_error_exit; // RAII对象在函数结束时释放资源 gdface::raii buffer_guard([&](){ jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); }); // 初始化压缩对象 jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, jpeg_data, (unsigned long)size); // 设置内存输出缓冲区 (void) jpeg_read_header(&cinfo, true);// 读取jpeg格式头获取图像基本信息 decompress_instance.custom_output((j_common_ptr)&cinfo); // 执行自定义参数设置函数 (void) jpeg_start_decompress(&cinfo); // 输出通道数必须是1/3/4 if (cinfo.output_components != 1 && cinfo.output_components != 3 && cinfo.output_components != 4) { throw jpeg_mem_exception( "load_jpeg_mem(): Failed to load JPEG data cause by output_components error"); } decompress_instance.start_output(cinfo); JDIMENSION num_scanlines; JDIMENSION expectd_lines; while (cinfo.output_scanline < cinfo.output_height) { num_scanlines = jpeg_read_scanlines(&cinfo, decompress_instance.buffer.data(), (JDIMENSION)decompress_instance.buffer.size()); expectd_lines=std::min((cinfo.output_height-cinfo.output_scanline),(JDIMENSION)decompress_instance.buffer.size()); // 如果取到的行数小于预期的行数,则图像数据不完整抛出异常 if (num_scanlines<expectd_lines) throw jpeg_mem_exception("load_jpeg_mem(): Incomplete data"); decompress_instance.put_pixel_rows(num_scanlines); } }
在上面的代码中用到了我之前一篇博客(《C++11实现模板化(通用化)RAII机制》)中实现的
raii对象,该对象保证,不论在解压缩过程中是否发生异常(exception),用于释放资源的函数
jpeg_finish_decompress和
jpeg_destroy_decompress都会被执行,以避免内存泄露问题。
对图像解码时出现的处理方式参见前一篇博客《libjpeg:实现jpeg内存压缩暨error_exit错误异常处理和个性化参数设置》。
sample,解压缩时转灰或压缩分辨率
下面代码为调用示例。在图像解压缩时就可以将图像转换为指定的色彩空间,也可以将图像分辨率按比例压缩。见代码中的注释说明#include <iostream> #include <fstream> #include <string> #include <iostream> #include "jpeg_mem.h" using namespace cimg_library; using namespace std; int main() { try { const char *input_jpg_file = "D:/tmp/sample-1.jpg"; // 将一个jpeg图像文件读取到内存 std::ifstream is (input_jpg_file, std::ifstream::binary); std::vector<uint8_t> jpeg_data; if (is) { // get length of file: is.seekg(0, is.end); // 获取文件长度 auto length = is.tellg(); is.seekg(0, is.beg); jpeg_data = std::vector<uint8_t>(length); // read data as a block: is.read((char*) jpeg_data.data(), jpeg_data.size()); is.close(); } jpeg_decompress_default default_decompress_instance; default_decompress_instance.custom_output = [](j_common_ptr cinfo) { // 下面这行注释打开,就是设置解压缩时直接将图像转为灰度图(也可转为其他色彩空间) //((j_decompress_ptr)cinfo)->out_color_space = JCS_GRAYSCALE; // 下面这两行注释打开,就是设置解压缩时直接将图像尺寸压缩1/2 //((j_decompress_ptr)cinfo)->scale_num=1; //((j_decompress_ptr)cinfo)->scale_denom=2; }; load_jpeg_mem(jpeg_data,default_decompress_instance); // 函数调用结束,图像解码后的数据保存在default_decompress_instance.img中 }catch (exception &e){ // 异常输出 cout<<e.what()<<endl; } return 0; }
jpeg_decompress_interface接口的差异化实现
对于不同的图像处理对象,图像数据的保存方式可能是不一样的,比如CImg,是将每个通道的数据连续存储的,所以每个像素的每个通道的颜色值并不是连续存储的。前面的jpeg_decompress_default对象就不适合这种存储方式,这时就需要自己实现
jpeg_decompress_interface接口,才能正确执行解压缩,就以CImg为例:
// 该函数为继承CImg的子类的成员函数,为了突出重点, 就不贴出子类的完整代码了 const CImgWrapper<T>& load_mem_jpeg(uint8_t *jpeg_data,size_t size,jpeg_custom_fun custom=jpeg_custom_default){ // 实现jpeg_decompress_interface 接口 struct jpeg_decompress_cimg:public jpeg_decompress_interface { // 行缓冲区 CImg<typename CImg<T>::ucharT> line_buffer; // 颜色通道指针 T *ptr_r=nullptr , *ptr_g=nullptr , *ptr_b=nullptr , *ptr_a=nullptr; CImgWrapper<T>& cimg_obj; jpeg_decompress_cimg(CImgWrapper<T>& cimg_obj):cimg_obj(cimg_obj){} virtual void start_output(const jpeg_decompress_struct&cinfo) { line_buffer=CImg<typename CImg<T>::ucharT>(cinfo.output_width*cinfo.output_components); cimg_obj.assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); ptr_r = cimg_obj._data, ptr_g = cimg_obj._data + 1UL*cimg_obj._width*cimg_obj._height, ptr_b = cimg_obj._data + 2UL*cimg_obj._width*cimg_obj._height, ptr_a = cimg_obj._data + 3UL*cimg_obj._width*cimg_obj._height; buffer=std::vector<JSAMPROW>(1); buffer[0] =(JSAMPROW) line_buffer._data; } virtual void put_pixel_rows(JDIMENSION num_scanlines) { const unsigned char *ptrs = line_buffer._data; switch (cimg_obj._spectrum) { case 1 : { cimg_forX(cimg_obj,x) *(ptr_r++) = (T)*(ptrs++); } break; case 3 : { cimg_forX(cimg_obj,x) { *(ptr_r++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_b++) = (T)*(ptrs++); } } break; case 4 : { cimg_forX(cimg_obj,x) { *(ptr_r++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_b++) = (T)*(ptrs++); *(ptr_a++) = (T)*(ptrs++); } } break; } } }jpeg_decompress_cimg_instance(*this); jpeg_decompress_cimg_instance.custom_output=custom; // 调用load_jpeg_mem解压缩 load_jpeg_mem(jpeg_data,size,jpeg_decompress_cimg_instance); return *this; }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- 解析C++中派生的概念以及派生类成员的访问属性