An Efficient Anti-Aliasing Technique [Paper Implementation]
Xiaolin Wu's 1991 Algorithm for Drawing Anti-Aliased Lines
LeetArxiv is Leetcode for implementing Arxiv and other research papers. This paper features in our Computer Graphics series.
1.0 Introduction
As mentioned in (Wu, 1991)1 aliasing is the staircasing effect observed along the bounds of digitized curves and boundaries. Anti-aliasing is a term used to describe techniques for removing the staircasing effect.
Before 1991, Bresenham’s line drawing algorithm (Bresenham, 1965)2 was the preferred method for drawing line-primitives due to its speed. However, the algorithm lacked anti-aliasing and integer-handling capabilities.
Xiaolin Wu published his line-drawing algorithm to ensure fast anti-aliasing. The algorithm implements a two-point anti-aliasing scheme to model the physical image of the curve.

2.0 Anti-aliased Line-Drawing Techniques
This section motivates Wu’s line drawing algorithm and provides a C code implementation.
2.1 Motivation for Wu’s Line Drawing Algorithm
As mentioned by Wu, two approaches to anti-aliasing were well established before his paper:
Higher resolution rasterization - a computationally expensive approach to deal with information loss during line rendering.
High-frequency filtering - another computationally expensive approach that introduced fuzzy edges along the line’s bounds.
Wu’s algorithm proposed a radically different approach: a two-point anti-aliasing scheme built by comparing the intensities of the pixel and the intensity of the curve.
As mentioned by Wu, greyscale pixel intensities should be inversely proportional to the distance between the pixel and the curves.
Wu’s line drawing algorithm demonstrates these advantages:
Smooth line generation that requires half as much integer arithmetic as Bresenham’s algorithm.
Easy to implement on hardware designed for graphics.
Equation 10 calculates the pixel brightness. This is important to note.
2.2 Wu’s Line Drawing Algorithm
This section implements Wu’s line drawing algorithm in C. First, we ensure our environment is working then we proceed to Wu’s line-drawing technique.
2.2.1 Coding Environment Setup
Our starter code is provided below. We use these libraries and data structures:
STB_image_write : a C library that convert pixels to an observable image.
Download the header file from the link and place it in your directory.
RGBA: a 32-bit integer that store 256-bit colours in R,G,B,A format.
Point: a struct to hold integer coordinates.
The code below draws a white background to a png file. Here’s the gist to our starter code.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
//Run : clear && gcc main.c -lm -o m.o && ./m.o
typedef struct point_struct Point;
typedef uint32_t RGBA;
struct point_struct
{
int x;
int y;
};
int main()
{
char *outputFile = "output.png";
int width = 512;
int height = 512;
int channels = 4;
//Create image with white background
uint8_t *pixels = malloc(width * height * channels);
for(int i = 0; i < width * height * channels; i += channels)
{
pixels[i + 0] = 255; // R
pixels[i + 1] = 255; // G
pixels[i + 2] = 255; // B
if(channels == 4)
{
pixels[i + 3] = 255; // A
}
}
//Write image to file
stbi_write_png(outputFile, width, height, channels, pixels, width * channels);
}
If your code is running then you should observe the plain white image below:
2.2.2 Alpha Blending Support
We implement this simple algorithm to enable alpha blending while drawing:
uint8_t BlendChannel(uint8_t background, uint8_t foreground, uint8_t alpha)
{
return (uint8_t)((fg * alpha + bg * (255 - alpha)) / 255);
}
2.2.3 Coding Wu’s Line Algorithm
Wu’s algorithm can be broken into two functions:
PlotPixel
: a function that calculates a pixel’s alpha-blended value given expected brightness (Equation 10 in Section 2.1 of this article)void PlotPixel(int width, int height, int channels, uint8_t *pixels, int x, int y, float brightness, RGBA color) { uint8_t r = (color >> 24) & 0xFF; uint8_t g = (color >> 16) & 0xFF; uint8_t b = (color >> 8) & 0xFF; uint8_t a = color & 0xFF; if(x < 0 || x >= width || y < 0 || y >= height){/*Do nothing if point is outside image bounds*/} else { //Find pixel's index in our pixel array int pixelIndex = (y * width + x) * channels; //Find alpha uint8_t effectiveAlpha = (uint8_t)(a * brightness); if(channels >= 3) { if(channels == 4 && effectiveAlpha < 255) { pixels[pixelIndex + 0] = BlendChannel(pixels[pixelIndex + 0], r, effectiveAlpha); pixels[pixelIndex + 1] = BlendChannel(pixels[pixelIndex + 1], g, effectiveAlpha); pixels[pixelIndex + 2] = BlendChannel(pixels[pixelIndex + 2], b, effectiveAlpha); if(channels == 4) { pixels[pixelIndex + 3] = 255; // Keep full opacity for blended pixels } } else { pixels[pixelIndex + 0] = r; pixels[pixelIndex + 1] = g; pixels[pixelIndex + 2] = b; if(channels == 4) { pixels[pixelIndex + 3] = a; } } } else if(channels == 1) { //Calculate the gray level of the pixel uint8_t gray = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); pixels[pixelIndex] = BlendChannel(pixels[pixelIndex], gray, effectiveAlpha); } } }
DrawLine
: a function that calculates the values passed toPlotPixel
.This function calculates values for two endpoints then uses a for loop to find values in-between the points.
We update y values in the variable
intery
using a gradient.void DrawLine(int width, int height, int channels, uint8_t *pixels, Point start, Point end, RGBA color) { //Extract colors and point coordinates uint8_t r = (color >> 24) & 0xFF; uint8_t g = (color >> 16) & 0xFF; uint8_t b = (color >> 8) & 0xFF; uint8_t a = color & 0xFF; int x0 = start.x, y0 = start.y; int x1 = end.x, y1 = end.y; //Find the greater difference int steep = abs(y1 - y0) > abs(x1 - x0); //Swap x and y if y has a greater difference than x if(steep){int tmp = x0; x0 = y0; y0 = tmp;tmp = x1; x1 = y1; y1 = tmp;} //Set the smaller x value to x0 if(x0 > x1){int tmp = x0; x0 = x1; x1 = tmp;tmp = y0; y0 = y1; y1 = tmp;} //Find new differences float dx = x1 - x0; float dy = y1 - y0; float gradient = (dx == 0) ? 1.0f : dy / dx; // First endpoint float xend = round(x0); float yend = y0 + gradient * (xend - x0); float xgap = 1.0f - ((x0 + 0.5f) - floor(x0 + 0.5f)); int xpxl1 = xend; int ypxl1 = floor(yend); if(steep) { PlotPixel(width, height, channels, pixels, ypxl1, xpxl1, 1.0f - (yend - floor(yend)) * xgap, color); PlotPixel(width, height, channels, pixels, ypxl1 + 1, xpxl1, (yend - floor(yend)) * xgap, color); } else { PlotPixel(width, height, channels, pixels, xpxl1, ypxl1, 1.0f - (yend - floor(yend)) * xgap, color); PlotPixel(width, height, channels, pixels, xpxl1, ypxl1 + 1, (yend - floor(yend)) * xgap, color); } float intery = yend + gradient; //Second endpoint xend = round(x1); yend = y1 + gradient * (xend - x1); xgap = (x1 + 0.5f) - floor(x1 + 0.5f); int xpxl2 = xend; int ypxl2 = floor(yend); if(steep) { PlotPixel(width, height, channels, pixels, ypxl2, xpxl2, 1.0f - (yend - floor(yend)) * xgap, color); PlotPixel(width, height, channels, pixels, ypxl2 + 1, xpxl2, (yend - floor(yend)) * xgap, color); } else { PlotPixel(width, height, channels, pixels, xpxl2, ypxl2, 1.0f - (yend - floor(yend)) * xgap, color); PlotPixel(width, height, channels, pixels, xpxl2, ypxl2 + 1, (yend - floor(yend)) * xgap, color); } //Move between endpoints for(int x = xpxl1 + 1; x < xpxl2; x++) { if(steep) { PlotPixel(width, height, channels, pixels, floor(intery), x, 1.0f - (intery - floor(intery)), color); PlotPixel(width, height, channels, pixels, floor(intery) + 1, x, intery - floor(intery), color); } else { PlotPixel(width, height, channels, pixels, x, floor(intery), 1.0f - (intery - floor(intery)), color); PlotPixel(width, height, channels, pixels, x, floor(intery) + 1, intery - floor(intery), color); } intery += gradient; } }
We run our code using this points:
Point start = {100, 100};
Point end = {400, 400};
DrawLine(width, height, channels, pixels, start, end, 0xFF0000FF); // Red line
start = (Point){50, 450};
end = (Point){450, 50};
DrawLine(width, height, channels, pixels, start, end, 0x00FF00FF); // Green line
This is the link to the current version of our code.
Running the code yields:
3.0 Anti-aliased Circle Drawing Techniques
The main idea is to calculate a function D, that represents a decrementing brightness. We call this variable coverage
in our implementation.
void DrawCircleOutline(int width, int height, int channels, uint8_t *pixels, Point center, int radius, RGBA color)
{
int cx = center.x;
int cy = center.y;
float outer = radius + 0.5f;
float inner = radius - 0.5f;
const int samples = 4; // 4x4 super-sampling
// Extract color components
uint8_t r = (color >> 24) & 0xFF;
uint8_t g = (color >> 16) & 0xFF;
uint8_t b = (color >> 8) & 0xFF;
uint8_t a = color & 0xFF;
// Calculate bounding box
int minX = cx - radius - 1;
int maxX = cx + radius + 1;
int minY = cy - radius - 1;
int maxY = cy + radius + 1;
// Clip to image bounds
minX = (minX < 0) ? 0 : minX;
maxX = (maxX >= width) ? width - 1 : maxX;
minY = (minY < 0) ? 0 : minY;
maxY = (maxY >= height) ? height - 1 : maxY;
for(int y = minY; y <= maxY; y++)
{
for(int x = minX; x <= maxX; x++)
{
float coverage = 0.0f;
float sampleStep = 1.0f / samples;
//Super-sampling loop
for(float sy = -0.5f + sampleStep/2; sy < 0.5f; sy += sampleStep)
{
for(float sx = -0.5f + sampleStep/2; sx < 0.5f; sx += sampleStep)
{
float dx = (x - cx) + sx;
float dy = (y - cy) + sy;
float distance = sqrtf(dx*dx + dy*dy);
// Only draw pixels between inner and outer radius
if(distance >= inner && distance <= outer)
{
coverage += 1.0f - fabsf(distance - radius);
}
}
}
coverage /= (samples * samples);
if (coverage > 0.0f)
{
PlotPixel(width, height, channels, pixels, x, y, coverage, color);
}
}
}
}
We draw using this function call:
Point circleCenter = {256, 256};
DrawCircleOutline(width, height, channels, pixels, circleCenter, 150, 0x0000FFFF); // Blue circle
to get this output
That concludes the paper. Full code can be found here.
Citations
Wu, Xiaolin (1991). "An efficient antialiasing technique". ACM SIGGRAPH Computer Graphics. 25 (4): 143–152. doi:10.1145/127719.122734. ISBN 0-89791-436-8.
Bresenham, J. E. (1965). "Algorithm for computer control of a digital plotter" (PDF). IBM Systems Journal. 4 (1): 25–30. doi:10.1147/sj.41.0025.