Zmień rozmiar zrasteryzowanego tekstu i spraw, aby wyglądał na niepikselowany

11

Oto zrzut ekranu z tekstem wpisanym w edytorze tekstu:

Tekst o wysokości 16 pikseli

To jest ten sam tekst w większym rozmiarze.

Tekst o wysokości 96 pikseli

Zauważ, jak widoczne jest aliasing na literach z wyraźnymi ukośnymi pociągnięciami, takimi jak xi z. Ten problem jest głównym powodem, dla którego czcionki rastrowe straciły popularność w „skalowalnych” formatach, takich jak TrueType.

Ale może nie jest to nieodłączny problem z czcionkami rastrowymi, tylko ze względu na sposób, w jaki zwykle skaluje się je. Oto alternatywne renderowanie przy użyciu prostej interpolacji dwuliniowej w połączeniu z progowaniem .

Tekst o wysokości 96 pikseli z interpolacją dwuliniową

Jest to płynniejsze, ale nie idealne. Ukośne kreski nadal wyboistych i zakrzywione litery jak ci onadal są wielokąty. Jest to szczególnie widoczne w dużych rozmiarach.

Czy jest więc lepszy sposób?

Zadanie

Napisz program, który pobiera trzy argumenty wiersza polecenia.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR

gdzie

  • INPUT_FILE to nazwa pliku wejściowego, który zakłada się, że jest to plik obrazu zawierający czarny tekst na białym tle. Możesz użyć dowolnego głównego formatu obrazu rastrowego (PNG, BMP itp.), Który jest wygodny.
  • OUTPUT_FILE to nazwa pliku wyjściowego. Może to być format rastrowy lub wektorowy. Możesz wprowadzić kolor, jeśli wykonujesz renderowanie subpikseli podobne do ClearType.
  • SCALE_FACTOR to dodatnia wartość zmiennoprzecinkowa, która wskazuje, o ile można zmienić rozmiar obrazu. Biorąc pod uwagę plik wejściowy x × y px i współczynnik skalowania s , wyjście będzie miało rozmiar sx × sy px (w zaokrągleniu do liczb całkowitych).

Możesz użyć biblioteki przetwarzania obrazu open-source trzeciej pary.

Oprócz swojego kodu, dołącz przykładowe wyniki swojego programu w współczynnikach skali 1,333, 1,5, 2, 3 i 4, używając mojego pierwszego obrazu jako danych wejściowych. Możesz także wypróbować go z innymi czcionkami, w tym proporcjonalnie rozmieszczonymi.

Punktacja

To konkurs popularności. Wpis z największą liczbą głosów pozytywnych minus głosy negatywne wygrywa. W przypadku dokładnego remisu wygrywa wcześniejszy wpis.

Edytować : Termin przedłużony z powodu braku wpisów. TBA.

Wyborcy są zachęcani do oceniania w oparciu przede wszystkim o to, jak dobrze wyglądają obrazy wyjściowe, a po drugie o prostotę / elegancję algorytmu.

dan04
źródło
Czy SCALE_FACTORzawsze> 1?
kennytm
@kennytm: Tak. Zredagowałem, aby wyraźnie wymienić współczynniki skali.
dan04
Czy możemy założyć, że na obrazie jest tylko jedna linia tekstu?
GiantTree
@GiantTree: Tak. Jeśli chcesz, możesz obsługiwać tekst wieloliniowy, ale nie jest to wymagane.
dan04

Odpowiedzi:

4

Ruby, z RMagick

Algorytm jest bardzo prosty - znajdź wzory pikseli, które wyglądają tak:

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

i dodaj trójkąty, aby wyglądały tak:

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

Kod:

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

Wyjścia (kliknij dowolne, aby wyświetlić obraz samodzielnie):

1,333

1,333

1.5

1.5

2)

2)

3)

3)

4

4

Klamka
źródło