torchbox/wagtail — pull request #757 by kaedroho (2014-10-27 17:01:21+00:00)
Title: [WIP] Image backends refactor

This pull request has a couple of big changes to wagtailimages. A new way to access backend APIs using a class called ``ImageBabel`` (working title). And a refactor of image operations to make them easier to extend, test and also chain them together.

### ImageBabel

Currently, Wagtail images has two backends that perform resizing operations on images (``PIL`` and ``Wand``). The user must select which backend that they would like to use and they must use it for all images. Wand is slow and PIL doesn't support animated GIFs so neither are ideal.

Also, we have a third backend (``OpenCV``) which isn't used for resizing images but it is used just for detecting features in images. These operations are not supported by the other two backends.

We may also want to add new backends in the future such as an SVG backend for example and reuse all the operations we already have (and also rasterise the image before sending to the browser).

The ``ImageBabel`` class aims to solve these problems. It allows you to register your backends with it and what operations they support. ``ImageBabel`` will then select the appropriate backend for a particular task and will even dynamically switch between backends if it needs to.

Examples:

Resizing an image (will use either the PIL or Wand backend depending on the filetype of the source image)
```
>>> babel = myimage.get_babel()
>>> babel.resize(100, 100)

>>> with open('myimage.jpg', 'wb') as f:
>>> babel.save_as_jpeg(f)
```

Detecting features (will use the OpenCV backend)
```
>>> babel = myimage.get_babel()
>>> features = babel.detect_features()
>>> faces = babel.detect_faces()
```

Adding a new operation and using it (will use the PIL backend as that's what the new operation is registered with)

```
from wagtail.wagtailimages.babel import ImageBabel, PILState

@ImageBabel.operation(PILState, 'greyscale')
def pil_greyscale(state):
# Converting to LA and back to RGB will remove colour
state.image = state.image.convert('LA').convert('RGB')

>>> babel = myimage.get_babel()
>>> babel.greyscale()
```

### Image operations

Note: Image operations are the things you add into your ``{% image %}`` tag (``fill``, ``max``, ``min``, etc).

Image operations are no longer baked into the backends code (yay!). Theres many advantages to the new approach, heres a few:
- Easier to add new operations with hooks
- Testable
- Chainable (eg: ``{% image myimage "fill-100x100 greyscale" %}``)

For example, if we wanted to turn the above greyscale ``ImageBabel`` operation to something we can use in a template. We would use the following code:

```
class GreyscaleOperation(object):
def __init__(self, method):
# Process parameters here
self.method = method

def run(self, babel, image):
# Run the filter
# "babel" is an ImageBabel object for the image. "image" is the Wagtail Image object itself
babel.greyscale()

# Register the operation
Filter.register_operation('greyscale', GreyscaleOperation)
```

Look here for more examples: https://github.com/kaedroho/wagtail/commit/21283b5ed22808334184f4fb1dab4cb3f0a96a7a