- Foi desenvolvida uma técnica de renderização ASCII que preserva contornos e formas da imagem, resolvendo o problema de bordas borradas dos métodos tradicionais
- Em vez de um mapeamento simples de brilho por pixel, ela usa uma abordagem baseada em vetores de alta dimensão que quantifica e compara a forma visual (shape) de cada caractere
- Medindo a densidade nas regiões superior, inferior, esquerda e direita de cada caractere, gera-se um shape vector expandido de 2 para 6 dimensões, permitindo uma seleção de caracteres mais precisa
- Para aumentar a nitidez das bordas, são aplicados algoritmos de realce de contraste global e direcional (contrast enhancement)
- Com aceleração por GPU, cache e busca por árvore k-d, o sistema atinge desempenho de renderização ASCII em tempo real, produzindo efeitos visuais de alta qualidade
Conversão de imagem para ASCII
- O ASCII tem 95 caracteres imprimíveis, e a imagem é dividida em uma grade usando uma fonte monoespaçada
- O brilho de cada célula é calculado e mapeado de acordo com a densidade do caractere
- A simples interpolação por vizinho mais próximo (nearest-neighbor interpolation) provoca o efeito de bordas serrilhadas conhecido como jaggies
- Com supersampling, ao coletar várias amostras dentro da célula e calcular o brilho médio, o resultado fica mais suave, mas ainda surgem bordas borradas
- O problema central está em tratar caracteres como se fossem pixels, sem considerar sua forma própria
Uso da forma (Shape) dos caracteres
- Cada caractere tem uma distribuição visual de densidade diferente dentro da célula
- Ex.:
T é mais denso na parte superior, enquanto L é mais denso na parte inferior
- Para quantificar isso, posiciona-se um círculo de amostragem dentro da célula e calcula-se a proporção ocupada pelo caractere em cada região
- As proporções das regiões superior e inferior são representadas como um vetor, gerando um shape vector bidimensional
- Os shape vectors de cada caractere são pré-calculados, e o caractere mais próximo do vetor amostrado da imagem é escolhido pela distância euclidiana (Euclidean distance)
Expansão para vetor de forma em 6 dimensões
- Apenas as 2 dimensões superior/inferior não bastam para representar bem caracteres centrados no meio ou na esquerda/direita, como
-, p e q
- A célula é expandida para 6 círculos de amostragem, capturando superior, meio, inferior e as diferenças entre esquerda e direita
- O shape vector em 6 dimensões reflete a forma dos caracteres com muito mais precisão e representa bem até caracteres circulares e diagonais
- Na renderização de cenas 3D, os contornos externos ficam nítidos, mas surge o problema de bordas borradas entre superfícies
Realce de contraste (Contrast Enhancement)
- Cada elemento do vetor amostrado é ajustado com um expoente (exponent), escurecendo mais os valores baixos e mantendo os valores altos
- O vetor é normalizado, recebe o expoente e depois é restaurado para sua faixa original
- Esse processo reforça a distinção visual das bordas, tornando a escolha dos caracteres mais clara
- Em áreas de brilho uniforme, quase não há mudança, preservando gradientes suaves
- Porém, em algumas bordas, surge o efeito de degraus (staircasing)
Realce de contraste direcional (Directional Contrast Enhancement)
- Círculos de amostragem externos também são posicionados fora de cada célula para coletar informações de brilho ao redor
- Valores claros no vetor de amostragem externo escurecem os elementos correspondentes do vetor interno, reforçando o contraste na direção da borda
- Ao ampliar a amostragem externa para aumentar a influência entre topo, meio e base, torna-se possível uma representação de bordas suave e nítida
- Quando combinado com o realce de contraste global, isso permite uma renderização ASCII de cenas 3D com contornos bem definidos e alta legibilidade
Otimização de desempenho
- Como repetir de forma simples a busca do vizinho mais próximo na seleção de caracteres é lento, usa-se uma árvore k-d para buscas rápidas em espaço multidimensional
- O cache permite reutilizar o resultado de vetores de amostragem idênticos
- Cada vetor é quantizado em unidades de 5 bits para criar uma chave de cache eficiente em memória
- Definir o intervalo como 8 mantém um equilíbrio entre qualidade e uso de memória
- A busca em cache é extremamente rápida, e até milhares de caracteres podem ser processados em tempo real
- O cálculo dos vetores de amostragem foi movido para a GPU, com amostragem interna e externa e operações de realce de contraste processadas no pipeline de shaders
- Desempenho várias vezes superior ao da CPU
Conclusão
- A abordagem que quantifica a forma dos caracteres em vetores melhora muito a resolução e a nitidez da renderização ASCII
- O método é semelhante ao conceito de word embedding e pode ser aplicado a outros problemas visuais
- A implementação inicial era lenta, mas, com aceleração por GPU, cache e busca por árvore k-d, passou a garantir FPS fluido até em dispositivos móveis
- A representação ASCII baseada em cores não foi tratada, mas o texto menciona a possibilidade de experimentar no futuro combinações mais variadas de forma e contraste
- Mais do que um simples efeito visual, a renderização ASCII mostra o potencial de expansão do reconhecimento de formas e das representações vetoriais
1 comentários
Comentários do Hacker News
Se você normalizar os vetores e depois calcular a distância euclidiana, dá para obter o mesmo resultado com um simples produto de matrizes (matmul)
Isso acontece porque, com vetores normalizados, a distância euclidiana é uma transformação linear da distância cosseno
Se o que importa é só o ranking, e não o valor exato da distância, também dá para pular a operação de sqrt e obter o mesmo resultado, com um cálculo um pouco mais rápido
Adoro esse tipo de texto. Parece simples à primeira vista, mas para ficar realmente bom exige uma exploração profunda
Também recomendo o texto que Lucas Pope escreveu ao desenvolver o sistema de dithering de Return of The Obra Dinn
Diário de desenvolvimento de Lucas Pope
Fiquei surpreso ao ler a frase “gerei a imagem de Saturno com o ChatGPT”
Há tantas fotos reais de Saturno em domínio público por aí; fico me perguntando por que alguém faria questão de criar uma imagem falsa para poluir a internet
Talvez um dia a gente nem escreva mais wikis, sites ou fóruns diretamente
Se for possível gerar na hora uma “imagem de Saturno em alto contraste no tamanho X×Y”, isso seria uma mudança quase mágica
Assim como calculadoras e a internet não mataram a criatividade, os humanos sempre escolhem a ferramenta com menos atrito e continuam avançando rumo às estrelas
Toda vez que eu via um exemplo pensava “legal, mas ainda dá para melhorar”, e fiquei impressionado porque o autor realmente resolveu isso
É um texto belíssimo, e o blog inteiro tem esse mesmo nível de profundidade, então vale a pena assinar
alexharri.com/blog
Quando criei o projeto ascii-side-of-the-moon, pensei em implementar eu mesmo um renderizador ASCII
No fim usei o chafa, mas pretendo tentar de novo algum dia
Fiquei curioso se há planos de lançar isso como biblioteca, ou se posso simplesmente me basear no código do site
No momento não há plano de transformar isso em biblioteca, mas, se precisar, fique à vontade para aproveitar o código do site
Se eu fosse fazer isso, provavelmente aumentaria a compatibilidade com uma conversão de WebGL 2 para WebGL 1, e também criaria uma ferramenta para pré-calcular os shape vectors de cada fonte
Sobre a frase “nunca vi um exemplo de uso de shape em arte ASCII”, na verdade existe um gerador que usa shape
É o projeto ascii-silhouettify, que usa um algoritmo para escolher o maior caractere que se encaixa no contorno das áreas de cor
Acerola tentou em 2024 um renderizador ASCII baseado em detecção de bordas
O método sobrepunha símbolos direcionais (| / - \) sobre uma camada baseada em brilho
Veja o vídeo relacionado
Por exemplo, daria para experimentar várias abordagens, como usar contornos grossos no estilo da arte 2D tradicional ou representar contrastes suaves de luz e sombra como em Chiaroscuro
A maioria dos filtros ASCII não considera a forma (shape) dos glifos
O chafa trata cada glifo como um bitmap 8×8, e achei essa abordagem impressionante
Olhando o código-fonte do chafa e a galeria, dá para sentir o nível de refinamento
Fico curioso se uma abordagem mais centrada em direcionalidade conseguiria representar melhor formas maiores
A lista de fontes de PCs antigos é um verdadeiro buraco sem fim
No meu tempo livre, estou experimentando com gráficos coloridos baseados em braille
A resolução é suficiente, mas falta precisão na representação das cores, então depois da amostragem é preciso fazer um ajuste de contraste (contrast fixup)
Acho que seria interessante aplicar a técnica de amostragem do autor para reforçar o contraste das cores
Antes eu tinha tentado aumentar o contraste com um filtro de Sobel, mas não funcionou porque ele não se alinhava com a grade de caracteres
Foi uma abordagem técnica realmente excelente, com uma análise profunda
No final eu esperava ver uma versão melhorada do Cognition cube array, então fiquei um pouco decepcionado por isso não aparecer
Isso me lembrou um designer que, anos atrás, no YouTube, implementou favicons melhores com contraste de cor por subpixel
Texto relacionado (Web Archive)
Mesmo assim, o texto em si foi excelente, e os exemplos dinâmicos foram realmente impressionantes