Skip to content

Commit 3249f92

Browse files
authored
Add JPEGField (#211)
1 parent 17da737 commit 3249f92

File tree

5 files changed

+104
-11
lines changed

5 files changed

+104
-11
lines changed

README.md

+14-6
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@ and add `'stdimage'` to `INSTALLED_APP`s in your settings.py, that's it!
3030

3131
## Usage
3232

33-
34-
``StdImageField`` works just like Django's own
33+
`StdImageField` works just like Django's own
3534
[ImageField](https://docs.djangoproject.com/en/dev/ref/models/fields/#imagefield)
3635
except that you can specify different sized variations.
3736

37+
The `JPEGField` works similar to the `StdImageField` but all size variations are
38+
converted to JPEGs, no matter what type the original file is.
39+
3840
### Variations
41+
3942
Variations are specified within a dictionary. The key will be the attribute referencing the resized image.
4043
A variation can be defined both as a tuple or a dictionary.
4144

4245
Example:
4346

4447
```python
4548
from django.db import models
46-
from stdimage.models import StdImageField
49+
from stdimage import StdImageField, JPEGField
4750

4851

4952
class MyModel(models.Model):
@@ -56,6 +59,12 @@ class MyModel(models.Model):
5659

5760
# is the same as dictionary-style call
5861
image = StdImageField(upload_to='path/to/img', variations={'thumbnail': (100, 75)})
62+
63+
# variations are converted to JPEGs
64+
jpeg = JPEGField(
65+
upload_to='path/to/img',
66+
variations={'full': (float('inf'), float('inf')), 'thumbnail': (100, 75)},
67+
)
5968

6069
# creates a thumbnail resized to 100x100 croping if necessary
6170
image = StdImageField(upload_to='path/to/img', variations={
@@ -67,11 +76,10 @@ class MyModel(models.Model):
6776
'large': (600, 400),
6877
'thumbnail': (100, 100, True),
6978
'medium': (300, 200),
70-
delete_orphans=True,
71-
})
79+
}, delete_orphans=True)
7280
```
7381

74-
For using generated variations in templates use `myimagefield.variation_name`.
82+
For using generated variations in templates use `myimagefield.variation_name`.
7583

7684
Example:
7785

stdimage/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .models import StdImageField # NOQA
1+
from .models import JPEGField, StdImageField # NOQA

stdimage/models.py

+66-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class StdImageFileDescriptor(ImageFileDescriptor):
1818
"""The variation property of the field is accessible in instance cases."""
1919

2020
def __set__(self, instance, value):
21-
super(StdImageFileDescriptor, self).__set__(instance, value)
21+
super().__set__(instance, value)
2222
self.field.set_variations(instance)
2323

2424

@@ -170,7 +170,7 @@ class StdImageField(ImageField):
170170
'width': float('inf'),
171171
'height': float('inf'),
172172
'crop': False,
173-
'resample': Image.ANTIALIAS
173+
'resample': Image.ANTIALIAS,
174174
}
175175

176176
def __init__(self, verbose_name=None, name=None, variations=None,
@@ -236,8 +236,9 @@ def __init__(self, verbose_name=None, name=None, variations=None,
236236

237237
def add_variation(self, name, params):
238238
variation = self.def_variation.copy()
239+
variation["kwargs"] = {}
239240
if isinstance(params, (list, tuple)):
240-
variation.update(dict(zip(("width", "height", "crop"), params)))
241+
variation.update(dict(zip(("width", "height", "crop", "kwargs"), params)))
241242
else:
242243
variation.update(params)
243244
variation["name"] = name
@@ -287,3 +288,65 @@ def save_form_data(self, instance, data):
287288
if file and file._committed and file != data:
288289
file.delete(save=False)
289290
super().save_form_data(instance, data)
291+
292+
293+
class JPEGFieldFile(StdImageFieldFile):
294+
295+
@classmethod
296+
def get_variation_name(cls, file_name, variation_name):
297+
path = super().get_variation_name(file_name, variation_name)
298+
path, ext = os.path.splitext(path)
299+
return '%s.jpeg' % path
300+
301+
@classmethod
302+
def process_variation(cls, variation, image):
303+
"""Process variation before actual saving."""
304+
save_kargs = {}
305+
file_format = 'JPEG'
306+
save_kargs['format'] = file_format
307+
308+
resample = variation['resample']
309+
310+
factor = 1
311+
while image.size[0] / factor \
312+
> 2 * variation['width'] \
313+
and image.size[1] * 2 / factor \
314+
> 2 * variation['height']:
315+
factor *= 2
316+
if factor > 1:
317+
image.thumbnail(
318+
(int(image.size[0] / factor),
319+
int(image.size[1] / factor)),
320+
resample=resample
321+
)
322+
323+
size = variation['width'], variation['height']
324+
size = tuple(int(i) if i != float('inf') else i
325+
for i in size)
326+
327+
# http://stackoverflow.com/a/21669827
328+
image = image.convert('RGB')
329+
save_kargs['optimize'] = True
330+
save_kargs['quality'] = 'web_high'
331+
if size[0] * size[1] > 10000: # roughly <10kb
332+
save_kargs['progressive'] = True
333+
334+
if variation['crop']:
335+
image = ImageOps.fit(
336+
image,
337+
size,
338+
method=resample
339+
)
340+
else:
341+
image.thumbnail(
342+
size,
343+
resample=resample
344+
)
345+
346+
save_kargs.update(variation['kwargs'])
347+
348+
return image, save_kargs
349+
350+
351+
class JPEGField(StdImageField):
352+
attr_class = JPEGFieldFile

tests/models.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db import models
66
from PIL import Image
77

8-
from stdimage import StdImageField
8+
from stdimage import JPEGField, StdImageField
99
from stdimage.models import StdImageFieldFile
1010
from stdimage.utils import render_variations
1111
from stdimage.validators import MaxSizeValidator, MinSizeValidator
@@ -59,6 +59,19 @@ class ThumbnailModel(models.Model):
5959
)
6060

6161

62+
class JPEGModel(models.Model):
63+
"""creates a thumbnail resized to maximum size to fit a 100x75 area"""
64+
image = JPEGField(
65+
upload_to=upload_to,
66+
blank=True,
67+
variations={
68+
'full': (float('inf'), float('inf')),
69+
'thumbnail': (100, 75, True),
70+
},
71+
delete_orphans=True,
72+
)
73+
74+
6275
class MaxSizeModel(models.Model):
6376
image = StdImageField(
6477
upload_to=upload_to,

tests/test_models.py

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.core.files.uploadedfile import SimpleUploadedFile
88
from PIL import Image
99

10+
from . import models
1011
from .models import (AdminDeleteModel, CustomRenderVariationsModel, ResizeCropModel,
1112
ResizeModel, SimpleModel, ThumbnailModel,
1213
ThumbnailWithoutDirectoryModel, UtilVariationsModel,)
@@ -222,3 +223,11 @@ def test_min_size_validator(self, admin_client):
222223
'image': self.fixtures['100.gif'],
223224
})
224225
assert not os.path.exists(os.path.join(IMG_DIR, '100.gif'))
226+
227+
228+
class TestJPEGField(TestStdImage):
229+
def test_convert(self, db):
230+
obj = models.JPEGModel.objects.create(image=self.fixtures['100.gif'])
231+
assert obj.image.thumbnail.path.endswith('img/100.thumbnail.jpeg')
232+
assert obj.image.full.width == 100
233+
assert obj.image.full.height == 100

0 commit comments

Comments
 (0)