co-infra / image
Build img.infra.coop URLs and srcsets. Zero dependencies, framework agnostic.
Requires
- php: ^8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.89
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-23 06:24:08 UTC
README
Build img.infra.coop URLs and srcsets for AT Protocol blobs.
What is this?
img.infra.coop is a free, public image CDN for the AT Protocol. It takes a blob, resizes and reformats it, and serves the result from a durable edge cache, so apps do not have to hand full-resolution originals to browsers.
This package builds the URLs. It does nothing else: no HTTP, no image processing, no configuration to hold. Give it a DID and a blob CID, get a string back.
It is the PHP twin of the @co-infra/image npm package, and produces byte-identical URLs for the same operations.
Why use it?
- Zero dependencies — PHP 8.3+, nothing else. Drops into any framework, or none.
- Clamped by construction — computed widths and qualities are pulled into valid ranges instead of producing a URL the CDN will reject.
- Cache-key safe — params are emitted in a fixed order, so the same transform is always the same URL, and therefore always the same cache entry.
- Presets and srcsets — named transforms and responsive candidate lists, without hand-writing param strings.
Install
composer require co-infra/image
Quick example
use CoInfra\Image\Image; $did = 'did:plc:bcgltzqazw5tb6k2g3ttenbj'; $cid = 'bafkreieaezg7ybnl2ax54owmaqi357gvwdo3rn3ui7b3wtu6i4yp6t3lbe'; Image::url($did, $cid, ['width' => 800, 'quality' => 80]); // https://img.infra.coop/blob/did:plc:.../bafkrei.../w=800,q=80 Image::srcset($did, $cid, [400, 800, 1200]); // .../w=400 400w, .../w=800 800w, .../w=1200 1200w Image::placeholder($did, $cid); // .../w=20,q=30,f=webp,blur=10
Operations
Pass an array, or an Operations object for type safety.
| Key | Type | Range | Param |
|---|---|---|---|
width |
int | 1–4096 | w |
height |
int | 1–4096 | h |
size |
int | 1–4096 | size |
dpr |
float | 1–2 | dpr |
quality |
int | 1–100 | q |
format |
Format|string |
auto, webp, avif, jpeg, png, gif |
f |
fit |
Fit|string |
cover, contain, pad, scale-down |
fit |
gravity |
string | auto, an edge, or a point like 0.5x0.3 |
g |
blur |
int | 1–250 | blur |
sharpen |
float | 0–10 | sharpen |
rotate |
int | 90, 180, 270 | rotate |
background |
string | hex, with or without # |
bg |
preset |
Preset|string |
see below | p |
Out-of-range numbers are clamped. A rotation that is not a right angle, or a background that is not hex, throws InvalidOperationException — there is no sensible nearest value for either.
use CoInfra\Image\{Fit, Format, Operations}; Image::url($did, $cid, new Operations( width: 1200, height: 630, fit: Fit::Cover, format: Format::Auto, ));
Presets
Named transforms the CDN resolves server-side.
| Preset | Shape |
|---|---|
Preset::AvatarSmall |
128px square, smart-cropped |
Preset::Avatar |
256px square, smart-cropped |
Preset::AvatarLarge |
512px square, smart-cropped |
Preset::Banner |
1500×500, 3:1 |
Preset::Feed |
1000px, scaled down |
Preset::Thumb |
500px square, smart-cropped |
Preset::Card |
1200×630 JPEG, 1.91:1 |
Preset::Blur |
20px wide, heavily blurred |
A preset is a starting point. Anything you set alongside it wins:
Image::url($did, $cid, ['preset' => Preset::Avatar, 'quality' => 60]); // .../p=avatar,q=60
The CDN serves its live definitions from /presets and is the source of truth. The enum mirrors it for autocompletion; if the two disagree, the CDN wins.
Private deployments
The public instance is open to everyone, so most apps do not need their own. If you run one, construct a builder with its base URL:
use CoInfra\Image\UrlBuilder; $images = new UrlBuilder('https://img.example.com'); $images->url($did, $cid, ['width' => 800]);
UrlBuilder is a plain injectable object, so a container can bind it once with the configured base and hand it around.
A note on privacy
Every URL this builds is public. The CDN has no viewer authentication, and com.atproto.sync.getBlob is itself public, so a blob in a repo is readable by anyone who knows the DID and CID. Do not use this for anything that is meant to stay private.
Testing
composer test
composer format
Credits
If you use the public instance, add a small acknowledgement linking to infra.coop somewhere on your site.
License
MIT. See LICENSE.