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_ICCfz_set_cmm_engine(ctx, NULL);
#elsefz_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);}elseset_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);XXXXXXfz_md5_icc(ctx, profile);XXXXXXcs = 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_ICCif (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;}
#endifreturn *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文件不一致情况。

mupdf颜色转换函数static fz_overprint *
resolve_color(fz_context *ctx, fz_overprint *op, const float *color, fz_colorspace *colorspace, float alpha, const fz_color_params *color_params, unsigned char *colorbv, fz_pixmap *dest),需要传入输入颜色值,输入颜色空间,alpha值,PDF颜色参数,输出颜色值,目标图片;内部调用了fz_convert_color,它有两部:先查找合适的颜色转换器,然后使用颜色转换器对颜色进行转换:

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;elsecc->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比对,颜色值预处理等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/746072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用适配器模式使用第三方库

文章目录 一、为什么要使用适配器模式二、适配器模式使用第三方库示例 一、为什么要使用适配器模式 适配器模式是一种设计模式&#xff0c;用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式的主要目的是使不兼容的接口能够一起工作。以下是一些使用适配器的原因&…

OpenCV开发笔记(七十七):相机标定(二):通过棋盘标定计算相机内参矩阵矫正畸变摄像头图像

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136616551 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 红胖子(红模仿)的博…

TypeScript的基础类型和高级类型梳理总结

一、基础类型 1、boolean 布尔类型 表示逻辑值&#xff0c;可以是 true 或 false let isBoolean:boolean false 2、number 数字类型 表示整数和浮点数&#xff08;例如 42&#xff0c;3.14159&#xff09;&#xff0c;不论是十进制、二进制、八进制还是十六进制&#xff…

Rust镜像配置

cargo镜像配置 找到.cargo目录并创建config文件,输入以下内容即可,windows一般在C:\Users\用户目录\.cargo,linux执行vim ~/.cargo/config即可。然后将下面内容赋值粘贴进去 [source.crates-io] registry = "https://github.com/rust-lang/crates.io-index" rep…

Visual Studio 2022 配置“Debug|x64”的 Designtime 生成失败。IntelliSense 可能不可用。

今天写代码&#xff0c;无缘无故就给我整个这个错误出来&#xff0c;我一头雾水。 经过我几个小时的奋战&#xff0c;终于解决问题 原因就是这个Q_INTERFACES(&#xff09;宏&#xff0c;我本想使用Q_DECLARE_INTERFACE Q_INTERFACES这两个Qt宏实现不继承QObject也能使用qobjec…

C语言自学笔记17----结构体struct与位域与关键字typedef

C 语言结构体(struct) 为什么在C中使用结构&#xff1f; 假设您要存储有关一个人的信息&#xff1a;他/她的姓名&#xff0c;身份证号和薪水。您可以创建不同的变量name&#xff0c;citNo和salary存储此信息。 如果您需要存储多个人的信息怎么办&#xff1f;现在&#xff0c;你…

VSCode提交代码

VSCode提交代码方式&#xff1a; 先在电脑本地文件夹中打开git的bash窗口使用git clone https://github.com/xxxx/克隆仓库地址到本地&#xff0c;并生成一个项目的文件夹打开VSCode&#xff0c;点击文件按钮&#xff0c;打开加载项目的文件夹对于VSCode设置Git路径&#xff…

Three 材质纹理 (总结三)

THREE.MeshLambertMaterial&#xff08;网格 Lambert 材质&#xff09; 该材质使用基于非物理的Lambertian模型来计算反射率。可以用来创建暗淡的并不光亮的表面&#xff0c;该材质非常易用&#xff0c;而且会与场景中的光源产生反应。 MeshLambertMaterial属性 # .color : …

蓝桥杯刷题|02入门真题

[蓝桥杯 2022 省 B] 刷题统计 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几天实现做题数大于等于 n 题? 输入格式 输入一行包含三个整数…

Linux之线程互斥

目录 一、问题引入 二、线程互斥 1、相关概念 2、加锁保护 1、静态分配 2、动态分配 3、锁的原理 4、死锁 三、可重入与线程安全 1、概念 2、常见的线程不安全的情况 3、常见的线程安全的情况 4、常见不可重入的情况 5、常见可重入的情况 6、可重入与线程安全联系…

google scholar谷歌学术搜索技巧合集【补充ing】

今天发现和百度学术更新速度差太多&#xff0c;引用数量也不对&#xff0c;故搜集谷歌学术搜索技巧&#xff0c;以尽快查找所需。 筛选合集&#xff1a; 谷歌学术高级搜索技巧大放送 - 知乎 Google Scholar 谷歌学术文献检索技巧总结 - 知乎 Google Scholar谷歌学术文献检索技…

Linux 部署 Samba 服务

一、Ubuntu 部署 Samba 1、安装 Samba # 更新本地软件包列表 sudo apt update# 安装Samba sudo apt install samba# 查看版本 smbd --version2、创建共享文件夹&#xff0c;并配置 Samba 创建需要共享的文件夹&#xff0c;并赋予权限&#xff1a; sudo mkdir /home/test sud…

vue3 依赖注入provide/inject

建议在非父子&#xff08;或不相关&#xff09;组件传值时进行使用 场景&#xff1a;App.vue 引用了A组件&#xff0c;A组件中有引用了B组件&#xff0c;甚至B有引用了C组件等时&#xff0c;这些组件共同使用了一个值&#xff0c;建议使用provide/inject的方式进行传值。 注意…

Kubernetes operator系列:webhook 知识学习【更新中】

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Kubernetes operator学习 系列文章&#xff0c;本节会对 kubernetes webhook 知识进行学习 本文的所有代码&#xff0c;都存储于github代码库&#xff1a;https://github.com/graham924/share-code-operator-st…

C语言例3-12:自增和自减运算的例子

i 先引用后加1--i 先减1后引用 代码如下&#xff1a; #include<stdio.h> int main(void) {int i2, j2;char c1d, c2D; //d(100) D(68)printf("i %d\n",i); //3, i3printf("j-- %d\n",j--); …

C++for语句

1.求平均年龄 班上有学生若干名,给出每名学生的年龄(整数),求班上所有学生的平均年龄,保留到小数点后两位 输入 第1行有一个整数n(1 <= n <=100),表示学生的人数;其后n行每行有1个整数,表示每个学生的年龄,取值为15~25 输出 一行,包含一个浮点数,为所求的平…

java实现压缩文件夹(层级压缩)下载,java打包压缩文件夹下载

工具类如下 打包下载方法&#xff1a;exportZip&#xff08;支持整个文件夹或单文件一起&#xff09; 注意:前端发送请求不能用ajax&#xff0c;form表单提交可以&#xff0c;location.href也可以&#xff0c;window.open也可以&#xff0c;总之就ajax请求就是不行 import com.…

生成式人工智能如何赋能SOC分析师?

以下是已经在全球SOC崭露头角的六大生成式人工智能应用。 在当今网络安全威胁日益严峻的形势下&#xff0c;安全运营中心(SOC)肩负着重大责任。然而&#xff0c;SOC分析师往往人手不足&#xff0c;工作繁重。生成式人工智能(GenAI)的出现为缓解这一困境带来了希望&#xff0c;使…

python:NP28---密码游戏

文章目录 前言一、题意描述输入描述&#xff1a;输出描述&#xff1a; 二、代码1.代码的实现2.读入数据 总结 前言 在python基础知识的学习中&#xff0c;我们很多时候会遇见让我们把数字拆分成各个位数的题&#xff0c;下面这道就是经典的数字拆分的l例题 一、题意 描述 牛…

工具篇--从零开始学Git

一、git概述 1.1版本控制方式 集中式版本控制工具 集中式版本控制工具&#xff0c;版本库是集中存放在中央服务器的&#xff0c; team 里每个人 work 时从中央服务器下载代码&#xff0c;是必须联网才能工作&#xff0c; 局域网或互联网&#xff0c;个人修改之…