Search by

lamka02sk / picturium

lamka02sk

PHP client for the picturium image server: signed URL, srcset and <picture> generation.

Package info

github.com/picturium/php-client

pkg:composer/lamka02sk/picturium

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-08-30 23:23 UTC

This package is auto-updated.

Last update: 2026-11-03 00:44:41 UTC


README

picturium

picturium PHP client

A tiny, dependency-free PHP client for picturium.
Build signed media URLs and responsive <img> / <picture> markup without template clutter.

PHP 8.4 or newer Zero runtime dependencies MIT license

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, and width/height to 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 width and height to prevent layout shift.
  • dpr([1, 2, 3]) generates an x-descriptor srcset. 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.