Redimensionar imagem no servidor com PHP: miniatura leve e original sob controle
Gere miniatura e tamanho médio no upload com PHP e GD, corrija a rotação da câmera, limite o original e sirva cada tela com o arquivo do tamanho dela.
A página de anúncios abre no celular, mostra 24 fotos de bicicleta em quadradinhos de 200 pixels e leva quinze segundos para parar de carregar. Cada foto veio direto da câmera do vendedor: 4032 por 3024 pixels, perto de 4 MB. A tag img desenha pequeno, mas o navegador baixa o arquivo inteiro, e a listagem passa de 90 MB. A resposta curta: redimensione no momento do upload, gerando uma miniatura para a listagem e uma versão média para a página de detalhe, corrija a orientação que a câmera gravou, e guarde o original só se o produto realmente precisa dele, num tamanho máximo que você escolheu.
Feito isso, a mesma listagem cai para algo na casa de 1 MB, e o servidor não gasta CPU redimensionando nada a cada visita, porque os arquivos já existem prontos no disco.
Por que o CSS não resolve o peso da foto
Largura no CSS ou no atributo width só muda como a imagem é pintada. O arquivo transferido é o mesmo. O navegador baixa os 4 MB, decodifica os 12 milhões de pixels na memória e depois joga quase tudo fora para caber no quadradinho. No celular, isso custa dados, bateria e memória, e é por isso que páginas com muitas fotos travam a rolagem.
Há um segundo problema escondido: a rotação. Muitas câmeras gravam os pixels sempre na mesma orientação e anotam no metadado EXIF, no campo Orientation, como a foto deve ser girada. O navegador costuma respeitar esse campo ao exibir o original. Quando você redimensiona com uma biblioteca que ignora o EXIF, os pixels saem na orientação bruta e o arquivo novo não carrega mais a anotação. Resultado: a miniatura aparece deitada, enquanto o original abre em pé.
O terceiro problema é memória no servidor. Para redimensionar, a biblioteca precisa decodificar a imagem inteira. Com GD, conte uns 4 bytes por pixel: uma foto de 12 megapixels ocupa perto de 48 MB na memória do PHP, antes de você criar qualquer cópia. Uma imagem de 20.000 por 20.000 pixels, que pode ter poucos megabytes comprimida, pede mais de 1,5 GB. Por isso a checagem de dimensão vem antes da decodificação.
Como gerar os tamanhos no upload
- Valide o upload primeiro: tamanho máximo do arquivo, tipo real lido do conteúdo e nome gerado por você. O guia de upload de arquivo em PHP cobre essa parte.
- Leia largura e altura com
getimagesize, que olha o cabeçalho sem decodificar a imagem, e recuse o que passar do limite de pixels. - Decodifique, corrija a orientação pelo EXIF e só então redimensione.
- Gere cada tamanho que a interface usa, mantendo a proporção.
- Salve com nome estável, derivado do ID do anúncio e do tamanho, para o cache funcionar.
- Decida o destino do original: descartar, ou guardar reduzido a um teto e fora da pasta pública.
O exemplo abaixo usa a extensão GD, que vem na maioria das instalações de PHP, e trata só JPEG, que é o formato de quase toda foto de celular depois da conversão do próprio aparelho. Ele pede PHP 8 ou mais novo e a extensão exif para ler a orientação.
<?php
// gera-tamanhos.php
const LARGURAS = ['thumb' => 400, 'media' => 1200];
const MAX_PIXELS = 40_000_000;
function gerarTamanhos(string $origem, string $destinoBase): array
{
$info = getimagesize($origem);
if ($info === false || $info[2] !== IMAGETYPE_JPEG) {
throw new RuntimeException('Este exemplo aceita só JPEG');
}
[$largura, $altura] = $info;
if ($largura * $altura > MAX_PIXELS) {
throw new RuntimeException('Imagem grande demais');
}
$img = imagecreatefromjpeg($origem);
if ($img === false) {
throw new RuntimeException('JPEG inválido');
}
$img = corrigirOrientacao($img, $origem);
$gerados = [];
foreach (LARGURAS as $nome => $max) {
$w = min($max, imagesx($img));
$copia = imagescale($img, $w, -1, IMG_BICUBIC);
$arquivo = "{$destinoBase}-{$nome}.webp";
imagewebp($copia, $arquivo, 80);
$gerados[$nome] = $arquivo;
}
return $gerados;
}
function corrigirOrientacao(GdImage $img, string $arquivo): GdImage
{
$exif = function_exists('exif_read_data') ? @exif_read_data($arquivo) : false;
$orientacao = is_array($exif) ? ($exif['Orientation'] ?? 1) : 1;
return match ($orientacao) {
3 => imagerotate($img, 180, 0),
6 => imagerotate($img, -90, 0),
8 => imagerotate($img, 90, 0),
default => $img,
};
}
// uso, depois de mover o upload para um caminho seu:
// gerarTamanhos('/var/app/uploads/tmp/8812.jpg', '/var/www/fotos/8812');
Três detalhes do código merecem atenção. O -1 no imagescale pede a altura proporcional, então a foto de 4032 por 3024 vira 400 por 300. O min evita ampliar uma foto que já chegou menor que o tamanho pedido, o que só deixaria o arquivo maior e borrado. E o imagerotate gira no sentido anti-horário, por isso a orientação 6, que pede um giro horário de 90 graus, usa -90.
A WebP com qualidade 80 costuma deixar a miniatura na faixa de dezenas de kilobytes. Se a GD do seu servidor não foi compilada com WebP, imagewebp não existe; troque por imagejpeg($copia, $arquivo, 80) e ajuste a extensão. PNG com transparência pede cuidado extra com o canal alfa e fica fora deste exemplo.
Um efeito colateral bom: o GPS some
A GD não copia metadados para o arquivo novo. A miniatura sai sem EXIF, o que inclui a coordenada GPS que muitas câmeras gravam. Num site de anúncios, isso evita publicar o endereço da casa do vendedor junto com a foto da bicicleta. Se você guardar o original e servi-lo em algum momento, lembre que ele ainda carrega esses dados.
Como servir o tamanho certo para cada tela
Com os arquivos prontos, a listagem aponta para a miniatura e deixa o navegador escolher a versão maior quando a tela pede. Os atributos width e height reservam o espaço antes do download, assunto do post sobre largura e altura na imagem.
<img src="/fotos/8812-thumb.webp"
srcset="/fotos/8812-thumb.webp 400w, /fotos/8812-media.webp 1200w"
sizes="(min-width: 60em) 25vw, 50vw"
width="400" height="300"
alt="Bicicleta aro 29 azul, vista de lado"
loading="lazy">
O srcset lista os arquivos e a largura real de cada um. O sizes diz quanto da tela a imagem ocupa: um quarto da largura em telas largas, metade nas estreitas. Com isso, um celular de tela densa pode pegar a versão de 1200 só se precisar. A página de detalhe aponta direto para a média. O original, se existir, aparece só num clique explícito de "ver em tamanho real".
Como o nome do arquivo não muda, configure cache longo no servidor web para a pasta de fotos. Se o vendedor trocar a foto, gere um nome novo em vez de sobrescrever, e o cache antigo não atrapalha.
Erros comuns
- Redimensionar a cada visualização, sem guardar o resultado. Cada visita decodifica 12 megapixels; com tráfego, a CPU do servidor fica presa nisso e o site inteiro fica lento.
- Ignorar a orientação do EXIF. Metade das fotos tiradas em pé aparece deitada na listagem, e o vendedor acha que o site estragou a foto.
- Decodificar antes de checar as dimensões. Um arquivo pequeno com dimensões absurdas estoura o
memory_limite derruba o processo do PHP. - Ampliar imagem pequena para preencher o tamanho padrão. O arquivo fica maior, a qualidade cai e nada de útil é ganho.
- Guardar o original de 4 MB na pasta pública "por garantia". O disco enche, o backup cresce e o GPS do vendedor fica a um endereço de distância.
- Confiar no tamanho exibido pelo CSS. A tela mostra pequeno, o painel de rede mostra megabytes, e o problema continua.
Checklist
- O upload recusa imagem acima de um limite de pixels antes de decodificar.
- A miniatura de uma foto tirada em pé aparece em pé.
- A listagem aponta para arquivos de dezenas de kilobytes, conferidos no painel de rede.
- Os tamanhos são gerados uma vez e salvos com nome estável.
- A tag
imgtemsrcset,sizes,widtheheight. - O original, se guardado, tem teto de tamanho e não fica em pasta pública.
- Os arquivos servidos não carregam metadado de GPS.
Para medir o efeito de verdade, compare o peso da página antes e depois, como no guia de site mais rápido. Abra agora a sua listagem com o painel de rede aberto e ordene por tamanho: quantas fotos ali ainda são o arquivo que saiu da câmera?
Referência: http://blog.thiagobelem.net/redimensionando-imagens-automaticamente-com-o-php/