PyMuPDFを探索する：PDFにおける画像の処理

![py-blog-imageextract.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573216/f270809e-b1fa-2b6c-9b2f-f59efb5bdfe2.png)


この記事は、PyMuPDFの機能に関するシリーズの一部です。

PyMuPDFは、MuPDFのPythonバインディングです。[MuPDF](https://mupdf.com/?utm_source=qiita&utm_medium=japanese-blog&utm_content=inline-link) は、軽量なPDF、XPS、電子書籍ビューア、レンダラー、およびツールキットです。PyMuPDFとMuPDFは、[Artifex Software, Inc.](https://artifex.com/?utm_source=qiita&utm_medium=japanese-blog&utm_content=inline-link)によってメンテナンスおよび開発が行われています

(Py-) MuPDFは、PDF、XPS、OpenXPS、CBZ、MOBI、EPUB、およびFB2（電子書籍）形式のファイルにアクセスできます。また、そのパフォーマンスの高さとレンダリング品質の高さで知られています。

PyMuPDFのホームページは [GitHub](https://github.com/pymupdf/PyMuPDF) にあります。`pip install pymupdf` を使用して、[PyPI](https://pypi.org/project/PyMuPDF/) からインストールすることができます。

要約
-----

PDFのテキスト抽出や挿入に加えて、同様の方法で画像を扱うこともしばしば求められます。

- **画像の抽出**： 文書に埋め込まれた画像（すべてまたは選択したいくつか）を抽出し、PNGやJPEGなどの通常の画像ファイルとして保存したい場合。
- **画像の挿入**： PDFを作成し、テキストと並んで特定の位置に`画像を挿入したい場合もあります。
- **画像の置換**： 別の要件として、埋め込まれた画像のためにPDFが大きすぎる場合があります。多くの画像はカラフルで解像度が高すぎるため、同じ目的にグレースケールのバージョンと適度な解像度で十分です。
- **画像の削除**： 画像を表示しないか、表示する必要がない場合もあります。
- **画像の再配置**： さらに難しい場合もあります。画像が適切な位置に表示されず、ボックスサイズが小さすぎたり、回転が正しくなかったりする場合があります。

これらのすべての状況において、PyMuPDFは助けになります。

画像の抽出
----------

これには少なくとも2つの方法があります。

メソッド1は、PDFに限らず、**すべてのドキュメントタイプで利用できます**。画像は、PyMuPDFの記事 [「PyMuPDFによるテキスト抽出」](https://qiita.com/jamie-lemon/items/b6a1d535ff13d2dff018)  で説明されているいくつかのページテキスト抽出のバリアントの一部として提供されます。

一般的なコーディングパターンは次のようなものです：

```
doc = fitz.open("some.file")  # open some supported document

# iterate over the pages
for page in doc:
    img_number = 0  # for enumerating images per page
    # iterate over the image blocks
    for block in page.get_text("dict")["blocks"]:
        # skip if no image block
        if block["type"] != 1:
            continue
        # build filename, like 'img17-3.jpg'
        name = f"img{page.number}-{img_number}.{block['ext']}"
        out = open(name, "wb")
        out.write(block["image"])  # write the binary content
        out.close()
        img_number += 1  # increase image counter
```
各画像ブロックには多くのメタデータが含まれており、関連する画像を選択したり、潜在的な重複を回避したりするのに役立ちます。

メソッド2は **PDFドキュメントにの** み適用されます。テキストの抽出や個々のページへのアクセスは必要ありません。PDF固有の情報を使用できるためです。

PDFのオブジェクト定義を反復処理し、画像オブジェクトのみを選択します。ページへのアクセスを回避することで、PDFの内部構造が正しくない場合でも画像を正常に抽出することができます。PDFの損傷は残念ながら稀ではなく、インターネット経由での不完全なダウンロードによってほとんど発生します。

```
doc = fitz.open("some.pdf")  # open the PDF

xreflen = doc.get_xreflength()  # count of all objects in file
# we will iterate through all objects in the PDF and select images
for i in range(1, xreflen):  # do not access item 0 of the table
    if doc.xref_get_key(i, "Subtype")[1] != "/Image":  # check if image
        continue  # not an image, skip
    # this is an image!
    img = doc.extract_image(i)  # extract it and store its content
    # build filename, like 'img-4711.png'
    name = f"img-{i}.{img['ext']}"
    out = open(name, "wb")
    out.write(img["image"])  # write the binary content
    out.close()
```

PDFドキュメントでは、このタスクの他のバリエーションも利用可能です。最良の結果を得るために選択できるスクリプトを作成しました：

[extract1](https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/examples/extract-imgb.py)  は、上記の戦略に従う単独のスクリプトであり、さらに大きさが十分であり、単色ではないなどの基準を満たす画像を選択します。
[extract2](https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/examples/extract-images/extract.py)  はページごとに画像を抽出し、前のスクリプトと似たような選択基準を適用します。

画像の挿入
-----------

PDFページに画像を表示して改善したいですか？ または、すべてのページの左上隅に会社のロゴを配置したいですか？ または、透かしを追加したいですか？

これらすべては、PyMuPDFの `Page` クラスの `insert_image()` メソッドを使用して実行できます。

このメソッドは、3つの異なるソースからの入力をサポートしています：画像ファイル、メモ内の画像、およびMuPDF独自の画像形式であるPixmap。

画像は、ページ上の指定された矩形に挿入することができます。その矩形は任意のサイズであり、その幅と高さの比率は画像と異なる場合もあります。

画像は、その中心と矩形の中心が一致するようにスケーリングされて配置されます。

オプションで、90度、180度、または270度の画像回転も選択できます。

画像挿入プロセスの**最適なパフォーマンスを実現**するために、多くの注意が払われています：

- このメソッドは、以前に他の場所に挿入した画像の追跡を自動的に行います。
- さらに、プログラマーはメソッドのパラメーターで任意の画像を明示的に識別することもできます。

どちらの場合でも、既存の画像への参照がページのオブジェクト定義に配置されます。
したがって、たとえ100,000ページにロゴを挿入しても、高速かつファイルサイズの影響は最小限に抑えられます！

これがメソッドの呼び出しパターンの重要な部分です：

```
page.insert_image(
    rect,  # the desired rectangle
    filename=filename,  # image file
    stream=buffer,  # image in memory
    pixmap=pixmap,  # image from pixmap
    rotate=angle,  # 0, 90, 180, 270
    xref=0,  # >0 refers to an existing image
    # more parameters
)
```

画像の置き換えまたは削除
------------------------

いくつかの使用例があります。例としては：

- 各ページに古い会社のロゴ画像が表示されているPDFがあり、ファイルを再作成せずにそれを置き換えたい場合。
- 画像を置換したい場合：たとえば、PNG形式の画像をJPEGバージョンや透明度を持つバージョン、または異なる色空間（例：カラーからグレースケール）に変更したい場合。
- 画像を削除したい場合。

PDFに埋め込まれた画像を置換または削除するために、以下の手順を使用することができます。

新しい画像は、古い画像が表示されていた場所、つまり古い画像を使用しているすべてのページに表示されます。新しい画像は、どこでも同じ矩形領域をカバーします。

各ページのレンダリング指示は変更を実現せず、従って以前と同じアスペクト比を仮定します。もし変更があれば、新しい画像は何らかの方法で歪んで表示されることになります。

このタスクを実行するためには、以下のアイテムが必要です：

1. ファイル [replacer.py](https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/image-replacement/replacer.py)  から `img_replace` メソッドをインポートします。
2. 古い画像の `xref` を知る必要があります。
3. 1つのPDFのページが必要です。通常は古い画像が表示されるページですが、必須ではありません。
4. 新しい画像が必要です。これはファイル名、メモリ内の画像データ、または `Pixmap` 形式のいずれかで提供できます。

それから、以下の手順を実行するだけです：

```
import fitz
from replacer import img_replace

doc = fitz.open("your.pdf")
page = doc[nnn]  # any page of doc
# ----------------------------------------
# find out the xref of the old image
# see some hints further down
# ----------------------------------------
filename = "image.jpg"  # some image
# or equivalently a bytes object containing it
# or equivalently a fitz.Pixmap
img_replace(page, xref,
    filename=filename,  # or one of the following:
    stream=None,  # alternatively
    pixmap=None,  # alternatively
)
doc.save("your-new.pdf",garbage=4)  # save changed PDF
```

古い画像を `xref`で削除するには、上記のスニペットを以下のように変更してください：

```
pix = fitz.Pixmap(fitz.csGRAY, (0, 0, 1, 1), 1)  # small pixmap
pix.clear_with()  # empty its content
img_replace(page,xref,
    pixmap=pix,
)
```
ここでは、小さな（2 x 2）の透明な`Pixmap`を作成し、それを元の画像と置き換えるために使用しました。この`Pixmap`は見えないため、画像が表示されていない場合と同じ効果があります。全体として、ファイルでは200バイト未満の容量が必要です。

画像のXREFの検索
-----------------
では、画像のクロスリファレンス番号（`xref`）をどのように見つけることができるでしょうか？

次のように、ページに表示される画像のリストを作成します：

```
In [1]: import fitz
In [2]: doc = fitz.open("original.pdf")
In [3]: page = doc[0]
In [4]: page.get_images()
Out[4]: [(46, 0, 439, 501, 8, 'DeviceRGB', '', 'fzImg0', 'FlateDecode')]
```

ここでは簡単です：このページには1つの画像しかありませんし、その`xref`は46です。もし複数の画像がある場合、表示位置（境界ボックス - bbox）を以下のように表示します：

```
In [5]: for item in page.get_images():
   ...:  xref = item[0]
   ...:  print(xref, page.get_image_rects(xref))
   ...:
46 [Rect(200.00001525878906, 240.63067626953125,
         497.6600341796875, 580.3292236328125)]
```

おそらく、画像がページのどこに表示されているかを知っているため、適切な`xref`を見つけることができます。

画像の位置を変更する
-------------------

画像を挿入する際、それがページ上でどのように見えるかを事前に予測することは難しい場合があります。

正しい位置に配置されていますか？矩形は十分に大きいですか？他のコンテンツとの重なりはありますか？

ここで、あなたをサポートするための [GUIスクリプト](https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/examples/edit-images/edit.py)を開発しました。

使用するには、最新バージョンのwxPythonをインストールする必要があります（`python -m pip install wxpython`）。それから、単にスクリプトを起動します。

それによってPDFを選択することができ、準備が整います！ページをめくり、既存の画像を選択したり、画像ファイルから選んで新しい画像を挿入したりすることができます。

画像をページ上で移動したり、表示矩形のサイズを変更したり、画像を完全に削除したりすることができます。

以下は、インターフェースの[ビジュアル](https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/examples/edit-images/figure-01.jpg)イメージです。

まとめ
-------

この記事では、PyMuPDFの画像処理機能について概要を提供しました。

PyMuPDFでは、MuPDFでサポートされているすべてのドキュメントタイプから画像を**抽出**することができます。これには、PDF、XPS、EPUB、MOBI、HTMLやXMLなどのインターネット形式などが含まれます。

画像の**挿入**はPDFファイルに対して可能です。

既存のPDF文書内の画像**を置換、削除、または再配置する**ためのツールが存在します。

PyMuPDFは、大規模かつ高機能なドキュメント処理のPythonパッケージです。優れたパフォーマンスと高品質なレンダリングに加えて、その優れた [ドキュメンテーション](https://pymupdf.readthedocs.io/ja/latest/?utm_source=qiita&utm_medium=japanese-blog&utm_content=inline-link-documentation) でも知られています。PDF版のドキュメントは現在、420ページ以上（レターサイズ）あり、そのうち70ページ以上がHow-To形式のレシピに充てられています。必読の価値があるでしょう。

もう1つの知識源は、[ユーティリティ](https://github.com/pymupdf/PyMuPDF-Utilities)リポジトリです。PDFを扱う際に何をする予定があっても、そこでスタートを切るためのいくつかのサンプルスクリプトを見つけることができるでしょう。

PyMuPDFに関する質問がある場合は、[#pymupdf](https://discord.gg/TSpYGBW4eq) Discordチャンネルで開発者に連絡することができます。開発者は、ご質問に対してガイダンスやサポートを提供することができます。