GD e Imagick en PHP: redimensionar, recortar y procesar imágenes

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP