Redimensionner le texte tramé et lui donner un aspect non pixellisé

11

Voici une capture d'écran d'un texte tapé dans un éditeur de texte:

Texte de 16 pixels

Il s'agit du même texte à une plus grande taille.

Texte haute résolution de 96 pixels

Remarquez à quel point l' aliasing est visible sur les lettres avec des traits diagonaux proéminents comme xet z. Ce problème est l'une des principales raisons pour lesquelles les polices raster ont perdu leur popularité au profit de formats «évolutifs» comme TrueType.

Mais ce n'est peut-être pas un problème inhérent aux polices raster, juste avec la manière dont la mise à l'échelle est généralement implémentée. Voici un rendu alternatif utilisant une simple interpolation bilinéaire combinée à un seuillage .

Texte haute résolution de 96 pixels avec interpolation bilinéaire

C'est plus fluide, mais pas idéal. Les traits diagonaux sont toujours cahoteux et les lettres courbes aiment cet osont toujours des polygones. Cela est particulièrement visible dans les grandes tailles.

Alors, y a-t-il une meilleure façon?

La tâche

Écrivez un programme qui accepte trois arguments de ligne de commande.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR

  • INPUT_FILE est le nom du fichier d'entrée, qui est supposé être un fichier image contenant du texte noir sur fond blanc. Vous pouvez utiliser n'importe quel format d'image raster courant (PNG, BMP, etc.) qui soit pratique.
  • OUTPUT_FILE est le nom du fichier de sortie. Il peut s'agir d'un format d'image raster ou vectorielle. Vous pouvez introduire la couleur si vous effectuez un rendu de sous-pixel de type ClearType.
  • SCALE_FACTOR est une valeur à virgule flottante positive qui indique dans quelle mesure l'image peut être redimensionnée. Étant donné un fichier d'entrée x × y px et un facteur d'échelle s , la sortie aura une taille de sx × sy px (arrondie aux entiers).

Vous pouvez utiliser une bibliothèque de traitement d'images open source de troisième partie.

En plus de votre code, incluez des exemples de sorties de votre programme à des facteurs d'échelle de 1,333, 1,5, 2, 3 et 4 en utilisant ma première image comme entrée. Vous pouvez également l'essayer avec d'autres polices, y compris celles à espacement proportionnel.

Notation

Il s'agit d'un concours de popularité. L'entrée avec le plus grand nombre de votes positifs moins les votes négatifs gagne. En cas d'égalité exacte, l'entrée précédente l'emporte.

Edit : Délai prolongé en raison du manque d'entrées. TBA.

Les électeurs sont encouragés à juger principalement en fonction de la qualité des images de sortie et, en second lieu, de la simplicité / élégance de l'algorithme.

dan04
la source
Est SCALE_FACTORtoujours> 1?
kennytm
@kennytm: Oui. Ont modifié pour répertorier explicitement les facteurs d'échelle.
dan04
Pouvons-nous supposer qu'il n'y a qu'une seule ligne de texte dans l'image?
GiantTree
@GiantTree: Oui. Vous pouvez prendre en charge le texte multiligne si vous le souhaitez, mais ce n'est pas obligatoire.
dan04

Réponses:

4

Ruby, avec RMagick

L'algorithme est très simple - trouvez des motifs de pixels qui ressemblent à ceci:

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

et ajoutez des triangles pour les faire ressembler à ceci:

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

Code:

#!/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')

Sorties (cliquez sur n'importe laquelle pour afficher l'image par elle-même):

1,333

1,333

1,5

1,5

2

2

3

3

4

4

Poignée de porte
la source