mupdf渲染过程(一):颜色

news2024/9/23 5:29:38

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文件不一致情况。

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

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

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

相关文章

2W10-ASEMI适配器专用2W10

编辑&#xff1a;ll 2W10-ASEMI适配器专用2W10 型号&#xff1a;2W10 品牌&#xff1a;ASEMI 封装&#xff1a;WOB-4 最大重复峰值反向电压&#xff1a;1000V 最大正向平均整流电流(Vdss)&#xff1a;2A 功率(Pd)&#xff1a;中小功率 芯片个数&#xff1a;4 引脚数量…

钡铼技术有限公司R40路由器工业4G让养殖环境监控更高效

钡铼技术有限公司的R40路由器是一款专为养殖环境监控而设计的工业级4G路由器。该路由器的出现极大地提高了养殖行业的监控效率&#xff0c;为养殖场主和管理者提供了更可靠、高效的解决方案。本文将从功能特点、优势以及应用案例等方面介绍钡铼技术有限公司的R40路由器在养殖环…

【SpringBoot】自定义工具类实现Excel数据新建表存入MySQL数据库

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

hololens2发布unity设置

生成vs工程再向hololens发布时&#xff0c; Architecture选X64或ARM64都可以成功发布

python-0002-linux安装pycharm

下载软件包 下载地址&#xff1a;https://download.csdn.net/download/qq_41833259/88944791 安装 # 解压 tar -zxvf 你的软件包 # 进入软件解压后的路径&#xff0c;如解压到了/home/soft/pycharm cd /home/soft/pycharm cd bin # 执行启动命令 sh pycharm.sh # 等待软件启…

京东云主机+京美建站SaaS版

京美建站SaaS版 京美建站搭建企业网站、小程序、3000精美模板 链接:https://daili.jd.com/s?linkNo57UBX34BZMWGNFYTOCPVUE7SN36CCIPKLTFLPCUCPYBKSYYBIPS2BJ57GP7RACLDHU66X526ZOULMIXL2VN7DT7IHU 京东云主机&#xff0c;安全稳定&#xff0c;性能强劲&#xff0c;新客下单…

VMware安装Ubuntu 18.04.2

下载Ubuntu映像 下载地址&#xff1a;http://old-releases.ubuntu.com/releases/18.04/ 下载名称&#xff1a; ubuntu-18.04.2-desktop-amd64.iso 清华镜像站&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/ 阿里云镜像站&#xff1a;https://mirrors.ali…

ASP.NET区域检验云LIS平台源码 标本全生命周期管理

目录 一、云LIS系统功能亮点 二、收费项目管理 三、检验项目管理 系统功能 云LIS系统源码是一款全面的实验室信息管理系统源码&#xff0c;其主要功能包括样本管理、检测项目管理、质控管理、报告管理、数据分析、两癌筛查等多个方面。具有独立的配套SaaS模式运维管理系统&…

科研学习|论文解读——词汇量及其对主题表示的影响 (IPM, 2017)

原文标题 Vocabulary size and its effect on topic representation 摘要 这项研究调查了如何通过选择性地从被建模的文本语料库的词汇中删除术语来减少主题模型训练的计算开销。我们使用三个数据集&#xff0c;比较了删除单独出现的术语、前0.5%、1%和 5% 最频繁出现的术语以及…

Gitee配置SSH登录

一、背景 新入手的电脑&#xff0c;需要对Gitee上存放的项目进行更改上传&#xff0c;发现上传不了需要登录&#xff0c;便采用SSH密钥进行登录&#xff0c;防止远程管理工程中的信息泄露 二、前提 电脑已下载Git Bash工具&#xff0c;在项目下点击鼠标右键&#xff0c;进入…

多线程(线程池)

线程池 池的概念意味着可以复用, 减少创建, 销毁线程的消耗 即事先把需要使用的线程创建好, 放到 “池” 中, 需要的时候从 “池” 里取, 用完再放回 池里取 这样全程只创建和销毁线程一次(之说是一次哦, 没说一次创建和销毁多少个)即可 标准库线程池的使用 public class Main…

【解读】区块链和分布式记账技术标准体系建设指南

大家好&#xff0c;这里是苏泽。一个从业Java后端的区块链技术爱好者。 今天带大家来解读这份三部门印发的行业建设指南《区块链和分布式记账技术标准体系建设指南》 原文件可查看P020240112840724196854.pdf (www.gov.cn) 以下是个人解读&#xff0c;如有纰漏请指正&#xff…

学习Android的第二十八天

目录 Android Service (服务) 线程 Service (服务) Service 相关方法 Android 非绑定 Service startService() 启动 Service 验证 startService() 启动 Service 的调用顺序 Android 绑定 Service bindService() 启动 Service 验证 BindService 启动 Service 的顺序 …

《LeetCode热题100》笔记题解思路技巧优化_Part_3

《LeetCode热题100》笔记&题解&思路&技巧&优化_Part_3 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f622;&#x1f622;&#x1f622; 开始刷题链表&#x1f7e2;1. 相交链表&#x1f7e2;2. 反转链表&…

html5cssjs代码 020 推荐网址

html5&css&js代码 020 推荐网址 一、代码二、解释 这段HTML代码定义了一个网页&#xff0c;它包含了一个标题、一些样式和一个表格。表格中列出了一些推荐的网址&#xff0c;包括序号、名称、网址、描述和备注。每个表格行都包含一个链接到相应网址的超链接。页面的样式…

一种基于宏和serde_json实现的rust web中统一返回类

本人rust萌新&#xff0c;写web碰到了这个&#xff0c;基于ChatGPT和文心一言学了宏&#xff0c;强行把这玩意实现出来了&#xff0c;做个学习记录&#xff0c;如果有更好的方法&#xff0c;勿喷。 先看效果&#xff0c;注意不支持嵌套&#xff0c;且kv映射要用>(因为它这个…

《JAVA与模式》之抽象工厂模式

系列文章目录 文章目录 系列文章目录前言一、使用简单工厂模式的解决方案二、引进抽象工厂模式三、抽象工厂模式结构四、抽象工厂模式的优缺点前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

MC78L05ACDR2G线性稳压器芯片中文资料规格书PDF数据手册引脚图参数图片价格

产品概述&#xff1a; MC78L00A系列线性稳压器价格便宜&#xff0c;易于使用&#xff0c;适用于各种需要最高100mA的调节电源的应用。与大功率MC7800和MC78M00系列一样&#xff0c;这款稳压器也提供内部电流限制和高温关断&#xff0c;因此非常坚固耐用。在很多应用中&#xf…

提高工作效率,这 10 款 AI 工具不能错过

作为一个职场打工人&#xff0c;我深知时间和效率的重要性。正是因为如此&#xff0c;我开始使用各种人工智能工具来帮助我提高工作效率。在这篇文章中&#xff0c;我将会分享 10 款必备的 AI 工具&#xff0c;这些工具可以让你的工作更加高效&#xff0c;从而为你节省更多的时…

windows 11访问Debian10上的共享目录

步骤 要在Windows 11上访问Debian 10.0.0的共享目录&#xff0c;可以通过以下步骤来实现&#xff1a; 1. 设置Samba服务&#xff1a;在Debian系统上&#xff0c;需要安装并配置Samba服务&#xff0c;以便能够实现文件夹共享。Samba是一个允许Linux/Unix服务器与Windows操作系…