2025年mupdf渲染过程(一):颜色

mupdf渲染过程(一):颜色mupdf 除了解析 PDF 功能之外 还有一个强大的功能就是渲染文字和图像 本文介绍 mupdf 渲染过程中涉及到的颜色问题 包括颜色空间 颜色转换 lcms 的使用 1 初始化 mupdf 初始化第一步是实例化 fz context ctx fz context 是 Mupdf 最基本的数据结构

大家好,我是讯享网,很高兴认识大家。

mupdf除了解析PDF功能之外,还有一个强大的功能就是渲染文字和图像,本文介绍mupdf渲染过程中涉及到的颜色问题:包括颜色空间,颜色转换,lcms的使用。

1.初始化

    mupdf初始化第一步是实例化fz_context *ctx,fz_context是Mupdf最基本的数据结构,它是文件句柄,fz_context结构体fz_colorspace_context *colorspace成员变量就是颜色空间内容。

实例化函数fz_new_context,初始化了一些列Mupdf操作,代码fz_new_colorspace_context(ctx)初始化MUPDF使用的颜色空间:

void fz_new_colorspace_context(fz_context *ctx) { ctx->colorspace = fz_malloc_struct(ctx, fz_colorspace_context); ctx->colorspace->ctx_refs = 1; set_no_icc(ctx->colorspace); #ifdef NO_ICC fz_set_cmm_engine(ctx, NULL); #else fz_set_cmm_engine(ctx, &fz_cmm_engine_lcms); #endif } 

讯享网

fz_new_colorspace_context内部使用了NO_ICC宏定义,通过宏定义,确定mupdf使用的颜色空间是否基于ICC文件,关于fz_set_cmm_engine函数,是否基于ICC也给出了明确的实现:

讯享网if (engine) { cct->gray = fz_new_icc_colorspace(ctx, FZ_ICC_PROFILE_GRAY, 1, NULL); cct->rgb = fz_new_icc_colorspace(ctx, FZ_ICC_PROFILE_RGB, 3, NULL); cct->bgr = fz_new_icc_colorspace(ctx, FZ_ICC_PROFILE_BGR, 3, NULL); cct->cmyk = fz_new_icc_colorspace(ctx, FZ_ICC_PROFILE_CMYK, 4, NULL); cct->lab = fz_new_icc_colorspace(ctx, FZ_ICC_PROFILE_LAB, 3, NULL); } else set_no_icc(cct);

对于icc方式,mupdf内部使用了5种颜色空间,分别是gray, rgb, bgr,cmyk,lab;各类颜色空间初始化方式:

fz_colorspace * fz_new_icc_colorspace(fz_context *ctx, const char *name, int num, fz_buffer *buf) { fz_colorspace *cs = NULL; fz_iccprofile *profile; int is_lab = 0; enum fz_colorspace_type type = FZ_COLORSPACE_NONE; int flags = FZ_COLORSPACE_IS_ICC; profile = fz_malloc_struct(ctx, fz_iccprofile); fz_try(ctx) { if (buf == NULL) { size_t size; const unsigned char *data; data = fz_lookup_icc(ctx, name, &size); profile->buffer = fz_new_buffer_from_shared_data(ctx, data, size); is_lab = (strcmp(name, FZ_ICC_PROFILE_LAB) == 0); profile->bgr = (strcmp(name, FZ_ICC_PROFILE_BGR) == 0); flags |= FZ_COLORSPACE_IS_DEVICE; } else { profile->buffer = fz_keep_buffer(ctx, buf); } fz_cmm_init_profile(ctx, profile); XXXXXX fz_md5_icc(ctx, profile); XXXXXX cs = fz_new_colorspace(ctx, name, type, flags, profile->num_devcomp, NULL, NULL, NULL, is_lab ? clamp_lab_icc : clamp_default_icc, free_icc, profile, sizeof(profile)); return cs; #endif }

mupdf使用fz_iccprofile结构表示一个icc文件的解析结果;fz_new_icc_colorspace函数内部,有两个重要步骤:第一个是fz_lookup_icc,解析icc文件生成数据,

讯享网const unsigned char * fz_lookup_icc(fz_context *ctx, const char *name, size_t *size) { #ifndef NO_ICC if (fz_get_cmm_engine(ctx) == NULL) return *size = 0, NULL; if (!strcmp(name, FZ_ICC_PROFILE_GRAY)) { extern const int fz_resources_icc_gray_icc_size; extern const unsigned char fz_resources_icc_gray_icc[]; *size = fz_resources_icc_gray_icc_size; return fz_resources_icc_gray_icc; } if (!strcmp(name, FZ_ICC_PROFILE_RGB) || !strcmp(name, FZ_ICC_PROFILE_BGR)) { extern const int fz_resources_icc_rgb_icc_size; extern const unsigned char fz_resources_icc_rgb_icc[]; *size = fz_resources_icc_rgb_icc_size; return fz_resources_icc_rgb_icc; } if (!strcmp(name, FZ_ICC_PROFILE_CMYK)) { extern const int fz_resources_icc_cmyk_icc_size; extern const unsigned char fz_resources_icc_cmyk_icc[]; *size = fz_resources_icc_cmyk_icc_size; return fz_resources_icc_cmyk_icc; } if (!strcmp(name, FZ_ICC_PROFILE_LAB)) { extern const int fz_resources_icc_lab_icc_size; extern const unsigned char fz_resources_icc_lab_icc[]; *size = fz_resources_icc_lab_icc_size; return fz_resources_icc_lab_icc; } #endif return *size = 0, NULL; }

这里用CRAY颜色空间举例,找到fz_resources_icc_gray_icc变量的定义,它是一个全局变量:

const int fz_resources_icc_gray_icc_size = 416;
const unsigned char fz_resources_icc_gray_icc[] = {
0,0,1,160,0,0,0,0,2,16,0,0,109,110,116,114,71,82,65,89,88,89,90,32,0,0,0,
0,0,0,0,0,0,0,0,0,97,99,115,112,65,80,80,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,246,214,0,1,0,0,0,0,211,45,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,5,100,101,115,99,0,0,0,192,0,0,0,125,99,112,114,116,0,0,1,64,0,0,0,40,
119,116,112,116,0,0,1,104,0,0,0,20,98,107,112,116,0,0,1,124,0,0,0,20,107,
84,82,67,0,0,1,144,0,0,0,14,100,101,115,99,0,0,0,0,0,0,0,35,65,114,116,105,
102,101,120,32,83,111,102,116,119,97,114,101,32,115,71,114,97,121,32,73,67,
67,32,80,114,111,102,105,108,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,116,101,120,116,0,0,0,0,
67,111,112,121,114,105,103,104,116,32,65,114,116,105,102,101,120,32,83,111,
102,116,119,97,114,101,32,50,48,49,49,0,88,89,90,32,0,0,0,0,0,0,243,84,0,
1,0,0,0,1,22,207,88,89,90,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,117,114,118,
0,0,0,0,0,0,0,1,1,205,0,0,};

到这里,可以明白这是gray icc文件内容,它是Mupdf自定义的icc文件,其他颜色空间icc文件也是同理。fz_lookup_icc生成profile内容之后,第二个就是fz_cmm_init_profile(ctx, profile)了,他内部调用了fz_lcms_init_profile(fz_cmm_instance *instance, fz_iccprofile *profile):

static void fz_lcms_init_profile(fz_cmm_instance *instance, fz_iccprofile *profile) { cmsContext cmm_ctx = (cmsContext)instance; fz_context *ctx = (fz_context *)cmsGetContextUserData(cmm_ctx); size_t size; unsigned char *data; DEBUG_LCMS_MEM(("@@@@@@@ Create Profile Start:: mupdf ctx = %p lcms ctx = %p \n", (void*)ctx, (void*)cmm_ctx)); size = fz_buffer_storage(ctx, profile->buffer, &data); profile->cmm_handle = cmsOpenProfileFromMemTHR(cmm_ctx, data, (cmsUInt32Number)size); if (profile->cmm_handle == NULL) { profile->num_devcomp = 0; fz_throw(ctx, FZ_ERROR_GENERIC, "cmsOpenProfileFromMem failed"); } profile->num_devcomp = fz_lcms_num_devcomps(cmm_ctx, profile); DEBUG_LCMS_MEM(("@@@@@@@ Create Profile End:: mupdf ctx = %p lcms ctx = %p profile = %p profile_cmm = %p \n", (void*)ctx, (void*)cmm_ctx, (void*)profile, (void*)profile->cmm_handle)); }

cmsOpenProfileFromMemTHR是lcms的函数,用于颜色空间的转换,他初始化了一个基于icc的的文件句柄,fz_cmm_init_profile结束后,cs = fz_new_colorspace将profile赋给颜色空间,这样一个gray的颜色空间初始化完毕了。

2 颜色转换

        mupdf在渲染文字和图像的时候,都要对颜色进行转换,因为设备的颜色空间和pdf文件样本的颜色空间可能存在不一致情况,如果不进行转换,会出现显示效果和原PDF文件不一致情况。


讯享网

讯享网void fz_convert_color(fz_context *ctx, const fz_color_params *params, const fz_colorspace *is, const fz_colorspace *ds, float *dv, const fz_colorspace *ss, const float *sv) { fz_color_converter cc; fz_find_color_converter(ctx, &cc, is, ds, ss, params); cc.convert(ctx, &cc, dv, sv); fz_drop_color_converter(ctx, &cc); } 

关于查找过程,如果非ICC模式,直接使用颜色值转换方式:

if (ds == default_gray) cc->convert = rgb2g; else if (ds == default_bgr) cc->convert = rgb2bgr; else if (ds == default_cmyk) cc->convert = rgb2cmyk; else cc->convert = std_conv_color;

如果是基于ICC模式,使用icc_conv_color函数,在使用icc_conv_color转换之前,需要做一个工作,就是要建立一个转换的句柄:cc->link = fz_get_icc_link(ctx, ds, 0, ss_base, 0, is, params, 2, 0, &cc->n);关于fz_get_icc_link,下面给出一组调用堆栈关系:

讯享网void fz_lcms_init_link(fz_cmm_instance *instance, fz_icclink *link, const fz_iccprofile *dst, int dst_extras, const fz_iccprofile *src, int src_extras, const fz_iccprofile *prf, const fz_color_params *rend, int cmm_flags, int num_bytes, int copy_spots) { cmsContext cmm_ctx = (cmsContext)instance; fz_context *ctx = (fz_context *)cmsGetContextUserData(cmm_ctx); cmsUInt32Number src_data_type, des_data_type; cmsColorSpaceSignature src_cs, des_cs; int src_num_chan, des_num_chan; int lcms_src_cs, lcms_des_cs; unsigned int flag = cmsFLAGS_LOWRESPRECALC | cmm_flags; DEBUG_LCMS_MEM(("@@@@@@@ Create Link Start:: mupdf ctx = %p lcms ctx = %p src = %p des = %p \n", (void*)ctx, (void*)cmm_ctx, (void*)src->cmm_handle, (void*)dst->cmm_handle)); /* src */ src_cs = cmsGetColorSpace(cmm_ctx, src->cmm_handle); lcms_src_cs = _cmsLCMScolorSpace(cmm_ctx, src_cs); if (lcms_src_cs < 0) lcms_src_cs = 0; src_num_chan = cmsChannelsOf(cmm_ctx, src_cs); src_data_type = (COLORSPACE_SH(lcms_src_cs) | CHANNELS_SH(src_num_chan) | DOSWAP_SH(src->bgr) | SWAPFIRST_SH(src->bgr && (src_extras != 0)) | BYTES_SH(num_bytes) | EXTRA_SH(src_extras)); /* dst */ des_cs = cmsGetColorSpace(cmm_ctx, dst->cmm_handle); lcms_des_cs = _cmsLCMScolorSpace(cmm_ctx, des_cs); if (lcms_des_cs < 0) lcms_des_cs = 0; des_num_chan = cmsChannelsOf(cmm_ctx, des_cs); des_data_type = (COLORSPACE_SH(lcms_des_cs) | CHANNELS_SH(des_num_chan) | DOSWAP_SH(dst->bgr) | SWAPFIRST_SH(dst->bgr && (dst_extras != 0)) | BYTES_SH(num_bytes) | EXTRA_SH(dst_extras)); /* flags */ if (rend->bp) flag |= cmsFLAGS_BLACKPOINTCOMPENSATION; if (copy_spots) flag |= cmsFLAGS_COPY_ALPHA; link->depth = num_bytes; link->src_extras = src_extras; link->dst_extras = dst_extras; link->copy_spots = copy_spots; if (prf == NULL) { link->cmm_handle = cmsCreateTransformTHR(cmm_ctx, src->cmm_handle, src_data_type, dst->cmm_handle, des_data_type, rend->ri, flag); if (!link->cmm_handle) fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateTransform failed"); } }

颜色空间转换句柄生成核心函数fz_lcms_init_link,依然是调用lcms接口cmsCreateTransformTHR,它需要输入源颜色空间lcms句柄,源颜色空间数据格式,关于数据格式,lcms提供了宏定义,这里主要使用了颜色空间值(COLORSPACE_SH),通道数(CHANNELS_SH),采样比特(多少字节的颜色值)(BYTES_SH)。颜色空间和通道数的获取,也是使用了lcms接口cmsGetColorSpace,_cmsLCMScolorSpace和cmsChannelsOf。

转换句柄生成之后,就要调用icc_conv_color进行颜色转换:

void fz_lcms_transform_color(fz_cmm_instance *instance, fz_icclink *link, unsigned short *dst, const unsigned short *src) { cmsContext cmm_ctx = (cmsContext)instance; cmsHTRANSFORM hTransform = (cmsHTRANSFORM) link->cmm_handle; cmsDoTransform(cmm_ctx, hTransform, src, dst, 1); }

icc_conv_color调用了fz_lcms_transform_color,其内部调用的cmsDoTransform也是lcms接口,结束之后,一次颜色和颜色空间的转换也结束了,最后生成和目标颜色空间对应的颜色值。

3 总结

      对于Mupdf颜色转换,总体过程总结如下

      颜色空间初始化->初始化mupdf定义的5种颜色空间->判断是否icc模式(如果是)->使用mupdf定义pro文件初始化数据->调用lcms函数生成profile句柄。

      颜色空间转换->生成转换器->判断是否icc模式(如果是)->创建icc转换句柄->调用lcms生成句柄->根据转换器转换颜色空间->调用lcms转换接口。

     以上都是基于icc模式的流程,对于非icc模式流程,则简单许多,不需要调用lcms接口,直接使用mupdf颜色值转换即可,流程也和icc差不多,这里不做介绍

     上面介绍了MUPDF颜色转换的全部流程,但是还有很多细节没有写到,还有非icc模式的转换,颜色空间hash表去重存储,颜色空间md5比对,颜色值预处理等。

小讯
上一篇 2025-03-16 10:22
下一篇 2025-01-10 11:48

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/67236.html