Scripts e automação de arquivos

Redimensione uma pasta de imagens com Pillow mantendo os originais

Redimensione várias imagens para uma nova pasta sem distorcê-las nem substituir os arquivos de origem. Um pequeno conjunto de imagens sintéticas facilita o cálculo manual das dimensões esperadas de saída.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é voltado a pessoas que precisam de cópias menores de muitas imagens, preservando as proporções de cada imagem e mantendo os originais intactos.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • Pillow instalado com python -m pip install Pillow.
  • Uma pasta de trabalho em que os scripts possam criar novas pastas dentro de outputs.
  • Espaço em disco suficiente para as imagens sintéticas originais e as cópias redimensionadas.

01Defina primeiro a regra de redimensionamento

O objetivo é ajustar cada imagem dentro de uma caixa de 300 by 300 pixel preservando sua proporção. Imagens que já são menores do que essa caixa não são ampliadas. As imagens de origem permanecem inalteradas, e cada cópia redimensionada é gravada em uma pasta de saída separada.

Para uma imagem com largura W e altura H, calcule scale como o menor valor entre 300/W, 300/H e 1. Depois, calcule a nova largura e altura a partir desse único scale factor. Usar um único fator para ambas as dimensões evita distorção.

text
scale = min(300 / width, 300 / height, 1)
new_width = int(width * scale)
new_height = int(height * scale)

02Crie uma pasta de imagens sintéticas

Os arquivos a seguir são sintéticos e foram criados especificamente para este artigo. O script de preparação cria quatro imagens PNG simples com dimensões conhecidas. O conteúdo visual não é importante; as dimensões foram escolhidas para que todos os resultados esperados do redimensionamento possam ser verificados manualmente.

python
from pathlib import Path
from PIL import Image

SOURCE = Path("outputs") / "image_resize_demo"
IMAGES = {
    "landscape.png": ((800, 600), (220, 80, 80)),
    "portrait.png": ((600, 800), (80, 120, 220)),
    "small.png": ((200, 100), (80, 180, 100)),
    "wide.png": ((1200, 300), (180, 120, 60)),
}

SOURCE.parent.mkdir(parents=True, exist_ok=True)
SOURCE.mkdir()  # Stop if this synthetic source already exists.

for filename, (size, color) in IMAGES.items():
    image = Image.new("RGB", size, color)
    target = SOURCE / filename
    image.save(target, format="PNG")
text
python create_resize_demo.py
ArquivoLargura originalAltura originalProporção
landscape.png8006004:3
portrait.png6008003:4
small.png2001002:1
wide.png12003004:1

Os quatro originais contêm 1,240,000 pixels no total: 480,000 + 480,000 + 20,000 + 360,000. A contagem de pixels é útil para compreender o exemplo, mas não equivale ao tamanho do arquivo em bytes porque a compactação PNG depende do conteúdo da imagem.

03Calcule manualmente as quatro dimensões esperadas

Para landscape.png, a dimensão limitante é a largura: 300/800 = 0.375. Multiplicar ambas as dimensões por 0.375 resulta em 300 by 225. Para portrait.png, a altura é o limite: 300/800 = 0.375, produzindo 225 by 300.

small.png já cabe porque ambas as dimensões são iguais ou inferiores a 300. O scale é limitado a 1, portanto permanece 200 by 100. Para wide.png, a largura limita o resultado: 300/1200 = 0.25, resultando em 300 by 75.

ArquivoScaleSaída esperadaPixels de saída
landscape.png0.375300 × 22567,500
portrait.png0.375225 × 30067,500
small.png1200 × 10020,000
wide.png0.25300 × 7522,500

Portanto, as cópias redimensionadas contêm 177,500 pixels no total. Os originais continuam presentes separadamente, então esse número não representa economia de espaço em disco.

04Redimensione a pasta para um novo destino

Salve o script a seguir como batch_image_resize.py. Ele processa arquivos PNG, JPEG e WebP diretamente dentro de SOURCE. Não pesquisa subpastas recursivamente. O destino é criado com mkdir sem exist_ok, portanto um diretório de saída já existente faz o script parar em vez de misturar novos resultados com uma execução anterior.

python
from pathlib import Path
from PIL import Image, ImageOps

SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"
MAX_WIDTH = 300
MAX_HEIGHT = 300
SUPPORTED = {".png", ".jpg", ".jpeg", ".webp"}


def target_size(width: int, height: int) -> tuple[int, int]:
    scale = min(MAX_WIDTH / width, MAX_HEIGHT / height, 1)
    new_width = max(1, int(width * scale))
    new_height = max(1, int(height * scale))
    return new_width, new_height


def main() -> None:
    if not SOURCE.is_dir():
        raise FileNotFoundError(f"Source folder not found: {SOURCE}")
    if MAX_WIDTH < 1 or MAX_HEIGHT < 1:
        raise ValueError("Maximum dimensions must be positive integers.")

    source_resolved = SOURCE.resolve()
    output_resolved = OUTPUT_DIR.resolve()
    if source_resolved == output_resolved:
        raise ValueError("SOURCE and OUTPUT_DIR must be different.")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Refuse to reuse an existing destination.

    candidates = sorted(
        path for path in SOURCE.iterdir()
        if path.is_file() and path.suffix.lower() in SUPPORTED
    )

    resized_count = 0
    unchanged_size_count = 0

    for source_path in candidates:
        output_path = OUTPUT_DIR / source_path.name
        if output_path.exists():
            raise FileExistsError(f"Output already exists: {output_path}")

        with Image.open(source_path) as opened:
            image = ImageOps.exif_transpose(opened)
            image.load()
            old_size = image.size
            new_size = target_size(*old_size)

            if new_size == old_size:
                result = image.copy()
                unchanged_size_count += 1
            else:
                result = image.resize(new_size, Image.Resampling.LANCZOS)
                resized_count += 1

            save_options = {}
            if source_path.suffix.lower() in {".jpg", ".jpeg"}:
                save_options["quality"] = 90
            result.save(output_path, **save_options)
            result.close()

        print(f"{source_path.name}: {old_size[0]}x{old_size[1]} -> {new_size[0]}x{new_size[1]}")

    print(f"Processed {len(candidates)} images.")
    print(f"Resized: {resized_count}; already within limit: {unchanged_size_count}.")
    print(f"Output folder: {OUTPUT_DIR.as_posix()}")


if __name__ == "__main__":
    main()

ImageOps.exif_transpose aplica a orientação EXIF da imagem antes do cálculo de redimensionamento quando esses dados de orientação existem. Os arquivos PNG sintéticos não têm essa necessidade, portanto suas dimensões exibidas continuam sendo as listadas acima.

05Compare a saída esperada

Como os nomes dos arquivos são processados em ordem, as linhas esperadas do console são landscape.png, portrait.png, small.png e wide.png. Três imagens ficam menores e uma permanece com suas dimensões originais.

text
landscape.png: 800x600 -> 300x225
portrait.png: 600x800 -> 225x300
small.png: 200x100 -> 200x100
wide.png: 1200x300 -> 300x75
Processed 4 images.
Resized: 3; already within limit: 1.
Output folder: outputs/image_resize_result

Esta saída esperada foi derivada manualmente, e não capturada de uma execução. As verificações importantes são as dimensões, o número de arquivos e o fato de que os originais permanecem em outputs/image_resize_demo.

VerificaçãoValor esperado
Arquivos na pasta de origem4
Arquivos na pasta de saída4
Arquivos redimensionados3
Arquivos que já estavam dentro do limite1
Maior largura de saída300
Maior altura de saída300

06Verifique você mesmo as imagens geradas

Não dependa apenas dos nomes dos arquivos ou do texto do console. Abra os arquivos resultantes e verifique suas dimensões. O verificador opcional abaixo lê as duas pastas e informa as dimensões. Ele não realiza gravações.

python
from pathlib import Path
from PIL import Image

SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"

for source_path in sorted(SOURCE.glob("*.png")):
    output_path = OUTPUT_DIR / source_path.name
    with Image.open(source_path) as original, Image.open(output_path) as resized:
        print(
            f"{source_path.name}: "
            f"original={original.size}, output={resized.size}"
        )
  • Confirme que toda largura de saída seja no máximo 300 e toda altura de saída seja no máximo 300.
  • Confirme que landscape.png permaneça 4:3, portrait.png permaneça 3:4 e wide.png permaneça 4:1.
  • Confirme que small.png permaneça 200 by 100 em vez de ser ampliada para 300 by 150.
  • Compare horários de modificação ou hashes se precisar de evidência adicional de que os próprios arquivos de origem não foram regravados.
  • Execute o script de redimensionamento uma segunda vez sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError antes de processar outro lote.

07Reconheça erros comuns

SintomaO que verificar
ModuleNotFoundError: No module named PILInstale Pillow no mesmo ambiente Python usado para executar o script com python -m pip install Pillow.
FileNotFoundErrorVerifique SOURCE e o diretório de trabalho atual do terminal. Neste exemplo, execute primeiro o script de preparação sintético.
FileExistsErrorA pasta de saída já existe. Revise-a e selecione um novo destino em vez de permitir uma sobrescrita automática.
Image cannot be identifiedA extensão pode parecer suportada enquanto o conteúdo está danificado ou não é realmente uma imagem. Trate o lote com falha como incompleto.
Unexpected rotationOs metadados de orientação e a orientação dos pixels podem diferir. ImageOps.exif_transpose é usado antes do redimensionamento, mas inspecione visualmente imagens importantes.
Unexpected output file size in bytesAs dimensões em pixels, por si só, não determinam o tamanho do arquivo compactado. Formato, conteúdo da imagem, metadados e qualidade JPEG também influenciam.

Se o processamento falhar depois que alguns arquivos já tiverem sido gravados, o script não exclui esses resultados parciais. Mantenha o destino para investigação ou use um novo diretório de saída depois de corrigir o problema.

08Entenda os limites antes de usar imagens reais

Este exemplo trata apenas de arquivos diretamente dentro de uma pasta e suporta extensões PNG, JPEG e WebP. Ele não reproduz uma árvore de diretórios aninhada, não redimensiona individualmente frames animados, não preserva todas as formas de metadados nem garante que as informações de gerenciamento de cores permaneçam inalteradas.

A reamostragem altera os dados de pixels. Mesmo quando uma imagem mantém as mesmas dimensões, abri-la e salvá-la pode alterar os bytes codificados ou os metadados. A saída JPEG é com perdas e este script salva cópias JPEG com quality 90; isso não preserva o bitstream JPEG original nem as configurações originais de compactação.

O cálculo com int arredonda dimensões fracionárias para baixo. Para tamanhos de origem arbitrários, a proporção de pixels resultante pode, portanto, diferir ligeiramente da proporção matemática porque as dimensões das imagens precisam ser números inteiros. As dimensões sintéticas deste guia se dividem exatamente, portanto os quatro resultados esperados não exigem arredondamento ambíguo.

Registro de execução e verificação

2026-09-20 · exemplo verificado manualmente · alvo: Python 3.12 · Pillow necessário · sem execução

  • Foram verificadas manualmente as quatro dimensões sintéticas de origem: 800x600, 600x800, 200x100 e 1200x300.
  • Foram calculados manualmente os scale factors 0.375, 0.375, 1 e 0.25.
  • Foram derivadas manualmente as dimensões esperadas de saída 300x225, 225x300, 200x100 e 300x75.
  • Foi calculado manualmente o total de pixels de origem como 1,240,000 e o total de pixels de saída como 177,500.
  • O código foi inspecionado para confirmar que os arquivos de origem são abertos para leitura, os resultados vão para uma pasta separada e uma pasta de destino já existente faz a execução parar.
  • A saída esperada do console e as contagens foram derivadas manualmente.
Limites da verificação
  • O código não foi executado pelo autor desta resposta; o comportamento do Pillow e as gravações no sistema de arquivos não foram testados aqui.
  • JPEG, WebP, orientação EXIF, arquivos corrompidos, perfis de cores, animação e preservação de metadados foram discutidos, mas não testados.
  • Nenhum tamanho de arquivo compactado em bytes foi previsto porque depende do conteúdo da imagem e da codificação.
  • As URLs da documentação oficial foram fornecidas com base em locais de documentação conhecidos, mas não foram verificadas ao vivo.

Princípios de redação e verificação de todo o site

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.