November 2, 2014

Pixelating Freestyle lines

Filed under: Blender, Freestyle — blenderyard @ 3:41 PM

Thin, pixelated lines without anti-aliasing are one of line styles that Blender artists may want to achieve using Freestyle time to time.  This line style is useful for preparing low resolution game assets and imitating old-fashion computer line drawing, just to name a few examples.  Such visual results can be obtained by disabling anti-aliasing and set line width to 1.  However, the resulting quality of pixelated line art may not be satisfactory due to possible thickening of lines here and there depending on input scenes.  Setting line width to a value less than 1 could remove undesirable thick portions of lines, but also may cause broken line in other parts because of sub-pixel line thickness.  Unlike traditional line drawing in old computers, Freestyle renders line art using full-featured 3D rendering engines (namely the Blender Internal and Cycles) that are not specifically designed for producing retro style aliased lines.

An easy way to address this limitation is just to implement a desirable aliased line drawing function within the framework of Freestyle line rendering.  A couple of proof-of-concept renders based on this approach are shown below:


The left and center panels of the image above show color and line components, while the right panel shows the composite result.  The image has been scaled 400% so as to show the pixelated lines clearly.


This one shows raw render results without scaling.

Now the fun part: these renders were generated using a custom style module written in Python.  In the style module, a traditional aliased line drawing function based on Bresenham’s algorithm has been used to create a set of pixels that comprise pixelated lines.  The computed X and Y coordinates of the pixels are then passed to an external program (the convert command of the ImageMagick graphics package) to rasterize the lines.  This last step can be done in different ways using other 2D graphics libraries (e.g., Cairo), and the use of the convert command is just for testing the idea.  The code is presented below (tested with Blender 2.72b).  This style module can be used in the Python Scripting mode of Freestyle in Blender.  If you want to try it yourself, then you need to modify the full path to ImageMagick’s convert command hard-coded in the program.


from freestyle.types import *
from freestyle.chainingiterators import *
from freestyle.predicates import *
from freestyle.shaders import *
from freestyle.utils import *
import bpy
import os

# full path to ImageMagick's "convert" command
CONVERT_COMMAND = r'C:\home\kajiyama\bin\convert.exe'

alpha = 0 # set to 1 for preview render
upred = QuantitativeInvisibilityUP1D(0)
Operators.bidirectional_chain(ChainSilhouetteIterator(), NotUP1D(upred))
shaders_list = [
    ConstantColorShader(0, 0, 0, alpha),
Operators.create(TrueUP1D(), shaders_list)

# Based on the implementation of Bresenham's line drawing algorithm
# by Steve Cunningham and Tim Worsham, October 1988, CSU Stanislaus
def line(x1, y1, x2, y2):
    pixels = []
    bx = x1
    by = y1
    dx = x2 - x1
    dy = y2 - y1
    if dy == 0: # horizontal line
        pixels.append((bx, by))
        xsign = -1 if dx < 0 else 1
        while bx != x2:
            bx += xsign
            pixels.append((bx, by))
    elif dx == 0: # vertical line
        pixels.append((bx, by))
        ysign = -1 if dy < 0 else 1
        while by != y2:
            by += ysign
            pixels.append((bx, by))
    else: # Bresenham's line drawing algorithm
        pixels.append((bx, by))
        xsign = -1 if dx < 0 else 1
        ysign = -1 if dy < 0 else 1
        dx = abs(dx)
        dy = abs(dy)
        if dx < dy: # the line is more vertical than horizontal
            p = 2 * dx - dy
            const1 = 2 * dx
            const2 = 2 * (dx - dy)
            while by != y2:
                by += ysign
                if p < 0:
                    p += const1
                    p += const2
                    bx += xsign
                pixels.append((bx, by))
        else: # the line is more horizontal than vertical
            p = 2 * dy - dx
            const1 = 2 * dy
            const2 = 2 * (dy - dx)
            while bx != x2:
                bx += xsign
                if p < 0:
                    p += const1
                    p += const2
                    by += ysign
                pixels.append((bx, by))
    return pixels

scene = getCurrentScene()
w = scene.render.resolution_x * scene.render.resolution_percentage / 100
h = scene.render.resolution_y * scene.render.resolution_percentage / 100

# write pixels as a set of drawing instructions in the Magick Vector Graphics (MVG) format
pixels = []
nstrokes = Operators.get_strokes_size()
for i in range(nstrokes):
    stroke = Operators.get_stroke_from_index(i)
    points = [(int(svert.point.x), (h-1)-int(svert.point.y)) for svert in stroke]
    prev = points.pop(0)
    for curr in points:
        pixels.extend(line(prev[0], prev[1], curr[0], curr[1]))
        prev = curr
mvg_filename = bpy.path.abspath(scene.render.filepath + "%04d_line.mvg" % scene.frame_current)
with open(mvg_filename, 'wt') as mvg:
    for x, y in pixels:
        mvg.write("point %d,%d\n" % (x, y))
print('Wrote', mvg_filename)

# render the pixels using the ImageMagick convert command
png_filename = bpy.path.abspath(scene.render.filepath + "%04d_line.png" % scene.frame_current)
cmd = '%s -size %dx%d -background none "%s" "%s"' % (CONVERT_COMMAND, w, h, mvg_filename,png_filename)
print('Wrote', png_filename)

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

Blog at

%d bloggers like this: