Trabalho 1 - Gerador de ASCII Art

Laboratório de Programação II - Prof. Marcelo Cohen

04/2014

1 Introdução

O uso de caracteres da tabela ASCII para produzir desenhos não é uma idéia recente: vem de tempos longínquos, onde os dispositivos de saída eram monitores monocromáticos e impressoras matriciais ou de linha. Mais recentemente, se tornou uma forma de "arte" - daí o termo ASCII Art. Ou seja, imagens criadas exclusivamente com o uso de caracteres da tabelas ASCII.
O objetivo deste trabalho é explorar os conceitos de programação C/C++ vistos em aula, criando um programa capaz de produzir a representação de uma imagem qualquer em ASCII. A saída do programa será na forma de um arquivo HTML, que pode ser visualizado no browser.
Se você nunca viu isso (em que planeta estava nos últimos anos ?), há diversos serviços na rede que fazem esse tipo de conversão:
O resultado pode ser visto na figura abaixo:
figure flower.jpg   figure flower_ascii.jpg

2 Funcionamento

Ao ser iniciado, o programa deve solicitar o nome de um arquivo de imagem, e carregá-lo. Para tanto, utilizaremos a biblioteca SFML (Simple and Fast Multimedia Library), que está disponível para Linux e Windows no projeto base fornecido (veja abaixo).
Parte do objetivo deste trabalho é a criação de um algoritmo que produza um resultado interessante. Uma possível estratégia é a seguinte:
  1. Obter do usuário o nome da imagem a ser carregada, e o fator de redução desejado. Por exemplo, o fator 50% vai utilizar apenas metade dos pixels originais na saída. Esse fator é usado para calcular o tamanho dos blocos de pixels (ver passo 5).
  2. Ler a imagem colorida, onde cada pixel (ponto da imagem) é representado em RGB.
  3. Converter a imagem para tons de cinza, isto é, eliminar a cor e considerar que qualquer pixel pode ser representado por um único número inteiro, representando variações de preto (0) a branco (255).
  4. Associar cada caractere a um tom de cinza específico. Você pode pesquisar nos sites indicados (ou outros) para ver como isso normalmente é realizado. Mas a idéia básica é que caracteres que ocupam mais espaço visualmente correspondam a cores mais claras (considerando um fundo preto). Por exemplo, "@" é mais "claro" do que "."
  5. De acordo com as proporções das letras (que não são quadradas), agrupar os pixels da imagem em blocos retangulares (por exemplo, 4 x 5). Cada bloco irá se transformar em um único caractere na saída. Isso é necessário, pois as imagens normalmente são muito grandes para fazer uma correspondência 1:1.
  6. Calcular o tom de cinza médio de cada bloco (por exemplo, fazendo a média de todos os pixels do bloco).
  7. Para cada bloco de pixels, escolher e gerar um caractere na saída, cujo tom de cinza é uma boa aproximação para a média do bloco.
As próximas seções dão algumas dicas de como realizar certas etapas.

Leitura da imagem

Para ler uma imagem usando a biblioteca SFML, use um código como segue:
#include <stdlib.h>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>

int main(int argc, char *argv[]) 
{
    sf::Image imagem;
    if (!imagem.loadFromFile("imagem1.png")) {
       // erro!
    }
 
    // Descobre o tamanho da imagem
    sf::Vector2u tam = imagem.getSize();
    int largura = tam.x;
    int altura  = tam.y;

    // A partir deste ponto, os pixels da imagem podem ser acessados através
    // do método imagem.getPixelsPtr() - consulte a documentação!
    const sf::Uint8* pixels = imagem.getPixelsPtr();
}
Observe que a varíavel imagem representa um objeto da classe sf::Image, que contém os pontos da imagem (pixels). Os pontos são armazenados em um array, que deve ser acessado corretamente. Basicamente, cria-se uma função para acessar os pixels como se fosse uma matriz. O resultado deve ser separado nas componentes R,G, B e A (vermelho, verde, azul, que definem a cor do pixel; e A, que define a opacidade do pixel):
struct Pixel {
   unsigned char r, g, b, a;
};

...
Pixel* img = (Pixel*) pixels;
// A partir daqui, o acesso é feito como array, normalmente:
cout << (int) img[0].r; // Acessa o valor da componente vermelha do primeiro pixel da imagem
...
A partir daí, a imagem pode ser processada normalmente como um array de Pixel.
O arquivo asciiart-lapro2ec.zip contém um projeto do Code::Blocks com as bibliotecas necessárias para compilá-lo, mais algumas imagens de teste.

Conversão para Tons de Cinza

O processo de conversão de uma imagem para tons de cinza pode ser feito com o algoritmo descrito abaixo. A idéia é simplicar a imagem, deixando as três componentes R,G e B de cada ponto iguais entre si - equivale a "tirar a cor" da imagem. O valor a ser colocado nestes 3 componentes deverá ser igual à intensidade da cor, dada pela fórmula
I = 0.3r + 0.59g + 0.11b
Para cada ponto (x,y) da imagem:
    Obtem a cor do pixel (r,g,b)
    // calcula o equivalente cinza da cor
    i = (0.3 * r + 0.59 * g + 0.11 * b)
    // armazena o cinza no pixel
    EscreveCor(x,y, i,i,i)

Geração do HTML de saída

Para gerar o código HTML resultante, é preciso escrever determinadas tags no arquivo de saída, de forma que o browser saiba como interpretar os caracteres.
<html><head></head>
<body style="background: black;" leftmargin=0 topmargin=0> 
<style> 
   pre  {
          color: white;
    font-family: Courier;
   }
</style>
Esse trecho define que o fundo será preto, enquanto os caracteres serão brancos, para o estilo pré-formatado (tag PRE). A partir deste ponto, a imagem deve aparecer dentro de um bloco <pre>...</pre>:
<pre>
... caracteres da imagem - linha 1 ...
... caracteres da imagem - linha 2 ...
...
</pre>
Finalmente, deve-se fechar o corpo da página e o HTML em si:
</body>
</html>
Para ver um exemplo de saída, clique neste link.

3 Requisitos

4 Avaliação

Leia com atenção os critérios de avaliação: