らっちゃいブログ

日々の学びと気づきを発信するブログ

【Python】Pillowを使ってサムネイルを生成してみよう

スポンサーリンク

サムネイルといえば ImageMagick ですが、自由にライブラリをインストールできない環境ではアプリケーション内でサムネイルを生成する必要がありますね。

Python では Pillow というライブラリを使ってサムネイルを生成することができますので、本記事では簡単な実装例を紹介してみます。

なお、本記事では Python2.7 ベースで記載することにします。(バイナリを扱う処理は Python3 よりPython2.7の方がわかりにくい気がするので)

インストール

pip を使ってインストールするだけです。

$ pip install pillow

適用にサムネイルを作成してみる

以下は 100x100 のサムネイルを生成するコードです。

from PIL import Image

img = Image.open('original.jpg', 'r')
thumbnail_size = (100, 100)
img.thumbnail(thumbnail_size)
img.save('thumbnail.jpg', 'JPEG')

いかがでしょうか?特に内容を解説しなくても、ぱっと見てなんとなく何をしているかわかりますね。シンプルなサムネイルを作る分には、これだけ知っておけば問題ないです。

ちなみに import 文が PIL となっていますが、これは Pillow が PIL プロジェクトの fork であるためです。PIL 自体の開発は止まっていますので、気にせず Pillow を使うようにしてください。

メモリ上でサムネイルを作成する

さきほどのコードはファイルシステムに保存された画像ファイル名を指定して、そのままファイルシステムへサムネイルを書き出す方式でした。

大体の場合はそれで十分なのですが、場合によってはファイルシステム一切経由せずネットワークのみで完結したいケースもありますので、その方法についても書いておきます。

以下は S3 から読み込んだ画像ファイルをサムネイル化してS3 にそのままアップロードすることを想定したコードになります。

from PIL import Image
from StringIO import StringIO
from io import BytesIO

# S3から画像ファイルを読み込む

img_bytes = fetch_image_from_s3()

img = Image.open(StringIO(img_bytes))
thumbnail_size = (100, 100)
img.thumbnail(thumbnail_size)
thumbnail = BytesIO()
img.save(thumbnail, 'JPEG')

# サムネイルを S3 へアップロードする
upload_thumbnail_to_s3(thumbnail.getvalue())

さきほどとは違い、img オブジェクトはバイトデータを使って取得しています。また、サムネイルの生成についても img.save には書き出し先として BytesIO を渡すようになりました。注意してほしいのが、この方式はメモリへの負担が大きいですので、サイズの大きい画像ファイルを扱う場合には避けるようにしましょう。

exif 情報を考慮してサムネイルを生成する

JPEG 画像を扱う場合の話ですが、前述のサムネイル処理では exif(Exchangeable digicame-exif file format)情報を反映してくれません。

そのため、オリジナル画像が90度回転しているものをサムネイル化すると、横向きのサムネイル画像が作成されてしまいます。(ウェブサービスでたまに横向きのサムネイルを見かけると思いますが、そのほとんどは exif 情報が反映されていないのが原因です)

そういったことにならないよう、サムネイル作成時にオリジナル画像と同じ向きになるように回転させてやる必要があります。 つまりは、ImageMagick でいう --auto-orient オプションと同じ処理を自前で用意しなければいけません。

自前で用意と聞くとめんどくさそうな印象を受けますが、ご安心ください。そんなに難しい処理ではありません。

まずは JPEG 画像から exif 情報を抽出します。 exif 情報の抽出には、Image オブジェクトの _getexifを使います。

img = Image.open('original.jpg', 'r')
exif = img._getexif()

次は回転方法を決定するために Orientation 情報を取得します。 exif 情報は辞書となっており、Orientation値 は 0x112 をキーとして取得することができます。

# exif が Orientation値を持っていないこともあるので、1 (回転なし) をデフォルトとして取得するようにします
orientation = exif.get(0x112, 1)

Orientation 値を得ることができたら、あとは決められた処理で画像を回転させてあげるだけです。

以下は Orientation 値に対応する回転関数を定義したものです。なお、回転関数については、こちらの記事を参考にさせていただきました。

convert_image = {
    # そのまま
    1: lambda img: img,
    # 左右反転
    2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT),
    # 180度回転
    3: lambda img: img.transpose(Image.ROTATE_180),
    # 上下反転
    4: lambda img: img.transpose(Image.FLIP_TOP_BOTTOM),
    # 左右反転&反時計回りに90度回転
    5: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90),
    # 反時計回りに270度回転
    6: lambda img: img.transpose(Image.ROTATE_270),
    # 左右反転&反時計回りに270度回転
    7: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270), 
    # 反時計回りに90度回転
    8: lambda img: img.transpose(Image.ROTATE_90),
}
thumbnail_img = convert_image[orientation](img)

やるべきこととしては以上です。あとは回転した画像をサムネイル化してやればOKですね。

では、最終的に必要となるコードを以下にまとめてみます。

img = Image.open('original.jpg', 'r')
exif = img._getexif()
orientation = exif.get(0x112, 1)
convert_image = {
    # そのまま
    1: lambda img: img,
    # 左右反転
    2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT),
    # 180度回転
    3: lambda img: img.transpose(Image.ROTATE_180),
    # 上下反転
    4: lambda img: img.transpose(Image.FLIP_TOP_BOTTOM),
    # 左右反転&反時計回りに90度回転
    5: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90),
    # 反時計回りに270度回転
    6: lambda img: img.transpose(Image.ROTATE_270),
    # 左右反転&反時計回りに270度回転
    7: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270), 
    # 反時計回りに90度回転
    8: lambda img: img.transpose(Image.ROTATE_90),
}
thumbnail_img = convert_image[orientation](img)
thumbnail_size = (100, 100)
thumbnail_img.thumbnail(thumbnail_size)
thumbnail_img.save('thumbnail.jpg', 'JPEG')

まとめ

いかがでしたでしょうか。ImageMagick ほど楽はできませんが、ある程度なら少ない実装量で実現できることがわかりましたね!

ぜひお試しください。