PDF 色彩保真工程实践【4】核心实现(上):从 TIFF 解码到 Flate 流构造 本文是系列文章的第 4 篇。工程已经能跑这一篇进入代码从 TIFF 中解码出未经色彩转换的 CMYK 采样同时提取 ICC Profile再将这些数据无损压缩并写入 PDF Flate Stream。TiffCmykReader::Read ↓ IccProfileParser::Parse ↓ ZlibUtil::Compress ↓ FoxitPdfObjectFactory::CreateFlateStream本项目完整源码https://github.com/AmyLin2013/pdf-cmyk-image本篇你将学到如何用 libtiff 的 scanline API 读取 CMYK而不触发任何 RGB 转换如何提取内嵌 ICC Profile并完成基础头部校验为什么我们自己用 zlib 压缩数据如何通过ReaderCallbackImportData把压缩字节写入 PDF 流。第一步用 scanline 读取 CMYK绝不走 RGBlibtiff 有个很方便的TIFFReadRGBAImage但它会把图像转成 RGBA——这正是我们要避开的。所以我们使用TIFFReadScanline逐行解码。它会还原 TIFF 的压缩和 Predictor但不会把 CMYK 转换成 RGB得到的是解码后的 CMYK 采样值。读取前先做严格校验确认它确实是 8 位、四通道的 CMYK颜色通道数需排除 extra samples// 关键校验TiffCmykReader.cpp 摘录示意constuint16_tcolor_samplesspp-extraSampleCount;// 排除额外通道boolisCmyk(photometricPHOTOMETRIC_SEPARATED)// 分色模式CMYK 印刷分色(inkSetINKSET_CMYK)// 墨色为 CMYK(color_samples4);// 恰好 4 个颜色通道if(!isCmyk)Fail(Not a standard CMYK TIFF.);if(bitsPerSample!8)Fail(Only 8-bit CMYK is supported.);if(TIFFIsTiled(tif))Fail(Tiled TIFF not supported.);// 仅支持 strip然后按 planar 配置逐行读取。TIFF 有两种像素排布CONTIG交错一行里 C M Y K C M Y K …若有多余通道则丢弃SEPARATE分平面C 平面、M 平面…分开存需要按平面重组若有多余平面同样只取前 4 个。两种都要正确处理最终得到一段紧凑的 CMYK 采样缓冲每像素 4 字节。 关键点整个读取过程没有发生色彩空间转换得到的是 TIFF 解码后的 CMYK 样本四个颜色分量的数值保持不变。第二步提取 ICC Profile 并检查关键头部字段CMYK TIFF 可以通过 TIFFTAG_ICCPROFILE 标签嵌入 ICC Profile。提取操作发生在第一步的读取流程中TiffCmykReader.cpp里通过TIFFGetField(tif, TIFFTAG_ICCPROFILE, ...)取得完整的 ICC 字节。本步骤不执行完整的 ICC 合规性验证只检查本工程后续构造 PDF 对象所需的几个关键头部字段// ICC 基本校验IccProfileParser.cpp 摘录示意if(raw.size()128)returnfalse;// 头部至少 128 字节if(memcmp(raw.data()36,acsp,4)!0)returnfalse;// ICC 签名if(memcmp(raw.data()16,CMYK,4)!0)isCmykfalse;// 数据色彩空间偏移 36 处必须是acspICC 文件魔数偏移 16 处的数据色彩空间为CMYK时isCmyk true对应 PDF 里的/N 4。偏移 0 处声明的 Profile 大小必须 ≥128 且不超过实际缓冲长度防止截断的 Profile 混入。若 TIFF 没有 ICC或 ICC 未通过本工程的基础头部校验程序会回退为 /DeviceCMYK。仍是 CMYK只是不带特性文件。第三步为什么自己用 zlib 压缩对于本项目这种需要无损保存原始 CMYK 采样的图像流FlateDecode 是合适的选择它使用 zlib/deflate 压缩解码后可以完整恢复原始字节。我们选择自己压缩而不是把原始数据交给 SDK 让它决定怎么存原因是——确定性我们明确知道流里存的就是“我们压缩后的字节”这里追求的“确定性”主要是数据路径和解码结果可验证而不是要求不同 zlib 版本生成完全相同的压缩字节序列。/Filter明确就是FlateDecode校验时用对应方式解压能拿回与源完全一致的原始数据做比对。压缩就是一层薄封装// zlib 压缩CmykImageEncoder.cpp 摘录示意uLongf boundcompressBound((uLong)input.size());output.resize(bound);compress2(output.data(),bound,input.data(),(uLong)input.size(),Z_BEST_COMPRESSION);output.resize(bound);实现中还检查了compress2的返回值非Z_OK即报错中止压缩失败会直接返回失败而不是把坏数据写入流——这也是“确定性”的一部分。第四步把压缩字节喂给 PDF 流以下 ImportData 行为基于 Foxit PDF SDK 11.1传入的数据已经按照指定 Filter 编码SDK 将其作为 Stream 的编码数据保存并在 Stream Dictionary 中设置对应的 /Filter。我们实现一个内存版的ReaderCallbackMemoryFileRead把压缩后的字节包起来声明 filter 为FlateDecode// 创建 Flate 流FoxitPdfObjectFactory.cpp 摘录示意std::vectoruint8_tcompressed;ZlibUtil::Compress(raw_data,compressed,err);// 先自己压PDFStream*streamPDFStream::Create(dict);// 用给定字典建流MemoryFileRead*readernewMemoryFileRead(std::move(compressed));stream-ImportData(reader,PDFStream::e_FlateDecode);// 存入 压缩字节filter Flateuint32 obj_numdoc_.AddIndirectObject(stream);// 生成间接对象这样流对象的原始字节就是压缩结果/Filter是FlateDecode将来读取时GetData(false)会返回解码后的原始数据——正好用于第 6 篇的逐字节比对。⚠️埋个伏笔这里new出来的MemoryFileRead由谁释放如果处理不当会在程序退出时崩溃。这个坑我们在第 6 篇专门讲——它是本项目最有价值的一个 bug。小结用 scanline 读 CMYK严格校验全程不转 RGB原样提取并校验 ICC Profile非法则回退 DeviceCMYK自己用 zlib 压缩以获得确定性通过ReaderCallbackImportData(FlateDecode)写入 PDF 流。下一篇预告第 5 篇《核心实现下拼装 Image XObject、页面资源与内容流》我们把这些流对象组装成完整图像挂到页面资源上并写出绘制指令。关于本系列完整源码https://github.com/AmyLin2013/pdf-cmyk-image 关于福昕 PDF SDK开发者站点 ·底层 PDF Object API 提供对流、字典、数组、引用的精确控制欢迎申请试用。 免费试用申请[https://developers.fuxinsoft.cn/free-trial/

相关新闻

最新新闻

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现 【免费下载链接】serenity The Serenity Operating System 🐞 项目地址: https://gitcode.com/GitHub_Trending/se/serenity 导读 本文以 getopt(3) 手册 为核心&a…

2026/10/1 19:32:24
轻量服务器还是ECS?大促云服务器选购与避坑实战指南

轻量服务器还是ECS?大促云服务器选购与避坑实战指南

每年大促节点,群里永远有人在问同一个问题:“38元的轻量服务器到底怎么抢?为什么我每次点进去都是已售罄?68元直购和99元的ECS我到底选哪个?”作为一个常年帮团队和自己采购云服务器的老用户,我太清楚这种纠…

2026/9/30 21:32:07
为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南 【免费下载链接】agents Multi-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity 项目地址:…

2026/9/30 19:41:56
PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署 【免费下载链接】PaddleOCR Turn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between i…

2026/10/1 19:32:23
Spring源码解析:构造器注入的类型转换与候选匹配机制

Spring源码解析:构造器注入的类型转换与候选匹配机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 19:32:35
openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由 【免费下载链接】openai-agents-python A lightweight, powerful framework for multi-agent workflows 项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-pyth…

2026/9/30 21:32:11

日新闻

周新闻

月新闻