Coder Social home page Coder Social logo

wagtail / willow Goto Github PK

View Code? Open in Web Editor NEW
272.0 29.0 53.0 21.04 MB

A wrapper that combines the functionality of multiple Python image libraries into one API

Home Page: https://willow.wagtail.org/

License: BSD 3-Clause "New" or "Revised" License

Python 99.89% Shell 0.11%

willow's Introduction

PyPI PyPI downloads Build Status

A wrapper that combines the functionality of multiple Python image libraries into one API.

Documentation

Overview

Willow is a simple image library that combines the APIs of Pillow, Wand and OpenCV. It converts the image between the libraries when necessary.

Willow currently has basic resize and crop operations, face and feature detection and animated GIF support. New operations and library integrations can also be easily implemented.

The library is written in pure Python and supports versions 3.8 3.9, 3.10, 3.11 and 3.12.

Examples

Resizing an image

from willow.image import Image

f = open('test.png', 'rb')
img = Image.open(f)

# Resize the image to 100x100 pixels
img = img.resize((100, 100))

# Save it
with open('test_thumbnail.png', 'wb') as out:
   img.save_as_png(out)

This will open the image file with Pillow or Wand (if Pillow is unavailable).

It will then resize it to 100x100 pixels and save it back out as a PNG file.

Detecting faces

from willow.image import Image

f = open('photo.png', 'rb')
img = Image.open(f)

# Find faces
faces = img.detect_faces()

Like above, the image file will be loaded with either Pillow or Wand.

As neither Pillow nor Wand support detecting faces, Willow would automatically convert the image to OpenCV and use that to perform the detection.

Available operations

Documentation

Operation Pillow Wand OpenCV
get_size()
get_frame_count() ✓** ✓**
resize(size)
crop(rect)
rotate(angle)
set_background_color_rgb(color)
transform_colorspace_to_srgb(rendering_intent)
auto_orient()
save_as_jpeg(file, quality)
save_as_png(file)
save_as_gif(file)
save_as_webp(file, quality)
save_as_heif(file, quality, lossless) ✓⁺
save_as_avif(file, quality, lossless) ✓⁺ ✓⁺
save_as_ico(file)
has_alpha() ✓*
has_animation() ✓* ✓*
get_pillow_image()
get_wand_image()
detect_features()
detect_faces(cascade_filename)

* Always returns False

** Always returns 1

⁺ Requires the pillow-heif library

willow's People

Contributors

andre-fuchs avatar arnartumi avatar dopry avatar findsje avatar florianludwig avatar frmdstryr avatar gasman avatar hpoul avatar item4 avatar jams2 avatar kaedroho avatar lb- avatar loicteixeira avatar maiksprenger avatar mans0954 avatar marksteve avatar mozgsml avatar mrchrisadams avatar nealtodd avatar realorangeone avatar roipoussiere avatar salty-ivy avatar sannykr avatar stanmattingly avatar stephanlachnit avatar stormheg avatar tomasolander avatar trumpet2012 avatar wgiddens avatar zerolab avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

willow's Issues

upload fails for animated gif using willow with wagtail

File "C:\Users\Student\Envs\CMS\lib\site-packages\willow\plugins\opencv.py", line 61, in from_buffer_rgb
image = image.reshape(image_buffer.size[1], image_buffer.size[0], 3)
ValueError: cannot reshape array of size 8952300 into shape (343,435,3)

I get this error on three different gif images while uploading in wagtail 2.2.2. two images I created and one random from the internet. I am not sure but it looks like trying to make an rgb array of 3 channels?

APNG

Add support for loading and saving animated PNG files

Affine transformations API

This API would allow us to combine many resize, crop, rotation, scaling, etc operations and apply them all in a single pass.

This would be useful for image editors in Wagtail. The operations that were added by the user can be combined with the operations added in the template and applied together. This is good for both performance and quality as the image would only need to be resampled once.

A minimal implementation would only require exposing the affine transformation operations from Pillow/Wand so we can use them from Wagtail. As a bonus, it might be nice to think about whether we should create an AffineMatrix type to help with constructing and combining matrices for the various operations.

Willow 0.3.1 tarball has wrong version number

Running the commands:

wget https://github.com/torchbox/Willow/archive/v0.3.1.tar.gz
tar xf v0.3.1.tar.gz 
cat Willow-0.3.1/setup.py | grep version=

produces the output

version='0.4a0'

AVIF support

In the 1.5 release, pillow-heif has been used to add .heic support. I noticed that pillow-heif also has .avif functionality. And as far as I can tell, .heic will probably not win the browser-adaptation game against avif, due to its licensing.
So I think I'd be a good addition to integrate avif, as well. It should be an easy to add to willow, at the very least.

Make buffers a bit smarter

Currently, buffers only store a single frame in RGB format.

This can cause the following problems:

  • Animation cannot be transferred between backends
  • Transparency gets lost
  • Images not in RGB format may lose colour data

Include test data in distribution

Please include all data necessary to run the tests.
Currently tests/images/* is missing making it not possible for packagers to run tests to verify whether the package actually built.

Feature: Support opening images by path

Currently, Willow requires a file-like object to construct an image. Pillow itself (and Willow when using the Pillow backend) support opening files by path, which can be much more convenient.

Use image orientation data

copied from wagtail/wagtail#928

Image renditions do not seem to be respecting orientation metadata from the camera. I am currently a version behind, so if this is already fixed, please close this issue. I searched but couldn't find anything.

Here is a few snippets for using orientation metadata for jpegs with Pillow that I've used before:

from PIL import Image, ExifTags

# get the exif tag for Orientation
can_respect_orientation = False
for ORIENTATION_KEY in ExifTags.TAGS.keys() : 
    if ExifTags.TAGS[ORIENTATION_KEY] == 'Orientation':
        can_respect_orientation = True
        break

def flip_horizontal(im):
    return im.transpose(Image.FLIP_LEFT_RIGHT)

def flip_vertical(im):
    return im.transpose(Image.FLIP_TOP_BOTTOM)

def rotate_180(im):
    return im.transpose(Image.ROTATE_180)

def rotate_90(im):
    return im.transpose(Image.ROTATE_90)

def rotate_270(im):
    return im.transpose(Image.ROTATE_270)

def transpose(im):
    return rotate_90(flip_horizontal(im))

def transverse(im):
    return rotate_90(flip_vertical(im))

orientation_funcs = [
    None,
    lambda x: x,
    flip_horizontal,
    rotate_180,
    flip_vertical,
    transpose,
    rotate_270,
    transverse,
    rotate_90
]

        try:
            if data:
                image = Image.open(data)
            else:
                image = Image.open(path)
        except:
            print "Error loading '%s'.  Skipping file" % path
            continue
        else:
            image.thumbnail(MAX_PIXEL_SIZE, Image.ANTIALIAS)

            if can_respect_orientation and hasattr(image, "_getexif"):
                # the thumbnail function does not copy exif data,
                # so manually rotate the image to respect orientation
                exif = image._getexif()
                if exif is not None:
                    orientation = exif.get(ORIENTATION_KEY, None)
                    if orientation:
                        image = orientation_funcs[orientation](image)

Orientation bug

I'm using wagtail and have a lot of images on the website. In the latest upgrade to wagtail 1.4.1 I got error from a few images from the willow plugin orientation code.
Some images gave zero as orientation value from exif.

I forked the project to fix the problem.
I added one line to the ORIENTATION_TO_TRANSPOSE in willow\plugins\pillow.py
0: (),
It's a quick fix but the code needs to validate the value from the exif data before the for loop is executed:
for transpose in ORIENTATION_TO_TRANSPOSE[orientation]:
Invalid value will lead to pages in wagtail not working

Does not detect the animation in this file

Hello! has_animation seems to be failing on this image, which is definitely animated:

20160927_blog_1million

Using 0.3.1:

>>> from willow.image import Image as WillowImage
>>> gif = open('/Users/karchnerr/Downloads/20160927_Blog_1million.gif')
>>> image = WillowImage(gif)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object() takes no parameters
>>> image = WillowImage.open(gif)
>>> image.has_animation
<function wrapper at 0x10154d938>
>>> image.has_animation()
False

I'm on a mac, but have confirmed this on Amazon Linux as well. Is there anything else I could provide to help troubleshoot this?

Cannot Write mode CMYK as PNG

Summary

When leveraging the save_as_png function in the pillow.py plugin, conversion to PNG fails if the image's mode is CMYK.

willow.plugins.pillow in auto_orient KeyError:0

I'm using wagtail and this exception is killing the image chooser at /admin/images/chooser/ because of one image and I can't figure why. None of the images render inside the chooser when this happens. I can see the images at /admin/images/26/, but that one image isn't rendering, and when I go into that image I get the same exception.

Stacktrace (most recent call last):

  File "django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/views/decorators/cache.py", line 43, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "wagtail/wagtailadmin/decorators.py", line 22, in decorated_view
    return view_func(request, *args, **kwargs)
  File "wagtail/wagtailadmin/utils.py", line 87, in wrapped_view_func
    return view_func(request, *args, **kwargs)
  File "wagtail/wagtailimages/views/images.py", line 147, in edit
    request.user, 'delete', image
  File "django/shortcuts.py", line 67, in render
    template_name, context, request=request, using=using)
  File "django/template/loader.py", line 97, in render_to_string
    return template.render(context, request)
  File "django/template/backends/django.py", line 95, in render
    return self.template.render(context)
  File "django/template/base.py", line 206, in render
    return self._render(context)
  File "django/template/base.py", line 197, in _render
    return self.nodelist.render(context)
  File "django/template/base.py", line 992, in render
    bit = node.render_annotated(context)
  File "django/template/loader_tags.py", line 69, in render
    result = block.nodelist.render(context)
  File "django/template/base.py", line 992, in render
    bit = node.render_annotated(context)
  File "django/template/base.py", line 959, in render_annotated
    return self.render(context)
  File "django/template/loader_tags.py", line 69, in render
    result = block.nodelist.render(context)
  File "django/template/base.py", line 992, in render
    bit = node.render_annotated(context)
  File "django/template/base.py", line 959, in render_annotated
    return self.render(context)
  File "wagtail/wagtailimages/templatetags/wagtailimages_tags.py", line 87, in render
    rendition = get_rendition_or_not_found(image, self.filter)
  File "wagtail/wagtailimages/shortcuts.py", line 14, in get_rendition_or_not_found
    return image.get_rendition(specs)
  File "wagtail/wagtailimages/models.py", line 252, in get_rendition
    generated_image = filter.run(self, BytesIO())
  File "wagtail/wagtailimages/models.py", line 388, in run
    willow = willow.auto_orient()
  File "willow/image.py", line 62, in wrapper
    return operation(image, *args, **kwargs)
  File "willow/plugins/pillow.py", line 112, in auto_orient
    for transpose in ORIENTATION_TO_TRANSPOSE[orientation]:

Local vars at this point:

image   
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=565x850 at 0x7FC2409FE310>
orientation 
0
self    
<willow.plugins.pillow.PillowImage object at 0x7fc2409fe450>

ORIENTATION_TO_TRANSPOSE    
1   
2   
Index   Value
0   
0
3   
Index   Value
0   
3
4   
Index   Value
0   
3
1   
0
5   
Index   Value
0   
4
1   
0
6   
Index   Value
0   
4
7   
Index   Value
0   
2
1   
0
8   
Index   Value
0   
2

I copied that from sentry. I unfortunately don't have the original image because a colleague deleted it as it rendered admin unusable because it was killing the image chooser, so I can't replicate on the local env. But maybe you guys can work out what's happening from the above.

Versions:

Willow==0.3
Pillow==3.2.0
wagtail==1.4.3

Cheers,

M

cannot write mode P as WEBP

I have a .png image that is throwing this error:

cannot write mode P as WEBP

from the line with self.image.save in Willow:

    @Image.operation
    def save_as_webp(self, f, quality=80, lossless=False):
        self.image.save(f, 'WEBP', quality=quality, lossless=lossless) …
        return WebPImageFile(f)

Screen Shot 2021-03-10 at 4 20 31 PM

Cant save converted PillowImage with save_as_jpg

I am trying to save an image i created with pillow by opening it with the PillowImage but i am getting an error 'PillowImage' object has no attribute 'save_as_jpg'. I may be misunderstanding the docs but should it not now be coverted to a willow image with the method save_as_jpg()?.
WillowImage from an existing Pillow object

Looking at the pillow plugin it does definitely seem to have the method

Code below

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

from willow.plugins.pillow import PillowImage

input_image_path = image.file.path
watermark_image_path = "/static/img/brand.png"

base_image = Image.open(input_image_path)
watermark = Image.open(watermark_image_path).convert('RGBA')
width, height = base_image.size

# Add watermark to the image
transparent = Image.new('RGBA', (width, height), (0, 0, 0, 0))
transparent.paste(base_image, (0, 0))
transparent.paste(watermark, (0, 0), mask=watermark)
transparent.convert('RGB')

# Convert to WillowImage 
final_image = PillowImage(transparent)
final_image.save_as_jpg(input_image_path)

The final image from local vars:
final_image | <willow.plugins.pillow.PillowImage object at 0x7f5e3c4ee1d0>

Checks not run on intermediate image classes

Each image class has a check() method that's called to check whether that image class may be used. This is so we can still register image classes without requiring their underlying libraries to be installed.

This check is run when looking for image classes with an operation, but not when looking for an intermediate image class.

For example, running face detection on an image file will typically take one of the following routes through Willow (a couple of steps cut out for brevity):
JPEGImageFile => PillowImage => OpenCVImage
GIFImageFile => WandImage => OpenCVImage

We currently do not run the check() method on PillowImage/WandImage in this situation which could lead to a crash if the underlying library isn't installed.

[RFC] Make OpenCV a separately installed module?

Just want to gather opinions on whether we should move the OpenCV plugin (along with the face/feature detection operations) into a separate module. This should be easy to do as it's decoupled already.

Reasons:

Drawbacks:

  • Breaks backwards compatibility
  • Will make face detection features less discoverable

Orientation test image license

Most of the images in tests/images/orientation/ are Copyright 2007 Apple Inc., all rights reserved.

Please can you advise what license(s) these images are being redistributed under?

Thanks.

Feature detection error in matrix.cpp

While uploading multiple images in Wagtail with feature detection enabled, one of my clients faced this error on each image:

File "/…/python3.5/site-packages/willow/plugins/opencv.py" in detect_features
  79.         points = cv2.goodFeaturesToTrack(self.image, 20, 0.04, 1.0)

Exception Type: error at /admin/images/multiple/add/
Exception Value: /io/opencv/modules/core/src/matrix.cpp:436: error: (-215) u != 0 in function create

The images themselves are pretty standard, JPGs of less than 1 MB each. They were generated from Adobe Illustrator vector drawings where most of the image is a white background.

Always getting AttributeError: 'JPEGImageFile' object has no attribute <bar>

Dear Willow-Team,

I am not able to use any attribute (eg. get_size() or resize) on a Willow Image instance. It seems these attributes are not available on the Image subclass (here: JPEGImageFile). I tried the code samples from the README but I must have taken a wrong turn somewhere and can't find my mistake ;-)

I'm using Willow 1.4.1

from willow.image import Image

f = open("/tmp/foo.jpg", "rb")
img = Image.open(f)
img
<willow.image.JPEGImageFile object at 0x7fc7445d2c10>

img.get_size()
AttributeError: 'JPEGImageFile' object has no attribute 'resize'
Full traceback
Python 3.11.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from willow.image import Image
>>> f = open("/tmp/foo.jpg", "rb")
>>> f
<_io.BufferedReader name='/tmp/foo.jpg'>
>>> img = Image.open(f)
>>> img
<willow.image.JPEGImageFile object at 0x7fc7445d2c10>
>>> img.get_size()
Traceback (most recent call last):
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 306, in find_operation
    func = self.get_operation(from_class, operation_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 79, in get_operation
    return self._registered_operations[image_class][operation_name]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'get_size'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/image.py", line 67, in __getattr__
    operation, _, conversion_path, _ = registry.find_operation(type(self), attr)
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 323, in find_operation
    raise UnroutableOperationError(
willow.registry.UnroutableOperationError: The operation 'get_size' is available in the image class 'RGBAImageBuffer, RGBImageBuffer' but it can't be converted to from 'JPEGImageFile'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/image.py", line 70, in __getattr__
    raise AttributeError("%r object has no attribute %r" % (
AttributeError: 'JPEGImageFile' object has no attribute 'get_size'
>>> img = img.resize((100, 100))
Traceback (most recent call last):
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 306, in find_operation
    func = self.get_operation(from_class, operation_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 79, in get_operation
    return self._registered_operations[image_class][operation_name]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'resize'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/image.py", line 67, in __getattr__
    operation, _, conversion_path, _ = registry.find_operation(type(self), attr)
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 313, in find_operation
    image_classes = self.get_image_classes(
                    ^^^^^^^^^^^^^^^^^^^^^^^
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/registry.py", line 109, in get_image_classes
    raise UnavailableOperationError('\n'.join([
willow.registry.UnavailableOperationError: The operation 'resize' is available in the following image classes but they all raised errors:
PillowImage: No module named 'PIL'
WandImage: No module named 'wand'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/projects/bar/.venv/lib/python3.11/site-packages/willow/image.py", line 70, in __getattr__
    raise AttributeError("%r object has no attribute %r" % (
AttributeError: 'JPEGImageFile' object has no attribute 'resize'

Environment Error

Hi,
I'm having trouble installing Wagtail on my Raspberrypi 4.
When I try to install it, I get an Environment Error for willow.

Bellow is a print out of trying to install willow by itself on the same system.

I am using a fresh virtual environment where I have only run pip install wagtail

(env) pi@raspberrypi:~/Dev/wagtail_start $ pip install willow
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting willow
Could not install packages due to an EnvironmentError: 404 Client Error: Not Found for url: https://www.piwheels.org/simple/willow/

Willow fails with Photshop 3.0 jpegs

Attempting to load a jpeg with the following header:

ÿØÿí,Photoshop 3.08BIM�í��,���,��ÿá¸�http://ns.adobe.com/xap/1.0/<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 3.0-29, framework 1.6'>

Causes Willow to fail with:

Traceback (most recent call last):
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/core/handlers/base.py", line 111, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/views/decorators/cache.py", line 38, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/usr/local/django/virtualenvs/gwwagtail/src/wagtail-master/wagtail/wagtailimages/views/chooser.py", line 77, in chooser
    'will_select_format': request.GET.get('select_format')
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/shortcuts.py", line 50, in render
    return HttpResponse(loader.render_to_string(*args, **kwargs),
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/loader.py", line 178, in render_to_string
    return t.render(context_instance)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 148, in render
    return self._render(context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 142, in _render
    return self.nodelist.render(context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/defaulttags.py", line 312, in render
    return nodelist.render(context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/django/template/defaulttags.py", line 208, in render
    nodelist.append(node.render(context))
  File "/usr/local/django/virtualenvs/gwwagtail/src/wagtail-master/wagtail/wagtailimages/templatetags/wagtailimages_tags.py", line 55, in render
    rendition = image.get_rendition(self.filter)
  File "./images/models.py", line 167, in get_rendition
    generated_image, output_format = filter.run(self, BytesIO())
  File "/usr/local/django/virtualenvs/gwwagtail/src/wagtail-master/wagtail/wagtailimages/models.py", line 318, in run
    with image.get_willow_image() as willow:
  File "/opt/tbx/python/3.4/lib/python3.4/contextlib.py", line 59, in __enter__
    return next(self.gen)
  File "./images/models.py", line 82, in get_willow_image
    yield WillowImage.open(image_file)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/willow/image.py", line 62, in open
    initial_backend = cls.find_loader(image_format)
  File "/usr/local/django/virtualenvs/gwwagtail/lib/python3.4/site-packages/willow/image.py", line 143, in find_loader
    raise cls.LoaderError("Cannot find backend that can load this image file")
willow.image.LoaderError: Cannot find backend that can load this image file

Need ability to try or specify alternate face detection

We have lots of standing full height (head to feet) photos where the faces are detected better with "haarscascade_frontface_alt_tree.xml". Currently, "haarscascade_frontalface_alt2.xml" is hardcoded and results in the focal point rect being larger than the image in many cases.

For what its worth I'm not sure if this need is shared by others but since we have over 150,000 images I imagine having some configuration options and/or ability to specify fall backs would be good.

Does this make sense in any way?

AttributeError: 'PNGImageFile' object has no attribute 'detect_faces'

Afternoon all

I'm pretty sure this isn't a problem with willow, but thought I'd ask in here to see if anyone can see my mistake.

We're getting an error trying to upload images.

Server Error Report this error to your webmaster with the following information:
Internal Server Error - 500

AttributeError: 'PNGImageFile' object has no attribute 'detect_faces'

I got the same for jpegs as well.

It's working locally in my docker setup, and I can't seem to spot the difference in the pip list output, they seem the same.

Anyone got any ideas?

notworking.txt
working.txt

Willow auto_orient() on jpeg images with No color-space metadata and no embedded color profile fails

Hey @kaedroho

We are noticing some strange behaviour with willow on jpeg images when calling auto_orient(). It seems to fail on images without colour space metadata or colour profiles. Its hard to replicate if you do not have the correct image (attached below)

To replicate:

Python 3.4.3 (default, Jan 16 2016, 10:01:29)

from willow.image import Image
f = open('IMG_20160602_013511.jpg', 'rb')
img = Image.open(f)
img.auto_orient()
Traceback (most recent call last):
File "", line 1, in
File "/Users/wayne/workspace/cms/venv/lib/python3.4/site-packages/willow/image.py", line 62, in wrapper
return operation(image, _args, *_kwargs)
File "/Users/wayne/workspace/cms/venv/lib/python3.4/site-packages/willow/plugins/pillow.py", line 112, in auto_orient
for transpose in ORIENTATION_TO_TRANSPOSE[orientation]:
KeyError: 0

img_20160602_013511

OpenCV 3 Support

Python 3 only works with OpenCV 3. Are there plans to migrate the opencv backend to the new version?

Many tests are currently failing.

While testing in the following environment:

mock==1.0.1
numpy==1.18.1
opencv-python==4.1.2.30
Pillow==7.0.0
Wand==0.5.8

I noticed many tests were failing.

  1. test_wand.py has two references to PillowImage instead of WandImage.
ERROR: test_open_webp (tests.test_wand.TestWandOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/istefan/workspace/Willow/tests/test_wand.py", line 195, in test_open_webp
    image = PillowImage.open(WebPImageFile(f))
NameError: name 'PillowImage' is not defined
  1. test_detect_faces in test_opencv.py is failing because OpenCV computes slightly different values. To me this proves that the test is not that well designed in the first place. Should this be dropped?
FAIL: test_detect_faces (tests.test_opencv.TestOpenCVOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/istefan/workspace/Willow/tests/test_opencv.py", line 57, in test_detect_faces
    self.assertEqual(faces, self.expected_faces)
AssertionError: Lists differ: [(272, 88, 365, 181), (90, 164, 188, 262)] != [(272, 89, 364, 181), (91, 165, 187, 261)]

First differing element 0:
(272, 88, 365, 181)
(272, 89, 364, 181)

- [(272, 88, 365, 181), (90, 164, 188, 262)]
?         ^    ^          ^    ^    ^    ^

+ [(272, 89, 364, 181), (91, 165, 187, 261)]
?         ^    ^          ^    ^    ^    ^
  1. One assertion in test_jpeg_with_orientation_5 (test_wand.py) fails due to a bigger delta.
FAIL: test_jpeg_with_orientation_5 (tests.test_wand.TestWandImageOrientation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/istefan/workspace/Willow/tests/test_wand.py", line 266, in test_jpeg_with_orientation_5
    self.assert_orientation_landscape_image_is_correct(image)
  File "/Users/istefan/workspace/Willow/tests/test_wand.py", line 218, in assert_orientation_landscape_image_is_correct
    self.assertAlmostEqual(colour.red * 255, 217, delta=12)
AssertionError: 204.0 != 217 within 12 delta (13.0 difference)
  1. test_set_background_color_rgb (test_wand.py) fails on the assertion below. It's a mystery to me why this happens.
FAIL: test_set_background_color_rgb (tests.test_wand.TestWandOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/istefan/workspace/Willow/tests/test_wand.py", line 60, in test_set_background_color_rgb
    self.assertFalse(red_background_image.has_alpha())
AssertionError: True is not false

IOError: cannot write mode P as JPEG

When attempting to resize an image using the {% image %} template tag in Wagtail, I am getting the error: IOError: cannot write mode P as JPEG. According to a question on StackOverflow, this is because JPEGs do not support P (palette?) mode images, and they must be converted to RGB mode before being saved:

Image.open('old.jpeg').convert('RGB').save('new.jpeg')

Stack trace is below:

Internal Server Error: /admin/pages/174/edit/preview/
Traceback (most recent call last):
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/views/decorators/cache.py", line 38, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/srv/acys-wagtail/venv/src/wagtail/wagtail/wagtailadmin/views/pages.py", line 450, in preview_on_edit
    response = page.serve_preview(page.dummy_request(), preview_mode)
  File "/srv/acys-wagtail/venv/src/wagtail/wagtail/contrib/wagtailroutablepage/models.py", line 72, in serve_preview
    return view(request, *args, **kwargs)
  File "/srv/acys-wagtail/acys/utils/views.py", line 38, in proxy
    return self.view(request, page, *args, **kwargs)
  File "/srv/acys-wagtail/acys/yfx/views.py", line 57, in yfxedition_detail
    'prev_yfxedition': prev_yfxedition,
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/shortcuts.py", line 50, in render
    return HttpResponse(loader.render_to_string(*args, **kwargs),
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/loader.py", line 178, in render_to_string
    return t.render(context_instance)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 148, in render
    return self._render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/test/utils.py", line 88, in instrumented_test_render
    return self.nodelist.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py", line 126, in render
    return compiled_parent._render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/test/utils.py", line 88, in instrumented_test_render
    return self.nodelist.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/loader_tags.py", line 65, in render
    result = block.nodelist.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/defaulttags.py", line 312, in render
    return nodelist.render(context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 844, in render
    bit = self.render_node(node, context)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/django/template/base.py", line 858, in render_node
    return node.render(context)
  File "/srv/acys-wagtail/venv/src/wagtail/wagtail/wagtailimages/templatetags/wagtailimages_tags.py", line 55, in render
    rendition = image.get_rendition(self.filter)
  File "/srv/acys-wagtail/venv/src/wagtail/wagtail/wagtailimages/models.py", line 180, in get_rendition
    generated_image = filter.run(self, BytesIO())
  File "/srv/acys-wagtail/venv/src/wagtail/wagtail/wagtailimages/models.py", line 307, in run
    willow.save_as_jpeg(output)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/willow/image.py", line 29, in operation
    return func(self.backend, *args, **kwargs)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/willow/backends/pillow.py", line 65, in pillow_save_as_jpeg
    backend.image.save(f, 'JPEG', quality=quality)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/PIL/Image.py", line 1682, in save
    save_handler(self, fp, filename)
  File "/srv/acys-wagtail/venv/local/lib/python2.7/site-packages/PIL/JpegImagePlugin.py", line 571, in _save
    raise IOError("cannot write mode %s as JPEG" % im.mode)
IOError: cannot write mode P as JPEG

Allow overriding of functions for specific file types

Recently I tried to make Wagtail use gifsicle when resizing gifs, rather than wand and imagemagik as it seemed to be quicker at it.

I thought I could do this by adding the following code with implementations of the functions that just called the gifsicle cli:

        registry.register_operation(GIFImageFile, "auto_orient", auto_orient)
        registry.register_operation(GIFImageFile, "resize", resize)
        registry.register_operation(GIFImageFile, "crop", crop)
        registry.register_operation(GIFImageFile, "save_as_gif", save_as_gif)

After some experimentation this approach worked as expected, however I then got intermittent errors with jpegs. Occasionally throwing an error here stating that cost was None.

This seems to boil down to the registry trying to figure out how it can convert a JPEGImageFile to a GIFImageFile, and adding the following has fixed it for our usecase:

        for image_class in registry.get_image_classes():
            if image_class is GIFImageFile:
                break
            registry.register_converter(image_class, GIFImageFile, None, float("inf"))

I'm unsure if you intended for register_operation to be used in this way, but it seems quite useful and am happy to work on a pull request to remove the need for the register_converter stuff and add tests.

IOError at /admin/images/ cannot write mode P as JPEG

While testing latest Wagtail master, I encountered "IOError at /admin/images/ cannot write mode P as JPEG" with
torch

Full stack trace: http://pastebin.com/Mhy42zHq
PIP freeze:

Django==1.7.1
Pillow==2.7.0
South==1.0
Unidecode==0.04.17
Willow==0.1
argparse==1.2.1
beautifulsoup4==4.3.2
django-appconf==1.0.1
django-compressor==1.4
django-libsass==0.2
django-modelcluster==0.5
django-taggit==0.12.2
django-treebeard==2.0
html5lib==0.999
libsass==0.6.2
psycopg2==2.6
requests==2.5.1
six==1.9.0
unicodecsv==0.9.4
wagtail==0.8.5
wsgiref==0.1.2

Do ignore wagtail==0.8.5 -- wagtaildemo is using the latest Wagtail master as per development instructions.

unhandled incorrect jpeg orientation exif tag

The auto_orient image operation is written fairly defensively, but we have just experienced an image poisoning attack in wagtail where the image had an existing but non-valid orientation value and willow failed to handle it:

  File "/export/apps/venv/lib/python3.10/site-packages/willow/plugins/pillow.py", line 218, in auto_orient
    if 1 <= orientation <= 8:

Exception Type: TypeError at /cms/images/chooser/
Exception Value: '<=' not supported between instances of 'int' and 'str'

Remove fully opaque transparent channels

As discussed in #66, it would be great to detect when a transparent channel is fully opaque and remove it before saving the new image.

This speeds up encoding while decreasing file size by around 10%.

Implementing this feature would essentially be:

from PIL import Image

image = Image.open(original_filename)
if image.mode == 'RGBA':
    colors = image.getchannel('A').getcolors()
    if len(colors) == 1 and colors[0][1] == 255:
        image = image.convert('RGB')
image.save(destination)

SVGs viewbox are skewed when dimensions contain decimal point value

Issue Summary

SVGs that contain a decimal point value in the height or width attribute of the svg tag the viewbox values change when wagtail (Willow?) processes it and saves it to disk. The difference is small sometimes but in the example below the viewbox changes from 6600 64071.3625 which is far away from it's original values.

Steps to Reproduce

  1. Upload the following SVG file to the image gallery:
 <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="800px" width="1732.4px" viewBox="6600 3500 12750 8050" xmlns:xlink="http://www.w3.org/1999/xlink">
    <rect x="676" y="36" width="386" height="924" fill="#72BDD5"/>
</svg>
  1. Inspect the uploaded image original source:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="800.0" width="1732.4" viewBox="64071.3625 35218.75 17438.3125 8050.0">
    <rect x="676" y="36" width="386" height="924" fill="#72BDD5" />
</svg>

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

  • I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

Technical details

  • Python version: 3.9
  • Django version: 4.2.2
  • Wagtail version: 5.0.1

Orientation API

Split from: #52 (comment)

This API would add support for rotations (in 90 degree increments), flipping and transposition of images.

The Orientation class.

This class represents a particular orientation. Orientations can be added or subtracted.

They can be constructed using one of the following methods:

Orientation.default()
Orientation.rotate(angle)  # angle must be in increments of 90
Orientation.flip_horizontal()
Orientation.flip_vertical()

The .get_exif_orientation() operation

Orientation can also be taken from the image's EXIF data using a new get_exif_orientation operation.

The .orient() operation

This takes an orientation object and applies it to the image.

transformed_image = image.orient(image.get_exif_orientation() + Orientation.flip_vertical() + Orientation.rotate(90))

Tests failing with IOError: cannot write mode RGBA as JPEG

python2.7 -m unittest discover -v
...
test_save_as_jpeg (tests.test_pillow.TestPillowOperations) ... ERROR
test_save_as_jpeg_optimised (tests.test_pillow.TestPillowOperations) ... ERROR
test_save_as_jpeg_progressive (tests.test_pillow.TestPillowOperations) ... ERROR
test_save_as_png (tests.test_pillow.TestPillowOperations) ... ok
test_save_as_png_optimised (tests.test_pillow.TestPillowOperations) ... ok
test_save_transparent_gif (tests.test_pillow.TestPillowOperations) ... ok
test_transparent_gif (tests.test_pillow.TestPillowOperations) ... ok

======================================================================
ERROR: test_save_as_jpeg (tests.test_pillow.TestPillowOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_pillow.py", line 31, in test_save_as_jpeg
    return_value = self.image.save_as_jpeg(output)
  File "willow/plugins/pillow.py", line 74, in save_as_jpeg
    image.save(f, 'JPEG', quality=quality, **kwargs)
  File "/usr/lib/python2.7/dist-packages/PIL/Image.py", line 1893, in save
    save_handler(self, fp, filename)
  File "/usr/lib/python2.7/dist-packages/PIL/JpegImagePlugin.py", line 604, in _save
    raise IOError("cannot write mode %s as JPEG" % im.mode)
IOError: cannot write mode RGBA as JPEG

======================================================================
ERROR: test_save_as_jpeg_optimised (tests.test_pillow.TestPillowOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_pillow.py", line 39, in test_save_as_jpeg_optimised
    unoptimised = self.image.save_as_jpeg(io.BytesIO())
  File "willow/plugins/pillow.py", line 74, in save_as_jpeg
    image.save(f, 'JPEG', quality=quality, **kwargs)
  File "/usr/lib/python2.7/dist-packages/PIL/Image.py", line 1893, in save
    save_handler(self, fp, filename)
  File "/usr/lib/python2.7/dist-packages/PIL/JpegImagePlugin.py", line 604, in _save
    raise IOError("cannot write mode %s as JPEG" % im.mode)
IOError: cannot write mode RGBA as JPEG

======================================================================
ERROR: test_save_as_jpeg_progressive (tests.test_pillow.TestPillowOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_pillow.py", line 46, in test_save_as_jpeg_progressive
    image = self.image.save_as_jpeg(io.BytesIO(), progressive=True)
  File "willow/plugins/pillow.py", line 74, in save_as_jpeg
    image.save(f, 'JPEG', quality=quality, **kwargs)
  File "/usr/lib/python2.7/dist-packages/PIL/Image.py", line 1893, in save
    save_handler(self, fp, filename)
  File "/usr/lib/python2.7/dist-packages/PIL/JpegImagePlugin.py", line 604, in _save
    raise IOError("cannot write mode %s as JPEG" % im.mode)
IOError: cannot write mode RGBA as JPEG

----------------------------------------------------------------------
Ran 92 tests in 1.376s

FAILED (errors=3, expected failures=7)

This is with pillow 4.2.1 installed. I believe this is due to an intentional change of behaviour in pillow 4.2.0 and newer:

https://github.com/python-pillow/Pillow/blob/master/docs/releasenotes/4.2.0.rst#removed-deprecated-items

Image save on Google App Engine PIL

I got an error on GAE using with Willow 0.4. When wagtail thumbnail backend processing an image, PIL can't save the image.

Error logs:

hero_header.html, error at line 22
   encoder error -2 when writing image file

   22 :      {% responsiveimage value.image width-1280 srcset="width-650 650w, width-1100 1100w, width-1600 1600w, width-1900 1900w" as image %} 


Traceback:

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/core/handlers/base.py" in get_response
  174.                     response = self.process_exception_by_middleware(e, request)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/core/handlers/base.py" in get_response
  172.                     response = response.render()

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/response.py" in render
  160.             self.content = self.rendered_content

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/response.py" in rendered_content
  137.         content = template.render(context, self._request)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/backends/django.py" in render
  95.             return self.template.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  206.                     return self._render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in _render
  197.         return self.nodelist.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  992.                 bit = node.render_annotated(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_annotated
  959.             return self.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/loader_tags.py" in render
  173.         return compiled_parent._render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in _render
  197.         return self.nodelist.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  992.                 bit = node.render_annotated(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_annotated
  959.             return self.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/loader_tags.py" in render
  69.                 result = block.nodelist.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  992.                 bit = node.render_annotated(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_annotated
  959.             return self.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/defaulttags.py" in render
  220.                     nodelist.append(node.render_annotated(context))

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_annotated
  959.             return self.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  1049.         return render_value_in_context(output, context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_value_in_context
  1026.     value = force_text(value)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/utils/encoding.py" in force_text
  78.                 s = six.text_type(s)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/wagtail/wagtailcore/blocks/base.py" in __str__
  524.         return self.block.render(self.value)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/wagtail/wagtailcore/blocks/base.py" in render
  296.         return mark_safe(render_to_string(template, new_context))

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/loader.py" in render_to_string
  97.         return template.render(context, request)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/backends/django.py" in render
  95.             return self.template.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  206.                     return self._render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in _render
  197.         return self.nodelist.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render
  992.                 bit = node.render_annotated(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/django/template/base.py" in render_annotated
  959.             return self.render(context)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/apps/core/templatetags/responsive_image.py" in render
  55.             rendition = image.get_rendition(self.filter)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/wagtail/wagtailimages/models.py" in get_rendition
  278.             generated_image = filter.run(self, BytesIO())

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/wagtail/wagtailimages/models.py" in run
  464.                 return willow.save_as_jpeg(output, quality=quality, progressive=True, optimize=True)

File "/base/data/home/apps/s~project-v2-staging/1.398769727526236024/sitepackages/prod/willow/plugins/pillow.py" in save_as_jpeg
  73.         image.save(f, 'JPEG', quality=quality, **kwargs)

File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/PIL-1.1.7/PIL/Image.py" in save
  1439.             save_handler(self, fp, filename)

File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/PIL-1.1.7/PIL/JpegImagePlugin.py" in _save
  471.     ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)])

File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/PIL-1.1.7/PIL/ImageFile.py" in _save
  491.                 raise IOError("encoder error %d when writing image file" % s)

Exception Type: IOError at /products/
Exception Value: encoder error -2 when writing image file

I solved this problem by removing optimize and progressive parameters on pillow plugin save methods.
Like this:https://github.com/codeandtheory/Willow/commit/4e5010b7f16ef09c38ad46236a380db5752dcba5

Thanks

MissingDelegateError: no decode delegate for this image format `/tmp/magick-FTfvbM7n' @ error/constitute.c/ReadImage/544

I am running into an issue I cannot figure out.

Here is the traceback:

======================================================================
ERROR: test_wand_image_reads_gif (wagtailimages_autoformat.tests.test_requirements.TestRequirements)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/wagtailimages_autoformat/tests/test_requirements.py", line 22, in test_wand_image_reads_gif
    self.assertIsInstance(image.get_wand_image(), WandImage)
  File "/usr/local/lib/python2.7/dist-packages/willow/image.py", line 60, in wrapper
    image = converter(image)
  File "/usr/local/lib/python2.7/dist-packages/willow/plugins/wand.py", line 124, in open
    image = _wand_image().Image(file=image_file.f)
  File "/usr/local/lib/python2.7/dist-packages/wand/image.py", line 2740, in __init__
    self.read(file=file, resolution=resolution)
  File "/usr/local/lib/python2.7/dist-packages/wand/image.py", line 2822, in read
    self.raise_exception()
  File "/usr/local/lib/python2.7/dist-packages/wand/resource.py", line 222, in raise_exception
    raise e
MissingDelegateError: no decode delegate for this image format `/tmp/magick-r2MTTG6x' @ error/constitute.c/ReadImage/544

Here is a copy of the tests:

from django.contrib.staticfiles import finders
from django.test import TestCase
from willow.plugins.pillow import PillowImage
from willow.plugins.wand import WandImage, _wand_image
from willow.image import Image


class TestRequirements(TestCase):
    def test_wand_image_checks(self):
        # wand is required for animated gifs
        WandImage.check()
    
    def test_pillow_image_checks(self):
        PillowImage.check()
    
    def test_wand_image_reads_gif(self):
        path = finders.find('wagtailimages_autoformat/tests/ajax-loader.gif')
        image = Image.open(open(path, 'rb'))
        self.assertIsInstance(image.get_wand_image(), WandImage)
    
    def test_wand_gif_path_directly(self):
        path = finders.find('wagtailimages_autoformat/tests/ajax-loader.gif')
        with _wand_image().Image(filename=path) as i:
            self.assertEqual(i.animation, True)
            self.assertEqual(i.size, (32, 32))

    def test_wand_gif_file_directly(self):
        path = finders.find('wagtailimages_autoformat/tests/ajax-loader.gif')
        with _wand_image().Image(file=open(path, 'rb')) as i:
            self.assertEqual(i.animation, True)
            self.assertEqual(i.size, (32, 32))

Note that using wand directly has no issues with the file. Only when using the willow api to open the file with wand. See tests test_wand_gif_path_directly and test_wand_gif_file_directly. This error occurs with your 'newtons_cradle.gif' test file as well so it isn't specific to my file or I would upload it as well.

    def test_wand_image_reads_gif(self):
        #path = finders.find('wagtailimages_autoformat/tests/ajax-loader.gif')
        path = finders.find('wagtailimages_autoformat/tests/newtons_cradle.gif')
        with open(path, 'rb') as f:
            image = Image.open(f)
            self.assertIsInstance(image, Image)
            self.assertIsInstance(image.get_wand_image(), WandImage)
            self.assertTrue(image.get_wand_image().has_animation())

Have you encountered this before and what other information can I provide to help narrow this down?

Here is a list of my installed packages:

pip freeze
amqp==1.4.9
anyjson==0.3.3
appdirs==1.4.3
asn1crypto==0.22.0
Babel==2.4.0
backports.ssl-match-hostname==3.5.0.1
beautifulsoup4==4.5.3
billiard==3.3.0.23
boto3==1.4.4
botocore==1.5.38
brotlipy==0.6.0
celery==3.1.25
certifi==2017.1.23
cffi==1.10.0
configparser==3.5.0
coverage==4.3.4
cryptography==1.8.1
csscompressor==0.9.4
Django==1.10.7
django-appconf==1.0.2
django-cachalot==1.4.1
django-classy-tags==0.8.0
django-compressor==2.1.1
django-db-locking==1.2.1
django-modelcluster==3.1
django-randomfields==0.1.7
django-sekizai==0.10.0
django-taggit==0.22.0
django-treebeard==4.1.0
djangorestframework==3.6.2
docutils==0.13.1
elasticsearch==5.3.0
enum34==1.1.6
flake8==3.3.0
flower==0.9.1
funcsigs==1.0.2
futures==3.0.5
html5lib==0.9999999
idna==2.5
ipaddress==1.0.18
isort==4.2.5
Jinja2==2.9.6
jmespath==0.9.2
jobtastic==0.3.1
kombu==3.0.37
librabbitmq==1.6.1
lxml==3.7.3
MarkupSafe==1.0
mccabe==0.6.1
mock==2.0.0
olefile==0.44
packaging==16.8
pbr==2.0.0
Pillow==4.1.0
psutil==3.4.2
psycopg2==2.7.1
pycodestyle==2.3.1
pycparser==2.17
pyflakes==1.5.0
Pygments==1.6
pyOpenSSL==16.2.0
pyparsing==2.2.0
python-dateutil==2.6.0
python-memcached==1.58
pytz==2017.2
rcssmin==1.0.6
requests==2.13.0
rjsmin==1.0.12
roman==2.0.0
s3transfer==0.1.10
six==1.10.0
Sphinx==1.2.2
tornado==4.2
Unidecode==0.4.20
urllib3==1.20
uWSGI==2.0.15
wagtail==1.9
Wand==0.4.4
whitenoise==3.3.0
Willow==0.4

Running in a Docker container based on Ubuntu 14.04 and pillow/wand deps are installed this way: apt-get build-dep -y python-pil python-wand

Convert opaque PNGs to JPGs

For optimisation, it would great to remove the alpha channel of PNGs when it contains only opaque pixels, and convert the image to JPG instead of PNG. It will generate a 1080p image of around 400 kB in JPG vs 2.2 MB for transparent PNG.

For those afraid of loosing quality in the PNG→JPG conversion, we can at least remove the alpha channel while keeping the PNG format.

Personally, I’m in favor of the PNG→JPG conversion for opaque images, because it makes sites load up to 5× faster with no visible difference (given the JPEG quality used by Willow).

Alpha channel on PNGs discarded (image converted to JPEG).

Using both the released Willow==0.2 and the current head of master (3c7e827), while running Wagtail==1.0b1.

To reproduce: upload a PNG with transparency. Renditions will be converted to JPEG, losing the alpha channel.

Sample image input and output attached.

Input:
good

Output:
bad

Animated WebP

Add support for loading and saving animated WebP files

Error handling gifs with transparency

I'm currently using wagtail (version 1.0rc2) and as instructed per the documentation I had wand (version 0.4.0) installed and also have Willow installed from the latest git. I've tried processing a gif and expect that will work seamlessly however this is not the case. One of the images will simply not allow me to upload it (stacktrace below) the other continues to do as what was reported in wagtail/wagtail#1084.

Image 1
Stacktrace:

Internal Server Error: /admin/images/chooser/upload/
Traceback (most recent call last):
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/django/views/decorators/cache.py", line 43, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/wagtail/wagtailimages/views/chooser.py", line 140, in chooser_upload
    {'image_json': get_image_json(image)}
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/wagtail/wagtailimages/views/chooser.py", line 22, in get_image_json
    preview_image = image.get_rendition('max-130x100')
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/wagtail/wagtailimages/models.py", line 191, in get_rendition
    generated_image, output_format = filter.run(self, BytesIO())
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/wagtail/wagtailimages/models.py", line 356, in run
    willow.save(willow.original_format, output)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/willow/image.py", line 67, in save
    return getattr(self, operation_name)(output)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/willow/image.py", line 28, in operation
    return func(self.backend, *args, **kwargs)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/willow/backends/pillow.py", line 81, in save_as_gif
    backend.image.save(f, 'GIF', transparency=backend.image.info['transparency'])
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/PIL/Image.py", line 1703, in save
    save_handler(self, fp, filename)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/PIL/GifImagePlugin.py", line 376, in _save
    _get_local_header(fp, im, (0, 0), flags)
  File "/home/vagrant/bacontrols_venv/local/lib/python2.7/site-packages/PIL/GifImagePlugin.py", line 412, in _get_local_header
    transparency = int(transparency)
TypeError: int() argument must be a string or a number, not 'tuple'

I have attached below the images that I seem to be having issues with.

Image 1(Will not upload, stacktrace above)
sedona-logo
Image 2(Continues to fill transparent area with other colours as per wagtail issue wagtail/wagtail#1084, wand 0.4.0 is installed.)
niagara-logo
Image 2 (Broken)
niagara-logo focus-none original

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.