Add animated profile picture support

This commit is contained in:
Alexandre Iooss 2020-09-06 18:54:21 +02:00
parent 72c004cb56
commit 82924c999a
2 changed files with 36 additions and 12 deletions

View File

@ -3,7 +3,7 @@
import io import io
from PIL import Image from PIL import Image, ImageSequence
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
@ -82,13 +82,19 @@ class ImageForm(forms.Form):
height = forms.FloatField(widget=forms.HiddenInput()) height = forms.FloatField(widget=forms.HiddenInput())
def clean(self): def clean(self):
"""Load image and crop""" """
Load image and crop
In the future, when Pillow will support APNG we will be able to
simplify this code to save only PNG/APNG.
"""
cleaned_data = super().clean() cleaned_data = super().clean()
# Image size is limited by Django DATA_UPLOAD_MAX_MEMORY_SIZE # Image size is limited by Django DATA_UPLOAD_MAX_MEMORY_SIZE
image = cleaned_data.get('image') image = cleaned_data.get('image')
if image: if image:
# Let Pillow detect and load image # Let Pillow detect and load image
# If it is an animation, then there will be multiple frames
try: try:
im = Image.open(image) im = Image.open(image)
except OSError: except OSError:
@ -96,20 +102,30 @@ class ImageForm(forms.Form):
# but Pil is unable to load it # but Pil is unable to load it
raise forms.ValidationError(_('This image cannot be loaded.')) raise forms.ValidationError(_('This image cannot be loaded.'))
# Crop image # Crop each frame
x = cleaned_data.get('x', 0) x = cleaned_data.get('x', 0)
y = cleaned_data.get('y', 0) y = cleaned_data.get('y', 0)
w = cleaned_data.get('width', 200) w = cleaned_data.get('width', 200)
h = cleaned_data.get('height', 200) h = cleaned_data.get('height', 200)
im = im.crop((x, y, x + w, y + h)) frames = []
im = im.resize( for frame in ImageSequence.Iterator(im):
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH), frame = frame.crop((x, y, x + w, y + h))
Image.ANTIALIAS, frame = frame.resize(
) (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS,
)
frames.append(frame)
# Save # Save
om = frames.pop(0) # Get first frame
om.info = im.info # Copy metadata
image.file = io.BytesIO() image.file = io.BytesIO()
im.save(image.file, "PNG") if len(frames) > 1:
# Save as GIF
om.save(image.file, "GIF", save_all=True, append_images=list(frames), loop=0)
else:
# Save as PNG
om.save(image.file, "PNG")
return cleaned_data return cleaned_data

View File

@ -271,9 +271,17 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
def form_valid(self, form): def form_valid(self, form):
"""Save image to note""" """Save image to note"""
image_field = form.cleaned_data['image'] image = form.cleaned_data['image']
image_field.name = "{}_pic.png".format(self.object.note.pk)
self.object.note.display_image = image_field # Rename as a PNG or GIF
extension = image.name.split(".")[-1]
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else:
image.name = "{}_pic.png".format(self.object.note.pk)
# Save
self.object.note.display_image = image
self.object.note.save() self.object.note.save()
return super().form_valid(form) return super().form_valid(form)