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.