見出し画像

DjangoでPillowを使って登録画像を特定のサイズにリサイズする

Webアプリケーション開発で画像を扱えると、実装できる機能が一気に広がると思います。例えばアカウントのプロフィール画像を登録できるようにしたり、SNSのような画像を投稿できる機能があります。

ただしこれらの機能はTwitterなどで使っているとあまりに簡単に使えるのでなかなか意識しないのですが、実際に実装することを考えると厄介です。

例えば何GBもの大きな容量の画像を、自由にDBに登録できてしまう状態だとどうなるでしょうか? 仕組みがわかっていれば簡単にサイバー攻撃されてサーバーが落ちてしまいます。大容量の画像を送りまくればいいわけです。

そうでなくても最近のスマホ写真は画質が良いので、容量がでかい画像をそのまま登録しているとDBの容量をやたら食ってしまいます。

そこでPillowの出番です。画像を読み込んで自由に編集できます。今回は簡単なDjangoアプリで登録した投稿の画像を指定サイズにリサイズするコードを考えます(Djangoがある程度わかってる人向け)。縦横比は崩れないようにします。

 まずpipでインストールします。

pip install Pillow 

models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=80)
    picture = models.ImageField(blank=True, null=True)   

    def __str__(self):
        return self.title

forms.py

from django import forms

class PostCreateForm(forms.ModelForm):
    class Meta:
        model = Recipe
        fields = ('title', 'picture')

templates(create_post.html)

<div class="container text-sans-serif">

    <form method="POST" enctype='multipart/form-data'>
    {% csrf_token %}

    <div style="margin: 20px;font-size: smaller;">
        
            タイトル<br>
            {{ form.title }}<br><br>
     
            画像<br>
            {{ form.picture }}<br><br>
     
            <button class="btn btn-primary" type="submit" style="width: 85vw">登録する</button>
       
    </div>
                    
    </form>

</div>
    
    

views.py

from django.views import generic
from .models import Post
from .forms import PostCreateForm
import os

class PostCreateView(generic.CreateView):
    model = Post
    template_name = 'create_post.html'
    form_class = PostCreateForm

    def form_valid(self, form):
        save_path = str("media/"+self.request.FILES['picture'])
        up_data = self.request.FILES['picture']
        with open(save_path, 'wb+') as i:
            for chunk in up_data.chunks():
                i.write(chunk)
        with PIL.Image.open(save_path) as image:
       os.remove(save_path)

            # 画像の縦横比をそのままに480×640以下にリサイズ
            resized_height = 640 / int(image.size[0]) * image.size[1]
            resized_image = image.resize((640, int(resized_height)))
            if resized_height > 480:
                resized_width = 480 / int(image.size[1]) * image.size[0]
                resized_image = resized_image.resize((int(resized_width), 480))

            image_io = io.BytesIO()
            resized_image.save(image_io, format="JPEG")
            image_file = InMemoryUploadedFile(image_io, field_name=None, name=save_path,
                                              content_type="image/jpeg", size=image_io.getbuffer().nbytes,
                                              charset=None)

        post = form.save(commit=False)
        post.picture = image_file
        post.save()

        return super().form_valid(form)

こんな感じになります。フォームからアップロードされた画像を縦横で480×640以下にリサイズしています。

まずフォームにアップされた画像はInMemoryUploadedFileというデータ型になっていて、そのままではPillowで読み込めない(そんなことないよという方がいましたらぜひコメントで教えてください)ので一旦ディレクトリ内にファイル保存しています。「media」フォルダをmanage.pyと同じ階層に作っておきましょう。読み込んだ後、os.removeで画像ファイルを掃除するのを忘れずに(DB容量節約してる意味がなくなります)。

その後Image.openで画像を読み込み、元の画像サイズを読み込んで何やかんやします。具体的には縦と横のサイズを別々に合わせます。

まず横サイズのリサイズ値である640に合わせるため、640を元サイズ(横)で割って比を算出し、これを元サイズ(縦)にかけてリサイズ後の縦のサイズ(resized_height)を求めます。この後resized_heightが指定値の480以上なら足が出てしまうので、分岐させて今度は縦横入れ替えて同じ処理をします。こうすれば縦横比が崩れません。これで合ってるはず(数学苦手な人)。

その後InMemoryUploadedFileに戻してmodelオブジェクトに上書きして保存すればOKです。Jpegなら大体100KB以下になってくれます。DBも節約できて良いですね。

Pythonで困ったことがおありでしたら、ココナラで相談受けてますのでよかったらこちらもどうぞ!


サポートは料理好きなのでの食材費にさせていただきます。