Django Pillow, save image as jpeg does not seem to work - django

Goodmorning,
I'm using Pillow to resize and save an image within a Django model called Post. The image is retrieved from the imagefield, get's a check to see if it is RGB or not, if not the image is converted to RGB.
Finally, I'm creating a thumbnail from the original image and try to save this in the MEDIA_ROOT.
Even though the image get's uploaded, it doesn't seem to convert the image to jpeg.
I followed the tutorial here Django 2+ edit images with Pillow and I'm trying to fit it to my needs.
What am I missing here?
models.py
import os
from django.core.validators import RegexValidator
from django.db import models
from django.utils import timezone
from PIL import Image
from django.conf import settings
from django.db.models.signals import post_save
class Post(models.Model):
# Custom validators
title_validator_specialchar = RegexValidator(regex=r'^[\s*\d*a-zA-Z]{5,60}$', message="The title can't contain any special characters")
category = models.ForeignKey('Category',default=1, on_delete=models.SET_NULL, null=True)
type = models.CharField(max_length=20)
title = models.CharField(max_length=200, validators=[title_validator_specialchar])
content = models.TextField(max_length=2000)
image = models.ImageField(upload_to='%Y/%m/%d/', blank=True)
created_at = models.DateTimeField(editable=False)
updated_at = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
#On save, update timestamp date created
if not self.id:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
def resize_image(instance, **kwargs):
if instance.image:
# we are opening image with Pillow
img = Image.open(instance.image)
# convert image to RGB
if img.mode not in ('L', 'RGB'):
img = img.convert('RGB')
# img.size is tuple with values (width, height)
if img.size[0] > 320 or img.size[1] > 640:
# Using thumbnail to resize image but keep aspect ratio
img.thumbnail((320, 640), Image.ANTIALIAS)
# saving to original place
# instance.image.name is in %Y/%m/%d/<name> format
output = os.path.join(settings.MEDIA_ROOT, instance.image.name)
img.save(output, "JPEG")
# Connect the signal with our model
post_save.connect(resize_image, Post)

A signal handler receives the sender as the first argument, which in case of the post_save signal is the model class, not the model instance.
So the argument instance of resize_image() should be named sender and does not contains what you want. Here is how you get the actual instance:
def resize_image(sender, **kwargs):
instance = kwargs.get('instance')
if instance and instance.image:
...

Since I couldn't find what I needed to get this to work, I decided to use django-imagekit.
I'm using the ProcessedImageField and the ResizeToFill processor on my model;
models.py
image = ProcessedImageField(upload_to='%Y/%m/%d/', processors=[ResizeToFill(384, 216)], format='JPEG', options={'quality': 60}, blank=True)

This happens beacuse You explicitly call img to save in the mediaroot but instance.image stays still. So django will save that image also. So I think You have to change the instance.image attribute rather than calling img to save. For that you have to use django InMemorUploadedFile
import io
from django.core.files.uploadedfile import InMemoryUploadedFile
import sys
if instance.image:
# we are opening image with Pillow
img = Image.open(instance.image)
# convert image to RGB
if img.mode not in ('L', 'RGB'):
img = img.convert('RGB')
# img.size is tuple with values (width, height)
if img.size[0] > 320 or img.size[1] > 640:
# Using thumbnail to resize image but keep aspect ratio
img.thumbnail((320, 640), Image.ANTIALIAS)
# saving to original place by changing instance.image. django will save it
#automatically in mediaroot
img_io = io.BytesIO()
img.save(img_io, "JPEG")
instance.image = InMemoryUploadedFile(img_io, 'ImageField', 'image.jpeg',
'image/jpeg',sys.getsizeof(img_io), None )
we can't pass the Image object directly to inastance.image because it raises an error.
so we have to convert img to InMemoryUploadedFile object.

Related

Django save override ImageField handling

After the problems I had on this thread, there is still a big problem in my models.py when I'm using the Django Admin. Here is my code (I removed stuff non related to my problem) :
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image as Img
import StringIO
class Mymodel(models.Model):
photo = models.ImageField(upload_to="photo/", blank=True, null=True)
def save(self, *args, **kwargs):
width = 500
height = 500
size = (width,height)
if self.photo:
image = Img.open(StringIO.StringIO(self.photo.read()))
(imw, imh) = image.size
if (imw>width) or (imh>height) :
image.thumbnail(size, Img.ANTIALIAS)
#If RGBA, convert transparency
if image.mode == "RGBA":
image.load()
background = Img.new("RGB", image.size, (255, 255, 255))
background.paste(image, mask=image.split()[3]) #3 is alpha channel
image=background
output = StringIO.StringIO()
image.save(output, format='JPEG', quality=60)
output.seek(0)
self.photo = InMemoryUploadedFile(output,'ImageField', "%s.jpg" %self.photo_principale.name.split('.')[0], 'image/jpeg', output.len, None)
try:
this = Mymodel.objects.get(id=self.id)
if this.photo != self.photo:
this.photo.delete(save=False)
except: pass # when new photo then we do nothing, normal case
super(Mymodel, self).save(*args, **kwargs)
It works, the files are uploaded, resized and converted to JPEG successfully when needed. The problem, every time I edit it, even when NOT uploading a new image, it creates a new image (for example, I save my model a first time with image "hello.jpg", then I edit it, it'll create a new image called "hello_1.jpg" even if I didn't upload anything).
I thought the try/except block would work when only editing (so no new file upload), but apparently not.
Thanks in advance for the help :)
Final solution, working for me :
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image as Img
import StringIO
from django.db.models.signals import post_delete
from django.dispatch import receiver
Class Mymodel(models.Model):
photo= models.ImageField(upload_to="photo/", blank=True, null=True)
def save(self, *args, **kwargs):
width = 500
height = 500
size = (width,height)
isSame = False
if self.photo:
try:
this = Mymodel.objects.get(id=self.id)
if this.photo==self.photo :
isSame= True
except: pass # when new photo then we do nothing, normal case
image = Img.open(StringIO.StringIO(self.photo.read()))
(imw, imh) = image.size
if (imw>width) or (imh>height) :
image.thumbnail(size, Img.ANTIALIAS)
#If RGBA, convert transparency
if image.mode == "RGBA":
image.load()
background = Img.new("RGB", image.size, (255, 255, 255))
background.paste(image, mask=image.split()[3]) # 3 is the alpha channel
image=background
output = StringIO.StringIO()
image.save(output, format='JPEG', quality=60)
output.seek(0)
self.photo = InMemoryUploadedFile(output,'ImageField', "%s.jpg" %self.photo.name.split('.')[0], 'image/jpeg', output.len, None)
try:
this = Mymodel.objects.get(id=self.id)
if this.photo==self.photo or isSame :
self.photo=this.photo
else :
this.photo.delete(save=False)
except: pass # when new photo then we do nothing, normal case
super(Mymodel, self).save(*args, **kwargs)
#receiver(post_delete, sender=Mymodel)
def photo_post_delete_handler(sender, **kwargs):
instance = kwargs['instance']
storage, path = instance.photo.storage, instance.photo.path
if (path!='.') and (path!='/') and (path!='photo/') and (path!='photo/.'):
storage.delete(path)
Hope it can help somebody ;)
This Builds on Ralph's answer worked for me for python 3 and django 2
first you must import io:
from io import BytesIO
To resize and add white background where necessary:
def resize_with_white_background(pil_image: Image.Image, desired_width, desired_height):
img_copy = pil_image.copy()
# get proportioned image ie (if image is 200X600 and trying to resize to 100X200
# thumbnail will NOT do this but resize to keep the ratio so it would be 67x200 to maintain the ratio (uses the larger)
# img_copy changed in place (does not create new image)
img_copy.thumbnail((desired_width, desired_height), Image.ANTIALIAS)
# create white background
background = Image.new('RGB', (desired_width, desired_height), (255,255,255))
pixels_to_move_left = int((background.width - img_copy.width) * 0.50) # centered horizontally
pixels_to_move_down = int((background.height - img_copy.height) * 0.50) # centered vertically
# paste image into white background box argument tells where to paste
background.paste(img_copy, box=(pixels_to_move_left, pixels_to_move_down))
return background # this will return the background with img_copy pasted in and will be resized to fit your desired size
To set the resized image to and ImageField create a method in your model:
def set_image(self, desired_width, desired_height):
try:
this = MyModel.objects.get(id=self.id)
except MyModel.DoesNotExist:
pass
else:
# will not resize or set to new image (this avoids setting image every single time you edit and save
if this.image == self.image and (self.image.width, self.image.height) == (desired_width, desired_height):
return
im = Image.open(BytesIO(self.image.read()))
resized_image = resize_with_white_background(
pil_image=im,
desired_width=desired_width,
desired_height=desired_height
)
# output (file like object)
output = BytesIO()
# save image into file-like object
resized_image.save(output, format='JPEG', quality=94)
# get size of file
a_size = output.tell()
# reset to beginning of file-like object
output.seek(0)
self.image.file = InMemoryUploadedFile(
output,
'ImageField',
f"{self.image.name.split('.')[0]}.jpg",
'image/jpeg',
a_size,
None
)
override the save() method of your Model and call the set_image() method before calling the Super().save(*args, **kwargs) method
def save(self, *args, **kwargs):
self.set_image(
desired_width=100, # can be whatever you want
desired_height=200
)
super().save(*args, **kwargs)
Try:
if self.photo.name != '':
or
if self.photo.size > 0:

In django, which format does models.ImageField take by default

I wanted to upload files with limited extension on imagefield. So, how can I validate my image field for jpg,bmp and gif only.
Also, which extension does image field take in by default?
This is how I do it:
from django.utils.image import Image
# settings.py
# ALLOWED_UPLOAD_IMAGES = ('gif', 'bmp', 'jpeg')
class ImageForm(forms.Form):
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data["image"]
# This won't raise an exception since it was validated by ImageField.
im = Image.open(image)
if im.format.lower() not in settings.ALLOWED_UPLOAD_IMAGES:
raise forms.ValidationError(_("Unsupported file format. Supported formats are %s."
% ", ".join(settings.ALLOWED_UPLOAD_IMAGES)))
image.seek(0)
return image
Works for ModelForm as well.
Unit test:
from StringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test.utils import override_settings
from django.test import TestCase
class ImageFormTest(TestCase):
def test_image_upload(self):
"""
Image upload
"""
content = 'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00' \
'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;'
image = StringIO(content)
image.name = 'image.gif'
image.content_type = 'image/gif'
files = {'image': SimpleUploadedFile(image.name, image.read()), }
form = ImageForm(data={}, files=files)
self.assertTrue(form.is_valid())
#override_settings(ALLOWED_UPLOAD_IMAGES=['png', ])
def test_image_upload_not_allowed_format(self):
image = StringIO('GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00'
'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
image.name = 'image'
files = {'image': SimpleUploadedFile(image.name, image.read()), }
form = ImageForm(data={}, files=files)
self.assertFalse(form.is_valid())
Pillow will allow a bunch of image formats

Django PIL error with reading file using S3

Django newbie :)
I'm using S3 storage via the package django-storages. This appears to work perfect when I upload/update a new image via the admin.
models.py (image field)
image = models.ImageField(
upload_to=path_and_rename("profiles"),
height_field="image_height",
width_field="image_width",
null=True,
blank=True,
editable=True,
help_text="Profile Picture",
verbose_name="Profile Picture"
)
image_height = models.PositiveIntegerField(null=True, blank=True, editable=False, default="100")
image_width = models.PositiveIntegerField(null=True, blank=True, editable=False, default="100")
I then decided I wanted to resize the image upon upload so try by adding the following code on save override method...
def save(self, *args, **kwargs):
if not self.id and not self.image:
return
super(Profile, self).save(*args, **kwargs)
image = Image.open(self.image).seek(0)
(width, height) = image.size
size = ( 100, 100)
image = image.resize(size, Image.ANTIALIAS)
image.save(self.image.path)
Here is the problem, this gave the following error....
cannot identify image file
I then posted a question on stack yesterday (which I deleted) and a user linked to this answer Django PIL : IOError Cannot identify image file which I sorta understand (because the image has not uploaded it cannot read it yet). But I'm not sure that that is my issue! When I get the error cannot identify image file I can see the original file has actually been uploaded to S3 (without the resize of course).
Remembering I'm a newbie can anyone modify my example save method (and explain) with a way to resolve this issue? i.e. a way to rezise a new image to 100x100 on upload?
Many thanks
Use the storage to read the file if its already written then resize....
def save(self, *args, **kwargs):
if not self.id and not self.image:
return
super(Profile, self).save(*args, **kwargs)
import urllib2 as urllib
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
'''Open original photo which we want to resize using PIL's Image object'''
img_file = urllib.urlopen(self.image.url)
im = StringIO(img_file.read())
resized_image = Image.open(im)
'''Convert to RGB if necessary'''
if resized_image.mode not in ('L', 'RGB'):
resized_image = resized_image.convert('RGB')
'''We use our PIL Image object to create the resized image, which already
has a thumbnail() convenicne method that constrains proportions.
Additionally, we use Image.ANTIALIAS to make the image look better.
Without antialiasing the image pattern artificats may reulst.'''
resized_image.thumbnail((100,100), Image.ANTIALIAS)
'''Save the resized image'''
temp_handle = StringIO()
resized_image.save(temp_handle, 'jpeg')
temp_handle.seek(0)
''' Save to the image field'''
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1].split('.')[0],
temp_handle.read(), content_type='image/jpeg')
self.image.save('%s.jpg' % suf.name, suf, save=True)
If you expect
image.save(self.image.path)
to work. Shouldn't you open it with
image = Image.open(self.image.path).seek(0)
?

How to resize the new uploaded images using PIL before saving?

I want to resize the new images in a height and width of 800px and save them. And the app mustn't store the real image. Any help?
This is my code, it saves the original image and don't the resized photo:
models.py:
class Photo(models.Model):
photo = models.ImageField(upload_to='photos/default/')
def save(self):
if not self.id and not self.photo:
return
super(Photo, self).save()
image = Image.open(self.photo)
(width, height) = image.size
"Max width and height 800"
if (800 / width < 800 / height):
factor = 800 / height
else:
factor = 800 / width
size = ( width / factor, height / factor)
image.resize(size, Image.ANTIALIAS)
image.save(self.photo.path)
image = image.resize(size, Image.ANTIALIAS)
resize is non-destructive, it returns a new image.
I use django-resized for my projects.
I searched for a solution to resize uploaded photo before saving. There are a lot of info bit and bit here and there (in StackOverflow). Yet, no complete solution. Here is my final solution that I think works for people who wants it.
Development Highlight
Using Pillow for image processing (two packages required: libjpeg-dev, zlib1g-dev)
Using Model and ImageField as storage
Using HTTP POST or PUT with multipart/form
No need to save the file to disk manually.
Create multiple resolutions and stores their dimensions.
Did not modify the Model itself
Install Pillow
$ sudo apt-get install libjpeg-dev
$ sudo apt-get install zlib1g-dev
$ pip install -I Pillow
myapp/models.py
from django.db import models
class Post(models.Model):
caption = models.CharField(max_length=100, default=None, blank=True)
image_w = models.PositiveIntegerField(default=0)
image_h = models.PositiveIntegerField(default=0)
image = models.ImageField(upload_to='images/%Y/%m/%d/', default=None,
blank=True, width_field='image_w', height_field='image_h')
thumbnail = models.ImageField(upload_to='images/%Y/%m/%d/', default=None,
blank=True)
This model saves a photo with thumbnail and an optional caption. This should be similar to the real-world use case.
Our goal is to resize the photo to 640x640 and generate a 150x150 thumbnail. We also need to return the dimension of the photo in our web API. image_w and image_h is a cached dimension in table. Note that we need to add quota ' when we declare an ImageField. However, thumbnail does not need the width and height field (so you can see the different).
That's the model. We don't need to override or add any functions to the model.
myapp/serializers.py
We will use a ModelSerializer to process the incoming data from HTTP POST.
from rest_framework import serializers
from myapp.models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('caption',)
We do not want the serializer to handle the uploaded photo - we will do it ourself. So, no need to include 'image' in fields.
myapp/views.py
#api_view(['POST'])
#parser_classes((MultiPartParser,))
def handle_uploaded_image(request):
# process images first. if error, quit.
if not 'uploaded_media' in request.FILES:
return Response({'msg': 'Photo missing.'}, status.HTTP_400_BAD_REQUEST)
try:
im = Image.open(StringIO(request.FILES['uploaded_media'].read()))
except IOError:
return Response({'msg': 'Bad image.'}, status.HTTP_400_BAD_REQUEST)
serializer = PostSerializer(data=request.DATA, files=request.FILES)
if not serializer.is_valid():
return Response({'msg': serializer.errors}, status.HTTP_400_BAD_REQUEST)
post = Post.create()
if serializer.data['caption'] is not None:
post.caption = serializer.data['caption']
filename = uuid.uuid4()
name = '%s_0.jpg' % (filename)
post.image.save(name=name, content=resize_image(im, 640))
name = '%s_1.jpg' % (filename)
post.thumbnail.save(name=name, content=resize_image(im, 150))
post.save()
return Response({'msg': 'success',
'caption': post.caption,
'image': {
'url': request.build_absolute_uri(post.image.url),
'width': post.image_w,
'height': post.image_h,
}
'thumbnail': request.build_absolute_uri(post.thumbnail.url),
}, status.HTTP_201_CREATED)
helper functions in a shared py
def resize_image(im, edge):
(width, height) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge)
content = StringIO()
im.resize((width, height), Image.ANTIALIAS).save(fp=content, format='JPEG', dpi=[72, 72])
return ContentFile(content.getvalue())
def scale_dimension(width, height, long_edge):
if width > height:
ratio = long_edge * 1. / width
else:
ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio)
Here, we do not use Image.thumbnail because it will change the original image. This code is suitable if we want to store multiple resolutions (such as low res and high res for different purpose). I found that resizing the incoming message twice will degrade the quality.
We also do not save the image directly to disk. We push the image via a ContentFile (a File object for content in memory) to ImageField and let the ImageField do its job. We wants the multiple copies of image have the same file name with different postfix.
Credits
Here are the links of codes that I had references to build this solution:
Use of ContentFile in ImageField.save, by Martey: https://stackoverflow.com/a/7022005/3731039
Use of StringIO for reading multipart/form, by johndoevodka: https://stackoverflow.com/a/15523422/3731039
Code for resizing: http://davedash.com/2009/02/21/resizing-image-on-upload-in-django/
If you want to validate the image as well, see this: https://stackoverflow.com/a/20762344/3731039
here is what worked for me, inspired a little from django-resized
#receiver(pre_save, sender=MyUser)
#receiver(pre_save, sender=Gruppo)
def ridimensiona_immagine(sender, instance=None, created=False, **kwargs):
foto = instance.foto
foto.file.seek(0)
thumb = PIL.Image.open(foto.file)
thumb.thumbnail((
200,
200
), PIL.Image.ANTIALIAS)
buffer = StringIO.StringIO()
thumb.save(buffer, "PNG")
image_file = InMemoryUploadedFile(buffer, None, 'test.png', 'image/png', buffer.len, None)
instance.foto.file = image_file
If you're using python < 3, you should consider using :
from __future__ import division
In this case the result number of your division will be float.
I've not yet test this solution, but it might help!!
#subidas.py
import PIL
from PIL import Image
def achichar_tamanho(path):
img = Image.open(path)
img = img.resize((230,230), PIL.Image.ANTIALIAS)
img.save(path)
In your models.py, make the following changes:
from subidas import achicar_tamanho
class Productos(models.Model):
imagen = models.ImageField(default="emg_bol/productos/interrogacion.png", upload_to='emg_bol/productos/', blank=True, null=True, help_text="Image of the product")
tiempo_ultima_actualizacion = models.DateTimeField( help_text="Cuando fue la ultima vez, que se modificaron los datos de este producto", auto_now=True)
prioridad = models.IntegerField(max_length=4, default=99, help_text="Frecuencia (medida en unidad), con que el producto es despachado para la venta")
proveedor = models.ForeignKey(Proveedores, help_text="El que provello del producto'")
def save(self, *args, **kwargs):
super(Productos,self).save(*args, **kwargs)
pcod = "%s_%s" % ( re.sub('\s+', '', self.descripcion)[:3], self.id)
self.producto_cod = pcod
achichar_tamanho(self.imagen.path)
super(Productos,self).save(*args, **kwargs)
I really don't know, what was the difference before and after implementing these changes.
A clean solution is to use this method to resize the image in the form before saving it: (you need pip install pillow)
import os
from io import BytesIO
from PIL import Image as PilImage
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
def resize_uploaded_image(image, max_width, max_height):
size = (max_width, max_height)
# Uploaded file is in memory
if isinstance(image, InMemoryUploadedFile):
memory_image = BytesIO(image.read())
pil_image = PilImage.open(memory_image)
img_format = os.path.splitext(image.name)[1][1:].upper()
img_format = 'JPEG' if img_format == 'JPG' else img_format
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
new_image = BytesIO()
pil_image.save(new_image, format=img_format)
new_image = ContentFile(new_image.getvalue())
return InMemoryUploadedFile(new_image, None, image.name, image.content_type, None, None)
# Uploaded file is in disk
elif isinstance(image, TemporaryUploadedFile):
path = image.temporary_file_path()
pil_image = PilImage.open(path)
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
pil_image.save(path)
image.size = os.stat(path).st_size
return image
Then use it in the clean method of the image field in your form:
class ImageForm(forms.Form):
IMAGE_WIDTH = 450
IMAGE_HEIGHT = 450
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data.get('image')
image = resize_uploaded_image(image, self.IMAGE_WIDTH, self.IMAGE_HEIGHT)
return image
As for me Django resized does the trick and it is very easy to understand just pip install it and have a look at the docs

resize image on save

How can I easily resize an image after it has been uploaded in Django? I am using Django 1.0.2 and I've installed PIL.
I was thinking about overriding the save() method of the Model to resize it, but I don't really know how to start out and override it.
Can someone point me in the right direction? Thanks :-)
#Guðmundur H:
This won't work because the django-stdimage package does not work on Windows :-(
I recommend using StdImageField from django-stdimage, it should handle all the dirty work for you. It's easy to use, you just specify the dimensions of the resized image in the field definition:
class MyModel(models.Model):
image = StdImageField(upload_to='path/to/img', size=(640, 480))
Check out the docs — it can do thumbnails also.
You should use a method to handle the uploaded file, as demonstrated in the Django documentation.
In this method, you could concatenate the chunks in a variable (rather than writing them to disk directly), create a PIL Image from that variable, resize the image and save it to disk.
In PIL, you should look at Image.fromstring and Image.resize.
I use this code to handle uploaded images, resize them on memory(whithout saving them permanently on disk), and then saving the thumb on a Django ImageField.
Hope can help.
def handle_uploaded_image(i):
import StringIO
from PIL import Image, ImageOps
import os
from django.core.files import File
# read image from InMemoryUploadedFile
image_str = “”
for c in i.chunks():
image_str += c
# create PIL Image instance
imagefile = StringIO.StringIO(image_str)
image = Image.open(imagefile)
# if not RGB, convert
if image.mode not in (“L”, “RGB”):
image = image.convert(“RGB”)
#define file output dimensions (ex 60x60)
x = 130
y = 130
#get orginal image ratio
img_ratio = float(image.size[0]) / image.size[1]
# resize but constrain proportions?
if x==0.0:
x = y * img_ratio
elif y==0.0:
y = x / img_ratio
# output file ratio
resize_ratio = float(x) / y
x = int(x); y = int(y)
# get output with and height to do the first crop
if(img_ratio > resize_ratio):
output_width = x * image.size[1] / y
output_height = image.size[1]
originX = image.size[0] / 2 - output_width / 2
originY = 0
else:
output_width = image.size[0]
output_height = y * image.size[0] / x
originX = 0
originY = image.size[1] / 2 - output_height / 2
#crop
cropBox = (originX, originY, originX + output_width, originY + output_height)
image = image.crop(cropBox)
# resize (doing a thumb)
image.thumbnail([x, y], Image.ANTIALIAS)
# re-initialize imageFile and set a hash (unique filename)
imagefile = StringIO.StringIO()
filename = hashlib.md5(imagefile.getvalue()).hexdigest()+’.jpg’
#save to disk
imagefile = open(os.path.join(‘/tmp’,filename), ‘w’)
image.save(imagefile,’JPEG’, quality=90)
imagefile = open(os.path.join(‘/tmp’,filename), ‘r’)
content = File(imagefile)
return (filename, content)
#views.py
form = YourModelForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
ob = form.save(commit=False)
try:
t = handle_uploaded_image(request.FILES[‘icon’])
ob.image.save(t[0],t[1])
except KeyError:
ob.save()
I highly recommend the sorl-thumbnail app for handling image resizing easily and transparently. It goes in every single Django project I start.
Here is a complete solution for ya using a form. I used admin views for this:
class MyInventoryItemForm(forms.ModelForm):
class Meta:
model = InventoryItem
exclude = ['thumbnail', 'price', 'active']
def clean_photo(self):
import StringIO
image_field = self.cleaned_data['photo']
photo_new = StringIO.StringIO(image_field.read())
try:
from PIL import Image, ImageOps
except ImportError:
import Image
import ImageOps
image = Image.open(photo_new)
# ImageOps compatible mode
if image.mode not in ("L", "RGB"):
image = image.convert("RGB")
image.thumbnail((200, 200), Image.ANTIALIAS)
image_file = StringIO.StringIO()
image.save(image_file, 'png')
image_field.file = image_file
return image_field
My inventory model looks like this:
class InventoryItem(models.Model):
class Meta:
ordering = ['name']
verbose_name_plural = "Items"
def get_absolute_url(self):
return "/products/{0}/".format(self.slug)
def get_file_path(instance, filename):
if InventoryItem.objects.filter(pk=instance.pk):
cur_inventory = InventoryItem.objects.get(pk=instance.pk)
if cur_inventory.photo:
old_filename = str(cur_inventory.photo)
os.remove(os.path.join(MEDIA_ROOT, old_filename))
ext = filename.split('.')[-1]
filename = "{0}.{1}".format(uuid.uuid4(), ext)
return os.path.join('inventory', filename)
#return os.path.join(filename)
def admin_image(self):
return '<img height="50px" src="{0}/{1}"/>'.format(MEDIA_URL, self.photo)
admin_image.allow_tags = True
photo = models.ImageField(_('Image'), upload_to=get_file_path, storage=fs, blank=False, null=False)
thumbnail = models.ImageField(_('Thumbnail'), upload_to="thumbnails/", storage=fs, blank=True, null=True)
....
I ended overwriting the save function of the model instead to save the photo and a thumb instead of just resizing the photo:
def save(self):
# Save this photo instance first
super(InventoryItem, self).save()
from PIL import Image
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
# Set our max thumbnail size in a tuple (max width, max height)
THUMBNAIL_SIZE = (200, 200)
# Open original photo which we want to thumbnail using PIL's Image object
image = Image.open(os.path.join(MEDIA_ROOT, self.photo.name))
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# Save the thumbnail
temp_handle = StringIO()
image.save(temp_handle, 'png') # image stored to stringIO
temp_handle.seek(0) # sets position of file to 0
# Save to the thumbnail field
suf = SimpleUploadedFile(os.path.split(self.photo.name)[-1],
temp_handle.read(), content_type='image/png') # reads in the file to save it
self.thumbnail.save(suf.name+'.png', suf, save=False)
#Save this photo instance again to save the thumbnail
super(InventoryItem, self).save()
Both work great though depending on what you want to do :)
I know this is old, but for anybody stumbling upon it, there is a package, django-thumbs at Django-thumbs - Easy powerful thumbnails for Django integrated with StorageBackend, which automatically generates thumbnails in sizes you specify, or none if you don't. You then call the thumbnail you want with the dimensions you want.
For instance, if you want an image to have thumbnails of 64x64 and 128x128, you simply import thumbs.models.ImageWithThumbsField, and use it in place of ImageField. Add a parameter sizes=((64,64),(128,128)) to the field definition, then from your template you can call:
{{ ClassName.field_name.url_64x64 }}
and
{{ ClassName.field_name.url_128x128 }}
to display the thumbnails. Voila! All the work is done in this package for you.
If you are using Django Rest Framework, this might of use:
First define function to compress and resize image
def compress_image(photo):
# start compressing image
image_temporary = Image.open(photo)
output_io_stream = BytesIO()
image_temporary.thumbnail((1250, 1250), Image.ANTIALIAS)
# change orientation if necessary
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image_temporary._getexif().items())
# noinspection PyUnboundLocalVariable
if exif.get(orientation) == 3:
image_temporary = image_temporary.rotate(180, expand=True)
elif exif.get(orientation) == 6:
image_temporary = image_temporary.rotate(270, expand=True)
elif exif.get(orientation) == 8:
image_temporary = image_temporary.rotate(90, expand=True)
# saving output
image_temporary.save(output_io_stream, format='JPEG', quality=75, optimize=True, progressive=True)
output_io_stream.seek(0)
photo = InMemoryUploadedFile(output_io_stream, 'ImageField', "%s.jpg" % photo.name.split('.')[0],
'image/jpeg', getsizeof(output_io_stream), None)
return photo
Second, now you can use the function in Serializers:
class SomeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).update(instance, validated_data)
def create(self, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).create(validated_data)