Skip to content

Image Operations

Before you continue

This section assumes you have worked through handling images and that you have your image model setup using an ImageAttachment class.

About Operations

Lets say you have a bunch of images of random sizes and image formats such as JPEG and PNG, but you want a consistent file output of say 100x100 and all PNG. Thats is the purpose of these operations. They take an image, use pillow to transform the image based on filters you specify and save a new version of the file.

Getting Started

You have your ImageAttachment class and Image table like below:

import sqlalchemy as sa
from starlette_files.constants import MB
from starlette_files.fields import ImageAttachment

class ImageType(ImageAttachment):
    # your storage 
    storage = my_storage
    # directory in the stroage to save files to
    directory = "images"
    # allowed content types
    allowed_content_types = ["image/jpeg", "image/png"]
    # maximum allowed size in bytes
    max_length = MB * 5

class Image(Base):
    image = sa.Column(ImageType.as_mutable(sa.JSON))

Next create another SQLAlchemy field that uses ImageRenditionAttachment. This class uses an instance of an ImageAttachment and has functionality to apply operations on the file before being saved to the storage.

from starlette_files.fields import ImageRenditionAttachment

class ImageRenditionType(ImageRenditionAttachment):
    storage = my_storage
    directory = "renditions"

and create another table to store the images:

class ImageRendition(Base):
    image = sa.Column(ImageRenditionType.as_mutable(sa.JSON))
    original_image_id = sa.Column(sa.Integer, sa.ForeignKey("image.id"), nullable=False)
    original_image = sa.orm.relationship("Image", backref="renditions")

Creating a Rendition

Once you have an instance of your Image:

original_image = session.query(Image).first()

you can create a new rendition of it using a filter:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["width-100"]
)

This will take the original image and make it 100px wide. You can then save it as normal:

session = Session()
instance = ImageRendition(image=rendition_obj, original_image=original_image)
session.add(instance)
session.commit()

Filters

Below are the pre-existing filter operations.

Crop

This allows you to take an image and crop it to a certain size.

The format required for this is:

f"crop-{left}x{top}x{width}x{height}"

For example an image of 200x200 that you want to crop to 100x100 in the center would be:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["crop-50x50x100x100"]
)

Do Nothing

This as it sounds does nothing to the image and is useful if you want to still create the rendition but you do not want to change the image.

The format required for this is:

f"original"

For example an image of 200x200 then you want to remain unchanged is:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["original"]
)

Fill

Resize and crop to fill the exact dimensions specified.

This can be particularly useful for websites requiring square thumbnails of arbitrary images. For example, a landscape image of width 2000 and height 1000 treated with the fill200x200 rule would have its height reduced to 200, then its width (ordinarily 400) cropped to 200.

This resize-rule will crop to the image’s focal point if it has been set. If not, it will crop to the centre of the image..

The format required for this is:

f"fill-{width}x{height}"

On images that won’t upscale

It’s possible to request an image with fill dimensions that the image can’t support without upscaling. e.g. an image of width 400 and height 200 requested with fill-400x400. In this situation the ratio of the requested fill will be matched, but the dimension will not. So that example 400x200 image (a 2:1 ratio) could become 200x200 (a 1:1 ratio, matching the resize-rule).

For example an image of 200x200 then you want to fill a 200x100 space is:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["fill-200x100"]
)

Cropping closer to the focal point

By default, we will only crop enough to change the aspect ratio of the image to match the ratio in the resize-rule.

In some cases (e.g. thumbnails), it may be preferable to crop closer to the focal point, so that the subject of the image is more prominent.

You can do this by appending -c<percentage> at the end of the resize-rule. For example, if you would like the image to be cropped as closely as possible to its focal point, add -c100:

f"fill-{width}x{height}-c100"

This will crop the image as much as it can, without cropping into the focal point.

If you find that -c100 is too close, you can try -c75 or -c50. Any whole number from 0 to 100 is accepted.

Format

This is an operation that will change the format of the file. You can only use either jpeg or png.

The format required for this is:

f"format-{format}"

For example an image of png then you want to save as a jpeg is:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["format-jpeg"]
)

Min and Max

[min]

This is to basically cover the given dimensions.

This may result in an image slightly larger than the dimensions you specify. A square image of width 2000 and height 2000, treated with the min-500x200 operation would have its height and width changed to 500, i.e matching the width of the resize-rule, but greater than the height.

The format required for this is:

f"min-{width}x{height}"

[max]

Fit within the given dimensions.

The longest edge will be reduced to the matching dimension specified. For example, a portrait image of width 1000 and height 2000, treated with the max-1000x500 rule (a landscape layout) would result in the image being shrunk so the height was 500 pixels and the width was 250.

The format required for this is:

f"max-{width}x{height}"

Scale

The scale operation reduces the image in size by the percentage specified.

The format required for this is:

f"scale-{percent}"

For example an image of 200x100 that you scale to 50 would end up 100x50:

rendition_obj = ImageRenditionType.create_from(
    attachment=original_image.image,
    filter_specs=["scale-50"]
)

Width and Height

[width]

Reduces the width of the image to the dimension specified.

The format required for this is:

f"width-{width}"

[height]

Reduces the height of the image to the dimension specified.

The format required for this is:

f"height-{height}"