首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jpeg2000(j2k)图像编码解码:c++实现openjpeg内存流接口(memory stream)

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

作者头像
10km
发布2019-05-25 22:41:03
1.5K0
发布2019-05-25 22:41:03
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433780

前阵子用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_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);
}

所有的准备工作完成,下面要上主菜了–> jpeg2000图像的内存压缩

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年01月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • openjpeg中file stream的实现
  • memory stream接口实现
  • create opj_stream_t from memory stream
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档