thumbs.py
214 lines
| 7.3 KiB
| text/x-python
|
PythonLexer
/ boards / thumbs.py
neko259
|
r22 | # -*- encoding: utf-8 -*- | |
""" | |||
django-thumbs by Antonio Melé | |||
http://django.es | |||
""" | |||
neko259
|
r452 | from django.core.files.images import ImageFile | |
neko259
|
r22 | 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 | |||
neko259
|
r765 | import io | |
neko259
|
r22 | ||
neko259
|
r113 | ||
neko259
|
r22 | def generate_thumb(img, thumb_size, format): | |
""" | |||
Generates a thumbnail image and returns a ContentFile object with the thumbnail | |||
neko259
|
r452 | ||
neko259
|
r22 | Parameters: | |
=========== | |||
img File object | |||
neko259
|
r452 | ||
neko259
|
r22 | thumb_size desired thumbnail size, ie: (200,120) | |
neko259
|
r452 | ||
neko259
|
r22 | format format of the original image ('jpeg','gif','png',...) | |
(this format will be used for the generated thumbnail, too) | |||
""" | |||
neko259
|
r113 | ||
neko259
|
r22 | img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details | |
image = Image.open(img) | |||
neko259
|
r113 | ||
neko259
|
r22 | # 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 | |||
neko259
|
r113 | minsize = min(xsize, ysize) | |
neko259
|
r22 | # largest square possible in the image | |
neko259
|
r113 | xnewsize = (xsize - minsize) / 2 | |
ynewsize = (ysize - minsize) / 2 | |||
neko259
|
r22 | # crop it | |
neko259
|
r113 | image2 = image.crop( | |
(xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize)) | |||
neko259
|
r452 | # load is necessary after crop | |
neko259
|
r22 | 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) | |||
neko259
|
r113 | ||
neko259
|
r767 | output = io.BytesIO() | |
neko259
|
r22 | # PNG and GIF are the same, JPG is JPEG | |
neko259
|
r113 | if format.upper() == 'JPG': | |
neko259
|
r22 | format = 'JPEG' | |
neko259
|
r113 | ||
neko259
|
r767 | image2.save(output, format) | |
return ContentFile(output.getvalue()) | |||
neko259
|
r113 | ||
neko259
|
r22 | ||
class ImageWithThumbsFieldFile(ImageFieldFile): | |||
""" | |||
See ImageWithThumbsField for usage example | |||
""" | |||
neko259
|
r113 | ||
neko259
|
r22 | def __init__(self, *args, **kwargs): | |
super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs) | |||
self.sizes = self.field.sizes | |||
neko259
|
r113 | ||
neko259
|
r22 | if self.sizes: | |
def get_size(self, size): | |||
if not self: | |||
return '' | |||
else: | |||
neko259
|
r113 | split = self.url.rsplit('.', 1) | |
thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |||
neko259
|
r22 | return thumb_url | |
neko259
|
r113 | ||
neko259
|
r22 | for size in self.sizes: | |
neko259
|
r113 | (w, h) = size | |
setattr(self, 'url_%sx%s' % (w, h), get_size(self, size)) | |||
neko259
|
r22 | def save(self, name, content, save=True): | |
super(ImageWithThumbsFieldFile, self).save(name, content, save) | |||
neko259
|
r113 | ||
neko259
|
r22 | if self.sizes: | |
for size in self.sizes: | |||
neko259
|
r113 | (w, h) = size | |
split = self.name.rsplit('.', 1) | |||
thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |||
neko259
|
r22 | # you can use another thumbnailing function if you like | |
thumb_content = generate_thumb(content, size, split[1]) | |||
neko259
|
r113 | ||
thumb_name_ = self.storage.save(thumb_name, thumb_content) | |||
neko259
|
r22 | if not thumb_name == thumb_name_: | |
neko259
|
r113 | raise ValueError( | |
'There is already a file named %s' % thumb_name) | |||
neko259
|
r22 | def delete(self, save=True): | |
neko259
|
r113 | name = self.name | |
neko259
|
r22 | super(ImageWithThumbsFieldFile, self).delete(save) | |
if self.sizes: | |||
for size in self.sizes: | |||
neko259
|
r113 | (w, h) = size | |
split = name.rsplit('.', 1) | |||
thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |||
neko259
|
r22 | try: | |
self.storage.delete(thumb_name) | |||
except: | |||
pass | |||
neko259
|
r113 | ||
neko259
|
r22 | 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 | |||
neko259
|
r114 | ||
neko259
|
r22 | ||
""" | |||
neko259
|
r113 | ||
neko259
|
r527 | preview_width_field = None | |
preview_height_field = None | |||
neko259
|
r113 | def __init__(self, verbose_name=None, name=None, width_field=None, | |
neko259
|
r452 | height_field=None, sizes=None, | |
preview_width_field=None, preview_height_field=None, | |||
**kwargs): | |||
neko259
|
r113 | self.verbose_name = verbose_name | |
self.name = name | |||
self.width_field = width_field | |||
self.height_field = height_field | |||
neko259
|
r22 | self.sizes = sizes | |
neko259
|
r114 | super(ImageField, self).__init__(**kwargs) | |
neko259
|
r452 | if sizes is not None and len(sizes) == 1: | |
self.preview_width_field = preview_width_field | |||
self.preview_height_field = preview_height_field | |||
def update_dimension_fields(self, instance, force=False, *args, **kwargs): | |||
""" | |||
Update original image dimension fields and thumb dimension fields | |||
(only if 1 thumb size is defined) | |||
""" | |||
super(ImageWithThumbsField, self).update_dimension_fields(instance, | |||
force, *args, | |||
**kwargs) | |||
thumb_width_field = self.preview_width_field | |||
thumb_height_field = self.preview_height_field | |||
if thumb_width_field is None or thumb_height_field is None \ | |||
or len(self.sizes) != 1: | |||
return | |||
original_width = getattr(instance, self.width_field) | |||
original_height = getattr(instance, self.height_field) | |||
if original_width > 0 and original_height > 0: | |||
thumb_width, thumb_height = self.sizes[0] | |||
w_scale = float(thumb_width) / original_width | |||
h_scale = float(thumb_height) / original_height | |||
scale_ratio = min(w_scale, h_scale) | |||
if scale_ratio >= 1: | |||
thumb_width_ratio = original_width | |||
thumb_height_ratio = original_height | |||
else: | |||
thumb_width_ratio = int(original_width * scale_ratio) | |||
thumb_height_ratio = int(original_height * scale_ratio) | |||
setattr(instance, thumb_width_field, thumb_width_ratio) | |||
neko259
|
r872 | setattr(instance, thumb_height_field, thumb_height_ratio) |