silarhi / picasso-bundle
Responsive image component for Symfony, inspired by next/image
Package info
github.com/silarhi/picasso-bundle
Type:symfony-bundle
pkg:composer/silarhi/picasso-bundle
Requires
- php: >=8.2
- symfony/deprecation-contracts: ^2.5 || ^3.0
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/ux-twig-component: ^2.13 || ^3.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.54.0
- friendsofphp/php-cs-fixer: ^3.95.27
- imagine/imagine: ^1.5.2
- kornrunner/blurhash: ^1.2
- league/flysystem-bundle: ^2.1 || ^3.0
- league/glide: ^2.3 || ^3.0 || ^4.0
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^2.3.1
- phpstan/phpstan-strict-rules: ^2.1.0
- phpstan/phpstan-symfony: ^2.1.0
- phpunit/phpunit: ^11.5.57 || ^12.5.38 || ^13.4.1
- psr/http-client: ^1.0.3
- psr/http-factory: ^1.1
- rector/rector: ^2.7.0
- symfony/http-client: ^6.4 || ^7.0 || ^8.0
- symfony/lock: ^6.4 || ^7.0 || ^8.0
- symfony/twig-bundle: ^6.4 || ^7.0 || ^8.0
- vich/uploader-bundle: ^2.9 || ^3.0
- vincentlanglet/twig-cs-fixer: ^4.1.1
Suggests
- imagine/imagine: Required for the BlurHash placeholder (supports GD and Imagick)
- intervention/image-driver-vips: Required for the Glide vips driver (league/glide 3 or later, needs libvips and the FFI extension)
- kornrunner/blurhash: Required for the BlurHash placeholder
- league/flysystem: Required for the Flysystem loader
- league/glide: Required for the Glide transformer (local image transformation)
- psr/http-client: Required for the URL loader (PSR-18 HTTP client interface)
- psr/http-factory: Required for the URL loader (PSR-17 HTTP factory interface)
- symfony/http-client: Provides a PSR-18 HTTP client implementation for the URL loader
- symfony/lock: Required for the Glide lock option (render each missing variant once)
- vich/uploader-bundle: Required for the VichUploader loader
Provides
None
Conflicts
None
Replaces
None
README
PicassoBundle
The missing image component for Symfony.
Inspired by Next.js Image — built for the Symfony ecosystem.
Write one line of Twig. Get AVIF, WebP, responsive srcset, blur placeholders, and lazy loading — automatically.
Before PicassoBundle
<!-- You write all of this manually... and maintain it forever --> <picture> <source type="image/avif" srcset="/images/hero-640.avif 640w, /images/hero-1080.avif 1080w, /images/hero-1920.avif 1920w" sizes="100vw" /> <source type="image/webp" srcset="/images/hero-640.webp 640w, /images/hero-1080.webp 1080w, /images/hero-1920.webp 1920w" sizes="100vw" /> <img src="/images/hero-1080.jpg" srcset="/images/hero-640.jpg 640w, /images/hero-1080.jpg 1080w, /images/hero-1920.jpg 1920w" sizes="100vw" width="1920" height="1080" loading="lazy" alt="Hero" /> </picture>
After PicassoBundle
<twig:Picasso:Image src="hero.jpg" width="1920" height="1080" sizes="100vw" alt="Hero" />
Same output. Zero boilerplate. All formats, srcsets, and placeholders generated automatically.
Why PicassoBundle?
Images account for the largest share of page weight on most websites. Serving them correctly — with modern formats, responsive srcsets, proper lazy loading, and blur placeholders — is critical for both Core Web Vitals and user experience, but the implementation is tedious and error-prone.
PicassoBundle solves this the same way Next.js Image did for React: a single component that handles everything.
| Without PicassoBundle | With PicassoBundle | |
|---|---|---|
| Format negotiation | Manual AVIF/WebP/JPEG <source> tags |
Automatic from config |
| Responsive srcset | Hand-crafted per breakpoint | Generated from sizes prop |
| Blur placeholders | DIY or skip it | Built-in (LQIP, BlurHash, or custom) |
| Dimension detection | Hardcoded or forgotten | Auto-detected from image stream |
| LCP optimization | Manually set loading/fetchpriority | One priority prop |
| Image sources | Filesystem only | Filesystem, S3, Flysystem, Vich, URL |
| CDN support | Build your own integration | Imgix out of the box, or plug in any CDN |
Features
- One component, full optimization —
<twig:Picasso:Image>renders a complete<picture>with AVIF, WebP, and JPEG sources - Automatic responsive srcset — generates width descriptors for all configured breakpoints, no manual work
- Blur placeholders — built-in LQIP and BlurHash support for instant perceived loading
- Smart dimension detection — reads image dimensions from the stream automatically, preserves aspect ratio
- Priority images — one prop for
loading="eager"+fetchpriority="high"(LCP optimization) - Multiple image sources — Local filesystem, Flysystem (S3, GCS, Azure), VichUploaderBundle, remote URLs
- Local or CDN transforms — Glide for self-hosted, Imgix for CDN, or bring your own
- Signed URLs — HMAC-signed transformation URLs prevent abuse
- Private images — serve thumbnails from your own routes, behind your security voters, with the same srcset and formats
- PSR-6 metadata caching — dimension detection and BlurHash results cached
- Fully extensible — add custom loaders, transformers, or placeholders
with PHP attributes (
#[AsImageLoader],#[AsImageTransformer],#[AsPlaceholder])
Requirements
- PHP 8.2+
- Symfony 6.4, 7.x or 8.x
- Symfony UX Twig Component 2.13+
Glide, BlurHash, Flysystem, VichUploaderBundle and remote URLs each need an optional package: see Installation.
Installation
composer require silarhi/picasso-bundle
# and a transformer: Glide renders the images locally (Imgix needs no extra package)
composer require league/glide
Upgrading from 1.x? Follow UPGRADE-2.0.md, or the upgrade guide.
Quick Start
1. Configure a loader, a transformer and a placeholder:
# config/packages/picasso.yaml picasso: loaders: filesystem: path: '%kernel.project_dir%/public/uploads' transformers: glide: sign_key: '%env(PICASSO_SIGN_KEY)%' placeholders: blur: { type: transformer } # optional: blurred preview while the image loads
2. Import the routes (required for Glide local serving):
# config/routes/picasso.yaml picasso: resource: '@PicassoBundle/config/routes.php'
3. Use the Twig component in your templates:
<twig:Picasso:Image src="photo.jpg" width="800" height="600" sizes="(max-width: 768px) 100vw, 800px" alt="A beautiful landscape" />
This renders a <picture> element with <source> tags for AVIF and
WebP, a fallback <img> with JPEG srcset, and an inline blur
placeholder — all automatically. Placeholders are opt-in: without the
placeholders entry, images render without one (see Placeholders).
Documentation
Everything else is on picasso-bundle.silarhi.dev:
- Guides: responsive images, fit modes, placeholders, priority images, private images, cache purge, JSON API
- Image sources: filesystem, Flysystem, VichUploaderBundle, remote URLs, chain, custom
- Transformers: Glide, Imgix, serving from a CDN, custom
- Reference: configuration, component, Twig functions, routes
- Concepts: how it works, performance
Contributing
See CONTRIBUTING.md. Release notes are in CHANGELOG.md.
License
MIT License. See LICENSE for details.
Built with care by SILARHI.
If PicassoBundle saves you time, consider giving it a star on GitHub.