The overlay contract 

\LIA\LiaImageserver\Processing\Overlay describes one instruction: composite a second image onto this one. It lives in this extension rather than in a delivery backend because every backend has to agree on it. An imgix-style backend maps it onto watermark parameters; a local compositor turns it into pixel operations. Neither owns the vocabulary, and a producer must not have to guess which spelling a particular backend expects.

Fields 

Field Type Meaning
imageIdentifier ?string Storage-relative path, for a backend that fetches the overlay by URL.
imagePath ?string EXT: path, for a backend that opens the file locally.
assetVersion ?string Opaque fingerprint of the asset's content; nothing renders it.
alignment OverlayAlignment Full anchor grid: top / middle / bottom × left / center / right. Default: OverlayAlignment::BottomRight .
padding int Pixels from the edge. Default: 5.
widthRatio ?float Fraction of the base image width, 0.01 to 1.0. Absent means the overlay keeps its own size.

At least one asset reference is required, and carrying only one is legitimate: each backend uses the reference it can work with and skips the overlay when its own is absent. A reference that is present but blank is rejected: backends tell "absent" from "given" by asking for null , so an empty string would reach URL building and cost the whole image rather than the overlay. Producers build the object and hand over toInstruction() ; backends receive the array and call fromInstruction() .

Producer and backend side
use LIA\LiaImageserver\Processing\Overlay;
use LIA\LiaImageserver\Processing\OverlayAlignment;

// Producer
$instructions[Overlay::INSTRUCTION_NAME] = (new Overlay(
    imageIdentifier: '/watermarks/logo.png',
    imagePath: 'EXT:my_extension/Resources/Public/logo.svg',
    assetVersion: '65dc2851fcf0',
    alignment: OverlayAlignment::BottomRight,
    padding: 20,
    widthRatio: 0.2,
))->toInstruction();

// Backend
$overlay = Overlay::fromInstruction($value);
$overlay->alignment->vertical();    // 'bottom'
$overlay->alignment->horizontal();  // 'right'
Copied!

Why the instruction stays an array 

On the wire the instruction stays a plain array. That is not incidental: it is handed to parameter mapping on one path and folded into a ProcessedFile checksum on the other, so it has to remain serialisable. This is also why absent values are omitted rather than emitted as null . The object is the validating boundary at both ends, not the transport.

Why assetVersion exists 

assetVersion exists because of that second path. A locally compositing backend reaches its rendering through a ProcessedFile looked up by a checksum over this instruction, and imagePath is a stable EXT: path: replace the file behind it and the instruction stays byte-identical, so the old rendering keeps being served and the compositor is never asked. A URL backend does not have that problem when its imageIdentifier is content-addressed, which is exactly why this cannot be left to the backends. The asymmetry sits in their caches, the knowledge sits at the producer, and the checksum is formed before any backend runs. Any string will do as long as it changes with the content; producers whose asset cannot change may omit it.

Errors and sizing 

A malformed instruction throws rather than being coerced or dropped: a backend that guesses at one turns a producer's bug into a rendering that is subtly wrong instead of loudly broken.

widthRatio is measured against the base image, so the resolution an overlay asset needs is the largest rendered base width times the ratio. A backend requests the asset as stored and does not cap it.