Algoritmos 2 IFMS NA

Projeto - Filtros de Imagem

Outubro 2021

Lembre-se: o projeto deve ser entregue no Moodle da sua classe!
O projeto pode ser feito em duplas (n <= 2)
Vai ser feita uma apresentação do projeto. O instrutor se reserva o direito de fazer questionamentos sobre todas as etapas do desenvolvimento do projeto para qualquer participante do trabalho.
A sua nota no projeto será parte pela implementação das funcionalidades, parte pela entrevista.

Um pouco sobre cores ...


O comprimento de onda que nossos olhos podem detectar (o espectro de luz visível) é apenas uma fração do espectro de energia eletromagnético. As ondas mais curtas visíveis são as ondas no espectro do azul. Ondas com comprimento de onda mais curtos, como o ultra-violeta ainda são detectáveis, apenas não são visíveis ao olho humano. Na outra ponta do espectro visível temos as ondas vermelhas. Onda infravermelhas, como as usadas em controle-remoto são comunmente usadas, mas não vísiveis.

Espectro de luz visível
Espectro de luz (cor) visível.

Se o espectro de luz vísivel for dividido, as cores predominantes são o vermelho, o verde e o azul (Red, Green, Blue - RGB). Essas cores são também chamadas de cores primárias. O modelo RGB é chamado de modelo de cor aditivo pois todas as outras cores são combinações das cores primárias. Por exemplo, o ciano é a mistura do azul puro com o verde puro.

Modelo aditivo RGB
O Modelo Aditivo de Cor RGB.

Representando cores em computadores ...


Televisores e monitores de computador (atualmente praticamente indistinguiveis uns dos outros atualmente) criam cores usando as cores primárias da luz. Cada pixel (a menor unidade de reprodução de luz de um dispotivo) em um monitor começa como preto (ausência de luz). Quando as unidades de luz vermelha, verde e azul de um pixel são iluminadas simultaneamente, esse píxel se torna branco. Controlando a quantidade de cada um das unidades de luz, chamadas de canais de cor, nós podemos representar as cores do espectro visível em um monitor.

Atualmente, o valor da cor de cada pixel é armazenado usando os valores de cada canal do modelo RGB. Cada canal é representado por um byte (8 bits). Como um byte pode representar no máximo 256 valores, cada canal varia de 0 (ausência total da cor) a 255 (presença total da cor). Como temos 3 canais de cores, são necessários 3 bytes (24 bits) para cada pixel de uma imagem! Pode não parecer muito atualmente, mas antigamente processar essa quantidade de dados era um grande gargalo...

Além dos três canais de cor (RGB), o modelo RGB Alpha utilizado pelos computadores atuais adicionaram um novo canal para transparência (alpha). Caso o valor do canal seja 0 o pixel será completamente transparente, caso o canal tenha o valor seja 255 o canal será completamente opaco.

Canais RGB
Canais RGB alpha.

Usando essa representação em três canais mais o canal de transparência (alfa), 256 valores por canal, podemos representar:

Manipulando imagens (e cores) em JavaScript


Geralmente, imagens são descritas em forma de matrizes. Por exemplo, uma imagem 29x20 tem 29 linhas e cada linha tem 20 pixels, num total de 580 pixels! Em geral é isso que chamamos de resolução da imagem. Um monitor Full HD suporta uma resolução de 1920 x 1080 pixels. Um monitor 4K suporta uma resolução de até 3840 x 2129 pixels.

Toad em Pixel Art
Representação de uma imagem usando apenas 29x20 pixels.

Uma imagem em JavaScript é representada por um array de valores. O array é organizado como uma sequência de pixels e cada pixel é organizado por uma sequência de seus quatro canais: R, G , B , alpha. Imagine que a imagem que você ve na tela do computador foi recortada, linha por linha de pixels e depois colada em uma longa tira.

Canais RGB em JavaScript
Canais RGB alpha em JavaScript.

Dessa forma, se tivéssemos uma imagem 4 x 5 pixels - 4 linhas de 5 pixels cada, teremos um total de 20 pixels. Logo o array que representa essa imagem no JavaScript terá 20 x 4 = 80 elementos! Para a nossa imagem acima (29x20) temos : 29 x 20 x 4 = 2320 elementos!

Por sorte, você não precisa se preocupar com quantos elementos uma imagem terá, pois as Higher-order Functions map, filter e reduce automaticamente percorrem todos os elementos de um array!

Filtros de Imagem


Filtros de imagem são manipulações dos pixels de uma imagem de modo a transforma-la mas sem descaracterizá-la por completo. Existem filtros complexos, tais como os filtros no Stories do Instagram, que requerem não apenas a manipulação pixel a pixel da imagem mas também a manipulação de várias características de uma imagem como um todo. Neste projeto específico vamos trabalhar apenas com filtros que manipulam os pixels de uma imagem, pois são mais simples de entender e implementar:

Nas próximas seções vamos apresentar a teoria de cada um dos filtros. Para os filtros negativo e escala de cinza também serão fornecidos exemplos para que vocês possam continuar o desenvolvimento dos demais filtros.

Todos os arquivos necessários para a implementação do projeto estão disponíveis aqui. Vocês podem desenvolver o projeto em uma máquina local, mas eu recomendo fortemente utilizar a REPL criada especialmente para o projeto.

O projeto tem a seguinte estrutura:

Os códigos usados para acessar os pixels de cada imagem já estão corretamente inseridos no arquivo script.js. Explicarei brevemente o que cada parte do código objetiva:

var idata = context.getImageData(0, 0, canvas.width, canvas.height);
Copia todos os pixels da imagem a ser manipulada.
negdata = idata.data.map(negativo);
O objeto idata contém um array, data. Aqui estamos aplicando um map com a função negativo como parâmetro e armazenando o resultado em um novo array, negdata.
idata.data.set(negdata);
Atribuimos o array com os valores transformados (pixels originais negativados) ao objeto com os pixels da imagem.
context.putImageData(idata, 0, 0);
Transferimos o objeto com os pixels para a imagem e pedimos para "pintar" a imagem novamente.