前阵子用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_filevirtual OPJ_BOOL seek (OPJ_OFF_T p_nb_bytes)const=0;
//  以stream当前位置为参照设置stream的游标(position indicator)到指定的位置,对应opj_skip_from_filevirtual 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 NDEBUGcerr<<"exception on call vector::resize"<<endl;
#endifreturn 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_inputopj_stream_mem_output两个流对象(分别用于图像解码和编码),这两个流对象的外部表现与openjpeg所要求的stream接口完全一致,但它们是c++的对象,不能直接用于c接口,所以还需要做一层封装。
于是,参照上面openjpeg的opj_stream_create_default_file_streamopj_stream_create_file_stream 函数,我们实现了memory stream对应的opj_stream_create_default_siopj_stream_create_si


// 对应opj_stream_create_file_stream
opj_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_stream
opj_stream_t* opj_stream_create_default_si(opj_stream_interface& stream) {return opj_stream_create_si(stream, OPJ_J2K_STREAM_CHUNK_SIZE);
}// 对应 fclose
void 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);
}

所有的准备工作完成,下面要上主菜了–>
《openjpeg:jpeg2000(j2k)图像内存压缩编码》

jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)相关推荐

  1. openjpeg:jpeg2000(j2k)图像内存解压缩(解码)

    我的上一篇博客<openjpeg:jpeg2000(j2k)图像内存压缩编码>详细讲述了调用openjpeg实现jpeg2000(j2k)图像的内存压缩过程.本文讲述如何调用openjpe ...

  2. openjpeg:jpeg2000(j2k)图像内存压缩编码

    上一篇博文实现了<jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)>中实现了openjpeg的memory stream接口,本文 ...

  3. FFmpeg通过摄像头实现对视频流进行解码并显示测试代码(旧接口)

    这里通过USB摄像头(注:windows7/10下使用内置摄像头,linux下接普通的usb摄像头(Logitech))获取视频流,然后解码,最后再用opencv显示.用到的模块包括avformat. ...

  4. c#中字节数组byte[]、图片image、流stream,字符串string、内存流MemoryStream、文件file,之间的转换

    字节数组byte[]与图片image之间的转化 字节数组转换成图片 public static Image byte2img(byte[] buffer) {MemoryStream ms = new ...

  5. C#字符串、字节数组和内存流间的相互转换 - IT浪潮之巅

    定义string变量为str,内存流变量为ms,比特数组为bt 1.字符串=>比特数组 (1)byte[] bt=System.Text.Encoding.Default.GetBytes(&q ...

  6. Java——I/O(字符编码、内存流、打印流、System、输入流、序列化)

    目录 1.常见的编码 2.乱码产生原因 3.内存流 3.1 分类 3.2应用 4.打印流 4.1 自定义打印流 4.2 系统提供的打印流 4.3 格式化输出 5.System对I/O的支持 6.两种输 ...

  7. Java学习总结:46(内存流)

    内存流 在Java中,针对内存操作提供了以下两组类: 字节内存流:ByteArrayInputStream(内存字节输入流).ByteArrayOutputStream(内存字节输出流): 字符内存流 ...

  8. Java内存模型(Java Memory Model,JMM)

    导读 本文通过对JSR133规范的解读,详细的介绍JMM的核心理论,并将开发中常用的关键字的实现原来做了详细的介绍.通过本文读者可以了解到并发的一些基本理论,并对一些同步原语有了更深层次的理解.希望读 ...

  9. Linux系统内存的Cached Memory

    Linux与Windows不同,会存在缓存内存,通常叫做Cache Memory.有些时候你会发现没有什么程序在运行,但是使用top或free命令看到可用内存会很少. 什么是Cache Memory( ...

最新文章

  1. java hibernate configuration 获取_1 Hibernate Configuration 配置
  2. [cb]NGUI组件基类之 UIWidget
  3. 用MFC类来操作数据库的方法
  4. ant design pro总是跨域,proxy也没设置错误,原来是浏览器缓存,清理Chrome缓存就可以了...
  5. Redis之Ubuntu开机启动
  6. PostgresSQL支持geometry类型
  7. 360 小程序来了,进攻 PC 端!
  8. mysql 关闭autocommit_mysql禁用autocommit,以及遇到的问题
  9. 在spring中手动编写事务
  10. RHadoop的安装与试验
  11. Mac 安装的虚拟机win10系统,设置拥有各自的桌面
  12. c语言用switch计算税收,求助。。关于用switch编写简易计算器
  13. 工匠精神消失的手机2020:衰落、变局、绝唱、破圈
  14. docker 打包镜像
  15. python模拟足球射门_用Python模拟2018世界杯夺冠之路
  16. echarts 自定义legend 初始化为灰色
  17. 网络唤醒的原理原来是这样的,GET!
  18. Python2.7安装Opencv3
  19. 解决磁盘空间不足的问题
  20. .net5项目集成百度富文本编辑器umeditor最全教程(含源码)

热门文章

  1. 计算机制作效果图常用软件有,计算机园林效果图有哪些绘制过程?
  2. 全国高校2022年经费预算排名(前150名)
  3. R语言基于库克距离统计量识别(Cook’s distance)对于回归模型性能或者预测影响(Influential observation)很大的观测样本、可视化库克距离并添加阈值线识别影响力大的样本
  4. “麒麟计划”即时响应国家政策,发力数字营销助推二手车出口贸易
  5. Excel获取指定数字格式文本——TEXT函数及其用法
  6. 高中数学40分怎么办_高一数学40分有救吗?
  7. 一个成功软件测试项目的经验(转载)
  8. c语言 显示一行中文 程序,关于C语言TurboC中显示汉字的问题程序如下(我已把hak16 爱问知识人...
  9. 图像处理中的边缘检测
  10. 无桥PFC的家族推演