libjpeg-turbo是与libjpeg接口兼容的一个jpeg编/解码库,其主要的特点就是利用SIMD指令(如X86架构的MMX/SSE/SSE2,ARM架构的NEON)来加速jpeg图像的编/解码,相比被广泛使用的libjpeg,编码和解码性能提高2~4倍左右。
本文介绍的内容适用于libjpeg-turbo和libjpeg(80以上版本)
关于如何用gcc对libjpeg-turbo编译,请参考我之前的一篇的博文《mingw(gcc)编译libjpeg-turbo》
先附上完整的代码,再做分别的讲解(代码用C++11撰写,在VS2015和gcc编译)。
jpeg_mem.h
#include <stdexcept>
#include <functional>
#include <string>
#include "jpeglib.h"
typedef struct _ImageInfo{
int32_t width; // 图像宽度
int32_t height; // 图像高度
uint8_t channels; // 通道数
J_COLOR_SPACE color_space; // 图像数据的色彩空间
uint8_t align; // 内存对齐方式 0为不对齐,>0为以2的n次幂对齐
uint8_t* data; // 图像数据起始地址
uint32_t size; // 图像数据长度
}ImagInfo;
/* 处理压缩解压缩后内存数据的回调函数 */
using mem_callback_fun=std::function<void(const uint8_t*,unsigned long)>;
/* 定制压缩解压缩参数 */
using jpeg_custom_fun=std::function<void(j_common_ptr)>;
/* 获取一行数据 */
using getline_fun=std::function<JSAMPROW(const ImagInfo &img,unsigned int line)>;
/* jpeg图像处理异常类 */
class jpeg_mem_exception:public std::logic_error{
public:
// 继承基类构造函数
using std::logic_error::logic_error;
};
JSAMPROW getline_default(const ImagInfo& img, unsigned int line);
void jpeg_custom_default(j_common_ptr);
void save_jpeg_mem( const ImagInfo& img,
const mem_callback_fun& callback,
const unsigned int quality = 100,
const jpeg_custom_fun&custom=jpeg_custom_default,
const getline_fun& getline = getline_default);
jpeg_mem.cpp
/* * jpeg_mem.cpp * * Created on: 2016年1月19日 * Author: guyadong */
#include "jpeg_mem.h"
enum { JPEG_COMPRESS_ERROR=1,JPEG_DECOMPRESS_ERROR=2 };
/* 自定义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);
}
JSAMPROW getline_default(const ImagInfo& img, unsigned int line) {
if(nullptr==img.data)
throw jpeg_mem_exception("image data is null");
auto row_stride = img.width * img.channels;
if (img.align)
row_stride = (row_stride + (1<<img.align) - 1) >> img.align;
return img.data + line * row_stride;
}
void jpeg_custom_default(j_common_ptr){}
/* 将图像数据输出为jpeg格式的内存数据块,调用传入的callback回调函数来处理压缩后的内存图像数据 * 图像信息描述在img参数中,getline用于从图像中获取指定行的数据,如果图像数据为普通图像矩阵,则getline可以置为nullptr * custom用于设置图像输出参数 * 出错抛出 jpeg_mem_exception */
void save_jpeg_mem(const ImagInfo& img,
const mem_callback_fun& callback,
const unsigned int quality,
const jpeg_custom_fun&custom,
const getline_fun& getline) {
// 输出图像数据缓冲区
unsigned char* outBuffer = nullptr;
// 输出图像数据缓冲区长度(压缩后的图像大小)
unsigned long bufferSize = 0;
// 定义一个压缩对象
jpeg_compress_struct cinfo;
//用于错误信息
jpeg_error_mgr jerr;
// 错误输出绑定到压缩对象
cinfo.err = jpeg_std_error(&jerr);
// 设置自定义的错误处理函数
jerr.error_exit = jpeg_mem_error_exit;
try {
// 初始化压缩对象
jpeg_create_compress(&cinfo);
jpeg_mem_dest(&cinfo, &outBuffer, &bufferSize); // 设置输出缓冲区
// 设定基本的图像宽高通道色彩空间等参数
cinfo.image_width = img.width;
cinfo.image_height = img.height;
cinfo.input_components = img.channels;
cinfo.in_color_space = img.color_space;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, true);
//jpeg_set_colorspace(&cinfo, JCS_GRAYSCALE);//将图像转为灰度
custom((j_common_ptr)(&cinfo));// 执行自定义参数设置函数
jpeg_start_compress(&cinfo, true);
JSAMPROW row_pointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
//调用 getline获取一行数据进行压缩
row_pointer[0] = getline(img, cinfo.next_scanline);
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
callback(outBuffer, bufferSize);
if (nullptr != outBuffer)
free(outBuffer);
}
catch (jpeg_mem_exception& e) {
// 处理压缩过程中抛出的异常
jpeg_destroy_compress(&cinfo);
if (nullptr != outBuffer)
free(outBuffer);
throw e;
}
}
在使用默认错误处理结构jpeg_error_mgr
的情况下,程序在遇到错误后将调用exit直接退出程序,用户如果不希望使用这种直接退出的方式处理错误的话可以通过设置jpeg_error_mgr.error_exit
指针的方式将错误处理指向自定义的错误处理函数,本例中将jpeg错误转为自定义的jpeg_mem_exception异常抛出 。
/* 自定义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_mem_error_exit
函数,在jpeg压缩初始化时将这个指针赋值给jpeg_error_mgr.error_exit
// 定义一个压缩对象
jpeg_compress_struct cinfo;
//用于错误信息
jpeg_error_mgr jerr;
// 错误输出绑定到压缩对象
cinfo.err = jpeg_std_error(&jerr);
// 设置自定义的错误处理函数
jerr.error_exit = jpeg_mem_error_exit;
剩下的事就像普通的c++程序一样用try{}catch{}将整个压缩过程代码包起来,在catch中对异常进行处理了。
这篇文章《JPEG图像的解压缩操作》中讲到可以用setjmp/longjmp
来实现错误处理,我本来也是按这个方式做的,等做完后再想,不对呀,C++本来就有更先进的异常处理类exception
,为什么要用C时代的setjmp
呢?所以果然改成了直接在jpeg_mem_error_exit
抛出异常
因为不同的应用需求不同,对于已经压缩完成的内存数据,如何处理,应该允许调用者有自己不同的选择,所以可以使用std::function<void(const uint8_t*,unsigned long)>
的参数作为回调函数,由调用函数自己处理返回结果
try{
// 初始化传入参数对象
ImagInfo img = { (int32_t)this->_width,(int32_t)this->_height,dimbuf ,colortype , (uint8_t)0, nullptr, (uint32_t)0 };
save_mem_jpeg(
img,//图像基本参数
90,//图像质量
jpeg_custom_default,
[&](const uint8_t *img, unsigned long size) {
// callback函数,img为压缩的jpeg图像数据地址,size为数据长度
// 将图像存成本地文件
std::ofstream ofs;
ofs.open(output_jpg_file, std::ofstream::binary);
ofs.write((const char*)img, size);
printf("%s saved,size=%d\n", output_jpg_file, size);
ofs.close();
}
);
}catch (exception &e){
cout<<e.what()<<endl;
}
同样为了满足调用函数图像处理的个性化需求,类型为jpeg_custom_fun
的custom是用来对输出图像参数进行调整的函数对象参数,比如如果想将图像压缩成灰度图,上面的例子代码就可以改成这样:
try{
// 初始化传入参数对象
ImagInfo img = { (int32_t)this->_width,(int32_t)this->_height,dimbuf ,colortype , (uint8_t)0, nullptr, (uint32_t)0 };
save_mem_jpeg(
img,//图像基本参数
90,//图像质量
[](j_common_ptr com_ptr) {
// 将输出图像的色彩空间改为灰度(JCS_GRAYSCALE)
jpeg_set_colorspace((j_compress_ptr)com_ptr, JCS_GRAYSCALE);
},
[&](const uint8_t *img, unsigned long size) {
// callback函数,img为压缩的jpeg图像数据地址,size为数据长度
// 将图像存成本地文件
std::ofstream ofs;
ofs.open(output_jpg_file, std::ofstream::binary);
ofs.write((const char*)img, size);
printf("%s saved,size=%d\n", output_jpg_file, size);
ofs.close();
}
);
}catch (exception &e){
cout<<e.what()<<endl;
}
大部分情况下,图像数据是以每行的像素点每个通道的数据连续存储的,这时获取每行的数据就比较简单
就像下面这样
JSAMPROW getline_default(const ImagInfo& img, unsigned int line) {
if(nullptr==img.data)
throw jpeg_mem_exception("image data is null");
// 根据line参数计算所在行像素的起始地址并返回
auto row_stride = img.width * img.channels;
if (img.align)//内存对象方式
row_stride = (row_stride + (1<<img.align) - 1) >> img.align;
return img.data + line * row_stride;//img.data是图像数据的起始指针
}
但有时并不完全如此,
对于不同的图像处理对象,图像数据的保存方式可能是不一样的,比如CImg,是将每个通道的数据连续存储的,所以每个像素的3个通道的颜色值并不是连续存储的。这时就需要自己写一个类型为getline_fun
的函数对象作为参数,才能正确执行压缩,就以CImg为例:
// 该函数为继承CImg的子类的成员函数,为了突出重点, 就不贴出子类的完整代码了
const CImgWrapper<T>& save_mem_jpeg(const mem_callback_fun& callback, const unsigned int quality = 100) const {
uint8_t dimbuf = 0;
J_COLOR_SPACE colortype = JCS_RGB;
switch (this->_spectrum) {
case 1:
dimbuf = 1;
colortype = JCS_GRAYSCALE;
break;
case 2:
dimbuf = 3;
colortype = JCS_RGB;
break;
case 3:
dimbuf = 3;
colortype = JCS_RGB;
break;
default:
dimbuf = 4;
colortype = JCS_CMYK;
break;
}
// 初始化传入参数对象
ImagInfo img = { (int32_t)this->_width,(int32_t)this->_height,dimbuf ,colortype , (uint8_t)0, nullptr, (uint32_t)0 };
// 用CImg生成一个工作缓冲区对象
// getline函数从CImg._save_jpeg中抄来
CImg<typename CImg<T>::ucharT> buffer((unsigned long) (this->_width*dimbuf));
save_jpeg_mem(img,
callback,
quality,
jpeg_custom_default,
// 自定义的getline函数对象用于从CImg对象中获取每一行的像数据
// 将每个像素对应在每个通道的数据取出顺序写入指定的buffer,然后返回buffer
[&](const ImagInfo &img,unsigned int line)->JSAMPROW{
unsigned char* ptrd = buffer._data;
// Fill pixel buffer
switch (this->_spectrum) {
case 1: {
// Greyscale images
const T* ptr_g = this->data(0, line);
for (unsigned int b = 0; b < this->_width; b++)
*(ptrd++) = (unsigned char) ((*(ptr_g++)));
}
break;
case 2: {
// RG images
const T *ptr_r = this->data(0, line, 0, 0),
*ptr_g = this->data(0, line, 0, 1);
for (unsigned int b = 0; b < this->_width; ++b) {
*(ptrd++) = (unsigned char) ((*(ptr_r++)));
*(ptrd++) = (unsigned char) ((*(ptr_g++)));
*(ptrd++) = 0;
}
}
break;
case 3: {
// RGB images
const T *ptr_r = this->data(0, line, 0, 0),
*ptr_g = this->data(0, line, 0, 1),
*ptr_b = this->data(0, line, 0, 2);
for (unsigned int b = 0; b < this->_width; ++b) {
*(ptrd++) = (unsigned char) ((*(ptr_r++)));
*(ptrd++) = (unsigned char) ((*(ptr_g++)));
*(ptrd++) = (unsigned char) ((*(ptr_b++)));
}
}
break;
default: {
// CMYK images
const T *ptr_r = this->data(0, line, 0, 0),
*ptr_g = this->data(0, line, 0, 1),
*ptr_b = this->data(0, line, 0, 2),
*ptr_a = this->data(0, line, 0, 3);
for (unsigned int b = 0; b < this->_width; ++b) {
*(ptrd++) = (unsigned char) ((*(ptr_r++)));
*(ptrd++) = (unsigned char) ((*(ptr_g++)));
*(ptrd++) = (unsigned char) ((*(ptr_b++)));
*(ptrd++) = (unsigned char) ((*(ptr_a++)));
}
}
}
return buffer.data();
});
return *this;
}