##// END OF EJS Templates
Fixed image decoding with the new python3 IO module
neko259 -
r767:92c00ade default
parent child Browse files
Show More
@@ -1,219 +1,219 b''
1 # -*- encoding: utf-8 -*-
1 # -*- encoding: utf-8 -*-
2 """
2 """
3 django-thumbs by Antonio Melé
3 django-thumbs by Antonio Melé
4 http://django.es
4 http://django.es
5 """
5 """
6 from django.core.files.images import ImageFile
6 from django.core.files.images import ImageFile
7 from django.db.models import ImageField
7 from django.db.models import ImageField
8 from django.db.models.fields.files import ImageFieldFile
8 from django.db.models.fields.files import ImageFieldFile
9 from PIL import Image
9 from PIL import Image
10 from django.core.files.base import ContentFile
10 from django.core.files.base import ContentFile
11 import io
11 import io
12
12
13
13
14 def generate_thumb(img, thumb_size, format):
14 def generate_thumb(img, thumb_size, format):
15 """
15 """
16 Generates a thumbnail image and returns a ContentFile object with the thumbnail
16 Generates a thumbnail image and returns a ContentFile object with the thumbnail
17
17
18 Parameters:
18 Parameters:
19 ===========
19 ===========
20 img File object
20 img File object
21
21
22 thumb_size desired thumbnail size, ie: (200,120)
22 thumb_size desired thumbnail size, ie: (200,120)
23
23
24 format format of the original image ('jpeg','gif','png',...)
24 format format of the original image ('jpeg','gif','png',...)
25 (this format will be used for the generated thumbnail, too)
25 (this format will be used for the generated thumbnail, too)
26 """
26 """
27
27
28 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
28 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
29 image = Image.open(img)
29 image = Image.open(img)
30
30
31 # get size
31 # get size
32 thumb_w, thumb_h = thumb_size
32 thumb_w, thumb_h = thumb_size
33 # If you want to generate a square thumbnail
33 # If you want to generate a square thumbnail
34 if thumb_w == thumb_h:
34 if thumb_w == thumb_h:
35 # quad
35 # quad
36 xsize, ysize = image.size
36 xsize, ysize = image.size
37 # get minimum size
37 # get minimum size
38 minsize = min(xsize, ysize)
38 minsize = min(xsize, ysize)
39 # largest square possible in the image
39 # largest square possible in the image
40 xnewsize = (xsize - minsize) / 2
40 xnewsize = (xsize - minsize) / 2
41 ynewsize = (ysize - minsize) / 2
41 ynewsize = (ysize - minsize) / 2
42 # crop it
42 # crop it
43 image2 = image.crop(
43 image2 = image.crop(
44 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
44 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
45 # load is necessary after crop
45 # load is necessary after crop
46 image2.load()
46 image2.load()
47 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
47 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
48 image2.thumbnail(thumb_size, Image.ANTIALIAS)
48 image2.thumbnail(thumb_size, Image.ANTIALIAS)
49 else:
49 else:
50 # not quad
50 # not quad
51 image2 = image
51 image2 = image
52 image2.thumbnail(thumb_size, Image.ANTIALIAS)
52 image2.thumbnail(thumb_size, Image.ANTIALIAS)
53
53
54 string_io = io.StringIO()
54 output = io.BytesIO()
55 # PNG and GIF are the same, JPG is JPEG
55 # PNG and GIF are the same, JPG is JPEG
56 if format.upper() == 'JPG':
56 if format.upper() == 'JPG':
57 format = 'JPEG'
57 format = 'JPEG'
58
58
59 image2.save(string_io, format)
59 image2.save(output, format)
60 return ContentFile(string_io.getvalue())
60 return ContentFile(output.getvalue())
61
61
62
62
63 class ImageWithThumbsFieldFile(ImageFieldFile):
63 class ImageWithThumbsFieldFile(ImageFieldFile):
64 """
64 """
65 See ImageWithThumbsField for usage example
65 See ImageWithThumbsField for usage example
66 """
66 """
67
67
68 def __init__(self, *args, **kwargs):
68 def __init__(self, *args, **kwargs):
69 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
69 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
70 self.sizes = self.field.sizes
70 self.sizes = self.field.sizes
71
71
72 if self.sizes:
72 if self.sizes:
73 def get_size(self, size):
73 def get_size(self, size):
74 if not self:
74 if not self:
75 return ''
75 return ''
76 else:
76 else:
77 split = self.url.rsplit('.', 1)
77 split = self.url.rsplit('.', 1)
78 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
78 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
79 return thumb_url
79 return thumb_url
80
80
81 for size in self.sizes:
81 for size in self.sizes:
82 (w, h) = size
82 (w, h) = size
83 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
83 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
84
84
85 def save(self, name, content, save=True):
85 def save(self, name, content, save=True):
86 super(ImageWithThumbsFieldFile, self).save(name, content, save)
86 super(ImageWithThumbsFieldFile, self).save(name, content, save)
87
87
88 if self.sizes:
88 if self.sizes:
89 for size in self.sizes:
89 for size in self.sizes:
90 (w, h) = size
90 (w, h) = size
91 split = self.name.rsplit('.', 1)
91 split = self.name.rsplit('.', 1)
92 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
92 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
93
93
94 # you can use another thumbnailing function if you like
94 # you can use another thumbnailing function if you like
95 thumb_content = generate_thumb(content, size, split[1])
95 thumb_content = generate_thumb(content, size, split[1])
96
96
97 thumb_name_ = self.storage.save(thumb_name, thumb_content)
97 thumb_name_ = self.storage.save(thumb_name, thumb_content)
98
98
99 if not thumb_name == thumb_name_:
99 if not thumb_name == thumb_name_:
100 raise ValueError(
100 raise ValueError(
101 'There is already a file named %s' % thumb_name)
101 'There is already a file named %s' % thumb_name)
102
102
103 def delete(self, save=True):
103 def delete(self, save=True):
104 name = self.name
104 name = self.name
105 super(ImageWithThumbsFieldFile, self).delete(save)
105 super(ImageWithThumbsFieldFile, self).delete(save)
106 if self.sizes:
106 if self.sizes:
107 for size in self.sizes:
107 for size in self.sizes:
108 (w, h) = size
108 (w, h) = size
109 split = name.rsplit('.', 1)
109 split = name.rsplit('.', 1)
110 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
110 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
111 try:
111 try:
112 self.storage.delete(thumb_name)
112 self.storage.delete(thumb_name)
113 except:
113 except:
114 pass
114 pass
115
115
116
116
117 class ImageWithThumbsField(ImageField):
117 class ImageWithThumbsField(ImageField):
118 attr_class = ImageWithThumbsFieldFile
118 attr_class = ImageWithThumbsFieldFile
119 """
119 """
120 Usage example:
120 Usage example:
121 ==============
121 ==============
122 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
122 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
123
123
124 To retrieve image URL, exactly the same way as with ImageField:
124 To retrieve image URL, exactly the same way as with ImageField:
125 my_object.photo.url
125 my_object.photo.url
126 To retrieve thumbnails URL's just add the size to it:
126 To retrieve thumbnails URL's just add the size to it:
127 my_object.photo.url_125x125
127 my_object.photo.url_125x125
128 my_object.photo.url_300x200
128 my_object.photo.url_300x200
129
129
130 Note: The 'sizes' attribute is not required. If you don't provide it,
130 Note: The 'sizes' attribute is not required. If you don't provide it,
131 ImageWithThumbsField will act as a normal ImageField
131 ImageWithThumbsField will act as a normal ImageField
132
132
133 How it works:
133 How it works:
134 =============
134 =============
135 For each size in the 'sizes' atribute of the field it generates a
135 For each size in the 'sizes' atribute of the field it generates a
136 thumbnail with that size and stores it following this format:
136 thumbnail with that size and stores it following this format:
137
137
138 available_filename.[width]x[height].extension
138 available_filename.[width]x[height].extension
139
139
140 Where 'available_filename' is the available filename returned by the storage
140 Where 'available_filename' is the available filename returned by the storage
141 backend for saving the original file.
141 backend for saving the original file.
142
142
143 Following the usage example above: For storing a file called "photo.jpg" it saves:
143 Following the usage example above: For storing a file called "photo.jpg" it saves:
144 photo.jpg (original file)
144 photo.jpg (original file)
145 photo.125x125.jpg (first thumbnail)
145 photo.125x125.jpg (first thumbnail)
146 photo.300x200.jpg (second thumbnail)
146 photo.300x200.jpg (second thumbnail)
147
147
148 With the default storage backend if photo.jpg already exists it will use these filenames:
148 With the default storage backend if photo.jpg already exists it will use these filenames:
149 photo_.jpg
149 photo_.jpg
150 photo_.125x125.jpg
150 photo_.125x125.jpg
151 photo_.300x200.jpg
151 photo_.300x200.jpg
152
152
153 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
153 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
154 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
154 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
155
155
156 To do:
156 To do:
157 ======
157 ======
158 Add method to regenerate thubmnails
158 Add method to regenerate thubmnails
159
159
160
160
161 """
161 """
162
162
163 preview_width_field = None
163 preview_width_field = None
164 preview_height_field = None
164 preview_height_field = None
165
165
166 def __init__(self, verbose_name=None, name=None, width_field=None,
166 def __init__(self, verbose_name=None, name=None, width_field=None,
167 height_field=None, sizes=None,
167 height_field=None, sizes=None,
168 preview_width_field=None, preview_height_field=None,
168 preview_width_field=None, preview_height_field=None,
169 **kwargs):
169 **kwargs):
170 self.verbose_name = verbose_name
170 self.verbose_name = verbose_name
171 self.name = name
171 self.name = name
172 self.width_field = width_field
172 self.width_field = width_field
173 self.height_field = height_field
173 self.height_field = height_field
174 self.sizes = sizes
174 self.sizes = sizes
175 super(ImageField, self).__init__(**kwargs)
175 super(ImageField, self).__init__(**kwargs)
176
176
177 if sizes is not None and len(sizes) == 1:
177 if sizes is not None and len(sizes) == 1:
178 self.preview_width_field = preview_width_field
178 self.preview_width_field = preview_width_field
179 self.preview_height_field = preview_height_field
179 self.preview_height_field = preview_height_field
180
180
181 def update_dimension_fields(self, instance, force=False, *args, **kwargs):
181 def update_dimension_fields(self, instance, force=False, *args, **kwargs):
182 """
182 """
183 Update original image dimension fields and thumb dimension fields
183 Update original image dimension fields and thumb dimension fields
184 (only if 1 thumb size is defined)
184 (only if 1 thumb size is defined)
185 """
185 """
186
186
187 super(ImageWithThumbsField, self).update_dimension_fields(instance,
187 super(ImageWithThumbsField, self).update_dimension_fields(instance,
188 force, *args,
188 force, *args,
189 **kwargs)
189 **kwargs)
190 thumb_width_field = self.preview_width_field
190 thumb_width_field = self.preview_width_field
191 thumb_height_field = self.preview_height_field
191 thumb_height_field = self.preview_height_field
192
192
193 if thumb_width_field is None or thumb_height_field is None \
193 if thumb_width_field is None or thumb_height_field is None \
194 or len(self.sizes) != 1:
194 or len(self.sizes) != 1:
195 return
195 return
196
196
197 original_width = getattr(instance, self.width_field)
197 original_width = getattr(instance, self.width_field)
198 original_height = getattr(instance, self.height_field)
198 original_height = getattr(instance, self.height_field)
199
199
200 if original_width > 0 and original_height > 0:
200 if original_width > 0 and original_height > 0:
201 thumb_width, thumb_height = self.sizes[0]
201 thumb_width, thumb_height = self.sizes[0]
202
202
203 w_scale = float(thumb_width) / original_width
203 w_scale = float(thumb_width) / original_width
204 h_scale = float(thumb_height) / original_height
204 h_scale = float(thumb_height) / original_height
205 scale_ratio = min(w_scale, h_scale)
205 scale_ratio = min(w_scale, h_scale)
206
206
207 if scale_ratio >= 1:
207 if scale_ratio >= 1:
208 thumb_width_ratio = original_width
208 thumb_width_ratio = original_width
209 thumb_height_ratio = original_height
209 thumb_height_ratio = original_height
210 else:
210 else:
211 thumb_width_ratio = int(original_width * scale_ratio)
211 thumb_width_ratio = int(original_width * scale_ratio)
212 thumb_height_ratio = int(original_height * scale_ratio)
212 thumb_height_ratio = int(original_height * scale_ratio)
213
213
214 setattr(instance, thumb_width_field, thumb_width_ratio)
214 setattr(instance, thumb_width_field, thumb_width_ratio)
215 setattr(instance, thumb_height_field, thumb_height_ratio)
215 setattr(instance, thumb_height_field, thumb_height_ratio)
216
216
217
217
218 from south.modelsinspector import add_introspection_rules
218 from south.modelsinspector import add_introspection_rules
219 add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"])
219 add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"])
General Comments 0
You need to be logged in to leave comments. Login now