Images
Resizing, cropping, presets, srcset and base64 placeholders for image fields.
On this page
Declaring an image field
Declare a field of type image, then list it by name in the view:
// site/fields/hero/_index.json
{ "label": "Hero", "type": "@phlat/image", "options": {} }
// site/views/post/_index.json
{ "fields": { "hero": {} } }
image decodes to a PageImage, a PageFile with transform and encoding helpers on top. SVGs are excluded even in an image field because the resize pipeline can't rasterize them; they decode to null. Use a file field for SVG.
Basic access
{$page->hero} {* original file's public URL *}
{$page->hero('w=400,h=300,crop')} {* transformed variant URL, shorthand for src() *}
Bare {$page->hero} calls url(), which exposes the original on first use and returns a cache-busted URL.
Transform options
src() takes a comma-separated option string, the same DSL used across PhlatPage. A bare name such as crop or grayscale is a flag, the same as crop=true, and a bare name that is a configured preset expands to that preset's options:
| Option | Effect |
|---|---|
w=400 |
Max width in pixels |
h=300 |
Max height in pixels |
crop |
Crop to exactly fill w x h (fit=crop is the same). Default scales to fit, no crop |
grayscale |
Desaturate |
invert |
Invert colors |
blur=5 |
Gaussian blur radius |
brightness=10 |
Brightness adjustment |
contrast=20 |
Contrast adjustment |
gamma=1.5 |
Gamma correction |
pixelate=8 |
Pixelate block size |
sharpen=5 |
Sharpen amount |
posterize=16 |
Reduce to at most this many colors |
quality=60 |
JPEG quality (1-100), overriding the config default |
rotate=90 |
Rotate clockwise by this many degrees |
flip=v |
Mirror the image, h (default) or v |
orient |
Auto-rotate upright from EXIF data, before any resize |
trim=10 |
Remove border areas within this color-similarity tolerance |
colorize=20:0:0 |
Adjust red:green:blue channel intensity |
format=webp |
Encode as png, webp, etc. instead of JPEG |
{$page->hero->src('w=800,h=400,fit=crop')}
{$page->hero->src('w=600,grayscale,blur=3')}
{$page->hero->src('orient,w=600')}
{$page->hero->src('rotate=90,flip=v')}
Each unique option string is its own cached variant.
Presets
Presets declared in config.json give a transform a name:
{
"images": {
"presets": {
"thumbnail": "w=150,h=150,crop",
"card": "w=400,h=300,crop",
"hero": "w=1600,h=600,crop",
"avatar": "w=80,h=80,crop,grayscale"
}
}
}
A bare name with no = is a preset. Trailing key=value pairs override it, and a preset may reference other presets:
{$page->hero->src('card')}
{$page->hero->src('card,grayscale,quality=60')}
srcset()
Builds a srcset value from preset names (the width descriptor comes from the preset's w=) or from explicit option-to-descriptor pairs:
<img
src="{$page->hero->src('card')}"
srcset="{$page->hero->srcset(['thumbnail', 'card', 'hero'])}"
>
<img srcset="{$page->hero->srcset(['w=400' => '400w', 'w=800' => '800w'])}">
base64()
Encodes a variant as a data: URI, for blur-up placeholders inlined in the first response. Unlike src(), it generates the variant immediately.
<img
src="{$page->hero->base64('w=20,h=20,blur=5')}"
data-src="{$page->hero->src('w=800')}"
loading="lazy"
>
placeholder()
A global template function, not a field method. It builds a static SVG data URI with no source file, for a src fallback or a fixed-ratio box before any upload:
<img src="{placeholder(400, 300, '#e5e5e5')}" alt="">
Dimensions
width(), height(), ratio(), isLandscape(), isPortrait() and isSquare() read the original file. They are on the decoded PageImage, so call decode() first:
{var $hero = $page->hero->decode()}
{if $hero}
<div class="{if $hero->isPortrait()}max-w-sm{else}max-w-2xl{/if}">
<img src="{$hero->src('w=800')}" width="{$hero->width()}" height="{$hero->height()}">
</div>
{/if}
draw()
draw() and drawLayers() return a variant URL with shapes and text drawn over it, after the usual options are applied. A layer is a string of the same key=value form as image options. Its first token names the shape: rect, ellipse, line, or text=....
| Key | Applies to | Meaning |
|---|---|---|
x, y, w, h |
rect, ellipse, text | Position and size in px or %. A negative x or y counts from the right or bottom edge. w and h default to 100% |
bg, border, border-width |
rect, ellipse | Fill, outline color and outline width |
x1, y1, x2, y2, color, width |
line | End points, color and thickness |
font, size, color |
text | A font file of the page (or {uuid}:{name} for any page), size in px, and color |
align, valign, wrap, line-height, angle |
text | Alignment, wrap width, line height and rotation |
<img src="{$page->cover->draw('rect,y=-80,h=80,bg=#000a', 'w=1200,h=630,crop')}">
{* several layers, in order; an array merges into the layer before it *}
<img src="{$page->cover->drawLayers(['rect,y=-80,h=80,bg=#000a', 'text=Hello,x=24,y=-56,color=#fff,size=32', ['text' => $page->title]], 'w=1200,h=630,crop')}">
A layer that is just a name refers to a drawing in images.drawings. A drawing is one layer string or a list of them, and may name other drawings. Pass the values that must not be parsed as a string, such as a title with commas, as an array merged into the layer before it:
{
"images": {
"drawings": {
"scrim": "rect,y=-80,h=80,bg=#000a",
"caption": ["scrim", "text=,x=24,y=-56,color=#fff,size=32"]
}
}
}
<img src="{$page->cover->drawLayers(['caption', ['text' => $page->title]], 'w=1200,h=630,crop')}">
How variants are generated
src() and transform() only register a variant and return its URL. The variant is rendered when that URL is first requested, and after that the web server serves the file statically when the page is open to anyone. A gated page's variants are not mirrored into site/public/. They are kept in site/data/cache/files/ and served through PHP, behind the page's access check.
If w/h don't shrink the source and no effects (including quality=) are requested, the original is mirrored as-is. Otherwise the variant is re-encoded as JPEG, unless format= asks for another, at quality= or the config default.
Config
{
"images": {
"driver": "gd",
"quality": 80
}
}
driver is the Intervention Image backend, gd or imagick (default imagick). quality is the JPEG quality (1-100) for re-encoded variants.
Example
{* Responsive card image with a blurred inline placeholder while the real variant loads *}
<figure class="relative aspect-[4/3] overflow-hidden rounded-box">
<img
src="{$page->hero->base64('w=24,h=18,blur=4')}"
data-src="{$page->hero->src('card')}"
srcset="{$page->hero->srcset(['card', 'hero'])}"
sizes="(min-width: 768px) 50vw, 100vw"
class="w-full h-full object-cover"
loading="lazy"
alt="{$page->title}"
>
</figure>