Laravel News publicó un tutorial sobre el procesamiento de imágenes nativo añadido en Laravel 13.20 a través del componente Illuminate\Image, introducido en el Pull Request #59276 del repositorio laravel/framework. Antes de esto, los desarrolladores dependían de paquetes de terceros para tareas como redimensionar avatares o convertir archivos subidos a WebP.
Los controladores GD e Imagick se apoyan en Intervention Image v4, que se instala por separado con `composer require intervention/image:^4.0`. GD es el controlador predeterminado; Imagick puede activarse globalmente mediante la configuración `images.default` o por llamada con `usingImagick()` o `using('imagick')`. Si se llama a un controlador sin Intervention Image instalado, Laravel lanza una `ImageException` indicando el paquete que falta.
Las imágenes pueden cargarse desde muchas fuentes: `$request->image('avatar')` para archivos subidos (devuelve null si el campo falta o no es un archivo), o mediante los métodos de la fachada `Image`: `fromPath()`, `fromUrl()`, `fromStorage()`, `fromBytes()` y `fromBase64()`, además del atajo `Storage::disk('s3')->image('ruta')`.
Cada transformación devuelve una nueva instancia `Image` inmutable, y nada se procesa hasta que se solicita una salida mediante `store()`, `toBytes()` o `width()`. Esto permite construir una imagen base (por ejemplo con `orient()`, que rota automáticamente según los datos EXIF) y derivar varias variantes, como una miniatura con `cover(300, 300)` y una versión de visualización con `scale(width: 1600)`, sin que una rama afecte a la otra.
Hay cinco métodos de redimensionado disponibles: `cover($a, $h)` recorta para llenar exactamente las dimensiones (ideal para avatares); `contain($a, $h, $fondo)` ajusta la imagen completa dentro del recuadro con relleno opcional; `scale($a, $h)` redimensiona proporcionalmente y nunca amplía (internamente usa `scaleDown()` de Intervention); `resize($a, $h)` fuerza dimensiones exactas y puede distorsionar; `crop($a, $h, $x, $y)` recorta una región en un desplazamiento dado.
Otros ajustes incluyen `rotate()`, `blur()` (0 a 100, por defecto 5), `sharpen()` (0 a 100, por defecto 10), `grayscale()`, `flip()`/`flipVertically()` y `flop()`/`flipHorizontally()`. La conversión de formato se hace con `toWebp()`, `toJpg()`, `toPng()`, `toGif()`, `toAvif()` y `toBmp()`, combinada con `quality()` (1 a 100) para formatos con pérdida. El atajo `optimize()` convierte por defecto a WebP con calidad 70, pero acepta formato y calidad personalizados, por ejemplo `optimize('avif', 60)`. Los formatos de entrada admitidos son JPEG, PNG, GIF, BMP y WebP.
El almacenamiento replica la API UploadedFile de Laravel: `store()`, `storeAs()` y `storePublicly()` escriben en el disco configurado, aplicando automáticamente la extensión correcta según el formato de salida. Para obtener los datos en bruto están `toBytes()`, `toBase64()` y `toDataUri()`, junto con los métodos de inspección `width()`, `height()`, `dimensions()`, `mimeType()` y `extension()`.
Un ejemplo de controlador de avatares valida el archivo subido y luego encadena `orient()->cover(512, 512)->optimize()->storePublicly('avatars', disk: 's3')`. Otro ejemplo genera variantes responsivas iterando sobre los anchos 480, 960 y 1440 con `scale(width: $width)`; como `scale()` nunca amplía, una imagen original de 900px se mantiene en 900px incluso en la iteración de 1440.
La clase `Image` utiliza el trait `Conditionable` de Laravel, por lo que `when()` y `unless()` permiten aplicar transformaciones de forma condicional. Los puntos de extensión incluyen `Image::extend()` para registrar un controlador personalizado y `Image::transformUsing()` para sobrescribir una transformación concreta por controlador, además de implementar el contrato `Illuminate\Contracts\Image\Transformation` para transformaciones totalmente propias.
Algunas advertencias: las instancias Image no se pueden serializar, por lo que enviarlas a un job en cola lanza una `ImageException` (hay que almacenar el archivo y pasar su ruta); el procesamiento es diferido y se cachea tras la primera salida; y los fallos, como formatos no admitidos o archivos indecodificables, lanzan de forma uniforme `ImageException`.