深度解析

如何将 PDF 页面转换为图片:实用指南

koboshiCo-founder
·5 分钟阅读
如何将 PDF 页面转换为图片:实用指南
概述

PDF 页面并不总能直接用于你需要的场景。本指南介绍何时将 PDF 转换为图片、各种方法的取舍、各操作系统的原生方案,以及五种语言的代码示例。

前端开发者收到一份 40 页的品牌规范 PDF,需要把其中三页放进 Figma 画板。技术支持工程师想把数据手册里的某张图表贴到 Slack 讨论串里。律师需要把已签合同的某一页作为附件发送邮件,而不是发送整份文件。

PDF 是为固定版式文档设计的,而网页、图片编辑器和聊天应用都是基于像素的。将 PDF 页面转换为图片正好弥合这个鸿沟,但选择的转换方法会直接影响输出质量、文件大小以及可控程度。

为什么 PDF 在这方面既好用又麻烦

PDF 以绘图指令流的形式存储页面:放置这个字形、绘制这个矢量对象、按这个尺寸渲染这张图片。这使它具备分辨率无关性和视觉一致性,同时也意味着 PDF 不是图片。要把它变成图片,必须有东西把这些指令渲染到光栅画布上。

优点:

  • 文字和矢量图形在任何缩放级别下都保持锐利,因为它们是数学描述,而非像素存储。
  • 单个 PDF 可以在一个文件中容纳数百页。
  • 字体、色彩配置文件和批注都会随文档一起携带。

难点:

  • 不同 PDF 阅读器的渲染结果存在差异。同一页面在 Adobe Acrobat、Preview、Chrome 或某个无头库中看起来可能略有不同。
  • 扫描版 PDF 只是被 PDF 容器包裹的图片,因此"转换"它们意味着重新编码,可能引入伪影或体积膨胀。
  • 带有透明度、图层或交互表单的复杂 PDF 可能在扁平化时产生不可预期的结果。
  • 页面尺寸不固定。US Letter 页面在 72 DPI 下是 612 x 792 像素,在 300 DPI 下则是 2550 x 3300。如果不指定分辨率,可能得到无法使用的输出。

人们通常把 PDF 转换成什么

大多数转换任务会落在以下三种输出格式之一。它们的用途各不相同。

格式最适合取舍
JPG照片、预览图、邮件附件、网页图库有损压缩,但文件体积小
PNG截图、示意图、需要透明度的场景无损压缩,照片体积通常大于 JPG
WebP现代网页、应用、任何需要考虑带宽的地方比 JPG/PNG 更小,兼容性稍逊

还有一些实际维度需要考虑:

  • 单页还是批量。 单页很容易;整文件夹的发票则需要自动化。
  • DPI。 150 DPI 适合做缩略图,300 DPI 是打印和 OCR 的标准,600 DPI 只有在需要放大观察细节时才用得上。
  • 色彩空间。 RGB 对屏幕是安全的。CMYK PDF 若用简单方式转成 RGB,可能出现偏色。

选择合适的方法

合适的工具取决于你要优化什么。

快速的浏览器端转换

如果你只需要把一页转成 JPG、PNG 或 WebP,且文件敏感度不需要上服务器处理,浏览器端转换器是最快的路径。我们的 PDF 转 JPGPDF 转 PNGPDF 转 WebP 工具会在本地渲染 PDF。文件不会离开你的设备,这对合同、身份证件和医疗记录很重要。

命令行批量处理

对于成文件夹的 PDF 或 CI 流水线,命令行工具更胜一筹。你能获得可重复的输出、DPI 控制以及脚本化能力。

应用内转换

当转换是产品的一部分时,直接调用库通常比调用 CLI 工具更干净。这能减少外部依赖,并让错误处理与代码库其他部分保持一致。

在 Windows 上转换

Adobe Acrobat

  1. 打开 PDF 并跳转到需要的页面。
  2. 选择 文件 > 导出为 > 图像 > JPEG/PNG/TIFF
  3. 在导出对话框中设置输出分辨率。
  4. 保存。

Acrobat 的输出稳定可靠,但它不是免费软件,而且不带付费 SDK 就无法脚本化。

PDF-XChange Editor

一个更轻量的替代方案,提供免费版本。文件 > 导出 > 导出页面为图像 允许你选择格式、DPI 和页面范围。

在 PowerShell 中使用 pdftoppm

安装 Windows 版 Poppler,然后在 PowerShell 中使用 pdftoppm

pdftoppm -jpeg -r 300 input.pdf output

这会生成 output-1.jpgoutput-2.jpg 等文件,每页一张,分辨率为 300 DPI。

需要透明背景的 PNG:

pdftoppm -png -r 300 input.pdf output

只转某一页:

pdftoppm -jpeg -r 300 -f 1 -l 1 input.pdf output

-f-l 分别设置起始页和结束页。

在 PowerShell 中使用 ImageMagick

ImageMagick 也能渲染 PDF,但在 Windows 上它通常底层会调用 Ghostscript:

magick -density 300 input.pdf[0] output.jpg

[0] 表示第一页。如果不加,ImageMagick 可能尝试生成多帧图像。

在 macOS 上转换

Preview

  1. 用 Preview 打开 PDF。
  2. 选中需要的页面缩略图。
  3. 选择 文件 > 导出,选择格式并设置分辨率。

Preview 快速且私密,但一次只能处理一页。

在终端中使用 sips

macOS 自带 sips,但它对 PDF 文字的渲染效果不好。仅当 PDF 本身已是位图时才使用:

sips -s format jpeg input.pdf --out output.jpg

要真正渲染 PDF,通过 Homebrew 安装 Poppler:

brew install poppler
pdftoppm -jpeg -r 300 input.pdf output

Automator 快速操作

你可以在 Automator 中构建一个右键服务,对选中的 PDF 运行 pdftoppm。如果你经常需要转换页面又不想记参数,这很实用。

在 Linux 上转换

Poppler 通常是 Linux 上的最佳选择。

sudo apt install poppler-utils

pdftoppm -jpeg -r 300 input.pdf output
pdftoppm -png -r 300 input.pdf output

要输出 WebP,先转成 PNG,再用 cwebp

pdftoppm -png -r 300 input.pdf temp
cwebp temp-1.png -o output.webp

批量转换

如果你有一文件夹的 PDF,并且每个 PDF 只想取第一页:

for f in *.pdf; do
    pdftoppm -jpeg -r 300 -f 1 -l 1 "$f" "${f%.pdf}"
done

ImageMagick

ImageMagick 在 Linux 上也能用,但对 PDF 通常较慢,因为它通过 Ghostscript 栅格化:

magick -density 300 input.pdf[0] -quality 90 output.jpg

-density 要在读取 PDF 之前设置。放在后面没有作用。

用代码转换

TypeScript / Node.js

使用 pdfjs-dist 将页面渲染到 canvas,然后导出为图片数据。

import * as pdfjsLib from "pdfjs-dist"
import { createCanvas } from "canvas"
import fs from "fs"

async function pdfPageToPng(
  pdfPath: string,
  pageNumber: number,
  outputPath: string,
  scale: number = 2
) {
  const data = new Uint8Array(fs.readFileSync(pdfPath))
  const pdf = await pdfjsLib.getDocument({ data }).promise
  const page = await pdf.getPage(pageNumber)

  const viewport = page.getViewport({ scale })
  const canvas = createCanvas(viewport.width, viewport.height)
  const context = canvas.getContext("2d")

  await page.render({ canvasContext: context, viewport }).promise

  const buffer = canvas.toBuffer("image/png")
  fs.writeFileSync(outputPath, buffer)

  await pdf.destroy()
}

await pdfPageToPng("input.pdf", 1, "output.png", 2)

scale 大致对应 DPI:在 72 DPI 下 2 等于 144 DPI 输出。如需 300 DPI,scale 大约为 4.17

在浏览器中,我们的 PDF 转 PNG 转换器也会执行同样的 render-to-canvas 步骤,且不会上传文件。

PHP

PHP 可以直接调用 pdftoppm,或者使用 spatie/pdf-to-image 这类封装了 ImageMagick 的库:

<?php

use Spatie\PdfToImage\Pdf;

$pdf = new Pdf('input.pdf');
$pdf->setPage(1)
    ->setOutputFormat('png')
    ->saveImage('output.png');

如果你不想增加依赖,可以直接调用 Poppler:

<?php

function pdfPageToPng(string $input, int $page, string $output, int $dpi = 300): void {
    $cmd = sprintf(
        'pdftoppm -png -r %d -f %d -l %d %s %s',
        $dpi,
        $page,
        $page,
        escapeshellarg($input),
        escapeshellarg($output)
    );
    exec($cmd);
}

pdfPageToPng('input.pdf', 1, 'output', 300);

这会输出 output-1.png。在用于生产环境前,请自行补充错误检查。

Go

Go 没有内置 PDF 渲染器,但 github.com/gen2brain/go-fitz 封装了 MuPDF:

package main

import (
	"image/jpeg"
	"os"

	"github.com/gen2brain/go-fitz"
)

func main() {
	doc, err := fitz.New("input.pdf")
	if err != nil {
		panic(err)
	}
	defer doc.Close()

	img, err := doc.Image(0)
	if err != nil {
		panic(err)
	}

	out, err := os.Create("output.jpg")
	if err != nil {
		panic(err)
	}
	defer out.Close()

	if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 90}); err != nil {
		panic(err)
	}
}

go-fitz 返回的是 PDF 默认渲染分辨率下的图片。如需 DPI 控制,请查阅该库文档。

Java

Apache PDFBox 是 Java PDF 处理的标准选择。

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class PdfToImage {
    public static void main(String[] args) throws IOException {
        try (PDDocument document = PDDocument.load(new File("input.pdf"))) {
            PDFRenderer renderer = new PDFRenderer(document);
            BufferedImage image = renderer.renderImageWithDPI(0, 300);
            ImageIO.write(image, "png", new File("output.png"));
        }
    }
}

Maven 依赖:

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>3.0.2</version>
</dependency>

renderImageWithDPI 接收从 0 开始的页面索引和一个 DPI 值。

Python

Python 用 pymupdfpdf2image 都能轻松实现。

使用 pymupdf

import fitz

doc = fitz.open("input.pdf")
page = doc[0]

mat = fitz.Matrix(2, 2)
pix = page.get_pixmap(matrix=mat)
pix.save("output.png")

Matrix 控制缩放。若要从 72 DPI 的 PDF 获得约 300 DPI,使用 fitz.Matrix(300/72, 300/72)

使用封装了 Poppler 的 pdf2image

from pdf2image import convert_from_path

images = convert_from_path("input.pdf", dpi=300, first_page=1, last_page=1)
images[0].save("output.jpg", "JPEG", quality=90)

pdf2image 很方便,但要求系统已安装 Poppler。

常见陷阱

  • 忘记 DPI。 PDF 默认渲染通常是 72 或 96 DPI,这个分辨率下文字会发虚。如果质量重要,务必指定输出 DPI。
  • 忽略色彩空间。 CMYK PDF 在没有色彩配置文件的情况下转成 RGB,可能显得过灰或过饱和。
  • 重新编码扫描版 PDF。 如果 PDF 本身已经是 JPEG 扫描件,转成 PNG 并不能恢复丢失的细节,只会让文件更大。
  • 字体替换。 无头服务器有时缺少 PDF 中嵌入的字体,渲染器会找替代字体,导致版式错乱。创建 PDF 时嵌入字体可避免此问题。
  • 页码差一。 代码 API 通常使用从 0 开始的页面索引,命令行工具通常使用从 1 开始。

不同场景的选择

  • 一次性个人使用: macOS 上的 Preview、Windows 上的 PDF 阅读器,或浏览器端转换器。
  • 服务器批量处理: pdftoppmpdf2image
  • 产品内部集成: Java 用 Apache PDFBox,Python 用 pymupdf,TypeScript 用 pdfjs-dist
  • 隐私敏感文件: 使用浏览器端工具,让 PDF 不离开设备。

如果你想在不安装任何东西的情况下最快完成转换,我们的 PDF 转 JPGPDF 转 PNGPDF 转 WebP 转换器完全在浏览器中运行。

更多推荐阅读