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

от автора

Доброго времени суток уважаемый читатель. Хотелось бы немного поговорить об оптимизации наших с вам любимых 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% процентов, согласитесь не плохо? А если представить, что вы разрабатываете крупную площадки по размещению объявлений, то реальная экономия может оказаться очень весомой.

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


ссылка на оригинал статьи https://habr.com/ru/articles/763528/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *