Image manipulation

Server-side image-transform met disk-cache + nginx-static-fallback. Eerste hit doet PHP/Imagick, daarna serveert nginx direct van schijf. Zie IMAGE_MANIPULATION.md in project-root voor het volledige design.

Interne file — sha256-hash uit FileStorage

URL bevat hash + transform-spec + extensie. Immutable: zelfde input → zelfde URL.

return $image->url(
    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    width:  800,
    height: 600,
    fit:    FitMode::Cover,
    format: Format::Webp,
);
/img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800x600-cover-q75.webp

Auto-format via Accept-header

Format::Auto kijkt naar de huidige request — avif > webp > jpeg. URL bevat altijd een concreet formaat.

return [
    'auto'  => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, format: Format::Auto),
    'webp'  => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, format: Format::Webp),
    'avif'  => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, format: Format::Avif),
    'jpeg'  => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, format: Format::Jpeg),
];
Array
(
    [auto] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800-cover-q75.jpg
    [webp] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800-cover-q75.webp
    [avif] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800-cover-q75.avif
    [jpeg] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800-cover-q75.jpg
)

Externe URL — HMAC-signed

Eerste request fetcht de remote bytes en ingest ze in FileStorage. Tampered sig → 403.

return $image->url(
    'https://upload.wikimedia.org/wikipedia/commons/2/27/PNG_transparency_demonstration_1.png',
    width:  600,
    height: 400,
    fit:    FitMode::Cover,
    format: Format::Webp,
);
/img/extern/19/19982d272daba44d9ead20a09ad0f4d19f2ac019d295988c85bc0b77e438c2b4/600x400-cover-q75.webp?u=aHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy8yLzI3L1BOR190cmFuc3BhcmVuY3lfZGVtb25zdHJhdGlvbl8xLnBuZw&sig=H43sPge2cg8pp0-8yELtRJM4p8Jjh5EwIZ11bCSYop8

Fit-modes vergeleken

Cover (vult+snijdt), Contain (letterbox), Fit (geen padding), Crop (exacte uitsnede).

return [
    'cover'   => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 400, height: 300, fit: FitMode::Cover),
    'contain' => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 400, height: 300, fit: FitMode::Contain),
    'fit'     => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 400, height: 300, fit: FitMode::Fit),
    'crop'    => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 400, height: 300, fit: FitMode::Crop),
];
Array
(
    [cover] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/400x300-cover-q75.jpg
    [contain] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/400x300-contain-q75.jpg
    [fit] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/400x300-fit-q75.jpg
    [crop] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/400x300-crop-q75.jpg
)

DPR / retina-variant

dpr=2 op een 800×600-design levert een 1600×1200-render — en een aparte cache-bucket.

return [
    '1x' => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, height: 600, dpr: 1),
    '2x' => $image->url('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', width: 800, height: 600, dpr: 2),
];
Array
(
    [1x] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800x600-cover-q75.jpg
    [2x] => /img/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/800x600-cover-q75-dpr2.jpg
)
Productie-checklist:
  • apt install php-imagick — voor image-transform
  • apt install ffmpeg — alleen voor video-thumbnails
  • .env: IMAGE_SIGNING_SECRET=$(php -r "echo bin2hex(random_bytes(32));")
  • Symlink: ln -s ../data/cache/img public/img (zodat nginx static-fallback werkt)
  • nginx: location ^~ /img/ { try_files $uri /index.php$is_args$args; expires 1y; add_header Cache-Control "public, immutable"; }