Custom Engines

Thumbor imports a class named Engine from the module configured by ENGINE and creates an instance with the current request context:

ENGINE = "my_package.engine"

Start by subclassing thumbor.engines.BaseEngine:

from thumbor.engines import BaseEngine


class Engine(BaseEngine):
    def create_image(self, buffer):
        """Decode buffer and return the backend image object."""

    @property
    def size(self):
        """Return the current (width, height)."""

    def crop(self, left, top, right, bottom):
        """Crop using Thumbor's pixel coordinates."""

    def resize(self, width, height):
        """Resize the current image."""

    def flip_horizontally(self):
        """Flip the current image horizontally."""

    def flip_vertically(self):
        """Flip the current image vertically."""

    def read(self, extension=None, quality=None):
        """Encode and return the final image bytes."""

Keep the defaults on read(). BaseEngine declares both arguments as required, but the frame filter calls read() without arguments on a new instance of the configured engine, as the PIL engine allows.

BaseEngine.load() detects the extension and converts SVG input before calling create_image(). After create_image() returns, it reads EXIF metadata from self.exif and wraps animated input in per-frame engines. Both steps depend on create_image(): set self.exif to the raw EXIF bytes when the format carries them, and return a list or tuple of frames for animated input so that ALLOW_ANIMATED_GIFS handling applies (ALLOW_ANIMATED_WEBP also enables it for WebP input). Reuse load() unless your format requires a different loading lifecycle.

A production engine must also implement the operations used by the filters and features it supports. These can include gen_image, rotate, image_data_as_rgb, get_image_data, set_image_data, get_image_mode, paste, enable_alpha, convert_to_grayscale, draw_rectangle, extract_cover, has_transparency, avif_enabled, heif_enabled and read_multiple. Unsupported optional operations should fail explicitly or be paired with configuration that prevents the corresponding feature from being selected.

The active request and configuration are available through self.context. Thumbor creates a new engine for every request and does not call cleanup() when the request ends; it only runs for the server context’s engine at shutdown. Release native or temporary resources inside the operations that allocate them instead of relying on cleanup().

Use thumbor.engines.pil.Engine as the reference implementation. Test at least load/read round trips, crop, resize, orientation, transparency, malformed input, animated input if supported, and every automatic output format the engine advertises.