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.

minWidth / maxWidth
minHeight / maxHeight
portrait / landscape
highContrast
media

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');
Builder and HTML
Size
Orientation and colour
Output
Documents and video
Grouped parameters

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.

width w
height h
aspectRatio ar
gravity g
quality q
format f
background bg
rotate rot
padding pad
metadata meta
time t
pages page
animate anim
autoRotate autorot
limit limits

Enum values

AspectRatio
Fit
Gravity
Extend
Resample
Rotate
Quality
Format
Metadata
Anchor

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, sends no-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

Picturium
Image URLs
Image HTML
Picture
Builder state

The source and issue tracker are on GitHub. For raw parameter names and status codes, see the Quick reference.