最可靠方法是用libpoppler+Caairo将PDF每页渲染为位图:先调poppler_page_get_size获取实际尺寸,再用poppler_page_render_to_cairo配合cairo_image_surface_create和cairo_surface_write_to_png输出PNG,注意缩放矩阵按DPI计算、必须调用cairo_surface_flush,且链接时需同时指定-lpoppler和-lcairo。

用 libpoppler + Cairo 提取 PDF 中的图片最可靠
PDF 本身不直接“存储图片”,而是把图像作为绘图操作嵌在内容流里,靠渲染引擎(如 Cairo)逐页光栅化后才能拿到像素数据。libpoppler 是目前 C++ 生态中对 PDF 图像语义支持最完整的库,配合 Cairo 可以稳定提取每一页的完整位图——不是“识别出图片区域”,而是把整页按指定 DPI 渲染成 PNG 或 JPEG。
注意:它不提取原始 JPEG/PNG 嵌入对象(比如 PDF 中直接 embed 的 /XObject),那种需要解析底层对象流,得用 libqpdf 或自己读 xref 表,但实际中多数 PDF 并不保留原始编码,而是转成内部绘制指令。
- 安装时确保启用了 Cairo 后端:
./configure --enable-cairo-output - 链接时必须同时带
-lpoppler -lcairo,漏掉-lcairo会导致poppler_page_render_to_cairo链接失败 - 渲染前调用
poppler_page_get_size获取宽高,别硬写 595×842(A4 点数)——缩放或裁剪过的 PDF 尺寸会变
用 poppler_page_render_to_cairo 保存为 PNG 的关键步骤
核心是把 poppler_page_t* 渲染到 Cairo surface,再用 cairo_surface_write_to_png 输出。中间不能跳过 cairo_surface_flush,否则文件可能为空或损坏。
- 创建 surface 时用
cairo_image_surface_create,别用cairo_pdf_surface_create——后者输出的是 PDF,不是图片 - DPI 控制精度:传给
poppler_page_render_to_cairo的矩阵需按 DPI 缩放,例如 150 DPI 下,scale_x = 150.0 / 72.0(PDF 默认 72 DPI) - 务必检查
cairo_surface_status返回值,CAIRO_STATUS_WRITE_ERROR常因目标目录不可写或磁盘满,不是代码逻辑问题 - 示例片段:
cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); cairo_t *cr = cairo_create(surf); cairo_scale(cr, scale_x, scale_y); poppler_page_render_to_cairo(page, cr); cairo_surface_flush(surf); cairo_surface_write_to_png(surf, "page-1.png"); cairo_destroy(cr); cairo_surface_destroy(surf);
为什么不用 pdfium 或 muPDF?
pdfium(Chromium 用的)C++ 接口极不友好:没有现成的 render-to-Cairo 封装,要自己配 Skia 或 FXGE,编译链长、头文件依赖混乱,FxBitmap_Destroy 和 CFX_DIBitmap::LoadFromData 文档几乎为零;muPDF 虽轻量,但默认输出是 fz_pixmap,转 PNG 需手动调 fz_write_png,且其 license(AGPL)在闭源项目中风险明确高于 poppler 的 GPL v2+(可商用,只要开源衍生作品)。
立即学习“C++免费学习笔记(深入)”;
- pdfium 的
RenderPage返回sk_sp<SkImage>,意味着你得引入整个 Skia,而 Skia 构建耗时 20+ 分钟 - muPDF 的
fz_new_draw_device必须配fz_new_pixmap,但 pixmap stride 计算易错,stride != width * 4(有对齐填充),直接 memcpy 会错位 - poppler 在 Ubuntu/Debian 上
apt install libpoppler-glib-dev一行到位,头文件路径干净,pkg-config --cflags --libs poppler-glib直接可用
提取“原始嵌入图像”需绕开渲染,改用 qpdf 解析对象流
如果 PDF 确实 embed 了原始 JPEG(比如用 Adobe Acrobat “插入图像”生成的),想拿到未压缩的字节,就得跳过渲染层,直接读 /XObject 子对象。这时 libqpdf 更合适——它专为解析 PDF 结构设计,不渲染,只解包。
- 遍历
QPDF::getAllObjects(),对每个QPDFObjectHandle检查isStream()和getKey("/Subtype") == "/Image" - 用
getDict().getKey("/Filter")判断压缩方式:/DCTDecode→ JPEG,/FlateDecode→ PNG(需补 PNG header),/JPXDecode→ JPEG2000 -
getStreamData()返回原始字节,但 JPEG 流可能缺 SOI(\xff\xd8),得前置校验并补全——不是所有 PDF 都严格遵循 JPEG 标准 - 这种路径无法处理 masked image、soft mask、color space 转换等,纯靠元数据,鲁棒性远低于渲染方案
真正难的不是选哪个库,而是接受一个事实:PDF 不是图片容器,它是页面描述语言。所谓“提取图片”,本质是在做“页面快照”还是“结构解析”,两者目标不同,手段必然分裂。选错方向,后面全是坑。


















