Image Processing

Resize, crop, and optimize images in templates

4 min read 778 words

Bengal provides image processing functions for resizing, cropping, format conversion, and responsive image generation.

Quick Start

{# Get an image resource #}
{% let hero = page.resources.get("hero.jpg") %}

{# Resize and crop to exact dimensions #}
{% let processed = hero.fill("800x600 webp q80") %}
<img src="{{ processed.rel_permalink }}" 
     width="{{ processed.width }}" 
     height="{{ processed.height }}">

Image Resources

Access images through page resources or the site's asset system:

{# From page bundle (co-located with content) #}
{% let img = page.resources.get("diagram.png") %}

{# From assets directory #}
{% let logo = site.resources.get("images/logo.png") %}

Processing Methods

fill — Crop to Exact Dimensions

Resizes and crops to fill the exact dimensions, maintaining aspect ratio:

{% let cropped = image.fill("800x600") %}
{% let cropped_webp = image.fill("800x600 webp q80 center") %}

Spec format: WIDTHxHEIGHT [format] [quality] [anchor]

fit — Fit Within Dimensions

Resizes to fit within the box without cropping:

{% let thumb = image.fit("400x400") %}
{% let thumb_avif = image.fit("400x400 avif q85") %}

resize — Resize by Width or Height

Resize by one dimension, auto-calculating the other:

{# Width only - height calculated automatically #}
{% let wide = image.resize("800x") %}

{# Height only - width calculated automatically #}
{% let tall = image.resize("x600") %}

Spec String Options

Component Example Description
Dimensions 800x600, 800x, x600 Width×Height (either can be omitted)
Format webp, avif, jpeg, png Output format
Quality q80, q95 JPEG/WebP quality (0-100)
Anchor center, top, smart Crop anchor point

Anchor Points

Forfilloperations, the anchor determines which part of the image to keep:

Anchor Description
center Center of image (default)
top, bottom, left, right Edge anchors
topleft, topright, bottomleft, bottomright Corner anchors
smart Face detection (requires smartcrop)

Responsive Images

srcset Generation

Generatesrcsetattributes for responsive images:

<img src="{{ image_url('hero.jpg', width=800) }}"
     srcset="{{ 'hero.jpg' | image_srcset([400, 800, 1200, 1600]) }}"
     sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1200px"
     alt="Hero image">

Default Sizes

Useimage_srcset_genfor common breakpoints:

{# Uses default sizes: 400, 800, 1200, 1600 #}
<img srcset="{{ image_srcset_gen('hero.jpg') }}" 
     sizes="100vw">

Building Your Own Responsive Image Helper

You can create a reusable responsive image pattern in your templates:

{# Define in a partial or macro #}
{% macro responsive_img(src, alt, widths=[320, 640, 1024, 1280], sizes='100vw', loading='lazy') %}
<img src="{{ image_url(src, width=widths[-1]) }}"
     srcset="{{ src | image_srcset(widths) }}"
     sizes="{{ sizes }}"
     alt="{{ alt }}"
     loading="{{ loading }}" />
{% end %}

{# Usage #}
{{ responsive_img('hero.jpg', alt='Hero image', sizes='(max-width: 768px) 100vw, 50vw') }}

Format Conversion

Convert images to modern formats for smaller file sizes:

{# Convert to WebP (typically 25-35% smaller than JPEG) #}
{% let webp = image.fill("800x600 webp q80") %}

{# Convert to AVIF (typically 50% smaller than JPEG) #}
{% let avif = image.fill("800x600 avif q80") %}

Note

AVIF support requirespillow-avif-plugin. Install with: pip install pillow-avif-plugin

Caching

Processed images are cached in.bengal/image-cache/. The cache key includes:

  • Source file path and modification time
  • Processing operation and parameters

Subsequent builds skip processing for unchanged images.

Template Functions Reference

Function Description Example
image_url(path, width, height, quality) Generate image URL with params {{ image_url('logo.png', width=200) }}
image_srcset(path, sizes) Generate srcset attribute {{ 'hero.jpg' \| image_srcset([400, 800]) }}
image_srcset_gen(path) srcset with default sizes {{ image_srcset_gen('hero.jpg') }}
image_dimensions(path) Get (width, height) tuple {% let dims = image_dimensions('photo.jpg') %}
image_alt(filename) Generate alt text from filename {{ 'my-photo.jpg' \| image_alt }}→ "My photo"
image_data_uri(path) Inline image as data URI {{ image_data_uri('icon.svg') }}

Complete Example

{# Article card with responsive hero image #}
{% let hero = page.resources.get("hero.jpg") %}
{% if hero %}
  {% let processed = hero.fill("1200x630 webp q85") %}
  <article class="card">
    <img 
      src="{{ processed.rel_permalink }}"
      srcset="{{ hero.source_path | image_srcset([400, 800, 1200]) }}"
      sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 1200px"
      width="{{ processed.width }}"
      height="{{ processed.height }}"
      alt="{{ page.title }}"
      loading="lazy"
    >
    <h2>{{ page.title }}</h2>
  </article>
{% end %}

Requirements

Image processing requires Pillow:

pip install bengal[images]
# Or: pip install Pillow

Optional dependencies:

  • smartcrop-py: Smart cropping with face detection
  • pillow-avif-plugin: AVIF format support

Seealso

  • Images & Media — Markdown syntax for images
  • [Image Functions Reference] — Complete function reference