diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index 0c6d34951b..ce07420e33 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -1,6 +1,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured + class InvalidImageBackendError(ImproperlyConfigured): pass @@ -8,23 +9,22 @@ class InvalidImageBackendError(ImproperlyConfigured): class BaseImageBackend(object): def __init__(self, params): self.quality = getattr(settings, 'IMAGE_COMPRESSION_QUALITY', 85) - + def open_image(self, input_file): """ - Open an image and return the backend specific image object to pass - to other methods. The object return has to have a size attribute + Open an image and return the backend specific image object to pass + to other methods. The object return has to have a size attribute which is a tuple with the width and height of the image and a format attribute with the format of the image. """ raise NotImplementedError('subclasses of BaseImageBackend must provide an open_image() method') - - + def save_image(self, image, output): """ Save the image to the output """ raise NotImplementedError('subclasses of BaseImageBackend must provide a save_image() method') - + def resize(self, image, size): """ resize image to the requested size, using highest quality settings @@ -32,11 +32,9 @@ class BaseImageBackend(object): """ raise NotImplementedError('subclasses of BaseImageBackend must provide an resize() method') - def crop_to_centre(self, image, size): raise NotImplementedError('subclasses of BaseImageBackend must provide a crop_to_centre() method') - def resize_to_max(self, image, size): """ Resize image down to fit within the given dimensions, preserving aspect ratio. @@ -58,10 +56,8 @@ class BaseImageBackend(object): final_size = (target_width, int(original_height * horz_scale)) else: final_size = (int(original_width * vert_scale), target_height) - - return self.resize(image, final_size) - + return self.resize(image, final_size) def resize_to_min(self, image, size): """ @@ -87,7 +83,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_width(self, image, target_width): """ Resize image down to the given width, preserving aspect ratio. @@ -104,7 +99,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_height(self, image, target_height): """ Resize image down to the given height, preserving aspect ratio. @@ -121,7 +115,6 @@ class BaseImageBackend(object): return self.resize(image, final_size) - def resize_to_fill(self, image, size): """ Resize down and crop image to fill the given dimensions. Most suitable for thumbnails. diff --git a/wagtail/wagtailimages/backends/pillow.py b/wagtail/wagtailimages/backends/pillow.py index 05d12d291c..96976c277f 100644 --- a/wagtail/wagtailimages/backends/pillow.py +++ b/wagtail/wagtailimages/backends/pillow.py @@ -1,6 +1,9 @@ -from base import BaseImageBackend +from __future__ import absolute_import + +from .base import BaseImageBackend import PIL.Image + class PillowBackend(BaseImageBackend): def __init__(self, params): super(PillowBackend, self).__init__(params) @@ -32,4 +35,4 @@ class PillowBackend(BaseImageBackend): top = (original_height - final_height) / 2 return image.crop( (left, top, left + final_width, top + final_height) - ) \ No newline at end of file + ) diff --git a/wagtail/wagtailimages/backends/wand.py b/wagtail/wagtailimages/backends/wand.py index 36e352715d..91f2d255a7 100644 --- a/wagtail/wagtailimages/backends/wand.py +++ b/wagtail/wagtailimages/backends/wand.py @@ -1,8 +1,9 @@ from __future__ import absolute_import from .base import BaseImageBackend - from wand.image import Image +from wand.api import library + class WandBackend(BaseImageBackend): def __init__(self, params): @@ -10,6 +11,7 @@ class WandBackend(BaseImageBackend): def open_image(self, input_file): image = Image(file=input_file) + image.wand = library.MagickCoalesceImages(image.wand) return image def save_image(self, image, output, format): @@ -18,8 +20,9 @@ class WandBackend(BaseImageBackend): image.save(file=output) def resize(self, image, size): - image.resize(size[0], size[1]) - return image + new_image = image.clone() + new_image.resize(size[0], size[1]) + return new_image def crop_to_centre(self, image, size): (original_width, original_height) = image.size @@ -34,7 +37,9 @@ class WandBackend(BaseImageBackend): left = (original_width - final_width) / 2 top = (original_height - final_height) / 2 - image.crop( + + new_image = image.clone() + new_image.crop( left=left, top=top, right=left + final_width, bottom=top + final_height ) - return image + return new_image diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 781b5cd676..8137971a73 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -194,27 +194,25 @@ class Filter(models.Model): generate an output image with this filter applied, returning it as another django.core.files.File object """ - backend = get_image_backend(backend_name) - + if not self.method: self._parse_spec_string() - + # If file is closed, open it input_file.open('rb') image = backend.open_image(input_file) file_format = image.format - + method = getattr(backend, self.method_name) image = method(image, self.method_arg) output = StringIO.StringIO() backend.save_image(image, output, file_format) - + # and then close the input file input_file.close() - # generate new filename derived from old one, inserting the filter spec string before the extension input_filename_parts = os.path.basename(input_file.name).split('.') @@ -224,7 +222,6 @@ class Filter(models.Model): output_filename = '.'.join(output_filename_parts) output_file = File(output, name=output_filename) - return output_file diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index a2dae86b29..9f05ccad98 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -84,10 +84,10 @@ class TestRenditions(TestCase): # default backend should be pillow backend = get_image_backend() self.assertTrue(isinstance(backend, PillowBackend)) - + def test_minification(self): rendition = self.image.get_rendition('width-400') - + # Check size self.assertEqual(rendition.width, 400) self.assertEqual(rendition.height, 300) @@ -121,7 +121,7 @@ class TestRenditions(TestCase): # Check that they are the same object self.assertEqual(first_rendition, second_rendition) - + class TestRenditionsWand(TestCase): def setUp(self): @@ -141,18 +141,18 @@ class TestRenditionsWand(TestCase): def test_minification(self): rendition = self.image.get_rendition('width-400') - + # Check size self.assertEqual(rendition.width, 400) self.assertEqual(rendition.height, 300) - + def test_resize_to_max(self): rendition = self.image.get_rendition('max-100x100') - + # Check size self.assertEqual(rendition.width, 100) self.assertEqual(rendition.height, 75) - + def test_resize_to_min(self): rendition = self.image.get_rendition('min-120x120') @@ -174,7 +174,7 @@ class TestRenditionsWand(TestCase): # Check that they are the same object self.assertEqual(first_rendition, second_rendition) - + class TestImageTag(TestCase): def setUp(self):