Docs / PHP library
PHP library v0.2.0
The official PHP client builds signed URLs, srcset values and complete <img> or
<picture> elements for you. It works with any framework (and ships optional
Laravel and Symfony bridges), has no runtime dependencies, and
needs PHP 8.4 or newer.
composer require lamka02sk/picturium
It only produces strings and arrays and never talks to the server itself, so you can use it from plain PHP or from templates like Blade, Twig and Latte.
Configure
Register your server once while the app boots. The secret has to match
security.signature_secret; leave it out if signed URLs
are off.
use Picturium\Enum\Format;
use Picturium\Picturium;
use function Picturium\img;
Picturium::configure(
url: 'https://img.example.com',
secret: $_ENV['PICTURIUM_SECRET'],
defaults: img()->dpr([1, 2, 3])->quality(80)->format(Format::Auto),
);
defaults is an optional Image that every image and picture starts from. Setters
return copies, so nothing you do later changes it.
You can also register named servers, or skip the static registry and use an instance:
Picturium::configure(url: 'https://cdn.example.com', secret: '...', name: 'cdn');
img('a.jpg')->src(); // default server
img('a.jpg', 'cdn')->src(); // named server
img('a.jpg')->on('cdn')->src(); // switch while retaining parameters
$picturium = new Picturium('https://img.example.com', $secret);
$picturium->image('a.jpg')->width(400)->src();
Images and output layers
Import the namespaced helpers. Because they live in a namespace, they can’t clash with functions in your app.
use function Picturium\{img, picture};
$image = img('photos/cat.jpg')->width(400)->aspectRatio('4/3');
$image->src(); // one URL
$image->srcset(); // comma-separated DPR candidates
$image->urls(); // ['1x' => '...', '2x' => '...']
$image->attributes(); // raw attribute array
$image->attrs(); // escaped attribute string
$image->toHtml(); // complete <img> element
Casting to a string renders the whole element. Inside an existing src attribute, call
src() instead, or you’ll end up with an <img> inside your <img>.
<?= img($product->photo)->width(400)->alt($product->name)->lazy() ?>
<img <?= img($product->photo)->width(400)->attrs() ?> alt="<?= $product->name ?>">
<img src="<?= img('a.jpg')->width(400)->src() ?>">
attributes() returns raw values for framework integrations; attrs() and toHtml()
escape them. The width and height attributes are only added when both are known, or
one can be worked out from an aspect ratio. They’re CSS pixels, not multiplied by DPR.
If you really want global helpers without a namespace, you can opt in:
require 'vendor/lamka02sk/picturium/src/globals.php';
Responsive images
Give dpr a list and you get x descriptors. The CSS width stays the same while
picturium renders each candidate at that multiple.
img('a.jpg')->width(400)->dpr([1, 2])->srcset();
// ...?dpr=1&w=400... 1x, ...?dpr=2&w=400... 2x
The client doesn’t try to guess w descriptors, since those depend on your layout. For
different sizes, crops or formats per breakpoint, use a picture:
use Picturium\Enum\Format;
echo picture('hero.jpg')
->quality(80)->dpr([1, 2])
->images(fn ($image) => [
$image->minWidth(1024)->width(1920)->height(1080),
$image->maxWidth(640)->portrait()->width(640)->aspectRatio('1/1'),
$image->format(Format::Avif)->width(1200),
])
->alt('Hero');
Parameters set on the picture apply to every <source> and the fallback <img>. The
closure gets its own Image, runs when the output is rendered and may run more than once,
so don’t put side effects in it. Sources keep the order of the array, and the browser
uses the first one whose media and type match.
Several conditions are combined with and. A source with a fixed format gets a MIME
type; with Format::Auto there’s no single type, so the attribute is left out.
bare() drops the picture’s shared parameters but keeps instance defaults and media
conditions; pass it a path to use a different file. sources() returns the source data
without rendering it, and image() returns the fallback image.
Transformation builder
Every URL parameter has a typed setter, and parameters with a fixed set of values use enums.
use Picturium\Enum\{
Anchor, Extend, Fit, Format, Gravity, Metadata, Quality, Resample, Rotate
};
img('a.jpg')
->width(800)->height(600)->aspectRatio('16/9')->dpr([1, 2])
->fit(Fit::Contain)->gravity(Gravity::Attention)->extend(Extend::Mirror)
->resample(Resample::Lanczos3)->upsize()->scale(1.5)->padding(10, 20)
->rotate(Rotate::Left)->autoRotate(false)->background('ff0000')
->quality(Quality::High)->format(Format::Webp)
->metadata(Metadata::Icc, Metadata::Exif)
->dpi(300)->style('svg { fill: red; }')->limit(width: 2000, size: '1.5M')
->pages(1, '3-5')->time('00:01:30')->download('cat.jpg')
->original()->force()->cache($version)->fallback('placeholder.jpg');
What each parameter does on the server is explained in Sizing & layout, Cropping, Filters & effects, Watermarking, Output format & quality, Animation & video, and Documents & vectors.
Grouped parameters
The grouped methods take named arguments, and calling one again merges in the new fields.
img('a.jpg')
->crop(width: 800, height: 600, gravity: Gravity::Attention, x: 10, y: 20)
->filter(blur: 4, sharpen: 2.0, saturate: 1.2,
palette: ['black', 'white', 0.8])
->watermark(text: 'ACME', anchor: Anchor::BottomRight,
opacity: 40, padding: [10, 20])
->animate(frames: 36, fps: 12.0, loop: 0);
img('a.jpg')->disableWatermark();
img('a.jpg')->disableAnimation();
animate() accepts frames, timing, fps, loop and stride; you can use timing
or fps, not both. The disable… methods override whatever the server is configured to do.
Short aliases
The aliases just call the long methods, with the same types and checks.
Enum values
Colours can be a CSS name, 3, 4, 6 or 8 digit hex without the #, or one of the
supported CSS colour functions. style() takes plain CSS and base64-encodes it for you.
Validation and immutability
Out-of-range values and invalid combinations throw
Picturium\Exception\InvalidParameter while you build the URL, so a broken one never
reaches your template. The server still has the final say on files and on limits that
depend on its configuration.
$base = img('hero.jpg')->quality(80);
$mobile = $base->width(400);
$desktop = $base->width(1600); // $base is unchanged
mutable() makes one builder update in place. You probably don’t need it: signing the
URL costs more than copying this small object.
Signing and caching
Path segments and query values are percent-encoded. Parameters and grouped sub-parameters are sorted before signing, so the same image always gets the same URL no matter which order you called the setters in. Signed URLs don’t expire; changing the secret invalidates all of them.
cache($version)changes the browser or CDN URL, not picturium’s generated cache key.force()skips the server caches and validators, sendsno-store, and replaces the cached entries.
Caching & force has the details. One thing to keep in
mind: with image.upsize=false, a high-DPR candidate can come out smaller than its
descriptor says if the source isn’t big enough. Output width and height limits are
applied after the DPR multiplication.
Laravel
With Laravel installed, the package’s service provider is discovered automatically;
nothing else to install. Publish the config file and set the server in .env:
php artisan vendor:publish --tag=picturium-config
PICTURIUM_URL=https://img.example.com
PICTURIUM_SECRET=your-signature-secret
config/picturium.php also takes defaults for every image, named presets, and extra
servers under connections. Keys are the builder’s setter names in kebab-case:
'presets' => [
'hero' => ['width' => 1600, 'dpr' => [1, 2]],
'thumb' => ['width' => 200, 'aspect-ratio' => '1/1', 'fit' => Picturium\Enum\Fit::Cover],
'banner' => ['quality' => 80, 'sources' => [
['min-width' => 1024, 'width' => 1920],
['max-width' => 640, 'portrait' => true, 'width' => 640, 'aspect-ratio' => '1/1'],
]],
],
A sources key turns a preset into a reusable <picture>: each entry is one <source>,
using the same keys as the sources prop. Sources you pass inline replace the preset’s.
Presets with sources only work on pictures, so using one on an img or a cast throws.
The bridge adds Blade components, where every setter (and alias) is a prop and anything
else, like class or loading, ends up on the tag:
<x-picturium::img src="hero.jpg" preset="hero" alt="Hero" class="rounded-lg" loading="lazy" />
<x-picturium::picture src="hero.jpg" preset="banner" alt="Hero" />
There’s also an Eloquent cast, AsPicturiumImage, that turns a stored path into an
Image, optionally with a preset and server (AsPicturiumImage::class . ':thumb,cdn').
Image and Picture are JsonSerializable, so with the cast in place you can pass
models straight to Inertia and spread the attributes onto an <img>.
Symfony
The package also ships a bundle. Register it, then configure it with the same keys as the Laravel config file:
// config/bundles.php
return [
// ...
Picturium\Symfony\PicturiumBundle::class => ['all' => true],
];
# config/packages/picturium.yaml
picturium:
url: '%env(PICTURIUM_URL)%'
secret: '%env(PICTURIUM_SECRET)%'
defaults: { quality: 80, format: auto }
presets:
hero: { width: 1600, dpr: [1, 2] }
thumb: { width: 200, aspect-ratio: '1/1', fit: cover }
banner:
quality: 80
sources:
- { min-width: 1024, width: 1920 }
- { max-width: 640, portrait: true, width: 640, aspect-ratio: '1/1' }
connections:
cdn: { url: '%env(PICTURIUM_CDN_URL)%', secret: '%env(PICTURIUM_CDN_SECRET)%' }
An empty secret turns signing off. Picturium autowires to the default server, each
server is also a picturium.connection.<name> service, and img() / picture() go
through the container.
With Twig installed you get two functions. Props use the same names as the Blade components, and anything that isn’t a setter ends up on the tag:
{{ picturium_img('hero.jpg', {alt: 'Hero', class: 'rounded-lg'}, preset: 'hero', on: 'cdn') }}
{{ picturium_picture('hero.jpg', {quality: 80, dpr: [1, 2], alt: 'Hero'}, sources: [
{'min-width': 1024, width: 1920},
{'max-width': 640, portrait: true, width: 640, 'aspect-ratio': '1/1'},
]) }}
style is the SVG image parameter, so inline CSS for the tag goes through
attributes: {style: '...'}.
With Doctrine DBAL installed, the bundle registers a picturium_image column type and a
picturium_image.<preset> type for each preset. The column stores the path and loads as an
Image:
#[ORM\Column(type: 'picturium_image.hero', nullable: true)]
public ?Image $hero = null;
Doctrine types don’t get per-column options, so unlike the Eloquent cast they always use the default server.
API summary
The source and issue tracker are on GitHub. For raw parameter names and status codes, see the Quick reference.