segunda-feira, 16 de novembro de 2015

Imagens à roda

Todos nos habituámos a brincar com programas que manipulam imagens. Com frequência queremos um programa que roda uma imagem de 90 ou de 180 graus. Vamos ver como podemos resolver o primeiro caso. Depois o segundo é trivial.

Já discutimos como podemos representar uma imagem a preto e branco em Python: uma lista de listas. Para resolver este problema sabemos que temos que percorrer toda a imagem e para cada elemento da imagem saber qual será a sua nova posição. Mas no caso de uma rotação de 90 graus isso é fácil de resolver se olharmos globalmente para a matriz que representa a nossa imagem: a coluna i da matriz vai passar a ser a linha i da nova matriz… Daí a solução seguinte:
def roda_90(imagem):
    """Baseia-se na construção da transposta da imagem vista como uma matriz."""
    imagem_aux = list()
    # troca colunas por linhas = transpõe
    for coluna in range(len(imagem[0])):
        nova_linha = list()
        for linha in imagem:
            nova_linha.append(linha[coluna])
        imagem_aux.append(nova_linha)
    # inverte dentro das linhas
    for linha in range(len(imagem_aux)):
        imagem_aux[linha] = imagem_aux[linha][::-1]
    return imagem_aux
Esta solução numa ideia simples: percorrer por colunas e para cada coluna ir buscar os elementos das diferentes linhas. O modo como fazemos isto obriga-nos no final a inverter todas as linhas. Esta ultima parte não é simpática. Mas podemos mudar as coisas.
def roda_90_b(imagem):
    """Baseia-se na construção da transposta da imagem vista como uma matriz."""
    imagem_aux = list()
    # troca colunas e linhas = transpõe
    for coluna in range(len(imagem[0])):
        nova_linha = list()
        for linha in imagem:
            nova_linha = [linha[coluna]] + nova_linha
        imagem_aux += [nova_linha]
    return imagem_aux
Como se pode ver, em vez de usarmos append, que coloca sempre no final, usamos a operação de concatenação de listas (+), colocando o novo elemento sempre à cabeça, o que permite ter no final a nova linha já invertida.

Como temos vindo a explicar ao longo destes pequenos posts há sempre muitas alternativas. Algumas fazem uso de um conhecimento mais profundo de Python. É o caso da solução que apresentamos a seguir.
def roda_90_c(imagem):
    copia = copy.deepcopy(imagem)
    transposta = list(zip(*copia))
    final = [linha[::-1] for linha in transposta]
    return final
Neste exemplo, usamos um argumento na forma *copia. Isto significa que o objecto copia vai ser desmembrado nos seus elementos e é sobre esses elementos que vai ser aplicada a função zip. Deste modo obtemos a transposta. Depois é só inverter cada linha usando listas por compreensão. Refira-se finalmente que usámos uma cópia profunda da imagem inicial para não a destruir.

Sem comentários:

Enviar um comentário