diff --git a/README.rst b/README.rst index b52319c..bed4c9a 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ of a model class:: class Photo(models.Model): original_image = models.ImageField(upload_to='photos') formatted_image = ImageSpec(image_field='original_image', format='JPEG', - quality=90) + options={'quality': 90}) Accessing the spec through a model instance will create the image and return an ImageFile-like object (just like with a normal @@ -51,7 +51,7 @@ your spec, you can expose different versions of the original image:: original_image = models.ImageField(upload_to='photos') thumbnail = ImageSpec([Adjust(contrast=1.2, sharpness=1.1), resize.Crop(50, 50)], image_field='original_image', - format='JPEG', quality=90) + format='JPEG', options={'quality': 90}) The ``thumbnail`` property will now return a cropped image:: @@ -77,7 +77,7 @@ implement a ``process()`` method:: class Photo(models.Model): original_image = models.ImageField(upload_to='photos') watermarked_image = ImageSpec([Watermark()], image_field='original_image', - format='JPEG', quality=90) + format='JPEG', options={'quality': 90}) Admin diff --git a/imagekit/models.py b/imagekit/models.py index 5297ab2..52135fc 100755 --- a/imagekit/models.py +++ b/imagekit/models.py @@ -15,11 +15,11 @@ from imagekit.processors import ProcessorPipeline, AutoConvert class _ImageSpecMixin(object): - def __init__(self, processors=None, quality=70, format=None, + def __init__(self, processors=None, format=None, options={}, autoconvert=True): self.processors = processors - self.quality = quality self.format = format + self.options = options self.autoconvert = autoconvert def process(self, image, file): @@ -35,16 +35,19 @@ class ImageSpec(_ImageSpecMixin): """ _upload_to_attr = 'cache_to' - def __init__(self, processors=None, quality=70, format=None, + def __init__(self, processors=None, format=None, options={}, image_field=None, pre_cache=False, storage=None, cache_to=None, autoconvert=True): """ :param processors: A list of processors to run on the original image. - :param quality: The quality of the output image. This option is only - used for the JPEG format. :param format: The format of the output file. If not provided, ImageSpec will try to guess the appropriate format based on the extension of the filename and the format of the input image. + :param options: A dictionary that will be passed to PIL's + ``Image.save()`` method as keyword arguments. Valid options vary + between formats, but some examples include ``quality``, + ``optimize``, and ``progressive`` for JPEGs. See the PIL + documentation for others. :param image_field: The name of the model property that contains the original image. :param pre_cache: A boolean that specifies whether the image should @@ -71,8 +74,8 @@ class ImageSpec(_ImageSpecMixin): """ - _ImageSpecMixin.__init__(self, processors, quality=quality, - format=format, autoconvert=autoconvert) + _ImageSpecMixin.__init__(self, processors, format=format, + options=options, autoconvert=autoconvert) self.image_field = image_field self.pre_cache = pre_cache self.storage = storage @@ -125,6 +128,7 @@ class _ImageSpecFileMixin(object): img = open_image(content) original_format = img.format img = self.field.process(img, self) + options = dict(self.field.options or {}) # Determine the format. format = self.field.format @@ -139,19 +143,14 @@ class _ImageSpecFileMixin(object): pass format = format or img.format or original_format or 'JPEG' - if format == 'JPEG': - img_to_fobj_kwargs = dict(quality=int(self.field.quality), - optimize=True) - else: - img_to_fobj_kwargs = {} - # Run the AutoConvert processor if getattr(self.field, 'autoconvert', True): autoconvert_processor = AutoConvert(format) img = autoconvert_processor.process(img) - img_to_fobj_kwargs.update(autoconvert_processor.save_kwargs) + options = dict(autoconvert_processor.save_kwargs.items() + \ + options.items()) - imgfile = img_to_fobj(img, format, **img_to_fobj_kwargs) + imgfile = img_to_fobj(img, format, **options) content = ContentFile(imgfile.read()) return img, content @@ -358,18 +357,22 @@ class ProcessedImageField(models.ImageField, _ImageSpecMixin): _upload_to_attr = 'upload_to' attr_class = ProcessedImageFieldFile - def __init__(self, processors=None, quality=70, format=None, + def __init__(self, processors=None, format=None, options={}, verbose_name=None, name=None, width_field=None, height_field=None, autoconvert=True, **kwargs): """ The ProcessedImageField constructor accepts all of the arguments that the :class:`django.db.models.ImageField` constructor accepts, as well - as the ``processors``, ``format``, and ``quality`` arguments of + as the ``processors``, ``format``, and ``options`` arguments of :class:`imagekit.models.ImageSpec`. """ - _ImageSpecMixin.__init__(self, processors, quality=quality, - format=format, autoconvert=autoconvert) + if 'quality' in kwargs: + raise Exception('The "quality" keyword argument has been' + """ deprecated. Use `options={'quality': %s}` instead.""" \ + % kwargs['quality']) + _ImageSpecMixin.__init__(self, processors, format=format, + options=options, autoconvert=autoconvert) models.ImageField.__init__(self, verbose_name, name, width_field, height_field, **kwargs) diff --git a/imagekit/processors/__init__.py b/imagekit/processors/__init__.py index 51b6282..c7ff304 100644 --- a/imagekit/processors/__init__.py +++ b/imagekit/processors/__init__.py @@ -187,10 +187,10 @@ class AutoConvert(object): def __init__(self, format): self.format = format - self.save_kwargs = {} def process(self, img): matte = False + self.save_kwargs = {} if img.mode == 'RGBA': if self.format in RGBA_TRANSPARENCY_FORMATS: pass @@ -250,4 +250,7 @@ class AutoConvert(object): bg.paste(img, img) img = bg.convert('RGB') + if self.format == 'JPEG': + self.save_kwargs['optimize'] = True + return img diff --git a/tests/core/tests.py b/tests/core/tests.py index 78e954a..3988dc4 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -16,10 +16,12 @@ class Photo(models.Model): original_image = models.ImageField(upload_to='photos') thumbnail = ImageSpec([Adjust(contrast=1.2, sharpness=1.1), Crop(50, 50)], - image_field='original_image', format='JPEG', quality=90) + image_field='original_image', format='JPEG', + options={'quality': 90}) smartcropped_thumbnail = ImageSpec([Adjust(contrast=1.2, sharpness=1.1), SmartCrop(50, 50)], - image_field='original_image', format='JPEG', quality=90) + image_field='original_image', format='JPEG', + options={'quality': 90}) class IKTest(TestCase):