Exercício 09-43:

Modifique o programa do exercício anterior para receber um segundo parâmetro com o nome do arquivo com o desenho. A ideia é ler o desenho desse arquivo.

Resposta:

##############################################################################
# Parte do livro Introdução à Programação com Python
# Autor: Nilo Ney Coutinho Menezes
# Editora Novatec (c) 2010-2024
# Quarta Edição - Março/2024 - ISBN 978-85-7522-886-9
#
# Site: https://python.nilo.pro.br/
#
# Arquivo: capitulo 09/exercicio-09-43.py
##############################################################################
import sys

# Verifica se o número de argumentos é válido
if len(sys.argv) != 3:
    print(
        "Uso: python exercicio-09-43.py nome_arquivo_saida.bmp nome_arquivo_desenho.txt"
    )
    sys.exit(1)

arquivo_saida = sys.argv[1]
arquivo_desenho = sys.argv[2]

# Lê o desenho do arquivo
try:
    with open(arquivo_desenho, "r") as f:
        desenho = [line.strip() for line in f.readlines()]
except FileNotFoundError:
    print(f"Erro: O arquivo de desenho '{arquivo_desenho}' não foi encontrado.")
    sys.exit(1)
except IOError:
    print(f"Erro: Não foi possível ler o arquivo de desenho '{arquivo_desenho}'.")
    sys.exit(1)


def bytes_little_endian(número, nbytes=4, sinal=False):
    """Converte um número inteiro para uma sequência de bytes usando a codificação little endian.
    Se sinal for passado, reserva um bit para representar o sinal."""
    return número.to_bytes(nbytes, "little", signed=sinal)


def padding(valor, tamanho=4):
    """Calcula o próximo múltiplo para tamanho"""
    if resto := valor % tamanho:
        return valor + tamanho - resto
    return valor


# Tabela de conversão de letra para cor
# no formato RGB (red, green, blue)
# Cada cor pode variar de 0 a 255.
letra_para_cor = {
    " ": (0, 0, 0),  # preto
    "r": (255, 0, 0),  # vermelho
    "g": (0, 255, 0),  # verde
    "b": (0, 0, 255),  # azul
}

# Multiplicador de pontos
# Cada ponto será copiado multiplicador vezes na imagem
# Se igual a 4, cada ponto gera um bloco de 4x4 pontos
multiplicador = 32

# Checa se todas as linhas têm o mesmo tamanho
largura_desenho = len(desenho[0])

for linha, z in enumerate(desenho):
    if len(z) != largura_desenho:
        raise ValueError(
            f"Linhas devem ter o mesmo tamanho. Linha com largura diferente: {linha} em vez de {len(z)}"
        )

# Calcula os dados com base no multiplicador
desenho_expandido = []
for linha in desenho:
    nova_linha = []
    for letra in linha:
        nova_linha.append(letra * multiplicador)
    for _ in range(multiplicador):
        desenho_expandido.append("".join(nova_linha))


largura = len(desenho_expandido[0])  # Número de colunas na imagem
altura = len(desenho_expandido)  # Número de linhas na imagem

# Checa se as letras representam as cores
dados_binário = []
for linha in desenho_expandido:
    linha_binária = []
    for caractere in linha:
        # Inverte a ordem dos bytes para o formato RGB do bmp
        linha_binária.append(bytes(letra_para_cor[caractere][::-1]))
    dados_binário.append(b"".join(linha_binária))

# Adiciona o padding
largura_bytes = largura * 3
largura_com_padding = padding(largura_bytes)
if largura_bytes != largura_com_padding:
    for p, d in enumerate(dados_binário):
        dados_binário[p] = b"".join(
            [dados_binário[p], bytes(largura_com_padding - largura_bytes)]
        )

# Calcula o tamanho em bytes da imagem com o padding
tamanho = padding(largura * 3) * altura

cabeçalho_bmp = [
    b"BM",  # Identificador
    bytes_little_endian(54 + tamanho),  # Tamanho da imagem em bytes
    bytes(4),  # 4 bytes 0x00
    bytes_little_endian(54),  # Tamanho dos cabeçalhos
]

cabeçalho_dib = [
    bytes_little_endian(40),  # Tamanho do cabeçalho DIB
    bytes_little_endian(largura),
    bytes_little_endian(
        -altura, sinal=True
    ),  # Altura negativa para montar a imagem de cima para baixo
    bytes_little_endian(1, 2),  # Planos de cor
    bytes_little_endian(24, 2),  # Bits por ponto
    bytes_little_endian(0),  # Sem compressão
    bytes_little_endian(tamanho),
    bytes_little_endian(2835),  # teto(72 dpi x 39.3701 pol/m) horizontal
    bytes_little_endian(2835),  # teto(72 dpi x 39.3701 pol/m) vertical
    bytes_little_endian(0),  # Número de cores na palete
    bytes_little_endian(0),  # Cores importantes
]

cabeçalho_bmp_binário = b"".join(cabeçalho_bmp)
cabeçalho_dib_binário = b"".join(cabeçalho_dib)
dados_binário = b"".join(dados_binário)

# Verifica o tamanho de cada cabeçalho binário
assert len(cabeçalho_bmp_binário) == 14
assert len(cabeçalho_dib_binário) == 40
assert len(dados_binário) == tamanho


# Grava a imagem
with open(arquivo_saida, "wb") as f:
    f.write(cabeçalho_bmp_binário)
    f.write(cabeçalho_dib_binário)
    f.write(dados_binário)

print(f"Arquivo {arquivo_saida} gerado. {largura=} x {altura=} {tamanho=} bytes")
Clique aqui para baixar o arquivo