Sub Pixel Rendering
written by Eclipzer
Copyright (c) Quinton Roberts 2006.
What's A Sub Pixel?
A sub-pixel is a pixel that can be defined using decimal coordinates and rendered accordingly. The
implications of this are visually quite dramatic. With the ability to be rendered at decimal intervals,
sub-pixels can be positioned exactly where you want them instead of at a pixel approximation. This
rendering improves the visual quality of images.
 |
sine wave sampled at 0.5 pixel increments |
left: pixel rendering |
right: sub-pixel rendering |
This wave is generated by sampling a sine wave at 0.5 pixel increments. The left side of the wave is
rendered using pixels, where as the right side uses sub-pixels. The sub-pixels are able to more
closely approximate the curve, producing a smoother looking image, because they take into account the
fractional portion of the samples. Pixels on the other hand, force the samples to whole number values,
creating a loss of information (the fractional portion of the sample). This loss of information is
visually evident in the wave on the left, where "jaggies" are consistently displayed throughout the
length of the wave.
Losing Information
Let's use an example sample function to demonstrate, numerically, this loss of information.
ysample = 50 sin(x°) 'sample function
x = 95.5°
ysample = 50 sin(95.5°) = 49.7698099184 'sampled value (high precision)
ysub-pixel = 49.76981
ytruncate = 49
yround = 50
Psub-pixel(95.5, 49.76981)
Ptruncate (95, 49)
Pround (100, 50)
|
Since physical pixels only exist as integer values, our x,y coordinates must be integer values. This is
normally achieved through truncation or rounding. In either case we end up losing information (0.5 from
our x-value and 0.76981 from our y-value). With sub-pixels, we keep the fractional information
intact and use it to construct a more accurate image.
Conceptual Rendering Method
To render a sub-pixel we actually 'map' it to four physical pixels. By determining how much area the
sub-pixel covers in each of these pixels, we can calculate how intense the color of the sub-pixel is for
each pixel.
 |
 |
 |
sub-pixel displayed on a pixel grid |
sub-pixel "mapped" to four physical pixels |
pixel representation of a sub-pixel |
Pixel Color Equation
Using the above method, we can deduce that the equation used to determine the color of each pixel of a
sub-pixel is:
Color = Area x Intensity
or
Colorpixel = Areasub-pixel x Colorsub-pixel
|
Let's explore why this equation makes sense. If the area is one then the sub-pixel lies fully within this
pixel and the pixel color would be the same as that of the sub-pixel.
Cpixel = Asub-pixel x Csub-pixel = 1 x Csub-pixel = Csub-pixel
Cpixel = Csub-pixel
|
If the area is zero then the sub-pixel lies completely outside this pixel the pixel color would be zero.
Cpixel = Asub-pixel x Csub-pixel = 0 x Csub-pixel = 0
Cpixel = 0
|
Computing Pixel Areas
We tend to think of pixels as points, which is why we describe them using a single coordinate. With
sub-pixels we see that our pixel is better thought of as a square possessing dimension (length, height
and area). For this reason, we need to create more points from our original data. In essence, we must
interpret our original data in terms of four coordinates describing a sub-pixel square instead of a
single coordinate describing a pixel point.
 |
sub-pixel corner coordinates |
Sub-Pixel Coordinate Definitions:
x1 'left edge (original x-coordinate)
y1 'top edge (original y-coordinate)
x2 = x1 + 1 'right edge
y2 = y1 + 1 'bottom edge
Sub-Pixel Corner Coordinates:
p1(x1, y1) 'top-left
p2(x2, y1) 'top-right
p3(x1, y2) 'bottom-left
p4(x2, y2) 'bottom-right
|
We must also consider the lines that divide the sub-pixel into the four areas we need to calculate.
 |
sub-pixel divided by lines x=x0 and y=y0 |
Sub-Pixel Division Lines:
x0 = truncate(x2) 'remove precision of x-coordinate (decimal portion)
y0 = truncate(y2) 'remove precision of y-coordinate (decimal portion)
|
With our sub-pixel definitions now in place, we can begin to calculate areas used to determine our four
pixel colors.
 |
sub-pixel divided into four seperate areas |
Area = Length x Height = (x2 - x1)(y2 - y1) 'general area equation
A1 = (x0-x1)(y0-y1) A2 = (x2-x0)(y0-y1)
A3 = (x0-x1)(y2-y0) A4 = (x2-x0)(y2-y0)
Define Common Quantities:
ax1 = x0-x1 ax2 = x2-x0
ay1 = y0-y1 ay2 = y2-y0
Simplify By Substitution:
A1 = (ax1)(ay1) A2 = (ax2)(ay1)
A3 = (ax1)(ay2) A4 = (ax2)(ay2)
|
Computing Pixel Color From Area
Once we've calculated our areas we can use them to determine our four pixel colors.
Because the color on a computer screen is quantified using three different color components (RGB) we must
first break our color into these components and then apply our pixel color equation to each one. These new
components are then used to construct each of our four pixel colors.
Compute Pixel (Pn) RGB Components (CPn = An x Csub-pixel) where n=1,2,3,4:
CPn = (RPn,GPn,BPn) 'pixel (Pn) color composed of RGB values
RPn = An x Rsub-pixel
GPn = An x Gsub-pixel
BPn = An x Bsub-pixel
|
What About The Background Color?
The last thing we need to consider is background color. Currently, if our sub-pixel lies completely
outside one of our four pixels, then that pixel's color is zero. This normally indicates the color black.
But what if our background isn't black, or our sub-pixel is simply being rendered over another image
altogether? To handle this situation, we just need to blend our background color with our sub-pixel color.
We do this with the standard alpha blending equation, where our calculated area acts as our alpha value.
Color = Alpha x (Color1 - Color2) + Color2
or
Coloralpha = Alpha x (Colorsub-pixel - Colorbackground) + Colorbackground
RPn = An x (Rsub-pixel - Rbackground) + Rbackground
GPn = An x (Gsub-pixel - Gbackground) + Gbackground
BPn = An x (Bsub-pixel - Bbackground) + Bbackground
|
 |
sub-pixel rendered over green background |
Putting It All Together
Psuedo-Code (Note: the "!" symbol refers to a single-precision variable):
Draw_SubPixel (x!,y!,c)
' Define sub-pixel attributes
x = truncate(x!): x0 = x+1: x1! = x!: x2! = x1!+1
y = truncate(y!): y0 = y+1: y1! = y!: y2! = y1!+1
' Determine area lengths
ax1! = x0 - x1!: ax2! = x2! - x0
ay1! = y0 - y1!: ay2! = y2! - y0
' Calculate areas
a1! = ax1!*ay1!: a2! = ax2!*ay1!
a3! = ax1!*ay2!: a4! = ax2!*ay2!
' Determine 4 background colors
bkg1 = pixel_color(x , y)
bkg2 = pixel_color(x+1, y)
bkg3 = pixel_color(x , y+1)
bkg4 = pixel_color(x+1, y+1)
' Determine RBG components of background colors
r1 = r_component(bkg1): r2 = r_component(bkg2): r3 = r_component(bkg3): r4 = r_component(bkg4)
g1 = g_component(bkg1): g2 = r_component(bkg2): g3 = r_component(bkg3): g4 = r_component(bkg4)
b1 = b_component(bkg1): b2 = r_component(bkg2): b3 = r_component(bkg3): b4 = r_component(bkg4)
' Determine RGB components of sub-pixel color
r0 = r_component(c)
g0 = g_component(c)
b0 = b_component(c)
' Calculate RGB components of sub-pixel's 4 pixels
c1 = color(a1!*(r0 - r1) + r1, a1!*(g0 - g1) + g1, a1!*(b0 - b1) + b1)
c2 = color(a2!*(r0 - r2) + r2, a2!*(g0 - g2) + g2, a2!*(b0 - b2) + b2)
c3 = color(a3!*(r0 - r3) + r3, a3!*(g0 - g3) + g3, a3!*(b0 - b3) + b3)
c4 = color(a4!*(r0 - r4) + r4, a4!*(g0 - g4) + g4, a4!*(b0 - b4) + b4)
' Draw 4 pixels
draw_pixel x , y , c1
draw_pixel x+1, y , c2
draw_pixel x , y+1, c3
draw_pixel x+1, y+1, c4
|
--Eclipzer