kopia lustrzana https://github.com/wagtail/wagtail
WebP images quality/lossless compression parameters (#6040)
* Add WebP-image quality and lossless * Add WebP-image quality and lossless to Docs * add WebP quality tests * Split image quality filter => jpegquality, webpquality * WebP lossless to inage format options * Update WebP quality/lossless docs * Updated Willow version * WebP quality/lossless minor fixpull/6137/head^2
rodzic
e10a9f26e7
commit
4abeb8232d
|
@ -283,6 +283,7 @@ Wagtail may automatically change the format of some images when they are resized
|
|||
- PNG and JPEG images don't change format
|
||||
- GIF images without animation are converted to PNGs
|
||||
- BMP images are converted to PNGs
|
||||
- WebP images are converted to PNGs
|
||||
|
||||
It is also possible to override the output format on a per-tag basis by using the
|
||||
``format`` filter after the resize rule.
|
||||
|
@ -295,6 +296,15 @@ For example, to make the tag always convert the image to a JPEG, use ``format-jp
|
|||
|
||||
You may also use ``format-png`` or ``format-gif``.
|
||||
|
||||
Lossless WebP
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
You can encode the image into lossless WebP format by using the ``format-webp-lossless`` filter:
|
||||
|
||||
.. code-block:: html+Django
|
||||
|
||||
{% image page.photo width-400 format-webp-lossless %}
|
||||
|
||||
.. _image_background_color:
|
||||
|
||||
Background color
|
||||
|
@ -315,19 +325,19 @@ representing the color you would like to use:
|
|||
{# Sets the image background to black #}
|
||||
{% image page.photo width-400 bgcolor-000 format-jpeg %}
|
||||
|
||||
.. _jpeg_image_quality:
|
||||
.. __image_quality:
|
||||
|
||||
JPEG image quality
|
||||
Image quality
|
||||
------------------
|
||||
|
||||
Wagtail's JPEG image quality setting defaults to 85 (which is quite high). This
|
||||
can be changed either globally or on a per-tag basis.
|
||||
Wagtail's JPEG and WebP image quality settings default to 85 (which is quite high).
|
||||
This can be changed either globally or on a per-tag basis.
|
||||
|
||||
Changing globally
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use the ``WAGTAILIMAGES_JPEG_QUALITY`` setting to change the global default JPEG
|
||||
quality:
|
||||
Use the ``WAGTAILIMAGES_JPEG_QUALITY`` and ``WAGTAILIMAGES_WEBP_QUALITY`` settings
|
||||
to change the global defaults of JPEG and WebP quality:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -335,6 +345,7 @@ quality:
|
|||
|
||||
# Make low-quality but small images
|
||||
WAGTAILIMAGES_JPEG_QUALITY = 40
|
||||
WAGTAILIMAGES_WEBP_QUALITY = 45
|
||||
|
||||
Note that this won't affect any previously generated images so you may want to
|
||||
delete all renditions so they can regenerate with the new setting. This can be
|
||||
|
@ -349,20 +360,23 @@ done from the Django shell:
|
|||
Changing per-tag
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
It's also possible to have different JPEG qualities on individual tags by using
|
||||
the ``jpegquality`` filter. This will always override the default setting:
|
||||
It's also possible to have different JPEG and WebP qualities on individual tags
|
||||
by using ``jpegquality`` and ``webpquality`` filters. This will always override
|
||||
the default setting:
|
||||
|
||||
.. code-block:: html+Django
|
||||
|
||||
{% image page.photo width-400 jpegquality-40 %}
|
||||
{% image page.photo_jpeg width-400 jpegquality-40 %}
|
||||
{% image page.photo_webp width-400 webpquality-50 %}
|
||||
|
||||
Note that this will have no effect on PNG or GIF files. If you want all images
|
||||
to be low quality, you can use this filter with ``format-jpeg`` (which forces
|
||||
all images to output in JPEG format):
|
||||
to be low quality, you can use this filter with ``format-jpeg`` or ``format-webp``
|
||||
(which forces all images to output in JPEG or WebP format):
|
||||
|
||||
.. code-block:: html+Django
|
||||
|
||||
{% image page.photo width-400 format-jpeg jpegquality-40 %}
|
||||
{% image page.photo width-400 format-webp webpquality-50 %}
|
||||
|
||||
Generating image renditions in Python
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
2
setup.py
2
setup.py
|
@ -32,7 +32,7 @@ install_requires = [
|
|||
"beautifulsoup4>=4.8,<4.9",
|
||||
"html5lib>=0.999,<2",
|
||||
"Unidecode>=0.04.14,<2.0",
|
||||
"Willow>=1.3,<1.4",
|
||||
"Willow>=1.4,<1.5",
|
||||
"requests>=2.11.1,<3.0",
|
||||
"l18n>=2018.5",
|
||||
"xlsxwriter>=1.2.8,<2.0",
|
||||
|
|
|
@ -254,9 +254,21 @@ class JPEGQualityOperation(Operation):
|
|||
env['jpeg-quality'] = self.quality
|
||||
|
||||
|
||||
class WebPQualityOperation(Operation):
|
||||
def construct(self, quality):
|
||||
self.quality = int(quality)
|
||||
|
||||
if self.quality > 100:
|
||||
raise ValueError("WebP quality must not be higher than 100")
|
||||
|
||||
def run(self, willow, image, env):
|
||||
env['webp-quality'] = self.quality
|
||||
|
||||
|
||||
class FormatOperation(Operation):
|
||||
def construct(self, fmt):
|
||||
self.format = fmt
|
||||
def construct(self, format, *options):
|
||||
self.format = format
|
||||
self.options = options
|
||||
|
||||
if self.format not in ['jpeg', 'png', 'gif', 'webp']:
|
||||
raise ValueError(
|
||||
|
@ -264,6 +276,7 @@ class FormatOperation(Operation):
|
|||
|
||||
def run(self, willow, image, env):
|
||||
env['output-format'] = self.format
|
||||
env['output-format-options'] = self.options
|
||||
|
||||
|
||||
class BackgroundColorOperation(Operation):
|
||||
|
|
|
@ -446,10 +446,8 @@ class Filter:
|
|||
# Allow changing of JPEG compression quality
|
||||
if 'jpeg-quality' in env:
|
||||
quality = env['jpeg-quality']
|
||||
elif hasattr(settings, 'WAGTAILIMAGES_JPEG_QUALITY'):
|
||||
quality = settings.WAGTAILIMAGES_JPEG_QUALITY
|
||||
else:
|
||||
quality = 85
|
||||
quality = getattr(settings, 'WAGTAILIMAGES_JPEG_QUALITY', 85)
|
||||
|
||||
# If the image has an alpha channel, give it a white background
|
||||
if willow.has_alpha():
|
||||
|
@ -461,7 +459,16 @@ class Filter:
|
|||
elif output_format == 'gif':
|
||||
return willow.save_as_gif(output)
|
||||
elif output_format == 'webp':
|
||||
return willow.save_as_webp(output)
|
||||
# Allow changing of WebP compression quality
|
||||
if ('output-format-options' in env
|
||||
and 'lossless' in env['output-format-options']):
|
||||
return willow.save_as_webp(output, lossless=True)
|
||||
elif 'webp-quality' in env:
|
||||
quality = env['webp-quality']
|
||||
else:
|
||||
quality = getattr(settings, 'WAGTAILIMAGES_WEBP_QUALITY', 85)
|
||||
|
||||
return willow.save_as_webp(output, quality=quality)
|
||||
|
||||
def get_cache_key(self, image):
|
||||
vary_parts = []
|
||||
|
|
|
@ -556,6 +556,20 @@ class TestFormatFilter(TestCase):
|
|||
|
||||
self.assertEqual(out.format_name, 'webp')
|
||||
|
||||
def test_webp_lossless(self):
|
||||
fil = Filter(spec='width-400|format-webp-lossless')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
f = BytesIO()
|
||||
with patch('PIL.Image.Image.save') as save:
|
||||
fil.run(image, f)
|
||||
|
||||
# quality=80 is default for Williw and PIL libs
|
||||
save.assert_called_with(f, 'WEBP', quality=80, lossless=True)
|
||||
|
||||
def test_invalid(self):
|
||||
fil = Filter(spec='width-400|format-foo')
|
||||
image = Image.objects.create(
|
||||
|
@ -649,6 +663,90 @@ class TestJPEGQualityFilter(TestCase):
|
|||
save.assert_called_with(f, 'JPEG', quality=40, optimize=True, progressive=True)
|
||||
|
||||
|
||||
class TestWebPQualityFilter(TestCase):
|
||||
def test_default_quality(self):
|
||||
fil = Filter(spec='width-400|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
|
||||
f = BytesIO()
|
||||
with patch('PIL.Image.Image.save') as save:
|
||||
fil.run(image, f)
|
||||
|
||||
save.assert_called_with(f, 'WEBP', quality=85, lossless=False)
|
||||
|
||||
def test_webp_quality_filter(self):
|
||||
fil = Filter(spec='width-400|webpquality-40|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
|
||||
f = BytesIO()
|
||||
with patch('PIL.Image.Image.save') as save:
|
||||
fil.run(image, f)
|
||||
|
||||
save.assert_called_with(f, 'WEBP', quality=40, lossless=False)
|
||||
|
||||
def test_webp_quality_filter_invalid(self):
|
||||
fil = Filter(spec='width-400|webpquality-abc|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
self.assertRaises(InvalidFilterSpecError, fil.run, image, BytesIO())
|
||||
|
||||
def test_webp_quality_filter_no_value(self):
|
||||
fil = Filter(spec='width-400|webpquality')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
self.assertRaises(InvalidFilterSpecError, fil.run, image, BytesIO())
|
||||
|
||||
def test_webp_quality_filter_too_big(self):
|
||||
fil = Filter(spec='width-400|webpquality-101|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
self.assertRaises(InvalidFilterSpecError, fil.run, image, BytesIO())
|
||||
|
||||
@override_settings(
|
||||
WAGTAILIMAGES_WEBP_QUALITY=50
|
||||
)
|
||||
def test_webp_quality_setting(self):
|
||||
fil = Filter(spec='width-400|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
|
||||
f = BytesIO()
|
||||
with patch('PIL.Image.Image.save') as save:
|
||||
fil.run(image, f)
|
||||
|
||||
save.assert_called_with(f, 'WEBP', quality=50, lossless=False)
|
||||
|
||||
@override_settings(
|
||||
WAGTAILIMAGES_WEBP_QUALITY=50
|
||||
)
|
||||
def test_jpeg_quality_filter_overrides_setting(self):
|
||||
fil = Filter(spec='width-400|webpquality-40|format-webp')
|
||||
image = Image.objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file_jpeg(),
|
||||
)
|
||||
|
||||
f = BytesIO()
|
||||
with patch('PIL.Image.Image.save') as save:
|
||||
fil.run(image, f)
|
||||
|
||||
save.assert_called_with(f, 'WEBP', quality=40, lossless=False)
|
||||
|
||||
|
||||
class TestBackgroundColorFilter(TestCase):
|
||||
def test_original_has_alpha(self):
|
||||
# Checks that the test image we're using has alpha
|
||||
|
|
|
@ -117,6 +117,7 @@ def register_image_operations():
|
|||
('height', image_operations.WidthHeightOperation),
|
||||
('scale', image_operations.ScaleOperation),
|
||||
('jpegquality', image_operations.JPEGQualityOperation),
|
||||
('webpquality', image_operations.WebPQualityOperation),
|
||||
('format', image_operations.FormatOperation),
|
||||
('bgcolor', image_operations.BackgroundColorOperation),
|
||||
]
|
||||
|
|
Ładowanie…
Reference in New Issue