lamka02sk / picturium
PHP client for the picturium image server: signed URL, srcset and <picture> generation.
Requires
- php: >=8.4
Requires (Dev)
- pestphp/pest: ^5.0
- phpstan/phpstan: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-03 00:44:41 UTC
README
picturium PHP client
A tiny, dependency-free PHP client for picturium.
Build signed media URLs and responsive <img> / <picture> markup without template clutter.
picturium.cc · PHP client docs · picturium server
Why this library?
Your templates describe an image; picturium PHP turns that description into the
correct URL or HTML. It handles query-string encoding, URL signing, DPR
srcsets, responsive sources, and intrinsic dimensions.
It works with plain PHP or any view layer that accepts a string, and ships optional bridges for Laravel and Symfony. The media processing happens in the picturium server.
Features
- Fluent, immutable builder: every server parameter is a typed setter, with enums for fits, formats, gravities and more
- Signed URLs: HMAC-SHA256 signatures that match the server, with a stable parameter order for repeatable CDN cache keys
- Responsive markup: DPR
srcsets,<picture>art direction with media and format sources, andwidth/heightto prevent layout shift - Multiple servers: named connections, each with its own secret and parameter defaults
- Laravel bridge: auto-discovered config, Blade components, an Eloquent cast and Inertia-ready JSON
- Symfony bridge: a bundle with autowiring, Twig functions and a Doctrine column type
- Zero runtime dependencies: framework integrations activate only when their packages are installed
Install
composer require lamka02sk/picturium
PHP 8.4 or newer is required.
Start here
Configure the server once during application boot. Omit secret only when
signing is disabled on the server.
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), );
Then let the library render the complete, escaped image element:
<?= img($product->photo) ->width(640) ->aspectRatio('4/3') ->alt($product->name) ->lazy() ?>
Or use only the piece your template needs:
$image = img('photos/cat.jpg')->width(400)->dpr([1, 2]); $image->src(); // One signed URL $image->srcset(); // "... 1x, ... 2x" $image->urls(); // ['1x' => '...', '2x' => '...'] $image->attributes(); // Raw attribute array for framework adapters $image->attrs(); // Escaped attributes for an existing <img> $image->toHtml(); // A complete <img> element
<img <?= img($product->photo)->width(400)->attrs() ?> alt="<?= $product->name ?>">
Responsive art direction
Use picture() when the image itself should change at a breakpoint. Put the
most specific source first; the browser selects the first matching source.
use Picturium\Enum\Format; use function Picturium\picture; <?= picture('hero.jpg') ->quality(80) ->dpr([1, 2]) ->images(fn ($image) => [ $image->maxWidth(640)->portrait()->width(640)->aspectRatio('1/1'), $image->minWidth(641)->format(Format::Avif)->width(1920)->height(1080), ]) ->alt('A mountain at sunrise') ?>
Each source is an independent image. Shared settings such as quality and DPR
apply to every source; media helpers include minWidth(), maxWidth(),
portrait(), landscape(), and media().
Image controls
Every picturium URL parameter has a typed setter. Use enums for fixed choices and named arguments for grouped controls:
use Picturium\Enum\{Anchor, Extend, Fit, Format, Gravity, Metadata, Resample, Rotate}; img('catalogue/shoes.jpg') ->width(800)->height(600)->aspectRatio('4/3') ->fit(Fit::Contain)->gravity(Gravity::Attention)->extend(Extend::Mirror) ->resample(Resample::Lanczos3)->upsize()->padding(10, 20) ->rotate(Rotate::Left)->background('111827') ->quality(82)->format(Format::Webp)->metadata(Metadata::Icc) ->crop(width: 800, height: 600, gravity: Gravity::Attention) ->watermark(text: 'ACME', anchor: Anchor::BottomRight, opacity: 40) ->download('shoes.webp');
Short aliases are available when URLs are the main concern: w(), h(),
ar(), q(), f(), g(), bg(), and more.
Laravel
The package ships an optional Laravel bridge. Nothing extra to install: it is auto-discovered when Laravel is present and inert when it is not.
php artisan vendor:publish --tag=picturium-config
PICTURIUM_URL=https://img.example.com PICTURIUM_SECRET=your-signature-secret
config/picturium.php also takes defaults applied to every image, named
presets, and additional connections. Keys are DSL setters in kebab-case —
the same vocabulary everywhere:
'defaults' => ['quality' => 80, 'format' => Picturium\Enum\Format::Auto], '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 makes a <picture> preset: each entry is a source, in the same
vocabulary as the sources prop. Sources passed inline replace the preset's.
Such a preset only works on a picture; an img or cast using it throws.
Blade
Every setter is a prop, including the short aliases. Anything the DSL does not
own — class, loading, data-* — lands on the tag:
<x-picturium::img src="hero.jpg" preset="hero" alt="Hero" class="rounded-lg" loading="lazy" /> <x-picturium::img src="hero.jpg" width="800" quality="80" format="webp" :dpr="[1, 2]" fit="cover" alt="Hero" /> <x-picturium::picture src="hero.jpg" quality="80" :dpr="[1, 2]" alt="Hero" class="w-full" :sources="[ ['min-width' => 1024, 'width' => 1920], ['max-width' => 640, 'portrait' => true, 'width' => 640, 'aspect-ratio' => '1/1'], ['max-width' => 640, 'width' => 800], ]" />
An inline prop overrides its preset. on="cdn" picks another configured
server. Grouped controls take an array, spread as named arguments:
<x-picturium::img src="a.jpg" :crop="['width' => 400, 'gravity' => 'attention']" />
One name to watch: style is an image parameter — CSS for SVG rendering,
which the library base64-encodes for you. Inline CSS for the tag goes through
:tag-attributes:
<x-picturium::img src="logo.svg" style="text { fill: red }" :tag-attributes="['style' => 'aspect-ratio:16/9']" />
Eloquent
Cast a column holding a source path, so image treatment is declared once on the model rather than in every controller. The arguments are a preset name and, optionally, a connection:
use Picturium\Laravel\Casts\AsPicturiumImage; protected function casts(): array { return [ 'hero' => AsPicturiumImage::class . ':hero', 'avatar' => AsPicturiumImage::class . ':thumb,cdn', ]; }
$product->hero is now an Image. The column still stores the path, and
assigning an Image back stores its source rather than its URL.
Inertia
Image and Picture are JsonSerializable, so with the cast above a page
needs no per-image work in the controller:
return Inertia::render('Products/Show', ['product' => $product]);
The prop arrives as an attribute object you spread onto an element:
<img v-bind="product.hero">
<img {...product.hero} />
A Picture serializes to { sources, img }:
<picture> <source v-for="s in card.sources" :key="s.srcset" :media="s.media" :type="s.type" :srcset="s.srcset"> <img v-bind="card.img"> </picture>
Symfony
The package also ships a Symfony bundle. Register it:
// 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 } connections: cdn: { url: '%env(PICTURIUM_CDN_URL)%', secret: '%env(PICTURIUM_CDN_SECRET)%' }
It takes the same keys as the Laravel config file. An empty secret means
signing is off. Picturium autowires to the default server, each server is
also a picturium.connection.<name> service, and img() / picture()
resolve through the container.
Twig
With Twig installed, the bundle registers two functions. Props use the Blade vocabulary: DSL setters become parameters (kebab-case or camelCase), anything else lands on the tag:
{{ picturium_img('hero.jpg', {width: 800, quality: 80, alt: 'Hero', class: 'rounded-lg'}) }}
{{ picturium_img('hero.jpg', {alt: 'Hero'}, 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'},
{'max-width': 640, width: 800},
]) }}
As in Blade, style is the SVG image parameter. Inline CSS for the tag goes
through attributes: {style: 'aspect-ratio:16/9'}.
Doctrine
With DBAL installed, the bundle registers a picturium_image column type, plus
picturium_image.<preset> for each preset. The column stores the source path
and hydrates to an Image:
use Picturium\Image; #[ORM\Column(type: 'picturium_image.hero', nullable: true)] public ?Image $hero = null;
Assigning an Image back stores its source rather than its URL. Unlike the
Eloquent cast, the type cannot pick another server: Doctrine types never see
per-column options.
Good to know
- Images are immutable by default, so one configured image can safely create desktop and mobile variants.
- When both dimensions—or one dimension and an aspect ratio—are known, generated HTML includes
widthandheightto prevent layout shift. dpr([1, 2, 3])generates anx-descriptorsrcset. Use<picture>for breakpoint-specific layouts.- URLs use a stable parameter order, keeping signatures and CDN cache keys repeatable.
- Signed URLs do not expire. Rotate the server secret to invalidate them.
Need bare global helpers? Opt in once:
require 'vendor/lamka02sk/picturium/src/globals.php';
Development
composer install
composer test
composer stan
The integration signature test is skipped unless a server is available:
PICTURIUM_URL=http://127.0.0.1:20046 PICTURIUM_SECRET=testsecret composer test
The server
This package builds requests; picturium does the transformation, caching, and media delivery. See the main project for server configuration and supported formats.
License
MIT. See LICENSE.
AI disclosure
This library was written mainly by AI agents and reviewed by a human.