I have successfully created and rotated an image that was uploaded via email to a directory on my server using the following code:
image = ContentFile(b64decode(part.get_payload()))
im = Image.open(image)
tempfile = im.rotate(90)
tempfile.save("/srv/www/mysite.com/public_html/media/images/rotate.jpg", "JPEG")
img = Photo(user=user)
img.img.save('rotate.jpg', tempfile)
img.save()
The rotated image exists in the directory, however when I try to add that image to my model, it is not saving. What am I missing? Any help would be greatly appreciated.
I solved the issue with the following code:
image = ContentFile(b64decode(part.get_payload()))
im = Image.open(image)
tempfile = im.rotate(270)
tempfile_io =StringIO.StringIO()
tempfile.save(tempfile_io, format='JPEG')
image_file = InMemoryUploadedFile(tempfile_io, None, 'rotate.jpg','image/jpeg',tempfile_io.len, None)
img = Photo(user=user)
img.img.save('rotate.jpg', image_file)
img.save()
I found the answer here How do you convert a PIL `Image` to a Django `File`?. Works flawlessly!!!
from django.db import models
from .abstract_classes import DateTimeCreatedModified
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
from enum import Enum
class MediaSize(Enum):
# size base file name
DEFAULT = ((1080, 1080), 'bus_default')
MEDIUM = ((612, 612), 'bus_medium')
THUMBNAIL = ((161, 161), 'bus_thumbnail')
class Media(DateTimeCreatedModified):
# The original image.
original = models.ImageField()
# Resized images...
default = models.ImageField()
medium = models.ImageField()
thumbnail = models.ImageField()
def resizeImg(self, img_size):
img: Image.Image = Image.open(self.original)
img.thumbnail(img_size, Image.ANTIALIAS)
outputIO = BytesIO()
img.save(outputIO, format=img.format, quality=100)
return outputIO, img
def handleResize(self, mediaSize: MediaSize):
imgSize = mediaSize.value[0]
imgName = mediaSize.value[1]
outputIO, img = self.resizeImg(imgSize)
return {
# Files aren't be overwritten
# because `AWS_S3_FILE_OVERWRITE = False`
# is set in `settings.py`.
'name': f'{imgName}.{img.format}',
'content': ContentFile(outputIO.getvalue()),
'save': False,
}
def save(self, **kwargs):
if not self.default:
self.default.save(
**self.handleResize(MediaSize.DEFAULT)
)
if not self.medium:
self.medium.save(
**self.handleResize(MediaSize.MEDIUM)
)
if not self.thumbnail:
self.thumbnail.save(
**self.handleResize(MediaSize.THUMBNAIL)
)
super().save(**kwargs)
Related
Goal: Upload a resized image (with same file name and aspect ratio) to AWS S3.
Problem: Currently upon saving, the original image is uploaded and not the resized one.
What have I tried?: I've tried multiple different ways to accomplish this but I run into various issues such as not the correct aspect ratio, poor image quality (when using django-resize) etc. The code below seems really close but I just can't seem to find where I am going wrong.
models.py
class Profile(BaseModel):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def save(self, commit=True, *args, **kwargs): #Edited
if commit:
img = Image.open(self.image)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.image.name, optimize=True, quality=100)
super().save()
Solution:
After a very long time I finally found the answer in this blog.
In the end I made a new function in the users/utils.py file:
from django.core.files import File
from pathlib import Path
from PIL import Image
from io import BytesIO
image_types = {
"jpg": "JPEG",
"jpeg": "JPEG",
"png": "PNG",
"gif": "GIF",
"tif": "TIFF",
"tiff": "TIFF",
}
def image_resize(image, width, height):
# Open the image using Pillow
img = Image.open(image)
# check if either the width or height is greater than the max
if img.width > width or img.height > height:
output_size = (width, height)
# Create a new resized “thumbnail” version of the image with Pillow
img.thumbnail(output_size)
# Find the file name of the image
img_filename = Path(image.file.name).name
# Spilt the filename on “.” to get the file extension only
img_suffix = Path(image.file.name).name.split(".")[-1]
# Use the file extension to determine the file type from the image_types dictionary
img_format = image_types[img_suffix]
# Save the resized image into the buffer, noting the correct file type
buffer = BytesIO()
img.save(buffer, format=img_format)
# Wrap the buffer in File object
file_object = File(buffer)
# Save the new resized file as usual, which will save to S3 using django-storages
image.save(img_filename, file_object)
and then overwrote the save() function in the models.py:
models.py
from users.utils import image_resize
class Profile(BaseModel):
#some other fields
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
def save(self, commit=True, *args, **kwargs):
if commit:
image_resize(self.image, 250, 250)
super().save(*args, **kwargs)
I have the following code with reference to https://github.com/jschneier/django-storages/issues/661, but it doesnt work, the default unsized image is being stored.
models.py
class Product(models.Model):
name=models.CharField(max_length=100)
image=models.ImageField(default='default.jpg',upload_to='productimages')
price=models.FloatField()
def generate_thumbnail(self,src):
image = Image.open(src) # in memory
image.thumbnail((400,300), Image.ANTIALIAS)
buffer = BytesIO()
image.save(buffer, 'JPEG')
file_name = os.path.join(settings.MEDIA_ROOT, self.image.name)
temp_file = InMemoryUploadedFile(buffer, None, file_name, 'image/jpeg', len(buffer.getbuffer()), None)
return temp_file
def save(self, *args, **kwargs):
self.image=self.generate_thumbnail(self.image)
print(self.image.width) #prints the original size
print(self.image.height)
super(Product,self).save(*args, **kwargs)
if the PIL library is not working then you might use OpenCV as a workaround
import cv2
image = cv2.imread(<your Image name>)
cv2.resize(image, size=(600, 600))
cv2.imshow(image)
cv2.imwrite(filename='resized image.png/.jpg', image)
In my views.py file, I am trying to add 1 to a BigIntegerField named visited_counter.
views.py :
def view_page(request,id_page):
page = get_object_or_404(Page,title=id_page)
page.visited_counter= page.visited_counter +1
page.save()
return render(request,'app/page.html',locals())
models.py :
class Page(models.Model):
title = models.CharField(max_length=30)
visited_counter= models.BigIntegerField()
landscape= models.BooleanField()
thumbnail = models.ImageField(storage=OverwriteStorage(),upload_to=thumbnail_path)
backgroundpicture =models.ImageField(storage=OverwriteStorage(),upload_to=background_path)
def save(self, *args, **kwargs):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
else:
self.landscape=False
if self.backgroundpicture:
from PIL import Image
import glob, os
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
image = Image.open(self.backgroundpicture) ####LINE ERROR####
try:
# thumbnail
THUMBNAIL_SIZE = (160, 160) # dimensions
# Convert to RGB if necessary
if image.mode not in ('L', 'RGB'): image = image.convert('RGB')
# create a thumbnail + use antialiasing for a smoother thumbnail
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# fetch image into memory
temp_handle = StringIO()
image.save(temp_handle, 'JPEG')
temp_handle.seek(0)
# save it
file_name, file_ext = os.path.splitext(self.backgroundpicture.name.rpartition('/')[-1])
suf = SimpleUploadedFile(file_name + file_ext, temp_handle.read(), content_type='JPEG')
self.thumbnail.save(file_name + '.jpg', suf, save=False)
except ImportError:
pass
super(Page, self).save(*args, **kwargs)
When I create a 'Page object', I have no problem.... The save function is doing her job very well, but when I want to access to the object via the view_page function. I get a I/O operation on closed file error on that line: image = Image.open(self.backgroundpicture).
I didn't find any other Q/A related to this case, so I am stuck...
The trick is to add an if condition in your save method and check if it is necessary to read the whole code in the save function.
For this you add a function named, has_changed
def has_changed(instance, field, manager='objects'):
"""Returns true if a field has changed in a model
May be used in a model.save() method.
"""
if not instance.pk:
return True
manager = getattr(instance.__class__, manager)
old = getattr(manager.get(pk=instance.pk), field)
return not getattr(instance, field) == old
And you use it in the model definition like this:
if has_changed(self, 'backgroundpicture'):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
...
I would like to scale an ImageField before the model gets saved to the disk, but somehow get an unreadable image out. The goal is to scale it without ever saving it to the disk.
This is my attempt so far:
IMAGE_MAX_SIZE = 800, 800
class Picture(models.Model):
...
image = models.ImageField(upload_to='images/%Y/%m/%d/')
# img is a InMemoryUploadedFile, received from a post upload
# removing the scale function results in a readable image
def set_image(self, img):
self.image = img
self.__scale_image()
def __scale_image(self):
img = Image.open(StringIO(self.image.read()))
img.thumbnail(IMAGE_MAX_SIZE, Image.ANTIALIAS)
imageString = StringIO()
img.save(imageString, img.format)
self.image.file = InMemoryUploadedFile(imageString, None, self.image.name, self.image.file.content_type, imageString.len, None)
I'm not getting an error, but the resulting image can not be displayed correctly. Any ideas how to correct this?
Thanks
Simon
I was close, but not quite there. This function works now fine and the image does not get saved to the disc at any point during the scaling.
IMAGE_MAX_SIZE = 800, 800
class Picture(models.Model):
...
image = models.ImageField(upload_to='images/%Y/%m/%d/')
# img is a InMemoryUploadedFile, received from a post upload
def set_image(self, img):
self.image = img
self.__scale_image(self.image, IMAGE_MAX_SIZE)
def __scale_image(self, image, size):
image.file.seek(0) # just in case
img = Image.open(StringIO(image.file.read()))
img.thumbnail(size, Image.ANTIALIAS)
imageString = StringIO()
img.save(imageString, img.format)
# for some reason content_type is e.g. 'images/jpeg' instead of 'image/jpeg'
c_type = image.file.content_type.replace('images', 'image')
imf = InMemoryUploadedFile(imageString, None, image.name, c_type, imageString.len, None)
imf.seek(0)
image.save(
image.name,
imf,
save=False
)
It would better idea to use sorl-thubnail.
I'm trying to convert an UploadedFile to a PIL Image object to thumbnail it, and then convert the PIL Image object that my thumbnail function returns back into a File object. How can I do this?
The way to do this without having to write back to the filesystem, and then bring the file back into memory via an open call, is to make use of StringIO and Django InMemoryUploadedFile. Here is a quick sample on how you might do this. This assumes that you already have a thumbnailed image named 'thumb':
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.
...
Let me know if you need more clarification. I have this working in my project right now, uploading to S3 using django-storages. This took me the better part of a day to properly find the solution here.
I've had to do this in a few steps, imagejpeg() in php requires a similar process. Not to say theres no way to keep things in memory, but this method gives you a file reference to both the original image and thumb (usually a good idea in case you have to go back and change your thumb size).
save the file
open it from filesystem with PIL,
save to a temp directory with PIL,
then open as a Django file for this to work.
Model:
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)
This is actual working example for python 3.5 and django 1.10
in 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(...)
Putting together comments and updates for 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())
To complete for those who, like me, want to couple it with Django's FileSystemStorage:
(What I do here is upload an image, resize it to 2 dimensions and save both files.
utils.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)
In 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()
Here is an app that can do that: 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}))
])
Make sure to pass keep_orphans=True to the field, if you want to keep old files, otherwise they are cleaned up upon replacement.
For those using django-storages/-redux to store the image file on S3, here's the path I took (the example below creates a thumbnail of an existing image):
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()