このページでは、PIL を用いて画像の縦横比(アスペクト比)を保ったまま、矩形に合わせて画像をリサイズ・拡大縮小する方法について解説していきます。
例えば下の図のように画像と矩形が与えられた場合、単純に矩形に合わせて画像をリサイズすると画像の縦横比が変化してしまい、縦方向もしくは横方向に不自然に画像が伸びてしまう可能性があります。見た目が悪くなりますね…。

今回の解説内容に基づいてリサイズを行うようにすれば、下の図のように矩形に合わせて、かつ、画像の縦横比を保ったまま画像をリサイズでき、先程の例のように不自然に画像が伸びてしまうことを防ぐことができます。

Contents
resize メソッドを用いる方法
まずは PIL の resizeメソッドを用いた方法について解説していきます。
縦横比を保ったまま矩形に合わせてリサイズする関数
まず結論を言うと、引数 path で与えられるファイルパスの画像を、引数 size で与えられるサイズの矩形に合わせて縦横比を保ったままリサイズする関数は下記のように記述することができます(以降では、この引数 path で与えられるファイルパスの画像、すなわちリサイズを行う画像を元画像と呼ばせていただきます)。
from PIL import Image
def keepAspectResize(path, size):
# 画像の読み込み
image = Image.open(path)
# サイズを幅と高さにアンパック
width, height = size
# 矩形の幅と画像の幅の比率を計算
x_ratio = width / image.width
# 矩形の高さと画像の高さの比率を計算
y_ratio = height / image.height
# 画像の幅と高さ両方に小さい方の比率を掛けてリサイズ後のサイズを計算
if x_ratio < y_ratio:
resize_size = (width, round(image.height * x_ratio))
else:
resize_size = (round(image.width * y_ratio), height)
# リサイズ後の画像サイズにリサイズ
resized_image = image.resize(resize_size)
return resized_image
以降では、まずは画像の縦横比を保ったまま矩形に合わせてリサイズするやり方の考え方について説明し、その後、上記関数で実行している処理の解説を行なっていきたいと思います。
スポンサーリンク
resize メソッドの動作
まず、resize メソッドは、引数に リサイズ後の画像のサイズ(幅と高さのタプル)を指定して実行することで、そのサイズに合わせて元画像のリサイズを行うメソッドになります。

例えば下記のように元画像のオブジェクトである image に resize メソッドを実行させれば、image の画像を幅 400 ピクセル&高さ 300 ピクセルにリサイズした画像を返却値として得ることができます。
size = (400, 300)
resized_image = image.resize(size)
resize メソッドで縦横比を保ったままリサイズする方法
ただし、resize メソッドでは、単純に引数で指定されたサイズに合わせて画像のリサイズが行われるため、「元画像のサイズ の縦横比」と引数に指定する「リサイズ後の画像のサイズ の縦横比」とが異なると、リサイズ後の画像の縦横比が元々の画像の縦横比から変化してしまうことになります。
逆に言えば、「元画像のサイズ の縦横比」と「リサイズ後の画像のサイズ の縦横比」が同じであれば、リサイズ後の画像の縦横比は元々の画像から変化しません。
したがって、画像の縦横比を保ったままリサイズを行うためには、「元画像のサイズ の縦横比」と引数に指定する「リサイズ後の画像のサイズ の縦横比」が同じであれば良いことになります。
元画像のサイズ の縦横比を持つ リサイズ後の画像のサイズ は、(元画像の幅 * 拡大率, 元画像の高さ * 拡大率) といった感じで、元画像の幅と高さに「同じ値」を掛け合わせることで求めることができます。

resize メソッドで縦横比を保ったまま矩形に合わせてリサイズする方法
前述の通り、リサイズ後の画像のサイズ を (元画像の幅 * 拡大率, 元画像の高さ * 拡大率) のように、元画像の幅と高さに「同じ値」を掛け合わせて求めることで縦横比を保ったままリサイズすることが可能です。
では、矩形に合わせてリサイズを行うためには、この 拡大率 はどうやって求めれば良いでしょうか?
矩形に合わせてリサイズを行う場合、この 拡大率 の候補は下記の2つのいずれかとなります。
元画像の幅に対する矩形の幅の比率(つまり矩形の幅 / 元画像の幅)元画像の高さに対する矩形の高さの比率(つまり矩形の高さ / 元画像の高さ)
前者を 拡大率 として リサイズ後の画像のサイズ を求めた場合、リサイズ後の画像のサイズ における幅と高さは下記のようになります。
リサイズ後の画像の幅:矩形の幅リサイズ後の画像の高さ:元画像の高さ * 矩形の幅 / 元画像の幅
つまり、リサイズ後の画像の幅 に関しては、必ず 矩形の幅 と一致することになります。つまり矩形に合わせてリサイズを行うことが可能です。

では、前述の 拡大率 の候補の後者を 拡大率 として リサイズ後の画像のサイズ を求めた場合はどうでしょう?この場合の リサイズ後の画像のサイズ における幅と高さは下記のようになります。
リサイズ後の画像の幅:元画像の幅 * 矩形の高さ / 元画像の高さリサイズ後の画像の高さ:矩形の高さ
この場合は、リサイズ後の画像の高さ が 矩形の高さ と一致することになります。ですので、この場合も矩形に合わせたリサイズを行うことができることになります。

いずれにしても、矩形に合わせたリサイズを行うことはできるのですが、候補のうちのどちらを 拡大率 を採用するかでリサイズ後の画像のサイズは大きく変わります。
まず、この 拡大率 として、前述の候補の2つのうち小さい方を選択した場合、矩形内に収まるようにリサイズ後の画像のサイズが算出されることになります。

逆に大きい方を選択した場合、矩形外にまではみ出すようにリサイズ後の画像のサイズが算出されることになります。

なので、矩形に合わせてリサイズする際に、矩形内に収まるようにリサイズしたい場合は候補のうちの小さい方を拡大率として選択し、さらに矩形外にはみ出るようにリサイズしたい場合は候補のうちの大きい方を拡大率として選択すれば良いことになります。
スポンサーリンク
keepAspectResize 関数の説明
上記の考えに基づいてリサイズを行うのが、前述で紹介した keepAspectResize 関数になります。
この keepAspectResize 関数は、矩形内に収まるようにリサイズを行う関数であり、下記の2つのうちの小さい方を拡大率として選択しています。
元画像の幅に対する矩形の幅の比率(つまり矩形の幅 / 元画像の幅)元画像の高さに対する矩形の高さの比率(つまり矩形の高さ / 元画像の高さ)
上記の2つの比率を求めているのが下記の箇所になります(image.width と image.height が元画像の幅と高さであり、width と height が矩形の幅の高さとなります)。
# 矩形の幅と画像の幅の比率を計算
x_ratio = width / image.width
# 矩形の高さと画像の高さの比率を計算
y_ratio = height / image.height
さらに、前者の方が小さい場合は、元画像の幅(image.width)に 拡大率(x_ratio )を掛けた値が 矩形の幅(width)となることを前提に、下記で 元画像の高さ * 拡大率(image.height * x_ratio )により リサイズ後の画像の高さ のみの計算を行い、これを リサイズ後の画像のサイズ(resized_size)としています。
if x_ratio < y_ratio:
resize_size = (width, round(image.height * x_ratio))
後者の方が小さい場合は、元画像の高さ(image.height)に 拡大率(y_ratio)を掛けた値が 矩形の高さ(height)となることを前提に、下記で 元画像の幅 * 拡大率(image.width * y_ratio)により リサイズ後の画像の幅 のみの計算を行い、これを リサイズ後の画像のサイズ(resized_size)としています。
else:
resize_size = (round(image.width * y_ratio), height)
最後に、この リサイズ後の画像のサイズ(resized_size)を引数として resize メソッドを実行することで、元画像 image のリサイズを行なっています。
resized_image = image.resize(resize_size)
多少誤差が出る可能性はありますが、おそらく下記の関数でも縦横比を保ったまま矩形に合わせて画像のリサイズを行うことはできると思います。こっちの方がシンプルなので私はこっちをよく使用します。
from PIL import Image
def keepAspectResizeSimple(path, size):
# 画像の読み込み
image = Image.open(path)
# サイズを幅と高さにアンパック
width, height = size
# 矩形と画像の幅・高さの比率の小さい方を拡大率とする
ratio = min(width / image.width, height / image.height)
# 画像の幅と高さに拡大率を掛けてリサイズ後の画像サイズを算出
resize_size = (round(ratio * image.width), round(ratio * image.height))
# リサイズ後の画像サイズにリサイズ
resized_image = image.resize(resize_size)
return resized_image
thumbnail メソッドを用いる方法
続いて thumbnail メソッドを用いて、画像の縦横比を保ったまま矩形に合わせて画像をリサイズする方法について解説していきます。
このリサイズを実現する上では、おそらく resize メソッドを用いるよりも thumbnail メソッドを用いた方が簡単だと思います。が、注意点もいくつかありますので、これらの点には気をつけてください。
thumbnail メソッドを用いたリサイズ
前述の通り、resize メソッドは引数にリサイズ後の画像のサイズを指定する必要があり、このサイズの縦横比と元画像の縦横比が異なる場合、リサイズ時に画像の縦横比が変化してしまいました。
その一方で、thumbnail メソッドはリサイズ時に画像の縦横比が変化しません。さらに、引数で指定したサイズ(幅と高さのタプル)に合わせてリサイズが行われます。また、この時、この引数で指定したサイズからはみ出ないようにリサイズが行われます。

したがって「画像の縦横比を保ったまま矩形に合わせて画像をリサイズする」は、thumbnail メソッドの引数に矩形の幅と高さのタプルを指定するだけで実現することができます。
例えば下記を実行すれば、引数 path で与えられるファイルパスの画像を引数 size で与えられるサイズの矩形に合わせて縦横比を保ったままリサイズすることができ、その結果を thumbnail_image として得ることができます。
thumbnail_image = Image.open(path)
thumbnail_image.thumbnail(size)
resize メソッドの時のように拡大率の計算などを求める必要がないため、簡単に「画像の縦横比を保ったまま矩形に合わせて画像をリサイズする」を実現することができます。
スポンサーリンク
thumbnail メソッドの注意点
ただし、簡単に実現できる分、注意点もあります。この注意点は下記の3つです。
- 実行したオブジェクトの画像が変化する
- 縮小にしか対応していない
- 矩形外にはみ出すようなリサイズに対応していない
実行したオブジェクトの画像が変化する
thumbnail メソッドでは、このメソッドを実行した画像オブジェクトそのものに対してリサイズが行われます。ですので、thumbnail メソッド実行後もリサイズ前の画像を使用したいのであれば、copy メソッドを利用して画像のコピーを行なった後に、thumbnail メソッドを実行する必要があります。
image = Image.open(path)
thumbnail_image = image.copy()
thumbnail_image.thumbnail(size)
resize メソッドの場合は、実行した画像オブジェクトではなく返却されるオブジェクトに対してリサイズが行われることになるため、resize メソッド実行後にリサイズ前の画像を扱いたい場合であっても、上記のようにわざわざ copy メソッドを実行する必要はありません。
縮小にしか対応していない
また、thumbnail メソッドで対応しているのは縮小のみです。リサイズ時に拡大が行われるようなサイズが引数で指定された場合、thumbnail メソッドでは何も行われません。
より具体的には、画像の幅と高さ両方が、引数で指定するサイズの幅と高さ以上である場合、thumbnail メソッドでは何も行われないので注意してください。
矩形外にはみ出すようなリサイズに対応していない
また、thumbnail メソッドでは、引数で指定されたサイズの矩形の中に必ず収まるようにリサイズが行われます。矩形に合わせてリサイズを行う場合に、矩形外にはみ出るようにリサイズしたい場合でも、引数等のパラメータではこれらを指定することができないので注意してください。
まとめ
このページでは、PIL を用いて画像の縦横比(アスペクト比)を保ったまま、矩形に合わせて画像をリサイズする方法について解説しました!
主な方法として、resize メソッドを利用する方法と thumbnail メソッドを利用する方法を紹介しました。
resize メソッドを用いる場合は、矩形に合わせる&縦横比を保つための計算が必要なので若干複雑ですが、自由度は高いかと思います。
また thumbnail メソッドでは簡単に上記のようなリサイズを実現できる分、拡大には対応してないなどの注意点もあるので気をつけてください。あくまでも thumbnail メソッドはサムネイルを作成する目的で利用するのが正しいのかなぁと思います。
画面のサイズやキャンバスのサイズ、用紙のサイズ等に合わせてリサイズを行いたいような場合、今回紹介した「画像の縦横比を保ったまま矩形に合わせて画像をリサイズする」方法は結構使えると思いますので、ぜひこの機会に考え方だけでも理解しておいてください!
また、今回紹介した resize メソッドと thumbnail メソッドの違いについても頭に入れておくと、今後リサイズをする際にどっちを使った方が良いかをすぐに判断できるようになると思います!

