Как стать автором
Обновить

Django, ImageField и .webp. Ещё немного про ускорение web приложения и экономию дискового пространства

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров7K

Доброго времени суток уважаемый читатель. Хотелось бы немного поговорить об оптимизации наших с вам любимых WEB приложений, написанных на нашем горячо любимом и всеми уважаемом фреймворке Django. В частности речь в этой статье пойдёт об оптимизации изображений. А теперь по порядку.

А что там Google Lighthouse?

Если вы хоть раз нажимали правой кнопкой мыши на экране вкладки, открытой в Google Chrome, а затем щёлкали "Просмотреть код", то Вы могли видеть инструмент для анализа вашего приложение под названием Lighthouse:

Lighthouse собственной персоной.
Lighthouse собственной персоной.

Проведя тест Вашего приложения вы можете увидеть следующее:

Нам дают понять, что мы используем старый и не эффективный формат изображений.
Нам дают понять, что мы используем старый и не эффективный формат изображений.

Ссылка Learn more about modern image formats. ведёт на эту страницу. Если коротко, то нам предлагают избавится от изображений в старых форматах и приобщиться к более современным, в частности к формату webp. Более подробно вы можете ознакомится самостоятельно, перейдя по ссылке.

Django, ты как там?

Коль скоро речь заходит о хранении изображений на стороне Back-end, то мы не будем томить и приведём пример хранения изображения в нашей базе данных, допустим у нас будет логотип компании, который нам необходимо сохранить в формате webp. Для этого напишем в файле models.py следующее:

class CompanyLogo(models.Model):

    logo = models.ImageField(
        upload_to='images/logo',
        verbose_name='Лого'
        )

А чего тут нового, спросите вы и будете абсолютно правы. Это вполне себе стандартный синтаксис модели Django. Давайте напишем функцию, которая на входе принимает файл изображения, допустим jpeg или png, а возвращает изображение в формате webp, да ещё и подрезанное, как того требует макет скажем в Figma. По желанию, вы можете хранить этот код в отдельном файле проекта и импортировать в случае необходимости, я же добавлю эту функцию в модель CompanyLogo.

from django.db import models

from io import BytesIO
from PIL import Image
from django.core.files import File
from django.core.files.base import ContentFile


class CompanyLogo(models.Model):

    logo = models.ImageField(
        upload_to='images/logo',
        verbose_name='Лого'
        )

    def compress_logo(self, image):
        im = Image.open(image)
        width, height = im.size[0], int(im.size[0] * 1.5)
        x, y = 0, int((im.size[1] - height) // 2)
        area = (x, y, x+width, y+height)
        im = im.crop((area))
        im = im.resize((200, 300))
        im_bytes = BytesIO()
        im.save(fp=im_bytes, format="WEBP", quality=100)
        image_content_file = ContentFile(content=im_bytes.getvalue())
        name = image.name.split('.')[0] + '.WEBP'
        new_image = File(image_content_file, name=name)
        return new_image

Следует упомянуть, что для работы с полем ImageField в Django, требуется модуль Pillow, которой нужно предварительно установить в нашу виртуальную среду pip install Pillow.

Наверное надо немного пояснить, что конкретно делает этот код. Он принимает на входе загруженное изображение, затем берёт за основу его ширину и вычисляет требуемую высоту, пропорционально соотношению сторон (300/200=1.5). Это сделано для того, что бы загрузив даже квадратное изображение вы получили на выходе прямоугольное без потери качества и искажения. Затем мы обрезаем изображение и сжимаем его в соответствии с параметрами макета. Выбираем формат выходного изображения и его качество. Получаем байтовое значение файла. Присваиваем ему имя и сохраняем файл. Вот что получается:

Входное квадратное изображение, с соотношением сторон 1:1
Входное квадратное изображение, с соотношением сторон 1:1
Выходное изображение с соотношением сторон 1:1.5 и разрешением 200 x 300 px.
Выходное изображение с соотношением сторон 1:1.5 и разрешением 200 x 300 px.

Функция написана, теперь надо понять как и когда ее вызвать. И для этого нам потребуется переопределить метод save класса модели, а именно:

    def save(self, *args, **kwargs):
        try:
            this = CompanyLogo.objects.get(id=self.id)
            if this.logo != self.logo:
                this.logo.delete(save=False)
                try:
                    new_logo = self.compress_logo(self.logo)
                    self.logo = new_logo
                    super(CompanyLogo, self).save(*args, **kwargs)
                except ValueError:
                    super(CompanyLogo, self).save(*args, **kwargs)
            else:
                super(CompanyLogo, self).save(*args, **kwargs)
        except CompanyLogo.DoesNotExist:
            try:
                new_logo = self.compress_logo(self.logo)
                self.logo = new_logo
                super(CompanyLogo, self).save(*args, **kwargs)
            except ValueError:
                super(CompanyLogo, self).save(*args, **kwargs)

Возможно код выглядит немного громоздко, но он работает, если у кого то будут идеи, как его переписать более лаконично я буду этому рад.

Так, что же делает этот код, по мимо того, что вызывает нашу функцию для сжатия изображения при сохранении записи в базе данных? Он позволяет при обновлении или удалении файла (допустим через Django admin), а так же при удалении записи из базы данных удалить физический файл с сервера (ведь все мы стараемся сберечь место на SSD), и если предположить, что у вас очень большой проект, который может содержать огромное количество подобных изображений, то момент с удалением ненужных файлов с сервера будет весьма и весьма полезным.

Что в итоге?

Тоже самое изображение, обработанное по алгоритму из функции compress_logoно имеющее разрешение png занимает на диске примерно 12,2 кб., когда как изображение в формате webp занимает всего 6 кб.

Изображение 200 x 300 px в формате png
Изображение 200 x 300 px в формате png

Вам может показаться, что в реальных цифрах разница не так велика, и вы подумаете, что экономите всего 6 килобайт, но на деле экономия чуть больше 50% процентов, согласитесь не плохо? А если представить, что вы разрабатываете крупную площадки по размещению объявлений, то реальная экономия может оказаться очень весомой.

Надеюсь, этот материал окажется для кого-то полезным. Спасибо, что дочитали текст до конца!

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+2
Комментарии42

Публикации

Работа

Ближайшие события