Prólogo
Este assunto aparece aqui no Stack Overflow de vez em quando, mas geralmente é removido por ser uma pergunta mal escrita. Eu vi muitas dessas perguntas e, em seguida, silêncio do OP (baixa repetição usual) quando informações adicionais são solicitadas. De vez em quando, se a entrada for boa o suficiente para mim, decido responder com uma resposta e geralmente obtém alguns votos positivos por dia enquanto ativo, mas depois de algumas semanas a pergunta é removida / excluída e tudo começa no começando. Então decidi escrever este Q&A para que possa fazer referência a essas perguntas diretamente, sem reescrever a resposta repetidamente ...
Outra razão é também esta meta discussão direcionado a mim, então, se você tiver informações adicionais, à vontade para comentar.
Questão
Como posso converter uma imagem bitmap em arte ASCII usando C ++ ?
Algumas restrições:
- imagens em escala de cinza
- usando fontes mono-espaçadas
- mantendo-o simples (não usando coisas muito avançadas para programadores de nível iniciante)
Aqui está uma página da Wikipedia relacionada com arte ASCII (graças a @RogerRowland).
Aqui, labirinto semelhante para perguntas e respostas de conversão de arte ASCII .
Respostas:
Existem mais abordagens para a conversão de imagem para arte ASCII que são baseadas principalmente no uso de fontes mono-espaçadas . Para simplificar, eu me concentro apenas no básico:
Com base na intensidade de pixel / área (sombreamento)
Essa abordagem trata cada pixel de uma área de pixels como um único ponto. A ideia é calcular a intensidade média da escala de cinza desse ponto e, em seguida, substituí-lo por um caractere com intensidade próxima o suficiente da calculada. Para isso, precisamos de uma lista de caracteres utilizáveis, cada um com uma intensidade pré-calculada. Vamos chamá-lo de personagem
map
. Para escolher mais rapidamente qual personagem é o melhor para qual intensidade, existem duas maneiras:Mapa de caráter de intensidade linearmente distribuído
Portanto, usamos apenas personagens que têm uma diferença de intensidade com o mesmo passo. Em outras palavras, quando classificado em ordem crescente, então:
Além disso, quando nosso personagem
map
é classificado, podemos computar o personagem diretamente a partir da intensidade (sem necessidade de pesquisa)Mapa de caráter de intensidade distribuída arbitrariamente
Portanto, temos uma variedade de caracteres utilizáveis e suas intensidades. Precisamos encontrar a intensidade mais próxima do
intensity_of(dot)
Então, novamente, se classificarmos omap[]
, podemos usar a pesquisa binária, caso contrário, precisamos de umO(n)
loop ouO(1)
dicionário de distância mínima de pesquisa. Às vezes, para simplificar, o caracteremap[]
pode ser tratado como uma distribuição linear, causando uma leve distorção gama, geralmente invisível no resultado, a menos que você saiba o que procurar.A conversão baseada em intensidade também é ótima para imagens em escala de cinza (não apenas em preto e branco). Se você selecionar o ponto como um único pixel, o resultado ficará grande (um pixel -> caractere único); portanto, para imagens maiores, uma área (multiplicação do tamanho da fonte) é selecionada para preservar a proporção do aspecto e não aumentar muito.
Como fazer isso:
Como personagem,
map
você pode usar qualquer caractere, mas o resultado fica melhor se o personagem tiver pixels dispersos uniformemente ao longo da área do personagem. Para começar, você pode usar:char map[10]=" .,:;ox%#@";
classificados em ordem decrescente e fingem ser linearmente distribuídos.
Portanto, se a intensidade do pixel / área for
i = <0-255>
, o caractere de substituição serámap[(255-i)*10/256];
Se
i==0
o pixel / área for preto, sei==127
o pixel / área for cinza e sei==255
o pixel / área for branco. Você pode experimentar diferentes personagens dentro demap[]
...Aqui está um exemplo antigo meu em C ++ e VCL:
Você precisa substituir / ignorar as coisas da VCL, a menos que use o ambiente Borland / Embarcadero .
mm_log
é o memorando onde o texto é enviadobmp
é o bitmap de entradaAnsiString
é uma string do tipo VCL indexada de 1, não de 0 comochar*
!!!Este é o resultado: Exemplo de imagem de intensidade ligeiramente NSFW
À esquerda está a saída de arte ASCII (tamanho da fonte 5 pixels) e à direita a imagem de entrada ampliada algumas vezes. Como você pode ver, a saída é pixel -> caractere maior. Se você usar áreas maiores em vez de pixels, o zoom será menor, mas é claro que a saída será menos agradável visualmente. Essa abordagem é muito fácil e rápida de codificar / processar.
Quando você adiciona coisas mais avançadas como:
Então você pode processar imagens mais complexas com melhores resultados:
Aqui está o resultado em uma proporção de 1: 1 (zoom para ver os caracteres):
Claro, para amostragem de área você perde os pequenos detalhes. Esta é uma imagem do mesmo tamanho que o primeiro exemplo com amostra de áreas:
Imagem de exemplo avançado de intensidade ligeiramente NSFW
Como você pode ver, isso é mais adequado para imagens maiores.
Ajuste de caracteres (híbrido entre sombreado e arte ASCII sólida)
Essa abordagem tenta substituir a área (não há mais pontos de pixel único) por caracteres com intensidade e forma semelhantes. Isso leva a melhores resultados, mesmo com fontes maiores usadas em comparação com a abordagem anterior. Por outro lado, essa abordagem é um pouco mais lenta, é claro. Existem mais maneiras de fazer isso, mas a ideia principal é calcular a diferença (distância) entre a área da imagem (
dot
) e o caractere renderizado. Você pode começar com uma soma ingênua da diferença absoluta entre os pixels, mas isso levará a resultados não muito bons, porque mesmo uma mudança de um pixel tornará a distância grande. Em vez disso, você pode usar correlação ou métricas diferentes. O algoritmo geral é quase o mesmo da abordagem anterior:Portanto, divida uniformemente a imagem em áreas retangulares (em escala de cinza) ponto 's
de preferência com a mesma proporção dos caracteres de fonte renderizados (isso preservará a proporção. Não se esqueça de que os caracteres geralmente se sobrepõem um pouco no eixo x)
Calcule a intensidade de cada área (
dot
)Substitua-o por um personagem do personagem
map
com a intensidade / forma mais próximaComo podemos calcular a distância entre um caractere e um ponto? Essa é a parte mais difícil dessa abordagem. Enquanto experimento, desenvolvo este meio-termo entre velocidade, qualidade e simplicidade:
Divida a área do personagem em zonas
map
).i=(i*256)/(xs*ys)
.Processa a imagem de origem em áreas retangulares
Este é o resultado para o tamanho da fonte = 7 pixels
Como você pode ver, a saída é visualmente agradável, mesmo com um tamanho de fonte maior usado (o exemplo de abordagem anterior era com um tamanho de fonte de 5 pixels). A saída tem aproximadamente o mesmo tamanho da imagem de entrada (sem zoom). Os melhores resultados são obtidos porque os caracteres estão mais próximos da imagem original, não apenas pela intensidade, mas também pela forma geral e, portanto, você pode usar fontes maiores e ainda preservar os detalhes (até certo ponto, é claro).
Aqui está o código completo para o aplicativo de conversão baseado em VCL:
É um aplicativo de formulário simples (
Form1
) com um únicoTMemo mm_txt
nele. Ele carrega uma imagem,"pic.bmp"
e, de acordo com a resolução, escolhe qual abordagem usar para converter para o texto que será salvo no"pic.txt"
e enviado para memo para visualização.Para aqueles sem VCL, ignore as coisas do VCL e substitua
AnsiString
por qualquer tipo de string que você tenha, e tambémGraphics::TBitmap
por qualquer bitmap ou classe de imagem que você tenha à disposição com capacidade de acesso a pixels.Uma observação muito importante é que ele usa as configurações de
mm_txt->Font
, portanto, certifique-se de definir:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
para fazer isso funcionar corretamente, caso contrário, a fonte não será tratada como mono-espaçada. A roda do mouse apenas muda o tamanho da fonte para cima / baixo para ver os resultados em diferentes tamanhos de fonte.
[Notas]
3x3
.Comparação
Finalmente, aqui está uma comparação entre as duas abordagens na mesma entrada:
As imagens marcadas com pontos verdes são feitas com a abordagem # 2 e as vermelhas com # 1 , todas em um tamanho de fonte de seis pixels. Como você pode ver na imagem da lâmpada, a abordagem sensível à forma é muito melhor (mesmo se o nº 1 for feito em uma imagem de origem com zoom 2x).
Aplicativo legal
Ao ler as novas perguntas de hoje, tive uma ideia de um aplicativo legal que pega uma região selecionada da área de trabalho e a alimenta continuamente no ASCIIart conversor e visualiza o resultado. Depois de uma hora de codificação, está pronto e estou tão satisfeito com o resultado que simplesmente preciso adicioná-lo aqui.
OK, o aplicativo consiste em apenas duas janelas. A primeira janela mestre é basicamente minha janela do conversor antigo, sem a seleção e visualização da imagem (todo o material acima está nela). Ele tem apenas as configurações de visualização e conversão ASCII. A segunda janela é um formulário vazio com o interior transparente para a seleção da área de captura (sem qualquer funcionalidade).
Agora, em um cronômetro, eu apenas pego a área selecionada pelo formulário de seleção, passo para a conversão e visualizo o ASCIIart .
Assim, você inclui uma área que deseja converter pela janela de seleção e visualiza o resultado na janela principal. Pode ser um jogo, visualizador, etc. Tem a seguinte aparência:
Agora posso assistir até vídeos em ASCIIart para me divertir. Alguns são muito legais :).
Se você quiser tentar implementar isso em GLSL , dê uma olhada nisso:
fonte
3x3
zonas e comparar os DCTs, mas isso diminuiria muito o desempenho, eu acho.