PDFファイルを画像ファイルに変換する（pdf2image）

2018年時点のことになりますが、仕事でPythonを使っていて、あるプロジェクトでPDFファイルを画像ファイルに変換する必要があり、利用できるライブラリについて調べました。

最初は**ImageMagic**を試したのですが、変換した画像ファイルにノイズが少し入っていることがあり、後続処理で余計に実装しなければならなかったので使用するのを断念しました。

次に試したのが**pdf2image**で、より綺麗に画像ファイルへ変換することができたので、pdf2imageを利用することに決めました。ちなみにpdf2imageはPDFファイルを画像ファイルに変換するユーティリティであるpdftoppmとpdftocairoをラッピングしたPythonモジュールになります。

## インストール
pdf2imageを使用するには、**poppler**をインストールする必要があります。

Windows
1. http://blog.alivate.com.au/poppler-windows/ から最新のパッケージをダウンロードします。
2. ダウンロードしたパッケージを解凍します
3. 解凍したディレクトリをシステムの任意の場所に移動します。
4. bin/ディレクトリをPATHに追加します。
5. cmd を開いて、pdftoppm -h を呼び出しできることを確認し、すべてが成功したことをテストしてください。

Mac
`brew install poppler`

Linux（Ubuntu）
`sudo apt-get install poppler-utils`

## 使用方法

PDFファイルから画像ファイルへ変換するメソッドは`convert_from_path`と`convert_from_bytes`があります。

| 番号 | パラメータ | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-|
| 1 | pdf_path | None | convert_from_pathメソッドを使用する場合に必ず指定。PDFファイルへのパスを示す文字列または pathlib.Pathオブジェクト。 |
| 2 | pdf_bytes | None | convert_from_bytesメソッドを使用する場合に必ず指定。PDFファイルのバイトデータ。 |
| 3 | dpi | 200 | ドット/インチは、出力PDFの相対的な解像度と見ることができ、高いほど良いですが、300以上のものは通常、肉眼では識別できません。特に圧縮しないファイル形式（PPMなど）を使用する場合、出力画像のサイズに直接関係することを覚えておいてください。 |
| 4 | output_folder | None | 生成したファイルの出力ディレクトリ。出力フォルダというよりも、「作業ディレクトリ」として見るべきでしょう。変換した画像はシステムメモリを節約するために指定したディレクトリに保存します。 |
| 5 | first_page | None | 変換する開始ページを指定します。first_page=5は1〜4ページ目をスキップする（5〜最終ページを変換する）。 |
| 6 | last_page | None | 変換する最終ページを指定します。last_page=5は6ページ以降をスキップする（1〜5ページを変換する）。 |
| 7 | fmt | "ppm" | ファイル形式または出力画像。サポートしている形式は、ppm、jpeg、png、tiff。 |
| 8 | jpegopt | None | jpeg出力形式に関する設定のため、fmt='jpeg'と一緒に使用します。<br>quality：JPEGの画質を選択します。値は0〜100の整数である必要があります。<br>progressive：JPEG出力を選択する。指定できる値はTrue, Falseで、それぞれプログレッシブ（はい）、非プログレッシブ（いいえ）であることを示します。<br>optimize。JPEG出力のために最適なハフマン符号化テーブルを計算するかどうかを設定します。これは、より小さなファイルを作成しますが、データに対して余分なパスを行います。この値はTrueまたはFalseでなければならず、Trueの場合は最適化が行われ、そうでない場合はデフォルトのハフマンテーブルが使用されます。 |
| 9 | thread_count | 1 | PDFを変換する際に使用するスレッドの数。実際のページ数に制限されます。 |
| 10 | userpw | None | PDFにパスワードを設定している場合、パスワードを指定します。 |
| 11 | use_cropbox | False | デフォルトのmediaboxの代わりにPDF cropboxを使用します。これはかなり暗い機能で、モジュールがあなたのデータで動作しないようであれば、trueに設定されるべきです。 |
| 12 | strict | False | PDFが部分的に不正確な場合、PDFSyntaxErrorが発生するようにします。ほとんどのPDFは部分的に不正であり、標準準拠があなたのユースケースに最も重要でない限り、Falseに保たれるべきです。 |
| 13 | transparent | False | 白い背景を返す代わりに、PDFの背景を透明にします。透明度をサポートするファイルフォーマットでのみ使用できます |
| 14 | single_file | False | PDFの先頭ページのみを変換し、出力ファイル名にはインデックスを付加しません。 |
| 15 | output_file | uuid_generator() | 出力ファイル名。通常は文字列を指定するが、文字列ジェネレータを受け取ることもできる。output_folderを設定している場合有効 |
| 16 | poppler_path | None | popplerのライブラリと実行可能ファイルを含むディレクトリへのパス。 |
| 17 | grayscale | False | Trueの場合グレースケール画像を返します。fmtがppmまたはtiffの場合のみ有効 |
| 18 | size | None | 出力画像のサイズ。Noneを指定すると、アスペクト比を維持したままリサイズします。有効なサイズの例は以下の通り。size=400 は、画像を 400x400 の枠に収め、アスペクト比を保持します。size=(400, None) は、アスペクト比を維持したまま、画像を400ピクセル幅にします。size=(500, 500) は、画像を 500x500 ピクセルにリサイズし、アスペクト比を維持しない。上記動作は、-scale-to, -scale-to-x, -scale-to-y パラメータから直接派生しています。 |
| 19 | paths_only | False | プリロードした画像ではなく、画像パスのリストを返します（※output_folderをしている場合のみ有効） |
| 20 | hide_annotations | False | リンクのバウンディングボックスと他のPDF注釈を隠す。現状、pdftoppmにのみ実装しているので、 pdftocairoフラグと組み合わせることはできません。 |

バージョン1.16.0で、`convert_from_path`メソッドを使って実際にいくつかのパラメータを動作検証したいと思います。
まずは必要なモジュールをインストールします。

```
$ from pdf2image import convert_from_path, convert_from_bytes
$ import tempfile
```

### 3. dpi
```

$ images = convert_from_path('/tmp/files/AAPL_10-Q.pdf', fmt='jpeg', dpi=300)

$ images[0].info['dpi']
(300, 300)
```
300dpiの画像に変換できました。

### 4. output_folder
```
with tempfile.TemporaryDirectory() as path:
    images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', output_folder=path)
    
images[0].filename
Out[23]: '/var/folders/zc/df1_19fs0jlgw3_nmlcdys800000gn/T/tmpqldzldx2/46f65646-b9fe-409d-80ee-8f593e620d87-01.jpg'
```
メモリにロードしながらも一時ファイルとして変換できました。

### 5. first_page
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', dpi=300)

$ len(images)
28

$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', first_page=5)

$ len(images)
24
```
5〜28ページを変換できました（1〜4ページ目はスキップ）。

### 6. last_page
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', dpi=300)

$ len(images)
28

$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', last_page=5)

$ len(images)
5
```
1〜5ページを変換できました（6ページ目以降はスキップ）。

### 13. transparent
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', transparent=True)

$ images[0].mode
'RGB'

$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='png', transparent=True)

$ images[0].mode
'RGBA'
```
背景を透明にした画像ファイルに変換できました。

### 14. single_file
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', single_file=True)

$ len(images)
1
```
1ページ目だけ変換できました。

### 15. output_file
```
$ with tempfile.TemporaryDirectory() as path:
      images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', output_folder=path, output_file='AAPL_')
    
$ images[0].filename
'/var/folders/zc/df1_19fs0jlgw3_nmlcdys800000gn/T/tmpn1w7m6ev/AAPL_0001-01.jpg'
```
ファイル名に「AAPL_」のプレフィックスをつけることができました。

### 17. grayscale
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', grayscale=True)

$ images[0].mode
'RGB'

$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', grayscale=True)

$ images[0].mode
'L'

$ images = convert_from_path('/Users/shinichi/Downloads/AAPL_10-Q.pdf', fmt='tiff', grayscale=True)

$ images[0].mode
'L'
```
形式がppmとtiffの場合、グレースケールに変換できました。

### 18. size
```
$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg')

$ images[0].size
(1700, 2200)

$ images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', size=(500, None))

$ images[0].size
(500, 648)
```
縦横比を維持したままサイズ変更できました。

### 19. paths_only
```
$ with tempfile.TemporaryDirectory() as path:
      images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', output_folder=path)

$ images[0]
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1700x2200>

$ with tempfile.TemporaryDirectory() as path:
      images = convert_from_path('/tmp/files/Downloads/AAPL_10-Q.pdf', fmt='jpeg', output_folder=path, paths_only=True)
    
$ images[0]
'/var/folders/zc/df1_19fs0jlgw3_nmlcdys800000gn/T/tmpt3_1y3z_/639ff5f6-e666-4bde-820a-33c94f237e05-01.jpg'
```
output_folderを指定している場合のみ、メモリにロードしないで一時ファイルとして保存したファイルのパスを取得できました。

### 参考
[pdf2image 1.16.0](https://pypi.org/project/pdf2image/)
[pdf2image Overview](https://pdf2image.readthedocs.io/en/latest/overview.html)
[pdf2image.py](https://github.com/Belval/pdf2image/blob/master/pdf2image/pdf2image.py)