Como redimensionar imagens e pôr marca-d’água com PHP e Imagick
Para redimensionar uma foto e aplicar uma marca-d’água nela com PHP Imagick, aplique primeiro a orientação da foto, redimensione os pixels e só depois desenhe o texto no tamanho final. O script abaixo recebe um JPEG ou PNG local, reduz larguras acima de 800 pixels sem recortar e salva um PNG separado com “© Your Company” no canto inferior direito. O seu arquivo original permanece inalterado, e uma saída existente nunca é sobrescrita.
Imagick é a interface do PHP para o ImageMagick. Com ele, você executa essas operações em uma imagem na memória. Este exemplo é para imagens estáticas que você controla: use uma foto ou um PNG em sRGB, inclusive com transparência. Entradas GIF, WebP, PDF e CMYK são rejeitadas. PNG animado está fora do escopo deste tutorial; um decodificador de PNG pode expor apenas o primeiro quadro, então verificar a contagem de quadros decodificados não comprova que um PNG é estático.
Configurar o PHP e o Imagick
A configuração a seguir usa Docker e um terminal compatível com Bash no Linux. Ela foi testada no
Linux x86_64 com PHP 8.4.26, Imagick 3.8.1, ImageMagick 7.1.2-29 e DejaVu Sans 2.37. Crie um
diretório de trabalho vazio e abra o terminal nele. Salve isto como Dockerfile:
FROM php:8.4.26-cli-alpine3.23
RUN apk add --no-cache imagemagick=7.1.2.29-r0 imagemagick-jpeg=7.1.2.29-r0 font-dejavu=2.37-r6 \
&& apk add --no-cache --virtual .build-deps $PHPIZE_DEPS imagemagick-dev=7.1.2.29-r0 \
&& pecl install imagick-3.8.1 \
&& docker-php-ext-enable imagick \
&& apk del .build-deps
WORKDIR /work
Faça o build da imagem local; esta etapa precisa de acesso à internet. A instalação da extensão segue as instruções PECL da imagem oficial do PHP.
docker build -t imagick-demo:local .
Aguarde o build terminar com sucesso antes de continuar. O Alpine empacota o
módulo de formato JPEG (coder) separadamente,
por isso a configuração o instala explicitamente. A imagem contém a fonte em
/usr/share/fonts/dejavu/DejaVuSans.ttf. Se você já tem esse stack PHP/Imagick instalado localmente,
pode executar o script PHP diretamente com o seu próprio arquivo de fonte; o contêiner abaixo
fornece as três dependências sem alterar a instalação do PHP no seu host.
Salvar o script de redimensionamento e marca-d’água
Salve o código completo abaixo como resize-watermark.php ao lado do Dockerfile. Os três
argumentos são a imagem de entrada, um novo caminho de saída .png e um
arquivo de fonte TrueType legível. O diretório pai da saída já precisa existir.
<?php
declare(strict_types=1);
$image = null;
$input = null;
$output = null;
$created = false;
$status = 0;
try {
if ($argc !== 4) {
throw new RuntimeException('Usage: php resize-watermark.php INPUT OUTPUT.png FONT.ttf');
}
if (!extension_loaded('imagick')) {
throw new RuntimeException('Enable the Imagick extension in your CLI PHP.');
}
$inputPath = realpath($argv[1]);
$outputDirectory = realpath(dirname($argv[2]));
$fontPath = realpath($argv[3]);
if ($inputPath === false || !is_file($inputPath) || !is_readable($inputPath)) {
throw new RuntimeException('Input must be a readable local image file.');
}
if ($fontPath === false || !is_file($fontPath) || !is_readable($fontPath)) {
throw new RuntimeException('Font must be a readable local TrueType file.');
}
if ($outputDirectory === false || strtolower(pathinfo($argv[2], PATHINFO_EXTENSION)) !== 'png') {
throw new RuntimeException('Use a .png output in an existing directory.');
}
$outputPath = $outputDirectory . DIRECTORY_SEPARATOR . basename($argv[2]);
$input = fopen($inputPath, 'rb');
if ($input === false) {
throw new RuntimeException('Could not open the input.');
}
$image = new Imagick();
$image->setOption('png:preserve-iCCP', 'true');
$image->readImageFile($input);
if ($image->getNumberImages() !== 1 || !in_array($image->getImageFormat(), ['JPEG', 'PNG'], true)) {
throw new RuntimeException('Use a still JPEG or PNG image.');
}
if ($image->getImageColorspace() === Imagick::COLORSPACE_CMYK) {
throw new RuntimeException('Convert CMYK images to sRGB before using this example.');
}
$image->autoOrient();
if ($image->getImageWidth() > 800) {
$image->resizeImage(800, 0, Imagick::FILTER_LANCZOS, 1);
}
$image->setImagePage(0, 0, 0, 0);
$text = '© Your Company';
$draw = new ImagickDraw();
$draw->setFont($fontPath);
$draw->setFontSize(24);
$draw->setFillColor('white');
$draw->setFillOpacity(0.8);
$draw->setStrokeColor('black');
$draw->setStrokeWidth(1);
$draw->setGravity(Imagick::GRAVITY_SOUTHEAST);
$metrics = $image->queryFontMetrics($draw, $text);
if ($metrics['textWidth'] + 32 > $image->getImageWidth()
|| $metrics['textHeight'] + 32 > $image->getImageHeight()) {
throw new RuntimeException('Image is too small for this watermark. Shorten the text or reduce its font size.');
}
$image->annotateImage($draw, 16, 16, 0, $text);
$profiles = $image->getImageProfiles('icc', true);
$image->stripImage();
// stripImage also tells the PNG encoder to omit ICC, even if we restore the profile.
$image->setImageArtifact('png:exclude-chunk', 'eXIf,date');
if (isset($profiles['icc'])) {
$image->profileImage('icc', $profiles['icc']);
}
// Decoded EXIF properties can otherwise be copied into PNG text chunks.
foreach ($image->getImageProperties('*', false) as $property) {
$image->deleteImageProperty($property);
}
$image->setImageFormat('png');
$output = @fopen($outputPath, 'xb');
if ($output === false) {
throw new RuntimeException('Cannot create output: it already exists or the directory is not writable.');
}
$created = true;
if (!$image->writeImageFile($output, 'png') || !fflush($output)) {
throw new RuntimeException('Could not finish writing the PNG.');
}
printf("Wrote %s (%d × %d)\n", $argv[2], $image->getImageWidth(), $image->getImageHeight());
} catch (Throwable $error) {
fwrite(STDERR, 'Resize failed: ' . $error->getMessage() . PHP_EOL);
$status = 1;
} finally {
if (is_resource($input)) {
fclose($input);
}
if (is_resource($output)) {
fclose($output);
}
if ($status !== 0 && $created) {
unlink($outputPath);
}
$image?->clear();
}
exit($status);
O script passa um handle de arquivo aberto para
readImageFile(), assim o ImageMagick não
interpreta o nome do arquivo de entrada como prefixo de formato ou seletor de quadros. Ele também
grava por meio de um handle de arquivo: o modo xb
do PHP recusa uma saída existente, inclusive a própria entrada. Use isto como um comando local
sequencial; a saída fica visível enquanto está sendo gravada. Uma falha de gravação capturada remove
o novo arquivo parcial, mas um processo interrompido abruptamente pode deixar um para trás.
Executar com uma imagem local
Copie uma imagem estática para este diretório como input.jpg ou altere o
argumento de entrada abaixo para o nome do seu arquivo PNG. Depois execute:
docker run --rm --network none --user "$(id -u):$(id -g)" \
--mount "type=bind,source=$PWD,target=/work" --workdir /work \
imagick-demo:local php resize-watermark.php input.jpg output.png \
/usr/share/fonts/dejavu/DejaVuSans.ttf
O diretório montado disponibiliza output.png no seu host. Para uma entrada de
1.600 × 1.000 sem rotação, o comando informa Wrote output.png (800 × 500). Abra esse arquivo: a
imagem inteira deve estar presente, com o texto afastado da borda inferior direita. Se você executar
o mesmo comando novamente, ele falha com status diferente de zero e deixa o PNG existente
inalterado. Escolha um novo nome de arquivo de saída para a próxima versão.
Ajustar o tamanho e a marca-d’água
autoOrient() aplica a orientação armazenada antes
de a largura ser medida. Isso é importante para fotos de celular: um retrato armazenado de lado
precisa ser girado antes de você escolher as dimensões de saída. Em
resizeImage(), uma altura igual a zero calcula
a altura proporcional. A verificação de largura evita ampliar imagens menores; não há limite de
altura.
Altere $text para a sua marca-d’água curta, de uma única linha. O script
define o tamanho da fonte depois do redimensionamento, então o rótulo não acompanha as dimensões em
pixels da imagem de origem. O preenchimento branco com contorno preto ajuda o texto a continuar
visível tanto em áreas claras quanto escuras. A verificação das métricas da fonte rejeita imagens
pequenas demais para comportar o rótulo com seu espaçamento interno. Esta é uma marca de atribuição
visível, não uma proteção contra alguém recortar ou editar a imagem.
Escolher o comportamento de saída e de metadados
O PNG mantém as áreas transparentes em vez de achatá-las sobre um fundo. Ele também pode ficar maior
que o JPEG original, mesmo após o redimensionamento; este script não promete reduzir o tamanho do
arquivo. Renomear a saída para .jpg não muda o codificador, por isso o
script exige a extensão .png.
stripImage() remove perfis e comentários.
Aqui, ele é executado depois que a orientação já foi aplicada. O script restaura o perfil de cores
ICC e ajusta as configurações de chunks do PNG para mantê-lo. Ele
também limpa as propriedades da imagem para que campos EXIF decodificados não sejam copiados para
chunks de texto do PNG. O codificador ainda pode adicionar metadados de formato e de cor; isto não
garante um arquivo sem metadados. Use imagens de origem em sRGB neste fluxo de trabalho e uma
conversão separada, com gerenciamento de cores, para arquivos de impressão ou de gama ampla.
Tratar falhas e arquivos não confiáveis
Uma entrada ausente, uma fonte ausente, uma imagem que não pode ser decodificada, um formato não
suportado ou uma saída já existente faz o processo terminar com status diferente de zero e exibir
uma mensagem no erro padrão. O arquivo de saída só é aberto depois que a decodificação, o
redimensionamento e o desenho são concluídos com sucesso. Se o caminho da fonte existir, mas não for
uma fonte válida, o Imagick informa a falha ao medir ou desenhar o texto. Para uma miniatura muito
pequena, encurte o rótulo ou reduza setFontSize(24) no script.
Este é um exemplo local de edição de imagens, não um endpoint de upload. O ImageMagick precisa decodificar a entrada antes que o script possa verificar o formato e a contagem de quadros, e um decodificador pode tolerar alguns arquivos danificados. Um processamento bem-sucedido não certifica que a entrada está completa ou é segura. Para uploads não confiáveis, configure uma política de segurança do ImageMagick que restrinja módulos de formato (coders), delegates, dimensões, quadros, memória, disco e tempo de processamento, além de limites de tamanho de requisição e isolamento dos workers. Limites de memória sozinhos podem fazer o cache de pixels transbordar para o disco; eles não limitam o processamento como um todo.
