Cambie el tamaño del texto rasterizado y haga que parezca no pixelado

11

Esta es una captura de pantalla de un texto escrito en un editor de texto:

Texto de 16 píxeles de alto

Este es el mismo texto en un tamaño más grande.

Texto de 96 píxeles de alto

Observe cuán visible es el alias en letras con trazos diagonales prominentes como xy z. Este problema es una de las principales razones por las que las fuentes ráster han perdido popularidad en formatos "escalables" como TrueType.

Pero tal vez este no sea un problema inherente con las fuentes ráster, solo con la forma en que normalmente se implementa el escalado de ellas. Aquí hay una representación alternativa que usa interpolación bilineal simple combinada con umbral .

Texto de 96 píxeles de alto con interpolación bilineal

Esto es más suave, pero no ideal. Los trazos diagonales todavía están llenos de baches, y las letras curvas siguen cy osiguen siendo polígonos. Esto es especialmente notable en tamaños grandes.

Entonces, ¿hay una mejor manera?

La tarea

Escriba un programa que tome tres argumentos de línea de comandos.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR

dónde

  • INPUT_FILE es el nombre del archivo de entrada, que se supone que es un archivo de imagen que contiene texto negro sobre un fondo blanco. Puede usar cualquier formato de imagen ráster convencional (PNG, BMP, etc.) que sea conveniente.
  • OUTPUT_FILE es el nombre del archivo de salida. Puede ser un formato de imagen ráster o vectorial. Puede introducir color si está haciendo algo de representación de subpíxeles tipo ClearType.
  • SCALE_FACTOR es un valor positivo de punto flotante que indica cuánto se puede cambiar el tamaño de la imagen. Dado un archivo de entrada x × y px y un factor de escala s , la salida tendrá un tamaño de sx × sy px (redondeado a enteros).

Puede usar una biblioteca de procesamiento de imágenes de código abierto de tercer pary.

Además de su código, incluya ejemplos de salidas de su programa en factores de escala de 1.333, 1.5, 2, 3 y 4 usando mi primera imagen como entrada. También puede probarlo con otras fuentes, incluidas las proporcionalmente espaciadas.

Puntuación

Este es un concurso de popularidad. La entrada con el mayor número de votos positivos menos votos negativos gana. En caso de empate exacto, gana la entrada anterior.

Editar : Fecha límite extendida debido a la falta de entradas. TBA

Se alienta a los votantes a juzgar basándose principalmente en lo bien que se ven las imágenes de salida y, en segundo lugar, en la simplicidad / elegancia del algoritmo.

dan04
fuente
¿ SCALE_FACTORSiempre es > 1?
kennytm
@kennytm: Sí Han editado para enumerar explícitamente los factores de escala.
dan04
¿Podemos suponer que solo hay una línea de texto en la imagen?
GiantTree
@GiantTree: Sí Puede admitir texto de varias líneas si lo desea, pero esto no es obligatorio.
dan04

Respuestas:

4

Ruby, con RMagick

El algoritmo es muy simple: encuentre patrones de píxeles que se vean así:

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

y agregue triángulos para que se vean así:

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

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')

Salidas (haga clic en cualquiera para ver la imagen sola):

1.333

1.333

1,5

1,5

2

2

3

3

4 4

4 4

Pomo de la puerta
fuente