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 detectionpillow-avif-plugin: AVIF format support
Seealso
- Images & Media — Markdown syntax for images
- [Image Functions Reference] — Complete function reference