web-dev-qa-db-fra.com

Comment convertir une `Image` PIL en un Django` File`?

J'essaie de convertir un UploadedFile en un objet PIL Image pour le miniaturiser, puis de convertir l'objet PIL Image que ma fonction de miniature retourne en File objet. Comment puis-je faire ceci?

52
orokusaki

La façon de le faire sans avoir à réécrire dans le système de fichiers, puis à remettre le fichier en mémoire via un appel ouvert, consiste à utiliser StringIO et Django InMemoryUploadedFile. Voici un rapide exemple sur la façon de procéder. Cela suppose que vous avez déjà une image miniature nommée "pouce":

import StringIO

from Django.core.files.uploadedfile import InMemoryUploadedFile

# Create a file-like object to write thumb data (thumb data previously created
# using PIL, and stored in variable 'thumb')
thumb_io = StringIO.StringIO()
thumb.save(thumb_io, format='JPEG')

# Create a new Django file-like object to be used in models as ImageField using
# InMemoryUploadedFile.  If you look at the source in Django, a
# SimpleUploadedFile is essentially instantiated similarly to what is shown here
thumb_file = InMemoryUploadedFile(thumb_io, None, 'foo.jpg', 'image/jpeg',
                                  thumb_io.len, None)

# Once you have a Django file-like object, you may assign it to your ImageField
# and save.
...

Faites-moi savoir si vous avez besoin de plus de précisions. J'ai ce travail dans mon projet en ce moment, le téléchargement vers S3 en utilisant Django-storages. Cela m'a pris une bonne partie de la journée pour trouver correctement la solution ici.

95
Skitz

J'ai dû faire cela en quelques étapes, imagejpeg () en php nécessite un processus similaire. Pour ne pas dire qu'il n'y a aucun moyen de garder les choses en mémoire, mais cette méthode vous donne une référence de fichier à la fois l'image d'origine et le pouce (généralement une bonne idée au cas où vous devriez revenir en arrière et changer la taille de votre pouce).

  1. enregistrez le fichier
  2. ouvrez-le à partir du système de fichiers avec PIL,
  3. enregistrer dans un répertoire temporaire avec PIL,
  4. puis ouvrez comme un fichier Django pour que cela fonctionne.

Modèle:

class YourModel(Model):
    img = models.ImageField(upload_to='photos')
    thumb = models.ImageField(upload_to='thumbs')

Usage:

#in upload code
uploaded = request.FILES['photo']
from Django.core.files.base import ContentFile
file_content = ContentFile(uploaded.read())
new_file = YourModel() 
#1 - get it into the DB and file system so we know the real path
new_file.img.save(str(new_file.id) + '.jpg', file_content)
new_file.save()

from PIL import Image
import os.path

#2, open it from the location Django stuck it
thumb = Image.open(new_file.img.path)
thumb.thumbnail(100, 100)

#make tmp filename based on id of the model
filename = str(new_file.id)

#3. save the thumbnail to a temp dir

temp_image = open(os.path.join('/tmp',filename), 'w')
thumb.save(temp_image, 'JPEG')

#4. read the temp file back into a File
from Django.core.files import File
thumb_data = open(os.path.join('/tmp',filename), 'r')
thumb_file = File(thumb_data)

new_file.thumb.save(str(new_file.id) + '.jpg', thumb_file)
13
Lincoln B

Ceci est un exemple de travail réel pour python 3.5 et Django 1.1

dans views.py:

from io import BytesIO
from Django.core.files.base import ContentFile
from Django.core.files.uploadedfile import InMemoryUploadedFile

def pill(image_io):
    im = Image.open(image_io)
    ltrb_border = (0, 0, 0, 10)
    im_with_border = ImageOps.expand(im, border=ltrb_border, fill='white')

    buffer = BytesIO()
    im_with_border.save(fp=buffer, format='JPEG')
    buff_val = buffer.getvalue()
    return ContentFile(buff_val)

def save_img(request)
    if request.POST:
       new_record = AddNewRecordForm(request.POST, request.FILES)
       pillow_image = pill(request.FILES['image'])
       image_file = InMemoryUploadedFile(pillow_image, None, 'foo.jpg', 'image/jpeg', pillow_image.tell, None)
       request.FILES['image'] = image_file  # really need rewrite img in POST for success form validation
       new_record.image = request.FILES['image']
       new_record.save()
       return redirect(...)
5
dEll

Regrouper les commentaires et les mises à jour pour Python 3+

from io import BytesIO
from Django.core.files.base import ContentFile
import requests

# Read a file in

r = request.get(image_url)
image = r.content
scr = Image.open(BytesIO(image))

# Perform an image operation like resize:

width, height = scr.size
new_width = 320
new_height = int(new_width * height / width)
img = scr.resize((new_width, new_height))

# Get the Django file object

thumb_io = BytesIO()
img.save(thumb_io, format='JPEG')
photo_smaller = ContentFile(thumb_io.getvalue())
3
Rob

Pour ceux qui utilisent Django-storages/-redux pour stocker le fichier image sur S3, voici le chemin que j'ai pris (l'exemple ci-dessous crée une miniature d'une image existante):

from PIL import Image
import StringIO
from Django.core.files.storage import default_storage

try:
    # example 1: use a local file
    image = Image.open('my_image.jpg')
    # example 2: use a model's ImageField
    image = Image.open(my_model_instance.image_field)
    image.thumbnail((300, 200))
except IOError:
    pass  # handle exception

thumb_buffer = StringIO.StringIO()
image.save(thumb_buffer, format=image.format)
s3_thumb = default_storage.open('my_new_300x200_image.jpg', 'w')
s3_thumb.write(thumb_buffer.getvalue())
s3_thumb.close()
1
Cloud Artisans

Voici une application qui peut le faire: Django-smartfields

from Django.db import models

from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor

class ImageModel(models.Model):
    image = fields.ImageField(dependencies=[
        FileDependency(processor=ImageProcessor(
            scale={'max_width': 150, 'max_height': 150}))
    ])

Assurez-vous de passer keep_orphans=True sur le terrain, si vous souhaitez conserver les anciens fichiers, sinon ils seront nettoyés lors du remplacement.

1
lehins

Pour compléter pour ceux qui, comme moi, veulent le coupler avec le FileSystemStorage de Django: (Ce que je fais ici, c'est télécharger une image, la redimensionner en 2 dimensions et enregistrer les deux fichiers.

tils.py

def resize_and_save(file):
    size = 1024, 1024
    thumbnail_size = 300, 300
    uploaded_file_url = getURLforFile(file, size, MEDIA_ROOT)
    uploaded_thumbnail_url = getURLforFile(file, thumbnail_size, THUMBNAIL_ROOT)
    return [uploaded_file_url, uploaded_thumbnail_url]

def getURLforFile(file, size, location):
    img = Image.open(file)
    img.thumbnail(size, Image.ANTIALIAS)
    thumb_io = BytesIO()
    img.save(thumb_io, format='JPEG')
    thumb_file = InMemoryUploadedFile(thumb_io, None, file.name, 'image/jpeg', thumb_io.tell, None)
    fs = FileSystemStorage(location=location)
    filename = fs.save(file.name, thumb_file)
    return fs.url(filename)  

Dans views.py

if request.FILES:
        fl, thumbnail = resize_and_save(request.FILES['avatar'])
        #delete old profile picture before saving new one
        try:
            os.remove(BASE_DIR + user.userprofile.avatarURL)
        except Exception as e:
            pass         
        user.userprofile.avatarURL = fl
        user.userprofile.thumbnailURL = thumbnail
        user.userprofile.save()
0
mrj