# Resize rasterized text and make it look non-pixellated

This is a screenshot of some text typed in a text editor:

This is the same text at a larger size.

Notice how visible the aliasing is on letters with prominent diagonal strokes like x and z. This issue is a major reason why raster fonts have lost popularity to “scalable” formats like TrueType.

But maybe this isn't an inherent problem with raster fonts, just with the way scaling of them is typically implemented. Here's an alternative rendering using simple bilinear interpolation combined with thresholding.

This is smoother, but not ideal. Diagonal strokes are still bumpy, and curved letters like c and o are still polygons. This is especially noticeable at large sizes.

So is there a better way?

Write a program that takes three command-line arguments.

resize INPUT_FILE OUTPUT_FILE SCALE_FACTOR


where

• INPUT_FILE is the name of the input file, which is assumed to be an image file containing black text on a white background. You may use any mainstream raster image format (PNG, BMP, etc.) that's convenient.
• OUTPUT_FILE is the name of the output file. It can be either a raster or vector image format. You may introduce color if you're doing some ClearType-like subpixel rendering.
• SCALE_FACTOR is a positive floating-point value that indicates how much the image may be resized. Given an x × y px input file and scaling factor s, the output will have a size of sx × sy px (rounded to integers).

You may use a third-pary open-source image processing library.

In addition to your code, include sample outputs of your program at scale factors of 1.333, 1.5, 2, 3, and 4 using my first image as input. You may also try it with other fonts, including proportionally-spaced ones.

# Scoring

This is a popularity contest. The entry with the greatest number of upvotes minus downvotes wins. In case of an exact tie, the earlier entry wins.

Edit: Deadline extended due to lack of entries. TBA.

Voters are encouraged to judge based primarily on how good the output images look, and secondarily on the simplicity/elegance of the algorithm.

• Is SCALE_FACTOR always > 1? Apr 30, 2015 at 19:17
• @kennytm: Yes. Have edited to explicitly list the scale factors. May 1, 2015 at 1:47
• Can we assume there is only one line of text in the image? May 1, 2015 at 20:40
• @GiantTree: Yes. You can support multi-line text if you want, but this isn't required. May 2, 2015 at 0:35

# Ruby, with RMagick

The algorithm is very simple—find patterns of pixels that look like this:

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


and add triangles to make them look like this:

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


Code:

#!/usr/bin/ruby

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

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


Outputs (click any to view image by itself):

1.333

1.5

2

3

4