Redimensione texto rasterizado e faça com que não pareça pixelizado


11

Esta é uma captura de tela de algum texto digitado em um editor de texto:

Texto com 16 px de altura

Este é o mesmo texto em tamanho maior.

Texto com 96px de altura

Observe como o alias é visível em letras com traços diagonais proeminentes como xe z. Esse problema é um dos principais motivos pelos quais as fontes raster perderam popularidade em formatos "escaláveis" como o TrueType.

Mas talvez esse não seja um problema inerente às fontes raster, apenas com a maneira como a escala delas é normalmente implementada. Aqui está uma renderização alternativa usando interpolação bilinear simples combinada com limiar .

Texto com 96px de altura e renderizado com interpolação bilinear

Isso é mais suave, mas não é o ideal. Os traçados diagonais ainda são irregulares e as letras curvas são ce oainda são polígonos. Isso é especialmente visível em tamanhos grandes.

Então, existe uma maneira melhor?

A tarefa

Escreva um programa que aceite três argumentos de linha de comando.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR

Onde

  • INPUT_FILE é o nome do arquivo de entrada, que se supõe ser um arquivo de imagem contendo texto em preto sobre fundo branco. Você pode usar qualquer formato de imagem rasterizada convencional (PNG, BMP etc.) que seja conveniente.
  • OUTPUT_FILE é o nome do arquivo de saída. Pode ser um formato de imagem raster ou vetorial. Você pode introduzir cores se estiver fazendo uma renderização de subpixel semelhante ao ClearType.
  • SCALE_FACTOR é um valor positivo de ponto flutuante que indica quanto a imagem pode ser redimensionada. Dado um arquivo de entrada x × y px e o fator de escala s , a saída terá um tamanho de sx × sy px (arredondado para números inteiros).

Você pode usar uma biblioteca de processamento de imagens de código aberto de terceiros.

Além do seu código, inclua saídas de amostra do seu programa em fatores de escala de 1.333, 1,5, 2, 3 e 4 usando minha primeira imagem como entrada. Você também pode experimentá-lo com outras fontes, incluindo as espaçadas proporcionalmente.

Pontuação

Este é um concurso de popularidade. A entrada com o maior número de votos positivos menos votos negativos. Em caso de empate exato, a entrada anterior vence.

Edit : Prazo prorrogado devido à falta de entradas. TBA.

Os eleitores são incentivados a julgar com base principalmente na aparência das imagens de saída e, secundariamente, na simplicidade / elegância do algoritmo.


SCALE_FACTORSempre é > 1?
Kennytm

@kennytm: Sim. Editado para listar explicitamente os fatores de escala.
Dan04

Podemos assumir que há apenas uma linha de texto na imagem?
GiantTree

@GiantTree: Sim. Você pode suportar texto de várias linhas, se quiser, mas isso não é necessário.
dan04

Respostas:


4

Ruby, com RMagick

O algoritmo é muito simples - encontre padrões de pixels parecidos com este:

    ####
    ####
    ####
    ####
########
########
########
########

e adicione triângulos para torná-los assim:

    ####
   #####
  ######
 #######
########
########
########
########

Código:

#!/usr/bin/ruby

require 'rmagick'
require 'rvg/rvg'
include Magick

img = Image.read(ARGV[0] || 'img.png').first
pixels = []
img.each_pixel{|px, x, y|
    if px.red == 0 && px.green == 0 && px.blue == 0
        pixels.push [x, y]
    end
}

scale = ARGV[2].to_f || 5.0
rvg = RVG.new((img.columns * scale).to_i, (img.rows * scale).to_i)
    .viewbox(0, 0, img.columns, img.rows) {|cnv|
    # draw all regular pixels
    pixels.each do |p|
        cnv.rect(1, 1, p[0], p[1])
    end
    # now collect all 2x2 rectangles of pixels
    getpx = ->x, y { !!pixels.find{|p| p[0] == x && p[1] == y } }
    rects = [*0..img.columns].product([*0..img.rows]).map{|x, y|
        [[x, y], [
            [getpx[x, y  ], getpx[x+1, y  ]],
            [getpx[x, y+1], getpx[x+1, y+1]]
        ]]
    }
    # WARNING: ugly code repetition ahead
    # (TODO: ... fix that)
    # find this pattern:
    # ?X
    # XO
    # where X = black pixel, O = white pixel, ? = anything
    rects.select{|r| r[1][0][1] && r[1][1][0] && !r[1][2][1] }
        .each do |r|
            x, y = r[0]
            cnv.polygon x+1,y+1, x+2,y+1, x+1,y+2
        end
    # OX
    # X?
    rects.select{|r| r[1][0][1] && r[1][3][0] && !r[1][0][0] }
        .each do |r|
            x, y = r[0]
            cnv.polygon x+1,y+1, x+0,y+1, x+1,y+0
        end
    # X?
    # OX
    rects.select{|r| r[1][0][0] && r[1][4][1] && !r[1][5][0] }
        .each do |r|
            x, y = r[0]
            cnv.polygon x+1,y+1, x+0,y+1, x+1,y+2
        end
    # XO
    # ?X
    rects.select{|r| r[1][0][0] && r[1][6][1] && !r[1][0][1] }
        .each do |r|
            x, y = r[0]
            cnv.polygon x+1,y+1, x+2,y+1, x+1,y+0
        end
}
rvg.draw.write(ARGV[1] || 'out.png')

Saídas (clique em qualquer uma para visualizar a imagem):

1,333

1,333

1.5

1.5

2

2

3

3

4

4

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.