Extending Willow

This section describes how to extend Willow with custom operations, image formats and plugins.

Don’t forget to look at the concepts section first!

Implementing new operations

You can add operations to any existing image class and register them by calling the Registry.register_operation() method passing it the image class, name of the operation and the function to call when the operation is used.

For example, let’s implement a blur operation for both the PillowImage and WandImage classes:

from willow.registry import registry
from willow.plugins.pillow import PillowImage
from willow.plugins.wand import WandImage

def pillow_blur(image):
    from PIL import ImageFilter

    blurred_image = image.image.filter(ImageFilter.BLUR)
    return PillowImage(blurred_image)

def wand_blur(image):
    # Wand modifies images in place so clone it first to prevent
    # altering the original image
    blurred_image = image.image.clone()
    blurred_image.gaussian_blur()
    return WandImage(blurred_image)


# Register the operations in Willow

registry.register_operation(PillowImage, 'blur', pillow_blur)
registry.register_operation(WandImage, 'blur', wand_blur)

It is not required to support both PillowImage and WandImage but it’s recommended that libraries support both for maximum compatibility. You must support Wand if you need animated GIF support.

Implementing custom image classes

You can create your own image classes and register them by calling the Registry.register_image_class() method. All image classes must be a subclass of willow.image.Image.

Methods on image classes can be decorated with @Image.operation, @Image.converter_from or @Image.converter_to which will make Willow automatically register those methods as operations or converters.

For example, let’s implement our own image class for Pillow:

from __future__ import absolute_import

import PIL.Image

from willow.image import (
    Image,
    JPEGImageFile,
    PNGImageFile,
    GIFImageFile,
)


class NewPillowImage(Image):
    def __init__(self, image):
        self.image = image


    # Informational operations

    @Image.operation
    def get_size(self):
        return self.image.size

    @Image.operation
    def has_alpha(self):
        img = self.image
        return img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info)

    @Image.operation
    def has_animation(self):
        # Animation is not supported by PIL
        return False


    # Resize and crop operations

    @Image.operation
    def resize(self, size):
        return PillowImage(image.resize(size, PIL.Image.ANTIALIAS))

    @Image.operation
    def crop(self, rect):
        return PillowImage(self.image.crop(rect))


    # Converter from supported file formats, this is where the image is opened

    # Pillow doesn't support GIFs very well. Adding a cost will make Willow try
    # a different image class first. The default cost for all converters is 100.

    @classmethod
    @Image.converter_from(JPEGImageFile)
    @Image.converter_from(PNGImageFile)
    @Image.converter_from(GIFImageFile, cost=200)
    @Image.converter_from(BMPImageFile)
    def open(cls, image_file):
        image_file.f.seek(0)
        image = PIL.Image.open(image_file.f)

        return cls(image)

The image class can then be registered by calling Registry.register_image_class():

from willow.registry import registry

from newpillow import NewPillowImage

registry.register_image_class(NewPillowImage)

This will also register all operations and converters defined on the class.

Plugins

Plugins allow multiple image classes and/or operations to be registered together. They are Python modules with any of the following attributes defined: willow_image_classes, willow_operations or willow_converters.

For example, we can convert the Python module in the example above into a Willow plugin by adding the following line at the bottom of the file:

willow_image_classes = [NewPillowImage]

It can now be registered using the Registry.register_plugin() method:

from willow.registry import registry

import newpillow

registry.register_plugin(newpillow)

Image optimizers

You can define new image optimizers by subclassing willow.optimizers.base.ImageOptimizer and defining the library_name and image_format attributes.

from willow.optimizers.base import OptimizerBase

class SvgoOptimizer(OptimizerBase):
    library_name = "svgo"
    image_format = "svg"

It can now be registered by calling the Registry.register_optimizer() method passing in the optimizer class name

 from willow.optimizers.base import OptimizerBase
 from willow.registry import registry

 class SvgoOptimizer(ImageOptimizer):
     library_name = "svgo"
     image_format = "svg"

 registry.register_optimizer(SvgoOptimizer)

Note that the registry will only register the optimizer if the library is available on the system. It does so by calling OptimizerBase.check_library() which will call the optimizer library with the --help attribute and return false if the library is not found or calling the library returns a non-zero exit code.

To ensure the registry can check your library, you can override the OptimizerBase.get_check_library_arguments() method to use different arguments that will return a zero exit code.