jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)
来源:互联网 发布:家用工具品牌 知乎 编辑:程序博客网 时间:2024/06/12 18:30
前阵子用libjpeg-turbo实现jpeg图像在内存中编码与解码
参见《libjpeg:实现jpeg内存解压缩塈转换色彩空间/压缩分辨率》,《libjpeg:实现jpeg内存压缩暨error_exit错误异常处理和个性化参数设置》
觉得libjpeg接口用起来挺麻烦的。。。但libjpeg 80以上的版本好歹提供了jpeg_mem_dest/jpeg_mem_src
API让我可以直接将实现内存编/解码。
当我开始着手做jpeg2000(j2k)图像的内存压缩的时候,看了openjpeg的接口,人家压根儿没有提供类似libjpeg中jpeg_mem_dest/jpeg_mem_src
这样的内存数据IO接口(感觉还是libjpeg厚道些,呵呵),而是提供了抽象stream接口,openjpeg代码中只实现了文件流(file stream)接口(参见opj_stream_create_default_file_stream
代码)
如果使用者想实现内存图像压缩,你得自己实现这stream接口。。。。这对使用者来说好处是非常灵活(可以实现内存流接口,也可以实现网络流接口。。。你想怎么干都成),麻烦的就是要写好多代码。
openjpeg中file stream的实现
先参考一下openjpeg中file stream的实现:
以下是openjpeg中opj_stream_create_default_file_stream
的实现代码
opj_stream_t* OPJ_CALLCONV opj_stream_create_default_file_stream (const char *fname, OPJ_BOOL p_is_read_stream){ return opj_stream_create_file_stream(fname, OPJ_J2K_STREAM_CHUNK_SIZE, p_is_read_stream);}opj_stream_t* OPJ_CALLCONV opj_stream_create_file_stream ( const char *fname, OPJ_SIZE_T p_size, OPJ_BOOL p_is_read_stream){ opj_stream_t* l_stream = 00; FILE *p_file; const char *mode; if (! fname) { return NULL; } if(p_is_read_stream) mode = "rb"; else mode = "wb"; p_file = fopen(fname, mode); if (! p_file) { return NULL; } l_stream = opj_stream_create(p_size,p_is_read_stream); if (! l_stream) { fclose(p_file); return NULL; } opj_stream_set_user_data(l_stream, p_file, (opj_stream_free_user_data_fn) fclose); opj_stream_set_user_data_length(l_stream, opj_get_data_length_from_file(p_file)); opj_stream_set_read_function(l_stream, (opj_stream_read_fn) opj_read_from_file); opj_stream_set_write_function(l_stream, (opj_stream_write_fn) opj_write_from_file); opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn) opj_skip_from_file); opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn) opj_seek_from_file); return l_stream;}/* ---------------------------------------------------------------------- *///下面的代码中实现了对文件流的close,read,write,seek,skip操作,//其实这里的close,read,write,seek,skip的实现与c标准库中的文件操作函数fclose,fread,fwrite,fseek的接口描述是基本一样的static OPJ_SIZE_T opj_read_from_file (void * p_buffer, OPJ_SIZE_T p_nb_bytes, FILE * p_file){ OPJ_SIZE_T l_nb_read = fread(p_buffer,1,p_nb_bytes,p_file); return l_nb_read ? l_nb_read : (OPJ_SIZE_T)-1;}static OPJ_SIZE_T opj_write_from_file (void * p_buffer, OPJ_SIZE_T p_nb_bytes, FILE * p_file){ return fwrite(p_buffer,1,p_nb_bytes,p_file);}static OPJ_OFF_T opj_skip_from_file (OPJ_OFF_T p_nb_bytes, FILE * p_user_data){ if (OPJ_FSEEK(p_user_data,p_nb_bytes,SEEK_CUR)) { return -1; } return p_nb_bytes;}static OPJ_BOOL opj_seek_from_file (OPJ_OFF_T p_nb_bytes, FILE * p_user_data){ if (OPJ_FSEEK(p_user_data,p_nb_bytes,SEEK_SET)) { return OPJ_FALSE; } return OPJ_TRUE;}
memory stream接口实现
参考上面opj_stream_create_default_file_stream
的实现代码,可以知道,自定义一个类似file stream的流对象只要实现close,read,write,seek,skip
这几个函数并保证输入输出的状态与对应的opj_xxx_from_file
函数一样,就可以让openjpeg将图像压缩或解压缩到你的stream对象中。
于是,要实现jpeg2000的内存编/解码首先要做的工作就是实现自定义的内存流(memory stream)对象。
#include <vector>#include <iostream>#include <algorithm>#include <utility>#include <cstring>#include "openjpeg-2.1/openjpeg.h"using namespace std;#define DEFAULT_MEM_STREAM_INIT_SIZE (1024*16)/* 流(stream)接口 */struct opj_stream_interface{// 从stream中读取指定长度的数据,对应opj_read_from_file virtual OPJ_SIZE_T read (void * p_buffer, OPJ_SIZE_T p_nb_bytes)const=0;// 向stream中写入指定长度的数据,对应opj_write_from_file virtual OPJ_SIZE_T write (void * p_buffer, OPJ_SIZE_T p_nb_bytes)=0;// 以stream起始位置为参照设置stream的游标(position indicator)到指定的位置,对应opj_seek_from_file virtual OPJ_BOOL seek (OPJ_OFF_T p_nb_bytes)const=0;// 以stream当前位置为参照设置stream的游标(position indicator)到指定的位置,对应opj_skip_from_file virtual OPJ_OFF_T skip (OPJ_OFF_T p_nb_bytes)const=0;// 返回流的长度 virtual OPJ_UINT64 stream_length()const=0;// 返回流内存数据地址 virtual uint8_t* stream_data()const=0;// 关闭流,对应fclose() virtual void close()=0;// 为TRUE时stream对象为read only ,否则为只写write only。 virtual OPJ_BOOL is_read_stream()const=0; virtual ~opj_stream_interface()=default;};/**abstract memory stream(内存流虚类)只实现opj_stream_interface中的seek,skip,close,stream_length*/class opj_stream_mem_abstract:public opj_stream_interface{protected: /** pointer to the start of the stream */ // 内存流数据起始位置 mutable uint8_t *start; /** pointer to the end of the stream */ // 内存流数据结尾位置 mutable uint8_t *last; /** pointer to the current position */ // 内存流数据当前游标位置 mutable uint8_t *cursor;public: virtual OPJ_SIZE_T write (void * p_buffer, OPJ_SIZE_T p_nb_bytes)=0; virtual uint8_t* stream_data()const=0; virtual OPJ_BOOL is_read_stream()const=0; virtual OPJ_SIZE_T read (void * p_buffer, OPJ_SIZE_T p_nb_bytes)const=0; virtual OPJ_BOOL seek (OPJ_OFF_T p_nb_bytes)const{ if(p_nb_bytes>=0){ cursor=start+p_nb_bytes; return OPJ_TRUE; } return OPJ_FALSE; } virtual OPJ_OFF_T skip (OPJ_OFF_T p_nb_bytes)const{ // 这个函数设计是有问题的,当p_nb_bytes为-1时返回会产生歧义, // 但openjpeg中opj_skip_from_file就是这么写的 // opj_stream_skip_fn定义的返回也是bool // 所以也只能按其接口要求这么定义 auto nc=cursor+p_nb_bytes; if(nc>=start){ cursor=nc; return p_nb_bytes; } return (OPJ_OFF_T)-1; } virtual OPJ_UINT64 stream_length()const{ return (OPJ_UINT64)(last-start); } virtual void close(){} virtual ~opj_stream_mem_abstract()=default;};/**memory output stream(内存输出流)从std::vector<uint8_t>继承,实现opj_stream_interface中的read,write,stream_data,is_read_stream*/class opj_stream_mem_output:public opj_stream_mem_abstract,private vector<uint8_t>{ /** pointer to the end of the vector */ uint8_t *end; using base=vector<uint8_t>;public: opj_stream_mem_output():opj_stream_mem_output(DEFAULT_MEM_STREAM_INIT_SIZE){} opj_stream_mem_output(size_t init_capacity):base(init_capacity){ start=stream_data(); end=stream_data()+size(); cursor=stream_data(); last=stream_data(); } virtual OPJ_SIZE_T read (void * p_buffer, OPJ_SIZE_T p_nb_bytes)const{ // output stream不能读取 return 0; } virtual OPJ_SIZE_T write (void * p_buffer, OPJ_SIZE_T p_nb_bytes){ auto left=(OPJ_SIZE_T)(end-cursor); if(p_nb_bytes>left){ // 容量不足时先扩充(至少扩充1倍) auto off_cur=cursor-start; auto off_last=last-start; try{ base::resize(base::size()+max(p_nb_bytes-left,(OPJ_SIZE_T)base::size())); }catch(...){ // 处理resize失败抛出的异常#ifndef NDEBUG cerr<<"exception on call vector::resize"<<endl;#endif return 0; } start=stream_data(); end=start+base::size(); last=start+off_last; cursor=start+off_cur; } memcpy(cursor,p_buffer,p_nb_bytes); auto old_cursor=cursor; cursor+=p_nb_bytes; if(cursor>last){ if(old_cursor>last){ memset(last,0,old_cursor-last); } last=cursor; } return p_nb_bytes; } virtual uint8_t* stream_data()const{ return const_cast<uint8_t*>(base::data()); } virtual OPJ_BOOL is_read_stream()const{return 0;}};/**memory input stream(内存输入流)实现opj_stream_interface中的read,write,stream_data,is_read_stream*/class opj_stream_mem_input:public opj_stream_mem_abstract{ const uint8_t* _data; const size_t size;public: opj_stream_mem_input(const void * data,size_t size):_data(reinterpret_cast<const uint8_t*>(data)),size(size){ if(nullptr==data) throw opj_stream_exception("input data is null"); start=const_cast<uint8_t*>(_data); cursor=start; last=start+size; } virtual OPJ_SIZE_T read (void * p_buffer, OPJ_SIZE_T p_nb_bytes)const{ if(last>cursor){ auto len=min((OPJ_SIZE_T)(last-cursor),p_nb_bytes); if(len){ memcpy(p_buffer,cursor,len); cursor+=len; return len; } } return (OPJ_SIZE_T)-1; } virtual OPJ_SIZE_T write (void * p_buffer, OPJ_SIZE_T p_nb_bytes){ // input stream不能写入 return 0; } virtual uint8_t* stream_data()const{ return const_cast<uint8_t*>(_data); } virtual OPJ_BOOL is_read_stream()const{return 1;}};
create opj_stream_t from memory stream
上面的代码中最终实现了opj_stream_mem_input
和opj_stream_mem_output
两个流对象(分别用于图像解码和编码),这两个流对象的外部表现与openjpeg所要求的stream接口完全一致,但它们是c++的对象,不能直接用于c接口,所以还需要做一层封装。
于是,参照上面openjpeg的opj_stream_create_default_file_stream
和opj_stream_create_file_stream
函数,我们实现了memory stream对应的opj_stream_create_default_si
和opj_stream_create_si
// 对应opj_stream_create_file_streamopj_stream_t* opj_stream_create_si(opj_stream_interface& stream, OPJ_SIZE_T p_size) { opj_stream_t* l_stream = opj_stream_create(p_size, stream.is_read_stream()); if (l_stream) { opj_stream_set_user_data(l_stream, std::addressof(stream), (opj_stream_free_user_data_fn) (opj_stream_interface_close)); opj_stream_set_user_data_length(l_stream, stream.stream_length()); opj_stream_set_read_function(l_stream, (opj_stream_read_fn) (opj_stream_interface_read)); opj_stream_set_write_function(l_stream, (opj_stream_write_fn) (opj_stream_interface_write)); opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn) (opj_stream_interface_skip)); opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn) (opj_stream_interface_seek)); return l_stream; } throw opj_exception("fail to ceate stream:opj_stream_create");}// 对应 opj_stream_create_default_file_streamopj_stream_t* opj_stream_create_default_si(opj_stream_interface& stream) { return opj_stream_create_si(stream, OPJ_J2K_STREAM_CHUNK_SIZE);}// 对应 fclosevoid opj_stream_interface_close(opj_stream_interface* stream_instance) { stream_instance->close();}// 对应opj_read_seek_file OPJ_BOOL opj_stream_interface_seek(OPJ_OFF_T p_nb_bytes, opj_stream_interface* stream_instance) { return stream_instance->seek(p_nb_bytes);}// 对应opj_read_skip_file OPJ_OFF_T opj_stream_interface_skip(OPJ_OFF_T p_nb_bytes, opj_stream_interface* stream_instance) { return stream_instance->skip(p_nb_bytes);}// 对应opj_read_write_file OPJ_SIZE_T opj_stream_interface_write(void* p_buffer, OPJ_SIZE_T p_nb_bytes, opj_stream_interface* stream_instance) { return stream_instance->write(p_buffer, p_nb_bytes);}// 对应opj_read_read_file OPJ_SIZE_T opj_stream_interface_read(void* p_buffer, OPJ_SIZE_T p_nb_bytes, opj_stream_interface* stream_instance) { return stream_instance->read(p_buffer, p_nb_bytes);}
所有的准备工作完成,下面要上主菜了–> jpeg2000图像的内存压缩
- jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)
- openjpeg:jpeg2000(j2k)图像内存解压缩(解码)
- openjpeg:jpeg2000(j2k)图像内存压缩编码
- 用 XMLDOM 和 ADODB.Stream 实现base64编码解码
- 利用ADO STREAM实现BASE64编码和解码
- Base64编码解码和URLEnocde编码解码的C实现
- JPEG图像编码解码
- base64编码解码的实现(C语言)
- base64编码解码的实现(C语言)
- Base64编码解码的实现(C语言)
- .net C#实现Base64编码与解码
- C#实现Base64编码与解码
- Base64编码解码c语言实现
- Base64编码解码C语言实现
- Base64编码解码的实现(C语言)
- base64 编码解码 c语言实现
- C#实现Base64编码与解码
- C#实现Base64编码与解码
- Andriod蓝牙开发总结
- Android源码分析—带你认识不一样的AsyncTask(串并行)
- ENVI 5.1破解版安装过程
- Socket send函数和recv函数详解
- 【C语言复习(六)】隐式类型转换相关分析
- jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)
- Spring中 @Autowired标签与 @Resource标签 的区别
- HTML必知-html规范
- mysql创建数据库以及分配用户权限
- 通过贝叶斯logistic回归看拉普拉斯近似
- nyoj--105--九的余数(水题)
- 【NOI2001】食物链
- jQuery学习之remove与detach区别
- 周TOP排名