Ridimensiona il testo rasterizzato e rendilo non pixelato


11

Questa è una schermata di alcuni testi digitati in un editor di testo:

Testo 16px

Questo è lo stesso testo a dimensioni maggiori.

Testo alto 96 px

Notare quanto è visibile l' aliasing sulle lettere con tratti diagonali prominenti come xe z. Questo problema è uno dei motivi principali per cui i caratteri raster hanno perso popolarità rispetto ai formati "scalabili" come TrueType.

Ma forse questo non è un problema intrinseco con i caratteri raster, solo con il modo in cui il loro ridimensionamento è generalmente implementato. Ecco un rendering alternativo usando una semplice interpolazione bilineare combinata con la soglia .

Testo ad alta 96px reso con interpolazione bilineare

Questo è più fluido, ma non ideale. I tratti diagonali sono ancora irregolari e le lettere curve sono simili ce osono ancora poligoni. Ciò è particolarmente evidente a grandi dimensioni.

Quindi c'è un modo migliore?

L'obiettivo

Scrivi un programma che accetta tre argomenti della riga di comando.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR

dove

  • INPUT_FILE è il nome del file di input, che si presume sia un file di immagine contenente testo nero su sfondo bianco. Puoi usare qualsiasi formato di immagine raster tradizionale (PNG, BMP, ecc.) Che sia conveniente.
  • OUTPUT_FILE è il nome del file di output. Può essere in formato immagine raster o vettoriale. Puoi introdurre il colore se stai eseguendo un rendering subpixel simile a ClearType.
  • SCALE_FACTOR è un valore a virgola mobile positivo che indica la quantità di ridimensionamento dell'immagine. Dato un file di input x × y px e il fattore di ridimensionamento s , l'output avrà una dimensione di sx × sy px (arrotondato agli interi).

È possibile utilizzare una libreria di elaborazione delle immagini open source di terze parti.

Oltre al tuo codice, includi output di esempio del tuo programma con fattori di scala di 1.333, 1.5, 2, 3 e 4 usando la mia prima immagine come input. Puoi anche provarlo con altri caratteri, compresi quelli spaziati proporzionalmente.

punteggio

Questo è un concorso di popolarità. Vince la voce con il maggior numero di voti meno i voti negativi. In caso di pareggio esatto, la voce precedente vince.

Modifica : scadenza estesa a causa della mancanza di voci. TBA.

Gli elettori sono incoraggiati a giudicare in base principalmente alla qualità delle immagini in uscita e, in secondo luogo, alla semplicità / eleganza dell'algoritmo.


È SCALE_FACTORsempre> 1?
kennytm,

@kennytm: Sì. Hanno modificato per elencare esplicitamente i fattori di scala.
dan04,

Possiamo supporre che nell'immagine sia presente solo una riga di testo?
GiantTree

@GiantTree: Sì. Se lo desideri, puoi supportare il testo su più righe, ma non è necessario.
dan04,

Risposte:


4

Ruby, con RMagick

L'algoritmo è molto semplice: trova schemi di pixel simili a questi:

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

e aggiungi triangoli per farli apparire così:

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

Codice:

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

Output (fai clic su uno qualsiasi per visualizzare l'immagine da solo):

1.333

1.333

1.5

1.5

2

2

3

3

4

4

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.