|
|
# -*- encoding: utf-8 -*-
|
|
|
"""
|
|
|
django-thumbs by Antonio Melé
|
|
|
http://django.es
|
|
|
"""
|
|
|
from django.db.models import ImageField
|
|
|
from django.db.models.fields.files import ImageFieldFile
|
|
|
from PIL import Image
|
|
|
from django.core.files.base import ContentFile
|
|
|
import cStringIO
|
|
|
|
|
|
def generate_thumb(img, thumb_size, format):
|
|
|
"""
|
|
|
Generates a thumbnail image and returns a ContentFile object with the thumbnail
|
|
|
|
|
|
Parameters:
|
|
|
===========
|
|
|
img File object
|
|
|
|
|
|
thumb_size desired thumbnail size, ie: (200,120)
|
|
|
|
|
|
format format of the original image ('jpeg','gif','png',...)
|
|
|
(this format will be used for the generated thumbnail, too)
|
|
|
"""
|
|
|
|
|
|
img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
|
|
|
image = Image.open(img)
|
|
|
|
|
|
# get size
|
|
|
thumb_w, thumb_h = thumb_size
|
|
|
# If you want to generate a square thumbnail
|
|
|
if thumb_w == thumb_h:
|
|
|
# quad
|
|
|
xsize, ysize = image.size
|
|
|
# get minimum size
|
|
|
minsize = min(xsize,ysize)
|
|
|
# largest square possible in the image
|
|
|
xnewsize = (xsize-minsize)/2
|
|
|
ynewsize = (ysize-minsize)/2
|
|
|
# crop it
|
|
|
image2 = image.crop((xnewsize, ynewsize, xsize-xnewsize, ysize-ynewsize))
|
|
|
# load is necessary after crop
|
|
|
image2.load()
|
|
|
# thumbnail of the cropped image (with ANTIALIAS to make it look better)
|
|
|
image2.thumbnail(thumb_size, Image.ANTIALIAS)
|
|
|
else:
|
|
|
# not quad
|
|
|
image2 = image
|
|
|
image2.thumbnail(thumb_size, Image.ANTIALIAS)
|
|
|
|
|
|
io = cStringIO.StringIO()
|
|
|
# PNG and GIF are the same, JPG is JPEG
|
|
|
if format.upper()=='JPG':
|
|
|
format = 'JPEG'
|
|
|
|
|
|
image2.save(io, format)
|
|
|
return ContentFile(io.getvalue())
|
|
|
|
|
|
class ImageWithThumbsFieldFile(ImageFieldFile):
|
|
|
"""
|
|
|
See ImageWithThumbsField for usage example
|
|
|
"""
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
|
|
|
self.sizes = self.field.sizes
|
|
|
|
|
|
if self.sizes:
|
|
|
def get_size(self, size):
|
|
|
if not self:
|
|
|
return ''
|
|
|
else:
|
|
|
split = self.url.rsplit('.',1)
|
|
|
thumb_url = '%s.%sx%s.%s' % (split[0],w,h,split[1])
|
|
|
return thumb_url
|
|
|
|
|
|
for size in self.sizes:
|
|
|
(w,h) = size
|
|
|
setattr(self, 'url_%sx%s' % (w,h), get_size(self, size))
|
|
|
|
|
|
def save(self, name, content, save=True):
|
|
|
super(ImageWithThumbsFieldFile, self).save(name, content, save)
|
|
|
|
|
|
if self.sizes:
|
|
|
for size in self.sizes:
|
|
|
(w,h) = size
|
|
|
split = self.name.rsplit('.',1)
|
|
|
thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1])
|
|
|
|
|
|
# you can use another thumbnailing function if you like
|
|
|
thumb_content = generate_thumb(content, size, split[1])
|
|
|
|
|
|
thumb_name_ = self.storage.save(thumb_name, thumb_content)
|
|
|
|
|
|
if not thumb_name == thumb_name_:
|
|
|
raise ValueError('There is already a file named %s' % thumb_name)
|
|
|
|
|
|
def delete(self, save=True):
|
|
|
name=self.name
|
|
|
super(ImageWithThumbsFieldFile, self).delete(save)
|
|
|
if self.sizes:
|
|
|
for size in self.sizes:
|
|
|
(w,h) = size
|
|
|
split = name.rsplit('.',1)
|
|
|
thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1])
|
|
|
try:
|
|
|
self.storage.delete(thumb_name)
|
|
|
except:
|
|
|
pass
|
|
|
|
|
|
class ImageWithThumbsField(ImageField):
|
|
|
attr_class = ImageWithThumbsFieldFile
|
|
|
"""
|
|
|
Usage example:
|
|
|
==============
|
|
|
photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
|
|
|
|
|
|
To retrieve image URL, exactly the same way as with ImageField:
|
|
|
my_object.photo.url
|
|
|
To retrieve thumbnails URL's just add the size to it:
|
|
|
my_object.photo.url_125x125
|
|
|
my_object.photo.url_300x200
|
|
|
|
|
|
Note: The 'sizes' attribute is not required. If you don't provide it,
|
|
|
ImageWithThumbsField will act as a normal ImageField
|
|
|
|
|
|
How it works:
|
|
|
=============
|
|
|
For each size in the 'sizes' atribute of the field it generates a
|
|
|
thumbnail with that size and stores it following this format:
|
|
|
|
|
|
available_filename.[width]x[height].extension
|
|
|
|
|
|
Where 'available_filename' is the available filename returned by the storage
|
|
|
backend for saving the original file.
|
|
|
|
|
|
Following the usage example above: For storing a file called "photo.jpg" it saves:
|
|
|
photo.jpg (original file)
|
|
|
photo.125x125.jpg (first thumbnail)
|
|
|
photo.300x200.jpg (second thumbnail)
|
|
|
|
|
|
With the default storage backend if photo.jpg already exists it will use these filenames:
|
|
|
photo_.jpg
|
|
|
photo_.125x125.jpg
|
|
|
photo_.300x200.jpg
|
|
|
|
|
|
Note: django-thumbs assumes that if filename "any_filename.jpg" is available
|
|
|
filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
|
|
|
|
|
|
To do:
|
|
|
======
|
|
|
Add method to regenerate thubmnails
|
|
|
|
|
|
|
|
|
"""
|
|
|
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, sizes=None, **kwargs):
|
|
|
self.verbose_name=verbose_name
|
|
|
self.name=name
|
|
|
self.width_field=width_field
|
|
|
self.height_field=height_field
|
|
|
self.sizes = sizes
|
|
|
super(ImageField, self).__init__(**kwargs)
|
|
|
|
|
|
|
|
|
from south.modelsinspector import add_introspection_rules
|
|
|
add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"])
|