深掘り

PDFページを画像に変換する方法:実践ガイド

koboshiCo-founder
·4 分で読めます
PDFページを画像に変換する方法:実践ガイド
要約

PDFページは必ずしも必要な場所に収まるわけではない。本ガイドでは、PDFを画像に変換すべき場面、各アプローチのトレードオフ、OS標準の方法、そして5言語のコード例を解説する。

フロントエンド開発者が40ページのブランドガイドラインPDFを受け取り、3ページだけFigmaボードに貼り付けたい。サポートエンジニアがデータシートの図をSlackスレッドに貼りたい。弁護士が契約書の署名済みページをメールに添付し、ファイル全体は送りたくない。

PDFは固定レイアウトドキュメント向けに作られている。Web、画像エディタ、チャットアプリはピクセル向きだ。PDFページを画像に変換することでその溝を埋められるが、選ぶ方法によって画質、ファイルサイズ、自由度が変わる。

PDFがこの作業に向いている点、面倒な点

PDFはページを描画コマンドのストリームとして保持する:このグリフを配置、ベクターを描画、画像をこのサイズでレンダリング。これにより解像度に依存せず、視覚的に一貫した出力が得られる。同時に、PDFは画像ではない。画像に変換するには、何らかの処理がこれらのコマンドをラスターキャンバス上にレンダリングする必要がある。

利点:

  • テキストとベクターは数学的に記述されているため、どの倍率でも鮮明に保たれる。ピクセルとして保存されていないからだ。
  • 1つのPDFに数百ページをまとめられる。
  • フォント、カラープロファイル、アノテーションが文書と一緒に移動する。

難点:

  • PDFリーダー間でレンダリングが異なる。同じページでもAdobe Acrobat、Preview、Chrome、ヘッドレスライブラリで微妙に見え方が変わる。
  • スキャンPDFはPDFコンテナに包まれた画像に過ぎないため、「変換」は再エンコードを意味し、ノイズが入ったりファイルが肥大したりする。
  • 透過、レイヤー、インタラクティブフォームを含む複雑なPDFは、予測できない形でフラット化されることがある。
  • ページサイズはさまざまだ。US Letterページは72 DPIで612×792ピクセル。300 DPIでは2550×3300だ。解像度を指定しなければ、使えない出力になることがある。

実際にPDFが変換される先

多くの変換作業は3つの出力形式のいずれかに該当する。用途はそれぞれ異なる。

FormatBest forTrade-off
JPG写真、プレビュー、メール添付、Webギャラリーロス圧縮だがファイルサイズが小さい
PNGスクリーンショット、図解、透過が必要なもの非可逆圧縮、写真ではJPGより大きい
WebPモダンなWeb、アプリ、帯域が気になる場所JPG/PNGより小さいが、普及率はやや低い

次に実務上のパラメータがある:

  • 1ページか一括か。 1ページなら簡単。請求書のフォルダなら自動化が必要だ。
  • DPI。 150 DPIはサムネイルに十分。300 DPIは印刷やOCRの標準。600 DPIは細部を拡大して確認しない限りオーバースペックだ。
  • カラースペース。 画面向けにはRGBが無難だ。CMYKのPDFをRGBに単純に変換すると、色がずれることがある。

適切なアプローチの選択

最適なツールは、何を最適化したいかに依存する。

ブラウザベースの高速変換

JPG、PNG、WebPとしてページが必要で、サーバー必須ほど機密性が高くないなら、ブラウザベースの変換ツールが最も手軽だ。当社のPDFをJPGへPDFをPNGへPDFを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を使う

Poppler for Windowsをインストールし、PowerShellからpdftoppmを使う:

pdftoppm -jpeg -r 300 input.pdf output

これによりoutput-1.jpgoutput-2.jpgのように、各ページごとに1枚、300 DPIで出力される。

透過背景のPNGが必要な場合:

pdftoppm -png -r 300 input.pdf output

1ページだけの場合:

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は高速でプライバシー性も高いが、一度に1ページしか処理できない。

ターミナルで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での変換

Linuxでは通常、Popplerが最適な選択だ。

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の最初のページを1枚ずつ画像にしたい場合:

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

ImageMagick

ImageMagickはLinuxでも動作するが、Ghostscript経由でラスタライズするため、PDFの処理は通常遅い:

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

-densityはPDFを読み込む前に指定する。後に置いても効果がない。

コードによる変換

TypeScript / Node.js

pdfjs-distを使ってページをキャンバスにレンダリングし、画像データとして書き出す。

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が必要なら、約4.17のスケールを使う。

ブラウザでは、当社のPDFをPNGへ変換ツールが同じキャンバスへのレンダリングを、ファイルをどこにも送信せずに行う。

PHP

PHPはpdftoppmを直接呼び出すか、ImageMagickをラップするspatie/pdf-to-imageのようなライブラリを使える:

<?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

JavaでのPDF作業にはApache PDFBoxが標準的な選択だ。

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:

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

renderImageWithDPIは0始まりのページインデックスとDPI値を受け取る。

Python

Pythonではpymupdfまたはpdf2imageで簡単に変換できる。

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)を使う。

pdf2imageを使う場合(Popplerをラップしている):

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作成時にフォントを埋め込んでおけば防げる。
  • ページ番号の1つずれ。 コードのAPIは通常0始まりのページインデックスを、コマンドラインツールは通常1始まりのページ番号を使う。

場面に応じた選択

  • 1回限りの個人利用: macOSのPreview、WindowsのPDFリーダー、またはブラウザベースの変換ツール。
  • サーバーでの一括処理: pdftoppmまたはpdf2image
  • プロダクト内: JavaならApache PDFBox、Pythonならpymupdf、TypeScriptならpdfjs-dist
  • 機密性の高いファイル: ブラウザベースのツールを使い、PDFが端末から外に出ないようにする。

何もインストールせずに最速で変換したい場合、当社のPDFをJPGへPDFをPNGへPDFをWebPへ変換ツールは完全にブラウザ内で動作する。

その他のおすすめ記事