PHP tiene dos extensiones principales para procesar imágenes: GD, que viene integrada en la mayoría de distribuciones, y Imagick, que es un wrapper de ImageMagick. GD es suficiente para el 90% de los casos: thumbnails, recortes de avatares, marcas de agua y conversión de formatos. Imagick ofrece mejor calidad en el reescalado y soporte para más formatos y operaciones, pero requiere instalar ImageMagick en el servidor.
Verificar qué tienes disponible
<?php
echo extension_loaded('gd') ? "GD disponible: " . GD_VERSION . "n" : "GD no disponiblen";
echo extension_loaded('imagick') ? "Imagick disponible: " . Imagick::getVersion()['versionString'] . "n" : "Imagick no disponiblen";
// GD: formatos soportados
$info = gd_info();
echo "JPEG: " . ($info['JPEG Support'] ? 'sí' : 'no') . "n";
echo "PNG: " . ($info['PNG Support'] ? 'sí' : 'no') . "n";
echo "WebP: " . ($info['WebP Support'] ? 'sí' : 'no') . "n";
echo "AVIF: " . ($info['AVIF Support'] ? 'sí' : 'no') . "n"; // PHP 8.1+
Redimensionar una imagen manteniendo proporciones con GD
<?php
function redimensionarGD(string $origen, string $destino, int $maxAncho, int $maxAlto, int $calidad = 85): void {
// Detectar tipo y cargar
$info = getimagesize($origen);
if ($info === false) throw new InvalidArgumentException("Imagen inválida: $origen");
[$anchoOrig, $altoOrig, $tipo] = $info;
$origen_img = match($tipo) {
IMAGETYPE_JPEG => imagecreatefromjpeg($origen),
IMAGETYPE_PNG => imagecreatefrompng($origen),
IMAGETYPE_GIF => imagecreatefromgif($origen),
IMAGETYPE_WEBP => imagecreatefromwebp($origen),
default => throw new InvalidArgumentException("Tipo de imagen no soportado: $tipo"),
};
// Calcular dimensiones manteniendo la proporción
$ratio = min($maxAncho / $anchoOrig, $maxAlto / $altoOrig);
// Si la imagen ya es más pequeña, no agrandar
if ($ratio >= 1.0) {
copy($origen, $destino);
imagedestroy($origen_img);
return;
}
$nuevoAncho = (int)round($anchoOrig * $ratio);
$nuevoAlto = (int)round($altoOrig * $ratio);
$destino_img = imagecreatetruecolor($nuevoAncho, $nuevoAlto);
// Preservar transparencia para PNG y WebP
if (in_array($tipo, [IMAGETYPE_PNG, IMAGETYPE_WEBP])) {
imagealphablending($destino_img, false);
imagesavealpha($destino_img, true);
$transparente = imagecolorallocatealpha($destino_img, 0, 0, 0, 127);
imagefilledrectangle($destino_img, 0, 0, $nuevoAncho, $nuevoAlto, $transparente);
}
// Reescalar con mejor calidad que imagecopyresized()
imagecopyresampled($destino_img, $origen_img, 0, 0, 0, 0, $nuevoAncho, $nuevoAlto, $anchoOrig, $altoOrig);
// Guardar en el formato correspondiente
match($tipo) {
IMAGETYPE_JPEG => imagejpeg($destino_img, $destino, $calidad),
IMAGETYPE_PNG => imagepng($destino_img, $destino, min((int)(($calidad - 1) / 10), 9)),
IMAGETYPE_GIF => imagegif($destino_img, $destino),
IMAGETYPE_WEBP => imagewebp($destino_img, $destino, $calidad),
};
// IMPORTANTE: liberar memoria
imagedestroy($origen_img);
imagedestroy($destino_img);
}
// Uso:
redimensionarGD('foto_original.jpg', 'foto_thumb.jpg', 800, 600);
redimensionarGD('avatar.png', 'avatar_small.png', 200, 200);
Recorte centrado para avatares
<?php
function recortarCuadradoGD(string $origen, string $destino, int $tamaño, int $calidad = 85): void {
$info = getimagesize($origen);
[$anchoOrig, $altoOrig, $tipo] = $info;
$img = match($tipo) {
IMAGETYPE_JPEG => imagecreatefromjpeg($origen),
IMAGETYPE_PNG => imagecreatefrompng($origen),
IMAGETYPE_WEBP => imagecreatefromwebp($origen),
default => throw new InvalidArgumentException("Tipo no soportado"),
};
// Calcular el cuadrado más grande que quepa, centrado
$lado = min($anchoOrig, $altoOrig);
$offsetX = (int)(($anchoOrig - $lado) / 2);
$offsetY = (int)(($altoOrig - $lado) / 2);
$cuadrado = imagecreatetruecolor($tamaño, $tamaño);
// Preservar transparencia PNG
if ($tipo === IMAGETYPE_PNG) {
imagealphablending($cuadrado, false);
imagesavealpha($cuadrado, true);
}
imagecopyresampled($cuadrado, $img, 0, 0, $offsetX, $offsetY, $tamaño, $tamaño, $lado, $lado);
match($tipo) {
IMAGETYPE_JPEG => imagejpeg($cuadrado, $destino, $calidad),
IMAGETYPE_PNG => imagepng($cuadrado, $destino),
IMAGETYPE_WEBP => imagewebp($cuadrado, $destino, $calidad),
};
imagedestroy($img);
imagedestroy($cuadrado);
}
recortarCuadradoGD('foto_perfil.jpg', 'avatar_200.jpg', 200);
Marca de agua con texto
<?php
function marcaDeAgua(string $origen, string $destino, string $texto, int $calidad = 85): void {
$info = getimagesize($origen);
[$ancho, $alto, $tipo] = $info;
$img = match($tipo) {
IMAGETYPE_JPEG => imagecreatefromjpeg($origen),
IMAGETYPE_PNG => imagecreatefrompng($origen),
default => throw new InvalidArgumentException("Tipo no soportado"),
};
$fuente = '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf';
$tam = max(12, (int)($ancho * 0.025)); // tamaño proporcional a la imagen
// Color blanco semi-transparente
$color = imagecolorallocatealpha($img, 255, 255, 255, 60); // alpha 0=opaco, 127=transparente
// Calcular posición (esquina inferior derecha)
$bbox = imagettfbbox($tam, 0, $fuente, $texto);
$textoAncho = abs($bbox[4] - $bbox[0]);
$textoAlto = abs($bbox[5] - $bbox[1]);
$x = $ancho - $textoAncho - 20;
$y = $alto - $textoAlto - 20 + $tam; // imagettftext usa la línea base
imagettftext($img, $tam, 0, $x, $y, $color, $fuente, $texto);
match($tipo) {
IMAGETYPE_JPEG => imagejpeg($img, $destino, $calidad),
IMAGETYPE_PNG => imagepng($img, $destino),
};
imagedestroy($img);
}
marcaDeAgua('foto.jpg', 'foto_marcada.jpg', '© ejemplo.com 2026');
Redimensionar con Imagick
Imagick usa el algoritmo de reescalado de ImageMagick, que produce resultados de mayor calidad que GD en la mayoría de los casos, especialmente al reducir imágenes grandes:
<?php
function redimensionarImagick(string $origen, string $destino, int $maxAncho, int $maxAlto, int $calidad = 85): void {
$img = new Imagick($origen);
// Auto-rotar según EXIF (fotos de móvil giradas)
$img->autoOrient();
// Eliminar perfiles EXIF/ICC para reducir tamaño del fichero (opcional)
$img->stripImage();
// Reescalar manteniendo proporción, sin agrandar
$img->thumbnailImage($maxAncho, $maxAlto, true, false);
// Configurar calidad para JPEG y WebP
$img->setImageCompressionQuality($calidad);
// Convertir a WebP para mejor compresión
// $img->setImageFormat('webp');
// $destino = preg_replace('/.w+$/', '.webp', $destino);
$img->writeImage($destino);
$img->clear();
$img->destroy();
}
redimensionarImagick('foto_grande.jpg', 'foto_thumb.jpg', 800, 600);
El error más habitual con imagedestroy
<?php
// MAL memoria no liberada en caso de excepción
function procesarImagen(string $origen): void {
$img = imagecreatefromjpeg($origen);
procesarAlgo($img); // si lanza excepción, $img nunca se destruye
imagedestroy($img);
}
// BIEN liberar siempre, incluso con excepciones
function procesarImagenSeguro(string $origen): void {
$img = imagecreatefromjpeg($origen);
try {
procesarAlgo($img);
} finally {
imagedestroy($img); // se ejecuta siempre
}
}
// O con PHP 8.0+: usar Imagick que implementa el destructor automáticamente
// Imagick libera su memoria automáticamente al salir del scope
function procesarConImagick(string $origen): void {
$img = new Imagick($origen);
// No necesitas liberar manualmente; el destructor lo hace
}
GD vs Imagick: cuándo elegir cada uno
- GD: viene de serie, sin dependencias externas, más rápida en imágenes pequeñas, ideal para thumbnails masivos y operaciones simples. Menor calidad de reescalado que Imagick.
- Imagick: mejor calidad de reescalado, soporta más formatos (PSD, SVG, TIFF, RAW), permite operaciones avanzadas (difuminar, detectar caras, convertir PDFs a imágenes), requiere ImageMagick instalado.
- Para thumbnails en producción: GD con
imagecopyresampled()es la elección estándar. - Para procesamiento de alta calidad (portadas de libro, imágenes de producto): Imagick.
