[Django]서버사이드 이미지 크롭/리사이징 하기

프론트엔드에서 Javascript로 온갖 삽질을 하다가, 그냥 서버사이드에서 처리하기로 했다.
훨씬 깔끔하게 끝났다(물론 이것도 삽질했지만 ^^).
HTML Canvas로 한 프론트 리사이징은 이미지 퀄리티도 안좋게 떨어진다. 웬만하면 파일 처리는 서버사이드에서 하는 것을 추천한다.

상황

내 모임 정보를 업로드 할 때, 썸네일을 올리면 자동으로 정사각형으로 크롭, 리사이징 시켜서 서버에 업로드 하고싶다.
Django Form에서 save()함수를 오버라이드 해서 전처리하는 방식으로 해결하자.

Forms.py

class MeetupEditForm(forms.ModelForm):
    desc = forms.CharField(widget=SummernoteWidget())

    class Meta:
        model = Meetup
        exclude = ('created_date', 'modified_date', )
        fields = ('title', 'desc', 'image_file', 'location', 'meetup_date', 'lat', 'lon', 'tags', )

    # (선택) 아래의 save함수에서 self.request.user를 쓰기 위해 views.py에서 전달해주었다.
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request')
        super(MeetupEditForm, self).__init__(*args, **kwargs)

    # form이 save될때 불리는 함수. 오버라이드해서 원하는 기능을 넣는다.
    def save(self, commit=True):
        instance = super(MeetupEditForm, self).save(commit=False)
        instance.author = self.request.user
        instance.published_date = timezone.now()

        # 이미지파일이 있으면, 리사이즈/크롭 한다.
        if instance.image_file:
            instance.image_file = self.rescale(self.cleaned_data.get('image_file'), 600, 600, force=True)
        if commit:
            instance.save()
        return instance

form save를 할 때, commit=False로 디비에 쓰는걸 잠시 막아두고, 전처리를 한다.
form에서 받은 이미지 파일과 원하는 width, height를 넘기면 리사이즈된 이미지파일이 넘어오는 함수를 짠다.

def rescale(self, data, width, height, force=True):
        from io import BytesIO
        from PIL import Image as pil

        max_width = width
        max_height = height

        input_file = BytesIO(data.read())
        img = pil.open(input_file)
        if not force:
            img.thumbnail((max_width, max_height), pil.ANTIALIAS)
        else:
            src_width, src_height = img.size
            src_ratio = float(src_width) / float(src_height)
            dst_width, dst_height = max_width, max_height
            dst_ratio = float(dst_width) / float(dst_height)

            if dst_ratio < src_ratio:
                crop_height = src_height
                crop_width = crop_height * dst_ratio
                x_offset = int(src_width - crop_width) // 2
                y_offset = 0
            else:
                crop_width = src_width
                crop_height = crop_width / dst_ratio
                x_offset = 0
                y_offset = int(src_height - crop_height) // 3
            img = img.crop((x_offset, y_offset, x_offset+int(crop_width), y_offset+int(crop_height)))
            img = img.resize((dst_width, dst_height), pil.ANTIALIAS)

        image_file = BytesIO()
        img.save(image_file, 'JPEG')
        data.file = image_file
        return data

MeetupEditForm클래스에 넣어준다.

https://djangosnippets.org/snippets/224/ 를 python3버전으로 바꿨다.
StringIOBytesIO로 변경
– offset계산시 나눌 때 /말고 //로 변경
– return하는 데이터 변경

force옵션을 켜면 지정한 width, height비율로 크롭되고, 끄면 max width, max height로만 적용된다.

views.py

class MeetupFormView(FormView):
    template_name = "meetup_edit.html"
    form_class = MeetupEditForm

    def get_success_url(self):
        return reverse('meetup_list')

    # form.py에 kwargs 넘기기 위함
    def get_form_kwargs(self):
        kw = super(MeetupFormView, self).get_form_kwargs()
        kw['request'] = self.request
        return kw

    def form_valid(self, form):
        form.save(self.request)
        return super(MeetupFormView, self).form_valid(form)

view에서 save가 호출되면, form에서 우리가 지정한 함수들이 실행된다.

Refer

https://djangosnippets.org/snippets/224/

Published by

Yurim Jin

아름다운 웹과 디자인, 장고와 리액트, 그리고 음악과 맥주를 사랑하는 망고장스터

One thought on “[Django]서버사이드 이미지 크롭/리사이징 하기”

  1. 궁금한게 서버단에서 이미지처리를 하면 서버에 부담은 없나요??

    리소스를 많이 잡아먹을것 같은데 궁금해서 남겨봐요!

Leave a comment