封装 libjpeg 库
直接使用 libjpeg 库提供的 API 进行图像的编码或解码,总有诸多的不便。这几天把以前使用的代码,好好整理了一翻,封装成一套简单易用的 C/C++ API 公布出来,便于自己以后使用的同时,也希望能帮到他人。
当前封装的 API 代码,只有 XJPEG_wrapper.h 和 XJPEG_wrapper.c 两个文件,其主要实现 RGB 格式的图像进行 编码 或 压缩。以后可能会增加 灰度、YCbCr、CMYK、YCCK 等格式的支持。下面介绍如何使用这套 API。
1. 主要数据类型与枚举常量
-
JPEG 编码/解码 所操作的图像像素格式。
/** * @enum jctrl_color_space_t * @brief JPEG 编码/解码 所操作的图像像素格式。 * @note * 枚举值的 比特位 功能,定义如下: * - [ 0 ~ 7 ] : 用于表示每个像素所占的比特数,如 24,32 等值; * - [ 8 ~ 15 ] : 用于标识小分类的类型,如区别 RGB24 与 BGR24; * - [ 16 ~ 23 ] : 用于区分颜色种类,如 RGB,YUV 等。 * - [ 24 ~ 31 ] : 暂未使用。 */ typedef enum jctrl_color_space_t { JCTRL_CS_UNKNOW = 0x00000000, ///< 未知格式 JCTRL_CS_RGB = 0x00010118, ///< RGB 24位 格式,顺序为 RGB JCTRL_CS_BGR = 0x00010218, ///< RGB 24位 格式,顺序为 BGR JCTRL_CS_RGBA = 0x00010120, ///< RGB 32位 格式,顺序为 RGBA,编码时忽略 ALPHA 通道值 JCTRL_CS_BGRA = 0x00010220, ///< RGB 32位 格式,顺序为 BGRA,编码时忽略 ALPHA 通道值 JCTRL_CS_ARGB = 0x00010320, ///< RGB 32位 格式,顺序为 ARGB,编码时忽略 ALPHA 通道值 JCTRL_CS_ABGR = 0x00010420, ///< RGB 32位 格式,顺序为 ABGR,编码时忽略 ALPHA 通道值 } jctrl_color_space_t;
-
JPEG 编码/解码 的操作模式。
/** * @enum jctrl_mode_t * @brief JPEG 编码/解码 的操作模式。 */ typedef enum jctrl_mode_t { JCTRL_MODE_UNKNOW, ///< 未知模式 JCTRL_MODE_MEM , ///< 内存模式 JCTRL_MODE_FIO , ///< 文件流模式 JCTRL_MODE_FILE , ///< 文件模式 } jctrl_mode_t;
-
JPEG 图像基本信息结构体,以及重定义 libjpeg 内部支持的色彩空间枚举值。
/** * @struct jpeg_info_t * @brief JPEG 图像基本信息。 */ typedef struct jpeg_info_t { j_int_t jit_width; ///< nominal image width (from SOF marker) j_int_t jit_height; ///< nominal image height j_int_t jit_channels; ///< # of color components in JPEG image j_int_t jit_cstype; ///< colorspace of JPEG image ( @see jpeg_color_space_t ) } jpeg_info_t, * jinfo_ptr_t; /** * @enum jpeg_color_space_t * @brief JPEG 图像所支持的像素格式。 */ typedef enum jpeg_color_space_t { JPEG_CS_UNKNOWN , ///< error/unspecified JPEG_CS_GRAYSCALE, ///< monochrome JPEG_CS_RGB , ///< red/green/blue, standard RGB (sRGB) JPEG_CS_YCbCr , ///< Y/Cb/Cr (also known as YUV), standard YCC JPEG_CS_CMYK , ///< C/M/Y/K JPEG_CS_YCCK , ///< Y/Cb/Cr/K JPEG_CS_BG_RGB , ///< big gamut red/green/blue, bg-sRGB JPEG_CS_BG_YCC , ///< big gamut Y/Cb/Cr, bg-sYCC } jpeg_color_space_t;
-
其他的,还有 编码器对象指针 jenc_ctxptr_t,解码器对象指针 jdec_ctxptr_t,以及重定义了一部分基本数据类型,如下:
#define J_NULL 0 #define J_FALSE 0 #define J_TRUE 1 typedef void j_void_t; typedef int j_int_t; typedef long j_long_t; typedef unsigned int j_uint_t; typedef unsigned long j_ulong_t; typedef unsigned char j_uchar_t; typedef unsigned int j_bool_t; typedef size_t j_size_t; typedef fpos_t j_fpos_t; typedef void * j_handle_t; typedef const char * j_cstring_t; typedef unsigned char * j_mem_t; typedef FILE * j_fio_t; typedef const char * j_path_t;
2. 编码操作
这里说的 JPEG 编码,是指将原始的 RGB 像素数据,压缩成 JPEG 数据流。这过程,是以 RGB 数据作为数据 输入源,JPEG 数据流则是 输出源。
RGB数据输入源,当下支持有 6 种,即 jctrl_color_space_t 中提到的 JCTRL_CS_RGB、JCTRL_CS_BGR、JCTRL_CS_RGBA、JCTRL_CS_BGRA、JCTRL_CS_ARGB、JCTRL_CS_ABGR 。
而输出的 JPEG 数据,当下只支持 JPEG_CS_RGB。也就是说,不支持编码成其他的色彩空间(libjpeg内是支持的),在以后的代码中,会考虑增加色彩转换的功能,如 RGB 转 灰度、RGB 转 YCbCr 等。
JPEG 编码操作,必要的步骤如下:
-
使用 jenc_alloc() 申请编码器对象。
jenc_ctxptr_t jenc_cptr = jenc_alloc(J_NULL);
-
使用 jenc_config_dst() 设置解码输出源,即编码后的 JPEG 数据存放位置。
/**********************************************************/ /** * @brief 配置 JPEG 编码操作的目标输出源信息。 * * @param [in ] jenc_cptr : JPEG 编码操作的上下文对象。 * @param [in ] jit_mode : JPEG 编码输出模式(参看 jctrl_mode_t )。 * * @param [in ] jht_optr : 指向输出源的操作信息。 * - 内存模式,jht_optr 的类型为 j_mem_t ,其为输出 JPEG 数据的缓存地址, * 若 ((J_NULL == jht_optr) || (0 == jut_mlen)) 时,则取内部缓存地址; * - 文件流模式,jht_optr 的类型为 j_fio_t ,其为输出 JPEG 数据的文件流; * - 文件模式,jht_optr 的类型为 j_path_t ,其为输出 JPEG 数据的文件路径。 * * @param [in ] jut_mlen : 只针对于 内存模式,表示缓存容量。 * * @return j_int_t : 错误码,请参看 jenc_errno_table_t 相关枚举值。 */ j_int_t jenc_config_dst( jenc_ctxptr_t jenc_cptr, j_int_t jit_mode, j_handle_t jht_optr, j_uint_t jut_mlen);
-
使用 jenc_rgb_to_dst() 执行 RGB 数据的编码操作。
/**********************************************************/ /** * @brief 将 RGB 图像数据编码成 JPEG 数据流,输出至所配置的输出源中。 * @note 调用该接口前,必须使用 jenc_config_dst() 配置好输出源信息。 * * @param [in ] jenc_cptr : JPEG 编码操作的上下文对象。 * @param [in ] jmem_iptr : RGB 图像数据 缓存。 * @param [in ] jit_stride : RGB 图像数据 像素行 步进大小。 * @param [in ] jit_width : RGB 图像数据 宽度。 * @param [in ] jit_height : RGB 图像数据 高度。 * @param [in ] jit_ctrlcs : 图像色彩空间的转换方式(参看 jenc_ctrlcs_t)。 * * @return j_int_t : * - 操作失败时,返回值 < 0,表示 错误码,参看 jenc_errno_table_t 相关枚举值。 * - 操作成功时: * 1. 使用 文件流模式 或 文件模式 时,返回值 == JENC_ERR_OK; * 2. 使用 内存模式 时,返回值 > 0,表示输出源中存储的 JPEG 数据的有效字节数; * 3. 使用 内存模式 时,且 返回值 == JENC_ERR_OK,表示输出源的缓存容量不足, * 而输出的 JPEG 编码数据存储在 上下文对象 jenc_cptr 的缓存中,后续可通过 * jenc_cached_data() 和 jenc_cached_size() 获取此次编码得到的 JPEG 数据。 */ j_int_t jenc_rgb_to_dst( jenc_ctxptr_t jenc_cptr, j_mem_t jmem_iptr, j_int_t jit_stride, j_int_t jit_width, j_int_t jit_height, j_int_t jit_ctrlcs);
JPEG 编码操作,还可通过事先调用 jenc_set_quality() 设置 JPEG 的压缩质量。
-
得到 JPEG 压缩后的数据。
在 步骤 2 中,使用 内存模式 时,且设置接收 JPEG 编码后的数据流缓存大小 不足,此时,步骤 4 编码操作后的结果(JPEG数据)存放在编码器内部的缓存中,可通过如下方式得到数据:
j_mem_t jmem_optr = jenc_cached_data(jenc_cptr); j_uint_t jut_msize = jenc_cached_size(jenc_cptr);
而若 步骤 2 配置的输出源为 文件流模式(FILE * 指针) 或 文件模式(文件存储路径名) ,则不会出现输出数据缓存不足的情况。
-
使用 jenc_release() 释放编码器对象。
可重复 2 -> 3 -> 4 步骤继续进行其他图像的编码操作,直至退出(或 不再需要编码数据)时,必须使用 jenc_release() 释放编码器对象。
jenc_release(jenc_cptr); jenc_cptr = J_NULL;
以上所提及的 5 个步骤,在测试程序 test.cpp 代码中,enc_mode_mem()、enc_mode_fio()、enc_mode_file() 三个函数的流程,全部体现出来。
3. 解码操作
解码操作,指的是将 JPEG 编码的数据流解码到 RGB 图像缓存中。而现在的 API 版本,只支持 RGB 色彩空间的 JPEG 数据解码操作,以后的版本中,会增加其他 色彩空间 的解码功能。
解码输出的 RGB 数据,同样支持 6 种色彩空间,请参看 jctrl_color_space_t 中的相关枚举值。
JPEG 的解码操作,则是以 JPEG 数据源作为 输入源,而 RGB 缓存成了数据 输出源 ,必要的操作步骤如下:
-
使用 jdec_alloc() 申请解码器对象。
jdec_ctxptr_t jdec_cptr = jdec_alloc(J_NULL);
-
使用 jdec_config_src() 配置待解码的 JPEG 图像源。
/**********************************************************/ /** * @brief 配置 JPEG 编码操作的数据输入源信息。 * * @param [in ] jenc_cptr : JPEG 编码操作的上下文对象。 * @param [in ] jit_mode : JPEG 编码输入模式(参看 jctrl_mode_t )。 * * @param [in ] jht_optr : 指向输入源的操作信息。 * - 内存模式,jht_dst 的类型为 j_mem_t ,其为输入 JPEG 数据的缓存地址; * - 文件流模式,jht_dst 的类型为 j_fio_t ,其为输入 JPEG 数据的文件流; * - 文件模式,jht_dst 的类型为 j_path_t ,其为输入 JPEG 数据的文件路径。 * * @param [in ] jut_size : 只针对于 内存模式,表示缓存中的有效字节数。 * * @return j_int_t : 错误码,请参看 jdec_errno_table_t 相关枚举值。 */ j_int_t jdec_config_src( jdec_ctxptr_t jdec_cptr, j_int_t jit_mode, j_handle_t jht_iptr, j_uint_t jut_size);
-
可先使用 jdec_src_info() 获取 JPEG 图像源的基本信息,得知图像的宽高,并判断是否为 RGB 色彩空间。
/**********************************************************/ /** * @brief 获取 JPEG 输入源中的图像基本信息。 * @note 调用该接口前,必须使用 jdec_config_src() 配置好输入源。 * * @param [in ] jdec_cptr : JPEG 解码操作的上下文对象。 * @param [out] jinfo_ptr : 操作成功返回的图像基本信息。 * * @return j_int_t : 错误码(参看 jdec_errno_table_t 相关枚举值)。 */ j_int_t jdec_src_info( jdec_ctxptr_t jdec_cptr, jinfo_ptr_t jinfo_ptr);
-
使用 jdec_src_to_rgb() 进行解码操作。
/**********************************************************/ /** * @brief 将 JPEG 输入源图像 解码成 RGB 图像像素数据。 * @note 调用该接口前,必须使用 jdec_config_src() 配置好输入源。 * * @param [in ] jdec_cptr : JPEG 解码操作的上下文对象。 * @param [out] jmem_optr : 输出的 RGB 图像像素数据 缓存。 * * @param [in ] jit_stride : * 输出的 RGB 图像像素行 步进大小;若为 0 时, * 则取 ((3 or 4) * jit_width) 为值(可能会 按 4 字节对齐)。 * * @param [in ] jut_mlen : * 输出 RGB 缓存容量,其值应 >= abs(jit_stride * jit_height) , * 所指定覆盖的内存区域为 [jmem_optr, jmem_optr + jit_stride * jit_height]。 * * @param [out] jit_width : 操作成功返回的图像宽度(像素为单位)。 * @param [out] jit_height : 操作成功返回的图像高度(像素为单位)。 * @param [in ] jit_ctrlcs : 要求输出的 RGB 像素格式(参看 jctrl_color_space_t )。 * * @return j_int_t : 错误码(参看 jdec_errno_table_t 相关枚举值)。 * @retval JDEC_ERR_STRIDE : * 可通过返回的 jit_width 值,重新调整 jit_stride 等参数,再进行尝试。 * @retval JDEC_ERR_CAPACITY : * 可通过返回的 jit_height 值,重新调整 jut_mlen 等参数,再进行尝试。 */ j_int_t jdec_src_to_rgb( jdec_ctxptr_t jdec_cptr, j_mem_t jmem_optr, j_int_t jit_stride, j_uint_t jut_mlen, j_int_t * jit_width, j_int_t * jit_height, j_int_t jit_ctrlcs);
有以下几点需要特别注意:
- 要想事先得知输出的 RGB 图像数据需要多大的缓存空间,在 步骤 3 得到的图像基本信息上,通过计算 4 x 宽 x 高 的值,可得到 RGBA 格式这样的图像所需要的缓存大小。
- 若要解码输出的 RGB 32位 的像素数据带上 ALPHA 通道值,只需在申请解码器对象后(步骤 1),调用 jdec_set_vpad() 设置解码时填充的 ALPHA 通道值即可。
- 对于解码输出 RGB 24位 的像素数据,则有可能需要考虑图像每行所占的字节数是否要 4字节对齐 的问题。这可以通过调用 jdec_set_align() 接口配置操作方式。
-
使用 jdec_release() 释放解码器对象。
可重复 2 -> 3 -> 4 步骤继续进行其他图像的解码操作,直至退出(或 不再需要解码数据)时,必须使用 jdec_release() 释放解码器对象。
jdec_release(jdec_cptr); jdec_cptr = J_NULL;
以上所提及的 5 个步骤,在测试程序 test.cpp 代码中,dec_mode_mem()、dec_mode_fio()、dec_mode_file() 三个函数的流程,全部体现出来。
4. C++ 的类封装
所有的 API 都是按面向对象的 C 接口封装的代码,但在使用 C++ 方面,还是 class 对象来得直接,故在 XJPEG_wrapper.h 代码下方,封装了 jenc_handle_t 编码器类 和 jdec_handle_t 解码器类,所有成员函数调用均是内联的,效率上并不会降低。
5. 关于测试代码
测试程序的代码,是用 C++ 写的,在 test.cpp 文件中,其功能是对 JPEG 图片裁剪出 中部 子图片来。各个 [ JPEG 编码/解码 的操作模式 ] 和 [ RGB色彩空间 ] 的组合方式,都有被测试通过。
6. 源码下载
- github 下载地址: https://github.com/Gaaagaa/jpeg_wrapper
- gitee 下载地址: https://gitee.com/Gaaagaa/jpeg_wrapper
- 另外,libgjpeg 的官方地址在这:http://www.ijg.org/ ,我所使用的版本是 jpegsr9d 。
以后,我仍会继续补充其他的功能,如支持其他色彩空间的编码/解码操作。现在,暂且先只支持 RGB 图像格式吧!!!