以下是针对 PDFCoreJPEG2000Manager
类的介绍大纲,采用技术文档的标准结构,突出核心功能和设计要点:
CompressToJp2
/GetJ2kImageInfo
)DecodeToRGB
:强制 RGB/RGBA 输出GetImageInfo
:仅解析头部元数据(快 3-5 倍)PDFCore_FS_JPEG2K_CODEC_JP2
(推荐)PDFCore_FS_JPEG2K_CODEC_J2K
(低开销)PDFCore_FS_JPEG2K_CODEC_JPX
(扩展功能)std::vector
自动管理std::invalid_argument
std::runtime_error
// 场景1:快速获取图像信息
PDFCoreJPEG2000Manager manager;
uint32_t w, h;
std::string colorSpace;
manager.GetImageInfo(j2kData, dataSize, w, h, ..., colorSpace);
// 场景2:高质量转码
auto rgbData = manager.DecodeToRGB(..., 4); // 输出RGBA
auto jp2Data = manager.EncodeToJPEG2000(rgbData.data(), w, h, 95);
GetImageInfo
requestComponents
减少转换输入数据必须有效至调用结束
输出缓冲区预分配建议:
std::vector output;
output.reserve(width*height*3); // 防止多次扩容
该大纲可作为:
#ifndef __JPX_WRAP__
#define __JPX_WRAP__
#include
#include
#include
#include
/**
* @file jpx_wrap.h
* @brief JPEG2000 图像编解码功能封装
* @note 本模块提供 JPEG2000 图像的压缩、解压缩及元数据提取功能
* @warning 修改需谨慎,必须经过兼容性测试
*/
// 原始函数声明(兼容旧接口)
void CompressToJp2(const std::vector<uint8_t>& source_image,
const int& width,
const int& height,
const int& quality,
const int& format,
std::vector<uint8_t>& target_image);
int GetJ2kImageInfo(std::vector<uint8_t>& image_data,
int& image_format,
int& image_width,
int& image_height,
int& image_channels,
int& image_bits_per_component,
int& image_colorspace,
bool white_background);
/**
* @class PDFCoreJPEG2000Manager
* @brief 增强版 JPEG2000 编解码管理器
*
* 提供完整的 JPEG2000 图像处理能力,包括:
* - 图像解码(支持 RGB/RGBA 输出)
* - 图像编码(支持质量参数控制)
* - 元数据提取(不解码像素数据)
* - 格式转换功能
*
* @version 2.1
* @author sylar ding
* @date 2025.7.9
* @copyright 内部使用,禁止未经授权的修改 Maintained by sylar ding. Do not modify without permission
*/
class PDFCoreJPEG2000Manager {
public:
/**
* @enum PDFCore_FS_JPEG2K_CODEC_FORMAT
* @brief JPEG2000 格式类型定义
*/
enum class PDFCore_FS_JPEG2K_CODEC_FORMAT : int {
PDFCore_FS_JPEG2K_CODEC_UNKNOWN = -1, ///< 未知格式标识
PDFCore_FS_JPEG2K_CODEC_J2K = 0, ///< 原始码流格式(全功能支持)
PDFCore_FS_JPEG2K_CODEC_JP2 = 1, ///< 标准 JP2 文件格式(全功能支持)
PDFCore_FS_JPEG2K_CODEC_JPT = 2, ///< JPT 传输流(仅解码)
PDFCore_FS_JPEG2K_CODEC_JPX = 3, ///< 扩展 JPX 格式(仅编码)
PDFCore_FS_JPEG2K_CODEC_JPP = 4 ///< JPP 渐进流(预留)
};
~PDFCoreJPEG2000Manager() = default;
PDFCoreJPEG2000Manager() = default;
/**
* @brief 解码 JPEG2000 数据到标准 RGB 格式
* @param j2kData 压缩数据指针(必须有效)
* @param dataSize 数据长度(必须 >0)
* @param[out] width 图像宽度(像素)
* @param[out] height 图像高度(像素)
* @param[out] componentCount 实际通道数(1/3/4)
* @param[out] bitsPerSample 位深度(通常8/16)
* @param[out] isSigned 样本符号标识
* @param[out] colorSpace 色彩空间描述
* @param[out] compression 压缩方式描述
* @param requestComponents 输出通道请求(1=GRAY, 3=RGB, 4=RGBA)
* @return 解码后的像素数据(连续存储)
* @throws std::invalid_argument 参数无效
* @throws std::runtime_error 解码失败
*
* @note 对于带Alpha通道的图像:
* - 当requestComponents=3时会自动混合白色背景
* - 输出缓冲区大小 = width*height*requestComponents
*/
std::vector<uint8_t> DecodeToRGB(
const uint8_t* j2kData,
size_t dataSize,
uint32_t& width,
uint32_t& height,
uint16_t& componentCount,
uint8_t& bitsPerSample,
bool& isSigned,
std::string& colorSpace,
std::string& compression,
uint8_t requestComponents = 3
);
/**
* @brief 快速获取图像元数据(不解码像素)
* @param j2kData 压缩数据指针
* @param dataSize 数据长度
* @param[out] width 图像宽度
* @param[out] height 图像高度
* @param[out] componentCount 通道数
* @param[out] bitsPerSample 位深度
* @param[out] isSigned 符号标识
* @param[out] colorSpace 色彩空间
* @param[out] compression 压缩方式
*
* @note 此方法比DecodeToRGB快3-5倍
* @warning 不验证像素数据有效性
*/
void GetImageInfo(
const uint8_t* j2kData,
size_t dataSize,
uint32_t& width,
uint32_t& height,
uint16_t& componentCount,
uint8_t& bitsPerSample,
bool& isSigned,
std::string& colorSpace,
std::string& compression
);
/**
* @brief 编码RGB数据为JPEG2000格式
* @param rgbData RGB像素数据(必须连续存储)
* @param width 图像宽度(>0)
* @param height 图像高度(>0)
* @param quality 压缩质量(1-100)
* @param format 输出格式(默认为JP2)
* @return 压缩后的数据
* @throws std::logic_error 编码过程错误
*
* @note 输入数据布局须为:
* - 3通道:R0G0B0 R1G1B1...
* - 4通道:R0G0B0A0 R1G1B1A1...
*/
std::vector<uint8_t> EncodeToJPEG2000(
const uint8_t* rgbData,
int width,
int height,
int quality,
PDFCore_FS_JPEG2K_CODEC_FORMAT format = PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_JP2
);
};
#endif /* __JPX_WRAP__ */
//#ifndef __JPEG2k__
//#define __JPEG2k__
#include "../fx_libopenjpeg/libopenjpeg20/openjpeg.h"
#include"fxcodec/fx_codec_jpx_wrap.h"
#include
#include
#include
#include
#include
/// 定义默认内存流初始大小
constexpr size_t kDefaultMemStreamInitSize = 1024 * 16;
/**
* @brief 流接口
*/
struct OpjStreamInterface {
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
virtual OPJ_SIZE_T Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const = 0;
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
virtual OPJ_SIZE_T Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) = 0;
/**
* @brief 设置流的游标到指定位置(相对于起始位置)。
* @param p_nb_bytes 相对于起始位置的偏移量。
* @return 成功返回 true,失败返回 false。
*/
virtual OPJ_BOOL Seek(OPJ_OFF_T p_nb_bytes) const = 0;
/**
* @brief 在流中跳过指定数量的字节。
* @param p_nb_bytes 要跳过的字节数。
* @return 实际跳过的字节数。
*/
virtual OPJ_OFF_T Skip(OPJ_OFF_T p_nb_bytes) const = 0;
/**
* @brief 返回流的长度。
* @return 流的长度。
*/
virtual OPJ_UINT64 StreamLength() const = 0;
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
virtual uint8_t* StreamData() const = 0;
/**
* @brief 关闭流。
*/
virtual void Close() = 0;
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
virtual OPJ_BOOL IsReadStream() const = 0;
/// 析构函数
virtual ~OpjStreamInterface() = default;
};
/**
* @brief 抽象内存流类
*/
class OpjStreamMemAbstract : public OpjStreamInterface {
protected:
/// 指向流起始位置的指针
mutable uint8_t* start_;
/// 指向流结束位置的指针
mutable uint8_t* last_;
/// 指向当前游标的指针
mutable uint8_t* cursor_;
public:
/**
* @brief 写入指定长度的数据到流中。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
virtual OPJ_SIZE_T Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) override = 0;
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
virtual uint8_t* StreamData() const override = 0;
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
virtual OPJ_BOOL IsReadStream() const override = 0;
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
virtual OPJ_SIZE_T Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const override = 0;
/**
* @brief 设置流的游标到指定位置(相对于起始位置)。
* @param p_nb_bytes 相对于起始位置的偏移量。
* @return 成功返回 true,失败返回 false。
*/
virtual OPJ_BOOL Seek(OPJ_OFF_T p_nb_bytes) const override;
/**
* @brief 在流中跳过指定数量的字节。
* @param p_nb_bytes 要跳过的字节数。
* @return 实际跳过的字节数。
*/
virtual OPJ_OFF_T Skip(OPJ_OFF_T p_nb_bytes) const override;
/**
* @brief 返回流的长度。
* @return 流的长度。
*/
virtual OPJ_UINT64 StreamLength() const override;
/**
* @brief 关闭流。
*/
virtual void Close() override {}
/// 析构函数
virtual ~OpjStreamMemAbstract() override = default;
};
/**
* @brief 内存输出流类
*/
class OpjStreamMemOutput : public OpjStreamMemAbstract, private std::vector<uint8_t> {
private:
/// 指向 vector 结尾的指针
uint8_t* end_;
using base = std::vector<uint8_t>;
public:
/**
* @brief 默认构造函数。
*/
OpjStreamMemOutput();
/**
* @brief 带初始化容量的构造函数。
* @param init_capacity 初始化容量。
*/
explicit OpjStreamMemOutput(size_t init_capacity);
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
OPJ_SIZE_T Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const override;
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
OPJ_SIZE_T Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) override;
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
uint8_t* StreamData() const override;
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
OPJ_BOOL IsReadStream() const override;
/**
* @brief 关闭流。
*/
void Close() override;
/**
* @brief 将输出的压缩数据封装为 vector 并返回。
* @return 包含压缩数据的 vector。
*/
std::vector<uint8_t> AsVector();
};
/**
* @brief 内存输入流类
* 实现了 OpjStreamInterface 中的 read, write, stream_data, is_read_stream 方法
*/
class OpjStreamMemInput : public OpjStreamMemAbstract {
private:
/// 输入数据指针
const uint8_t* data_;
/// 输入数据大小
const size_t size_;
public:
/**
* @brief 构造函数。
* @param data 输入数据指针。
* @param size 输入数据大小。
*/
OpjStreamMemInput(const void* data, size_t size);
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
OPJ_SIZE_T Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const override;
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
OPJ_SIZE_T Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) override;
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
uint8_t* StreamData() const override;
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
OPJ_BOOL IsReadStream() const;
};
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @param stream_instance 流实例指针。
* @return 实际读取的字节数。
*/
OPJ_SIZE_T OpjStreamInterfaceRead(void* p_buffer, OPJ_SIZE_T p_nb_bytes, OpjStreamInterface* stream_instance);
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @param stream_instance 流实例指针。
* @return 实际写入的字节数。
*/
OPJ_SIZE_T OpjStreamInterfaceWrite(void* p_buffer, OPJ_SIZE_T p_nb_bytes, OpjStreamInterface* stream_instance);
/**
* @brief 在流中跳过指定数量的字节。
* @param p_nb_bytes 要跳过的字节数。
* @param stream_instance 流实例指针。
* @return 实际跳过的字节数。
*/
OPJ_OFF_T OpjStreamInterfaceSkip(OPJ_OFF_T p_nb_bytes, OpjStreamInterface* stream_instance);
/**
* @brief 设置流的游标到指定位置(相对于起始位置)。
* @param p_nb_bytes 相对于起始位置的偏移量。
* @param stream_instance 流实例指针。
* @return 成功返回 true,失败返回 false。
*/
OPJ_BOOL OpjStreamInterfaceSeek(OPJ_OFF_T p_nb_bytes, OpjStreamInterface* stream_instance);
/**
* @brief 关闭流。
* @param stream_instance 流实例指针。
*/
void OpjStreamInterfaceClose(OpjStreamInterface* stream_instance);
// 假设 opj_stream_t 是在其他地方定义的
/**
* @brief 创建一个流实例。
* @param stream 流接口引用。
* @param p_size 流大小。
* @return 流实例指针。
*/
opj_stream_t* OpjStreamCreateSi(OpjStreamInterface& stream, OPJ_SIZE_T p_size);
/**
* @brief 创建一个默认大小的流实例。
* @param stream 流接口引用。
* @return 流实例指针。
*/
opj_stream_t* OpjStreamCreateDefaultSi(OpjStreamInterface& stream);
/***** opj_stream_mem_abstract 实现部分************************************************************/
OPJ_BOOL OpjStreamMemAbstract::Seek(OPJ_OFF_T p_nb_bytes) const {
if (p_nb_bytes >= 0) {
cursor_ = start_ + p_nb_bytes;
return OPJ_TRUE;
}
return OPJ_FALSE;
}
OPJ_OFF_T OpjStreamMemAbstract::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;
}
OPJ_UINT64 OpjStreamMemAbstract::StreamLength() const {
return static_cast<OPJ_UINT64>(last_ - start_);
}
/***** OpjStreamMemOutput 实现部分************************************************************/
/**
* @brief 默认构造函数。
*/
OpjStreamMemOutput::OpjStreamMemOutput() : OpjStreamMemOutput(kDefaultMemStreamInitSize) {}
/**
* @brief 带初始化容量的构造函数。
* @param init_capacity 初始化容量。
*/
OpjStreamMemOutput::OpjStreamMemOutput(size_t init_capacity) : base(init_capacity) {
start_ = StreamData();
end_ = start_ + base::size();
cursor_ = start_;
last_ = start_;
/*start_ = StreamData();
end_ = StreamData() + size();
cursor_ = StreamData();
last_ = StreamData();*/
}
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
OPJ_SIZE_T OpjStreamMemOutput::Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const {
// 输出流不能读取
return 0;
}
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
OPJ_SIZE_T OpjStreamMemOutput::Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) {
// 计算当前缓冲区中剩余空间的大小。
auto available_space = static_cast<OPJ_SIZE_T>(end_ - cursor_);
// 检查可用空间是否足够存放要写入的字节数。
if (p_nb_bytes > available_space) {
// 如果空间不足,计算当前缓冲区指针位置的偏移。
auto cur_offset = cursor_ - start_;
// 记录最后一个字节后的偏移量。
auto last_offset = last_ - start_;
// 计算新的缓冲区大小,需要确保足够放下新数据。(最少是两倍扩容), (std::max)是为了避免被宏转义
OPJ_SIZE_T new_size = base::size() + (std::max)(p_nb_bytes - available_space, base::size());
// 调整缓冲区的大小。
base::resize(new_size);
// 重新计算并更新指针以反映缓冲区的变化。
start_ = StreamData();
end_ = start_ + base::size();
last_ = start_ + last_offset;
cursor_ = start_ + cur_offset;
}
// 将数据从输入缓冲区复制到内部缓冲区中。
std::memcpy(cursor_, p_buffer, p_nb_bytes);
// 更新指针位置以反映新数据的末尾。
auto new_cursor_position = cursor_ + p_nb_bytes;
// 如果新数据位置超出了之前最后一个字节的位置,进行填充操作。(为了保持数据的连续性以及防止使用未初始化内存(未定义行为),我们需要将任何新的空白区域填补为明确的值(如零)。)
if (new_cursor_position > last_) {
// 如果当前指针超过了之前的最后一个字节位置,用零填充缺口部分。
if (cursor_ > last_) {
std::memset(last_, 0, cursor_ - last_);
}
// 更新 `last` 位置以反映新的结束位置。
last_ = new_cursor_position;
}
// 更新当前指针位置。
cursor_ = new_cursor_position;
// 返回写入的字节数。
return p_nb_bytes;
}
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
uint8_t* OpjStreamMemOutput::StreamData() const {
return const_cast<uint8_t*>(base::data());
}
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
OPJ_BOOL OpjStreamMemOutput::IsReadStream() const { return 0; }
/**
* @brief 关闭流。
*/
void OpjStreamMemOutput::Close() {
base::resize(0);
}
/**
* @brief 将输出的压缩数据封装为 vector 并返回。
* @return 包含压缩数据的 vector。
*/
std::vector<uint8_t> OpjStreamMemOutput::AsVector() {
return std::vector<uint8_t>(StreamData(), StreamData() + StreamLength());
}
/***** opj_stream_mem_input 实现部分************************************************************/
/**
* @brief 构造函数。
* @param data 输入数据指针。
* @param size 输入数据大小。
*/
OpjStreamMemInput::OpjStreamMemInput(const void* data, size_t size)
: data_(reinterpret_cast<const uint8_t*>(data)), size_(size) {
if (data_ == nullptr) {
// 处理空指针的情况
start_ = nullptr;
cursor_ = nullptr;
last_ = nullptr;
}
else {
start_ = const_cast<uint8_t*>(data_);
cursor_ = start_;
last_ = start_ + size_;
}
}
/**
* @brief 从流中读取指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要读取的字节数。
* @return 实际读取的字节数。
*/
OPJ_SIZE_T OpjStreamMemInput::Read(void* p_buffer, OPJ_SIZE_T p_nb_bytes) const {
if (last_ > cursor_) {
auto len = std::min(static_cast<OPJ_SIZE_T>(last_ - cursor_), p_nb_bytes);
if (len > 0) {
std::memcpy(p_buffer, cursor_, len);
cursor_ += len;
return len;
}
}
return static_cast<OPJ_SIZE_T>(-1);
}
/**
* @brief 向流中写入指定长度的数据。
* @param p_buffer 数据缓冲区指针。
* @param p_nb_bytes 要写入的字节数。
* @return 实际写入的字节数。
*/
OPJ_SIZE_T OpjStreamMemInput::Write(void* p_buffer, OPJ_SIZE_T p_nb_bytes) {
// 输入流不能写入
return 0;
}
/**
* @brief 返回流内存数据地址。
* @return 内存数据地址。
*/
uint8_t* OpjStreamMemInput::StreamData() const {
return const_cast<uint8_t*>(data_);
}
/**
* @brief 判断流是否为只读流。
* @return 如果是只读流返回 true,否则返回 false。
*/
OPJ_BOOL OpjStreamMemInput::IsReadStream() const { return true; }
/***** opj_stream_mem_input 实现部分******* 为了适配 openjpeg的 c 接口************/
void OpjStreamInterfaceClose(OpjStreamInterface* stream_instance) {
stream_instance->Close();
}
OPJ_BOOL OpjStreamInterfaceSeek(OPJ_OFF_T p_nb_bytes, OpjStreamInterface* stream_instance) {
return stream_instance->Seek(p_nb_bytes);
}
OPJ_OFF_T OpjStreamInterfaceSkip(OPJ_OFF_T p_nb_bytes, OpjStreamInterface* stream_instance) {
return stream_instance->Skip(p_nb_bytes);
}
OPJ_SIZE_T OpjStreamInterfaceWrite(void* p_buffer, OPJ_SIZE_T p_nb_bytes, OpjStreamInterface* stream_instance) {
return stream_instance->Write(p_buffer, p_nb_bytes);
}
OPJ_SIZE_T OpjStreamInterfaceRead(void* p_buffer, OPJ_SIZE_T p_nb_bytes, OpjStreamInterface* stream_instance) {
return stream_instance->Read(p_buffer, p_nb_bytes);
}
opj_stream_t* OpjStreamCreateSi(OpjStreamInterface& stream, OPJ_SIZE_T p_size) {
opj_stream_t* l_stream = opj_stream_create(p_size, stream.IsReadStream());
if (l_stream) {
opj_stream_set_user_data_v3(l_stream, std::addressof(stream), (opj_stream_free_user_data_fn)(OpjStreamInterfaceClose));
opj_stream_set_user_data_length(l_stream, stream.StreamLength());
opj_stream_set_read_function(l_stream, (opj_stream_read_fn)(OpjStreamInterfaceRead));
opj_stream_set_write_function(l_stream, (opj_stream_write_fn)(OpjStreamInterfaceWrite));
opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)(OpjStreamInterfaceSkip));
opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)(OpjStreamInterfaceSeek));
return l_stream;
}
}
opj_stream_t* OpjStreamCreateDefaultSi(OpjStreamInterface& stream) {
return OpjStreamCreateSi(stream, OPJ_J2K_STREAM_CHUNK_SIZE);
}
/*******************************************开始使用上面的内存解码接口,解码图像**********************************/
/**
* @brief 枚举定义 JPEG2000 编解码格式
*
* 该枚举类定义了支持的不同 JPEG2000 编解码格式。
* - 包括 J2K、JP2、JPT 等标准和专用的 JPEG2000 格式。
*/
enum class FS_JPEG2K_CODEC_FORMAT : int {
FS_JPEG2K_CODEC_UNKNOWN = -1, /**< 未知格式,作为占位符 */
FS_JPEG2K_CODEC_J2K = 0, /**< JPEG-2000 codestream 格式(支持读写) */
FS_JPEG2K_CODEC_JP2 = 1, /**< JP2 文件格式(支持读写) */
FS_JPEG2K_CODEC_JPT = 2, /**< JPT 流格式(JPEG 2000, JPIP,仅支持读取) */
FS_JPEG2K_CODEC_JPX = 3, /**< JPX 文件格式(JPEG 2000 Part-2,仅待编码) */
FS_JPEG2K_CODEC_JPP = 4 /**< JPP 流格式(JPEG 2000, JPIP,待编码) */
};
/**
* @brief 错误回调函数
*
* 输出错误信息到标准错误流(stderr)。
*
* @param[in] msg 错误信息的消息字符串
* @param[in] client_data 客户端数据(目前未使用)
*/
void error_callback(const char* msg, void* client_data) {
fprintf(stderr, "[ERROR] %s\n", msg);
}
/**
* @brief 警告回调函数
*
* 输出警告信息到标准错误流(stderr)。
*
* @param[in] msg 警告信息的消息字符串
* @param[in] client_data 客户端数据(目前未使用)
*/
void warning_callback(const char* msg, void* client_data) {
fprintf(stderr, "[WARNING] %s\n", msg);
}
/**
* @brief 信息回调函数
*
* 输出普通信息到标准输出流(stdout)。
*
* @param[in] msg 信息消息的字符串
* @param[in] client_data 客户端数据(目前未使用)
*/
void info_callback(const char* msg, void* client_data) {
fprintf(stdout, "[INFO] %s\n", msg);
}
/**
* @brief 从图像数据中解码并返回解码后的 JPEG2000 图像结构。
*
* 此函数用于解码给定的 JPEG2000 图像数据,并返回解码后的图像结构。根据传入的图像格式,函数会选择合适的解码器进行解码操作。
* 支持的图像格式包括 J2K、JP2 和 JPT 格式。解码过程中,如果出现任何错误或图像格式不受支持,函数将返回空指针。
*
* @param[in] image_data 输入的 JPEG2000 图像数据,以字节向量的形式传入。
* @param[in] img_format 输入的图像格式,指定了解码器的类型。可以是以下值之一:
* - FS_JPEG2K_CODEC_J2K
* - FS_JPEG2K_CODEC_JP2
* - FS_JPEG2K_CODEC_JPT
*
* @return opj_image_t* 解码后的图像结构指针,成功时返回图像结构,失败时返回空指针。
* - 返回的图像结构包含了图像的所有基本信息,如宽度、高度、分辨率、颜色空间、图像数据等。
* - 如果图像解码失败或不支持的格式,将返回 `nullptr`。
*
* @note 函数内部将使用默认解码参数配置解码器,并设置相应的回调函数来处理信息、警告和错误。如果图像格式为 J2K、JP2 或 JPT,函数将执行解码操作。
* 若图像包含瓦片数据,函数会根据需要选择解码指定的瓦片。
*/
//这个函数用来解码JPEG2000格式的图像数据,并返回解码后的图像结构,如果解码失败则返回空指针。
static opj_image_t* GetImageStruct(std::vector<uint8_t>& image_data, FS_JPEG2K_CODEC_FORMAT img_format)
{
// 检查输入的图像数据是否为空
if (image_data.empty()) {
return nullptr; // 无数据可处理,直接返回空指针
}
// 从图像数据中初始化内存输入流
OpjStreamMemInput src(image_data.data(), image_data.size());
opj_dparameters_t parameters;
// 设置解码器的默认参数
opj_set_default_decoder_parameters(¶meters);
parameters.decod_format = static_cast<int>(img_format); // 设置解码格式
// 创建默认的流接口
auto l_stream = OpjStreamCreateDefaultSi(src);
// 检查支持的图像格式
if (img_format != FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_J2K &&
img_format != FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JP2 &&
img_format != FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JPT)
{
return nullptr; // 不支持的图像格式
}
// 创建解压缩编解码器
opj_codec_t* l_codec = opj_create_decompress(static_cast<OPJ_CODEC_FORMAT>(parameters.decod_format));
if (!l_codec) {
return nullptr; // 创建解码器失败
}
// 设置回调处理程序
opj_set_info_handler(l_codec, info_callback, nullptr);
opj_set_warning_handler(l_codec, warning_callback, nullptr);
opj_set_error_handler(l_codec, error_callback, nullptr);
// 配置解码器
if (!opj_setup_decoder(l_codec, ¶meters)) {
opj_destroy_codec(l_codec); // 解码器配置失败时释放资源
return nullptr;
}
opj_image_t* image = nullptr; // 用于存储解码后的图像
// 读取图像的头信息
if (!opj_read_header(l_stream, l_codec, &image)) {
opj_destroy_codec(l_codec); // 读取头信息失败时释放资源
return nullptr;
}
bool success = false;
// 根据是否指定了瓦片来选择解码方式
if (parameters.nb_tile_to_decode == 0) {
success = opj_set_decode_area(l_codec, image,
static_cast<OPJ_INT32>(parameters.DA_x0),
static_cast<OPJ_INT32>(parameters.DA_y0),
static_cast<OPJ_INT32>(parameters.DA_x1),
static_cast<OPJ_INT32>(parameters.DA_y1)) &&
opj_decode(l_codec, l_stream, image) &&
opj_end_decompress(l_codec, l_stream);
}
else {
success = opj_get_decoded_tile(l_codec, l_stream, image, parameters.tile_index);
}
// 清理编解码器并返回解码结果
opj_destroy_codec(l_codec);
return success ? image : nullptr; // 成功解码返回图像,失败返回空指针
}
/**
* @brief 获取 JPEG2000 图像的基本信息,并将图像数据存储为 RGB 格式。
*
* 此函数从 JPEG2000 图像数据中提取图像宽度、高度、通道数、每个通道的位深度、色彩空间等信息,并将图像数据转换为 RGB 格式。
* 若图像包含透明度通道(Alpha 通道),则可选择将其与白色背景进行混合。
*
* @param[out] image_data 输出图像数据(RGB 格式),即解码后的图像数据。
* @param[in] image_format 输入的图像格式,用于指定 JPEG2000 解码器的格式。
* @param[out] image_width 输出图像宽度(像素)。
* @param[out] image_height 输出图像高度(像素)。
* @param[out] image_channels 输出图像的通道数(通常为 3 或 4)。
* @param[out] image_bits_per_component 输出每个通道的位深度(通常为 8)。
* @param[out] image_colorspace 输出图像的色彩空间信息。
* @param[in] white_background 是否使用白色背景填充透明度(默认值为 false)。如果为 true,透明度将与白色背景进行混合。
*
* @return int 返回值:
* - 0:表示成功获取图像信息并解码图像。
* - -1:表示图像解码失败或图像组件不匹配。
*/
int GetJ2kImageInfo(std::vector<uint8_t>& image_data, int& image_format, int& image_width, int& image_height, int& image_channels, int& image_bits_per_component, int& image_colorspace, bool white_background)
{
auto opj_image_struct = GetImageStruct(image_data, static_cast<FS_JPEG2K_CODEC_FORMAT>(image_format));
if (!opj_image_struct || opj_image_struct->numcomps == 0) {
return -1;
}
// 校验每个组件的参数
auto w0 = opj_image_struct->comps[0].w;
auto h0 = opj_image_struct->comps[0].h;
auto prec0 = opj_image_struct->comps[0].prec;
for (int i = 1; i < opj_image_struct->numcomps; ++i) {
auto& comp = opj_image_struct->comps[i];
if (comp.w != w0 || comp.h != h0 || comp.prec > 8 || comp.bpp > 8) {
return -1; // 这里可以根据需求决定如何处理不匹配的情况
}
}
image_width = w0;
image_height = h0;
image_channels = opj_image_struct->numcomps;
image_bits_per_component = prec0;
image_colorspace = opj_image_struct->color_space;
// 如果通道数大于4,去除透明度通道
bool has_alpha = image_channels > 3;
if (has_alpha) {
image_channels = 3; // 只保留RGB通道
}
auto image_total_size = image_width * image_height * image_channels;
image_data.resize(image_total_size);
// 复制图像数据到向量
size_t index = 0;
for (size_t y = 0; y < image_height; ++y) {
for (size_t x = 0; x < image_width; ++x) {
if (has_alpha && white_background) {
// 获取透明度通道的值
uint8_t alpha = static_cast<uint8_t>(opj_image_struct->comps[3].data[y * image_width + x]);
for (size_t ch = 0; ch < 3; ++ch) {
uint8_t original_pixel = static_cast<uint8_t>(opj_image_struct->comps[ch].data[y * image_width + x]);
uint8_t white_background = 255; // 白色背景
uint8_t mixed_pixel = (original_pixel * alpha + white_background * (255 - alpha)) / 255;
image_data[index++] = mixed_pixel;
}
}
else {
for (size_t ch = 0; ch < 3; ++ch) {
image_data[index++] = static_cast<uint8_t>(opj_image_struct->comps[ch].data[y * image_width + x]);
}
}
}
}
return 0;
}
/**
* @brief 创建并初始化用于编码 RGB 图像的 JPEG 2000 编码参数。
*
* 该函数根据传入的图像质量和图像格式设置 JPEG 2000 编码器的默认参数,
* 并根据用户指定的质量参数调整编码器的行为。
*
* @param image_quality 图像的压缩质量(范围:0-100),越高代表质量越好,文件越大。
* 如果值大于 100,会将其限制为 100。
* @param image_format 要编码的图像格式。支持 JPEG-2000 格式的不同变种(如 J2K、JP2 等)。
*
* @return 返回一个包含编码参数的 `opj_cparameters_t` 智能指针。该指针需要在编码过程中传递给编码器。
*/
//这个函数用来编码 RGB 像素 为 JP2格式的图像
std::unique_ptr<opj_cparameters_t> CreateEncoderParameters(int image_quality, FS_JPEG2K_CODEC_FORMAT image_format) {
auto parameters = std::make_unique<opj_cparameters_t>();
opj_set_default_encoder_parameters(parameters.get());
// 设置编码层数为 1
parameters->tcp_numlayers = 1;
// 设置失真比为给定的图像质量,确保最大值不超过 100
parameters->tcp_distoratio[0] = static_cast<float>(std::min(image_quality, 100));
// 设置固定质量编码模式
parameters->cp_fixed_quality = 1;
// 设置编码格式(如 J2K、JP2 等)
parameters->cod_format = static_cast<int>(image_format);
return parameters;
}
/**
* @brief 构建一个 OpenJPEG 图像结构(opj_image_t)。
*
* 该函数接受一个包含 RGB 图像像素数据的向量,并根据给定的宽度、高度和编码参数
* 构建一个 OpenJPEG 图像结构。图像的颜色空间设置为 SRGB,且每个通道的精度和位深度
* 都设置为 8 位。图像数据被逐行处理并填充到 OpenJPEG 图像结构的组件中。
*
* @param source_image 输入的 RGB 图像数据,格式为 8 位每通道的顺序数据。
* @param width 图像的宽度(像素)。
* @param height 图像的高度(像素)。
* @param parameters 编码器的参数,包含图像的偏移量、子采样率等信息。
*
* @return 返回构建好的 OpenJPEG 图像结构(opj_image_t*),如果参数无效或创建失败,则返回 nullptr。
*/
// 构建图像,填充图像组件数据
opj_image_t* ConstructImage(const std::vector<uint8_t>& source_image, int width, int height, const opj_cparameters_t* parameters) {
// 检查参数是否为空,若为空则返回 nullptr
if (!parameters) {
return nullptr;
}
// 定义图像的颜色空间为 SRGB
const OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_SRGB;
// 定义 RGB 图像的通道数为 3
const int channels = 3;
// 创建并初始化一个大小为 3 的 opj_image_cmptparm_t 向量来保存每个图像组件的参数
std::vector<opj_image_cmptparm_t> cmptparm(channels);
// 填充每个组件的参数
for (int i = 0; i < channels; ++i) {
// 设置每个组件的精度为 8 位
cmptparm[i].prec = 8;
// 设置每个组件的位深度为 8
cmptparm[i].bpp = 8;
// 设置每个组件的符号为 0(无符号)
cmptparm[i].sgnd = 0;
// 设置子采样率在 X 方向上的步长
cmptparm[i].dx = static_cast<OPJ_UINT32>(parameters->subsampling_dx);
// 设置子采样率在 Y 方向上的步长
cmptparm[i].dy = static_cast<OPJ_UINT32>(parameters->subsampling_dy);
// 设置组件的宽度
cmptparm[i].w = static_cast<OPJ_UINT32>(width);
// 设置组件的高度
cmptparm[i].h = static_cast<OPJ_UINT32>(height);
}
// 使用创建的图像组件参数,创建一个新的图像对象
opj_image_t* image = opj_image_create(static_cast<OPJ_UINT32>(channels), cmptparm.data(), color_space);
// 如果图像创建失败,返回 nullptr
if (!image) {
return nullptr;
}
// 设置图像的偏移量和边界
image->x0 = static_cast<OPJ_UINT32>(parameters->image_offset_x0);
image->y0 = static_cast<OPJ_UINT32>(parameters->image_offset_y0);
// 计算图像的右下角坐标
image->x1 = image->x0 + (width - 1) * parameters->subsampling_dx + 1;
image->y1 = image->y0 + (height - 1) * parameters->subsampling_dy + 1;
// 计算每行像素数据的跨度
const int row_stride = width * channels; // 对于 RGB 图像,每行数据的大小
// 索引变量,用来遍历每个像素的数据
int index = 0;
// 遍历图像的每一行
for (int y = 0; y < height; ++y) {
// 获取当前行的起始地址
const uint8_t* scanline = source_image.data() + y * row_stride;
// 遍历当前行的每一个像素
for (int x = 0; x < width; ++x) {
// 获取当前像素的 RGB 数据
const uint8_t* pixel = scanline + x * channels;
// 将 RGB 数据复制到图像的组件中
for (int ch = 0; ch < channels; ++ch) {
image->comps[ch].data[index] = static_cast<OPJ_INT32>(pixel[ch]);
}
// 增加索引,处理下一个像素
++index;
}
}
// 返回构造好的图像对象
return image;
}
/**
* @brief 将输入的 RGB 图像压缩为 JP2 格式。
*
* 该函数接受 RGB 图像数据,并根据给定的质量和格式将其压缩为 JP2(JPEG 2000)格式。
* 压缩过程包括初始化编码参数、构建图像结构、设置编码器、创建流并执行压缩操作。
*
* @param source_image 输入的 RGB 图像数据。
* @param width 图像的宽度(像素)。
* @param height 图像的高度(像素)。
* @param quality 压缩质量,取值范围为 1 到 100。
* @param format 压缩格式,指定 JPEG 2000 格式类型。
* @param target_image 输出的压缩后 JP2 格式图像数据。
*/
void CompressToJp2(const std::vector<uint8_t>& source_image, const int& width, const int& height,
const int& quality, const int& format, std::vector<uint8_t>& target_image) {
// 创建内存输出流,用于存储压缩后的图像数据
OpjStreamMemOutput dest;
// 创建编码器参数
auto parameters = CreateEncoderParameters(quality, static_cast<FS_JPEG2K_CODEC_FORMAT>(format));
if (!parameters) {
// 错误处理: 参数初始化失败
return;
}
// 构建 OpenJPEG 图像结构
opj_image_t* image = ConstructImage(source_image, width, height, parameters.get());
if (!image) {
// 错误处理: 图像构建失败
return;
}
// 设置 JP2 格式的注释信息
std::unique_ptr<char[]> comment;
if (!parameters->cp_comment) {
// 如果没有设置注释,创建默认注释
const char comment_template[] = "Created by OpenJPEG version ";
const std::string version = opj_version(); // 获取 OpenJPEG 版本
comment = std::make_unique<char[]>(strlen(comment_template) + version.size() + 1);
sprintf(comment.get(), "%s%s", comment_template, version.c_str());
parameters->cp_comment = comment.get(); // 将注释信息设置到参数中
}
// 创建 JPEG 2000 编码器
opj_codec_t* codec = opj_create_compress(static_cast<CODEC_FORMAT>(parameters->cod_format));
if (!codec) {
// 错误处理: 编码器创建失败
opj_image_destroy(image);
return;
}
// 设置编码器的回调函数
opj_set_info_handler(codec, info_callback, nullptr);
opj_set_warning_handler(codec, warning_callback, nullptr);
opj_set_error_handler(codec, error_callback, nullptr);
// 设置编码器并创建流
opj_setup_encoder(codec, parameters.get(), image);
opj_stream_t* stream = OpjStreamCreateDefaultSi(dest);
if (!stream) {
// 错误处理: 流创建失败
opj_destroy_codec(codec);
opj_image_destroy(image);
return;
}
// 开始图像编码,压缩图像数据
bool success = opj_start_compress(codec, image, stream) &&
opj_encode(codec, stream) &&
opj_end_compress(codec, stream);
if (!success) {
// 错误处理: 编码失败
}
// 清理资源
opj_stream_destroy(stream); // 销毁流
opj_destroy_codec(codec); // 销毁编码器
opj_image_destroy(image); // 销毁图像
// 将压缩后的图像数据存储到目标输出
target_image = std::move(dest.AsVector());
}
#include
#include
#include
#include
#include
//#include
#include
class JPEG2000Manager {
public:
// 图像元数据结构
struct JPEG2000ImageInfo {
uint32_t width;
uint32_t height;
uint16_t componentCount; // 颜色分量数 (1=灰度, 3=RGB, 4=RGBA)
uint8_t bitsPerSample; // 每个样本的位数 (通常8或16)
bool isSigned; // 样本是否带符号
std::string colorSpace; // 色彩空间描述
std::string compression; // 压缩方式描述
};
// 构造函数:初始化编码器
JPEG2000Manager() = default;
// 编码RGB数据为JPEG 2000格式
std::vector<uint8_t> EncodeToJPEG2000(
const uint8_t* rgbData,
int width,
int height,
int quality = 80,
FS_JPEG2K_CODEC_FORMAT format = FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JP2
) {
// 1. 验证输入参数
if (!rgbData || width <= 0 || height <= 0) {
throw std::invalid_argument("Invalid image parameters");
}
// 2. 准备编码参数
auto parameters = CreateEncoderParameters(quality, format);
// 3. 构建OpenJPEG图像结构
std::vector<uint8_t> sourceImage(rgbData, rgbData + width * height * 3);
opj_image_t* image = ConstructImage(sourceImage, width, height, parameters.get());
if (!image) {
throw std::runtime_error("Failed to construct image");
}
// 4. 创建内存输出流
OpjStreamMemOutput outputStream;
// 5. 执行编码
bool success = PerformEncoding(image, parameters.get(), outputStream);
// 6. 清理资源
opj_image_destroy(image);
if (!success) {
throw std::runtime_error("JPEG 2000 encoding failed");
}
// 7. 返回编码后的数据
return outputStream.AsVector();
}
/**
* @brief 解码 JPEG2000 图像并获取元数据
* @param j2kData JPEG2000 压缩数据指针
* @param dataSize 数据大小
* @param[out] output 输出的RGB/RGBA像素数据
* @param whiteBackground 是否用白色背景混合透明通道
* @return JPEG2000ImageInfo 包含图像元数据的结构体
* @throws std::runtime_error 解码失败时抛出
*/
JPEG2000ImageInfo DecodeImage(
const uint8_t* j2kData,
size_t dataSize,
std::vector<uint8_t>& output,
bool whiteBackground = false
) {
// 1. 自动检测格式
auto format = DetectFormat(j2kData, dataSize);
// 2. 解码图像
auto image = DecodeToImageStruct(j2kData, dataSize, format);
if (!image) {
throw std::runtime_error("Failed to decode JPEG2000 image");
}
// 3. 提取元数据
JPEG2000ImageInfo info = ExtractImageInfo(image);
// 4. 转换为RGB/RGBA
output = ConvertImageData(image, whiteBackground);
// 5. 清理资源
opj_image_destroy(image);
return info;
}
/**
* @brief 获取 JPEG2000 图像的元数据信息
*
* 该函数通过解析 JPEG2000 压缩数据,提取图像的宽度、高度、通道数、位深度等元数据信息。
* 函数内部会自动检测图像格式(JP2/J2K),并仅解码头部信息以提高性能。
*
* @param[in] j2kData JPEG2000 压缩数据指针
* @param[in] dataSize 压缩数据长度(字节数)
* @return JPEG2000ImageInfo 包含完整图像元数据的结构体
**
**/
JPEG2000ImageInfo GetImageInfo(
const uint8_t* j2kData,
size_t dataSize
) {
// 1. 自动检测格式
auto format = DetectFormat(j2kData, dataSize);
// 2. 解码图像
auto image = DecodeToImageStruct(j2kData, dataSize, format);
if (!image) {
throw std::runtime_error("Failed to decode JPEG2000 image");
}
// 3. 提取元数据
JPEG2000ImageInfo info = ExtractImageInfo(image);
// 4. 清理资源
opj_image_destroy(image);
return info;
}
private:
// 边界保护函数
int clamp(int value, int min, int max) {
return (std::max)(min, (std::min)(value, max));
}
// 创建编码参数
std::unique_ptr<opj_cparameters_t> CreateEncoderParameters(int quality, FS_JPEG2K_CODEC_FORMAT format) {
auto params = std::make_unique<opj_cparameters_t>();
opj_set_default_encoder_parameters(params.get());
params->tcp_numlayers = 1;
params->tcp_distoratio[0] = static_cast<float>(clamp(quality, 1, 100));
params->cp_fixed_quality = 1;
params->cod_format = static_cast<int>(format);
return params;
}
// 构建OpenJPEG图像结构
opj_image_t* ConstructImage(const std::vector<uint8_t>& source, int width, int height,
const opj_cparameters_t* params) {
const OPJ_COLOR_SPACE colorspace = OPJ_CLRSPC_SRGB;
const int channels = 3;
// 设置组件参数
std::vector<opj_image_cmptparm_t> cmptparm(channels);
for (int i = 0; i < channels; ++i) {
cmptparm[i] = {
static_cast<OPJ_UINT32>(params->subsampling_dx), // dx
static_cast<OPJ_UINT32>(params->subsampling_dy), // dy
static_cast<OPJ_UINT32>(width), // w
static_cast<OPJ_UINT32>(height), // h
static_cast<OPJ_UINT32>(params->image_offset_x0), // x0
static_cast<OPJ_UINT32>(params->image_offset_y0), // y0
8, // prec (精度)
8, // bpp (位深度)
0 // sgnd (无符号)
};
}
// 创建图像
opj_image_t* image = opj_image_create(channels, cmptparm.data(), colorspace);
if (!image) return nullptr;
// 设置图像边界
image->x0 = params->image_offset_x0;
image->y0 = params->image_offset_y0;
image->x1 = image->x0 + (width - 1) * params->subsampling_dx + 1;
image->y1 = image->y0 + (height - 1) * params->subsampling_dy + 1;
// 填充图像数据
const int row_stride = width * channels;
for (int y = 0, idx = 0; y < height; ++y) {
const uint8_t* row = source.data() + y * row_stride;
for (int x = 0; x < width; ++x, ++idx) {
const uint8_t* pixel = row + x * channels;
for (int ch = 0; ch < channels; ++ch) {
image->comps[ch].data[idx] = pixel[ch];
}
}
}
return image;
}
// 执行编码过程
bool PerformEncoding(opj_image_t* image, opj_cparameters_t* params, OpjStreamMemOutput& output) {
// 创建编码器
opj_codec_t* codec = opj_create_compress(static_cast<OPJ_CODEC_FORMAT>(params->cod_format));
if (!codec) return false;
// 设置回调
opj_set_info_handler(codec, [](const char* msg, void*) {
std::cout << "[INFO] " << msg << std::endl;
}, nullptr);
opj_set_warning_handler(codec, [](const char* msg, void*) {
std::cerr << "[WARN] " << msg << std::endl;
}, nullptr);
opj_set_error_handler(codec, [](const char* msg, void*) {
std::cerr << "[ERROR] " << msg << std::endl;
}, nullptr);
// 配置编码器
if (!opj_setup_encoder(codec, params, image)) {
opj_destroy_codec(codec);
return false;
}
// 创建流
opj_stream_t* stream = OpjStreamCreateDefaultSi(output);
if (!stream) {
opj_destroy_codec(codec);
return false;
}
// 执行编码
bool success = opj_start_compress(codec, image, stream) &&
opj_encode(codec, stream) &&
opj_end_compress(codec, stream);
// 清理资源
opj_stream_destroy(stream);
opj_destroy_codec(codec);
return success;
}
// 内部解码实现
opj_image_t* DecodeToImageStruct(const uint8_t* data, size_t size, OPJ_CODEC_FORMAT format) {
OpjStreamMemInput stream(data, size);
auto l_stream = OpjStreamCreateDefaultSi(stream);
opj_dparameters_t params;
opj_set_default_decoder_parameters(¶ms);
auto codec = opj_create_decompress(format);
opj_set_error_handler(codec, [](const char* msg, void*) {
throw std::runtime_error(msg);
}, nullptr);
opj_image_t* image = nullptr;
if (!opj_read_header(l_stream, codec, &image) || !image) {
opj_destroy_codec(codec);
throw std::runtime_error("Header read failed");
}
if (!opj_decode(codec, l_stream, image)) {
opj_image_destroy(image);
opj_destroy_codec(codec);
throw std::runtime_error("Decoding failed");
}
opj_destroy_codec(codec);
return image;
}
// 格式检测
OPJ_CODEC_FORMAT DetectFormat(const uint8_t* data, size_t size) {
if (size > 12 && memcmp(data + 4, "\x6A\x50\x20\x20", 4) == 0) {
return OPJ_CODEC_JP2; // JP2格式
}
if (size > 4 && memcmp(data, "\xFF\x4F\xFF\x51", 4) == 0) {
return OPJ_CODEC_J2K; // J2K格式
}
throw std::runtime_error("Unrecognized JPEG2000 format");
}
// 元数据提取
JPEG2000ImageInfo ExtractImageInfo(opj_image_t* image) {
JPEG2000ImageInfo info;
info.width = image->comps[0].w;
info.height = image->comps[0].h;
info.componentCount = image->numcomps;
info.bitsPerSample = image->comps[0].prec;
info.isSigned = image->comps[0].sgnd;
info.colorSpace = ColorSpaceToString(image->color_space);
return info;
}
// 色彩空间描述
std::string ColorSpaceToString(OPJ_COLOR_SPACE space) {
switch (space) {
case OPJ_CLRSPC_SRGB: return "sRGB";
case OPJ_CLRSPC_GRAY: return "Grayscale";
case OPJ_CLRSPC_SYCC: return "YCC";
default: return "Unknown";
}
}
// 数据转换
std::vector<uint8_t> ConvertImageData(opj_image_t* image, bool whiteBg) {
const int width = image->comps[0].w;
const int height = image->comps[0].h;
const bool hasAlpha = image->numcomps >= 4;
const int outChannels = hasAlpha ? 4 : 3;
std::vector<uint8_t> pixels(width * height * outChannels);
size_t idx = 0;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
const size_t pos = y * width + x;
// 处理RGB通道
for (int c = 0; c < 3; ++c) {
int val = image->comps[c].data[pos];
if (hasAlpha && whiteBg) {
uint8_t alpha = image->comps[3].data[pos];
val = (val * alpha + 255 * (255 - alpha)) / 255;
}
pixels[idx++] = static_cast<uint8_t>(val);
}
// 处理Alpha通道
if (hasAlpha) {
pixels[idx++] = static_cast<uint8_t>(image->comps[3].data[pos]);
}
}
}
return pixels;
}
};
//
//// 使用示例
//int main() {
// try {
// // 准备测试图像数据 (8x8 RGB)
// const int width = 8, height = 8;
// std::vector testImage(width * height * 3);
// for (int y = 0; y < height; y++) {
// for (int x = 0; x < width; x++) {
// int idx = (y * width + x) * 3;
// testImage[idx] = x * 32; // R
// testImage[idx + 1] = y * 32; // G
// testImage[idx + 2] = 128; // B
// }
// }
//
// // 编码为JPEG 2000
// JPEG2000Encoder encoder;
// auto jp2Data = encoder.encodeToJPEG2000(
// testImage.data(), width, height,
// 85, FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JP2
// );
//
// std::cout << "Encoded JPEG 2000 size: " << jp2Data.size() << " bytes\n";
//
// // 保存到文件
// std::ofstream out("output.jp2", std::ios::binary);
// out.write(reinterpret_cast(jp2Data.data()), jp2Data.size());
//
// }
// catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return 1;
// }
//
// return 0;
//}
/**
* @brief 将 PDFCore 格式枚举安全转换为标准格式枚举
*
* 该函数提供从 PDFCore 内部格式到标准 JPEG 2000 格式的单向转换,
* 确保格式兼容性并处理所有可能的输入值。
*
* @param pdfCoreFormat PDFCore 内部格式枚举值
* @return 对应的标准格式枚举值
*
* @throws std::invalid_argument 当输入为无效枚举值时抛出
*
* @note 转换是单向的,不支持反向转换
* @warning 对于 PDFCore_FS_JPEG2K_CODEC_UNKNOWN 会转换为 FS_JPEG2K_CODEC_UNKNOWN
*
* 转换规则:
* - 相同数值的枚举直接转换
* - 未知格式保持 UNKNOWN
* - 其他无效值抛出异常
*/
FS_JPEG2K_CODEC_FORMAT static ConvertPDFCoreToStandardFormat(
PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT pdfCoreFormat)
{
switch (pdfCoreFormat) {
// 直接对应转换
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_UNKNOWN:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_UNKNOWN;
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_J2K:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_J2K;
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_JP2:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JP2;
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_JPT:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JPT;
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_JPX:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JPX;
case PDFCoreJPEG2000Manager::PDFCore_FS_JPEG2K_CODEC_FORMAT::PDFCore_FS_JPEG2K_CODEC_JPP:
return FS_JPEG2K_CODEC_FORMAT::FS_JPEG2K_CODEC_JPP;
// 处理意外值
default:
throw std::invalid_argument(
"Invalid PDFCore JPEG2000 format value: " +
std::to_string(static_cast<int>(pdfCoreFormat))
);
}
}
std::vector<uint8_t> PDFCoreJPEG2000Manager::EncodeToJPEG2000(const uint8_t* rgbData, int width, int height, int quality, PDFCore_FS_JPEG2K_CODEC_FORMAT format)
{
FS_JPEG2K_CODEC_FORMAT inner_format = ConvertPDFCoreToStandardFormat(format);
JPEG2000Manager encoder;
auto jp2Data = encoder.EncodeToJPEG2000(
rgbData, width, height,
quality, inner_format
);
}
std::vector<uint8_t> PDFCoreJPEG2000Manager::DecodeToRGB(const uint8_t* j2kData, size_t dataSize, uint32_t& width, uint32_t& height, uint16_t& componentCount, uint8_t& bitsPerSample, bool& isSigned, std::string& colorSpace, std::string& compression,uint8_t requestComponents)
{
JPEG2000Manager jpeg2k_manager;
std::vector<uint8_t> rgb_output;
JPEG2000Manager::JPEG2000ImageInfo jpeg2k_info;
jpeg2k_info = jpeg2k_manager.DecodeImage(j2kData,dataSize, rgb_output);
// 填充输出参数
width = jpeg2k_info.width;
height = jpeg2k_info.height;
componentCount = jpeg2k_info.componentCount;
bitsPerSample = jpeg2k_info.bitsPerSample;
isSigned = jpeg2k_info.isSigned;
colorSpace = jpeg2k_info.colorSpace;
compression = jpeg2k_info.compression;
}
void PDFCoreJPEG2000Manager::GetImageInfo(const uint8_t* j2kData, size_t dataSize, uint32_t& width, uint32_t& height, uint16_t& componentCount, uint8_t& bitsPerSample, bool& isSigned, std::string& colorSpace, std::string& compression)
{
JPEG2000Manager jpeg2k_manager;
jpeg2k_manager.GetImageInfo(j2kData, dataSize);
JPEG2000Manager::JPEG2000ImageInfo jpeg2k_info;
// 填充输出参数
width = jpeg2k_info.width;
height = jpeg2k_info.height;
componentCount = jpeg2k_info.componentCount;
bitsPerSample = jpeg2k_info.bitsPerSample;
isSigned = jpeg2k_info.isSigned;
colorSpace = jpeg2k_info.colorSpace;
compression = jpeg2k_info.compression;
}