Open In App

Anti-aliased Line | Xiaolin Wu’s algorithm

Last Updated : 21 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Anti-Aliased Line Drawing

Below is the image showing line drawn with Bresenham’s line algorithm (left) and Xiaolin Wu’s line algorithm (right) which smooths the line. Which one looks better to you ?

AALine

Anti Aliasing concept

Suppose we want to draw a line from point(1 , 1) to point(8 , 4) with rectangular edges. The ideal line would be the one shown in figure A . Since I want to display it on screen I cannot use that. Line needs to go through a process called

Rasterization

which would determine color of individual pixels. Several algorithms can be used like

Bresenham’s Line Algorithm

,

Digital Differential Analyzer

, Xiaolin Wu’s line algorithm , Gupta-Sproull algorithm . Later two perform anti-aliasing or line smoothing. The result produced by first two algorithm is show in figure B.

solidfillfirstrast

There are few problems in Line( figure B ). 1. Pixel (4,2) has less coverage than Pixel (3,2), yet they’re both drawn fully black. 2. Pixel (2,2) has almost as much coverage as (4,2), and yet it’s drawn fully white. To overcome these drawbacks and produce a much smoother looking line we use Xiaolin Wu’s line algorithm

Xiaolin Wu’s line algorithm

Consider the figure shown below which is drawn using Bresenham’s Line Generation Algorithm . Take a segment and its initial coordinate x. By the X in the loop is added 1 towards the end of the segment. At each step, the error is calculated – the distance between the actual y-coordinate at that location and the nearest grid cell. If the error does not exceed half the height of the cell, it is filled. That’s the whole algorithm.

original-a04cf694-556504fba7413

We will modify this algorithm so that it can produce an anti-aliased line . Xiaolin Wu’s line algorithm is characterized by the fact that at each step of the calculation is carried out for the two closest to the line of pixels, and they are colored with different intensity, depending on the distance. Current intersection middle pixel intensity gives 100% if the pixel is within 0.9 pixel, the intensity will be 10%. In other words, one hundred percent of the intensity is divided between the pixels which limit vector line on both sides.

original-ccf57794-556504fc0787f

In the picture the red and green color shows the distance to the two adjacent pixels. To calculate the error, you can use floating point and take the error value of the fractional part.

NOTE:

The following implementation uses

SDL

library to draw pixels on screen . If you are on debian system like ubuntu just run following command to install SDL library.

sudo apt-get install libsdl2-dev

To build use

gcc filename.c -lSDL2

Note:

If the projection of the segment on the x-axis is less than the projection on the y-axis, or the beginning and end of the segment are swapped, then the algorithm will not work. To avoid this, you need to check the direction of the vector and its slope, and then swap the coordinates of the line , ultimately to reduce everything to some one or at least two cases. Following algorithm assumes that only integer co-ordinates will be given as inputs since pixel value cannot be floating point.

CPP
#include <SDL2/SDL.h>

// SDL stuff
SDL_Window* pWindow = 0;
SDL_Renderer* pRenderer = 0;

// swaps two numbers
void swap(int* a , int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// returns absolute value of number
float absolute(float x)
{
    if (x < 0) return -x;
    else return x;
}

// returns integer part of a floating point number
int iPartOfNumber(float x)
{
    return (int)x;
}

// rounds off a number
int roundNumber(float x)
{
    return iPartOfNumber(x + 0.5);
}

// returns fractional part of a number
float fPartOfNumber(float x)
{
    if (x > 0) return x - iPartOfNumber(x);
    else return x - (iPartOfNumber(x)+1);
}

// returns 1 - fractional part of number
float rfPartOfNumber(float x)
{
    return 1 - fPartOfNumber(x);
}

// draws a pixel on screen of given brightness
// 0 <= brightness <= 1. We can use your own library
// to draw on screen
void drawPixel(int x, int y, float brightness)
{
    int c = 255 * brightness;
    SDL_SetRenderDrawColor(pRenderer, c, c, c, 255);
    SDL_RenderDrawPoint(pRenderer, x, y);
}

void drawAALine(int x0, int y0, int x1, int y1)
{
    int steep = absolute(y1 - y0) > absolute(x1 - x0);

    // swap the co-ordinates if slope > 1 or we
    // draw backwards
    if (steep)
    {
        swap(&x0, &y0);
        swap(&x1, &y1);
    }
    if (x0 > x1)
    {
        swap(&x0, &x1);
        swap(&y0, &y1);
    }

    // compute the slope
    float dx = x1 - x0;
    float dy = y1 - y0;
    float gradient = dy / dx;
    if (dx == 0.0)
        gradient = 1;

    int xpxl1 = x0;
    int xpxl2 = x1;
    float intersectY = y0;

    // main loop
    if (steep)
    {
        int x;
        for (x = xpxl1; x <= xpxl2; x++)
        {
            // pixel coverage is determined by fractional
            // part of y co-ordinate
            drawPixel(iPartOfNumber(intersectY), x,
                rfPartOfNumber(intersectY));
            drawPixel(iPartOfNumber(intersectY) - 1, x,
                fPartOfNumber(intersectY));
            intersectY += gradient;
        }
    }
    else
    {
        int x;
        for (x = xpxl1; x <= xpxl2; x++)
        {
            // pixel coverage is determined by fractional
            // part of y co-ordinate
            drawPixel(x, iPartOfNumber(intersectY),
                rfPartOfNumber(intersectY));
            drawPixel(x, iPartOfNumber(intersectY) - 1,
                fPartOfNumber(intersectY));
            intersectY += gradient;
        }
    }

}

// Driver code
int main(int argc, char* args[])
{

    SDL_Event event;

    // initialize SDL
    if (SDL_Init(SDL_INIT_EVERYTHING) >= 0)
    {
        // if succeeded create our window
        pWindow = SDL_CreateWindow("Anti-Aliased Line ",
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            640, 480,
            SDL_WINDOW_SHOWN);

        // if the window creation succeeded create our renderer
        if (pWindow != 0)
            pRenderer = SDL_CreateRenderer(pWindow, -1, 0);
    }
    else
        return 1; // sdl could not initialize

    while (1)
    {
        if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
            break;

        // Sets background color to white
        SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, 255);
        SDL_RenderClear(pRenderer);

        // draws a black AALine
        drawAALine(80, 200, 550, 150);

        // show the window
        SDL_RenderPresent(pRenderer);
    }

    // clean up SDL
    SDL_Quit();
    return 0;
}
Java
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.ImageIcon;

// Class to represent a pixel
class Pixel {
    int x, y;
    float brightness;

    public Pixel(int x, int y, float brightness) {
        this.x = x;
        this.y = y;
        this.brightness = brightness;
    }
}

public class Main extends JFrame {

    private BufferedImage canvas;

    // Constructor to set up the canvas and the JFrame
    public Main() {
        canvas = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
        setContentPane(new JLabel(new ImageIcon(canvas)));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(640, 480);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    // Method to draw a pixel on the canvas with a given brightness
    // 0 <= brightness <= 1
    public void drawPixel(int x, int y, float brightness) {
        int c = (int)(255 * brightness);
        int color = new Color(c, c, c).getRGB();
        canvas.setRGB(x, y, color);
    }

    // Method to swap two integers
    public void swap(Pixel p1, Pixel p2) {
        Pixel temp = new Pixel(p1.x, p1.y, p1.brightness);
        p1.x = p2.x;
        p1.y = p2.y;
        p1.brightness = p2.brightness;
        p2.x = temp.x;
        p2.y = temp.y;
        p2.brightness = temp.brightness;
    }

    // Method to get the absolute value of a number
    public float absolute(float x) {
        if (x < 0) return -x;
        else return x;
    }

    // Method to get the integer part of a floating point number
    public int iPartOfNumber(float x) {
        return (int)x;
    }

    // Method to round off a number
    public int roundNumber(float x) {
        return iPartOfNumber(x + 0.5f);
    }

    // Method to get the fractional part of a number
    public float fPartOfNumber(float x) {
        if (x > 0) return x - iPartOfNumber(x);
        else return x - (iPartOfNumber(x)+1);
    }

    // Method to get 1 - fractional part of number
    public float rfPartOfNumber(float x) {
        return 1 - fPartOfNumber(x);
    }

    // Method to draw an anti-aliased line
    public void drawAALine(int x0, int y0, int x1, int y1) {
        boolean steep = absolute(y1 - y0) > absolute(x1 - x0);

        Pixel p1 = new Pixel(x0, y0, 1);
        Pixel p2 = new Pixel(x1, y1, 1);

        // Swap the coordinates if slope > 1 or we draw backwards
        if (steep) {
            swap(p1, p2);
        }
        if (p1.x > p2.x) {
            swap(p1, p2);
        }

        // Compute the slope
        float dx = p2.x - p1.x;
        float dy = p2.y - p1.y;
        float gradient = dy / dx;
        if (dx == 0.0)
            gradient = 1;

        float intersectY = p1.y;

        // Main loop
        if (steep) {
            for (int x = p1.x; x <= p2.x; x++) {
                // Pixel coverage is determined by fractional part of y coordinate
                drawPixel(iPartOfNumber(intersectY), x, rfPartOfNumber(intersectY));
                drawPixel(iPartOfNumber(intersectY) - 1, x, fPartOfNumber(intersectY));
                intersectY += gradient;
            }
        } else {
            for (int x = p1.x; x <= p2.x; x++) {
                // Pixel coverage is determined by fractional part of y coordinate
                drawPixel(x, iPartOfNumber(intersectY), rfPartOfNumber(intersectY));
                drawPixel(x, iPartOfNumber(intersectY) - 1, fPartOfNumber(intersectY));
                intersectY += gradient;
            }
        }
    }

    // Driver code
    public static void main(String[] args) {
        Main frame = new Main();
        frame.drawAALine(80, 200, 550, 150);
    }
}
Python3
# Importing required modules
import pygame
import sys

# Function to swap two numbers
def swap(a, b):
    return b, a

# Function to return absolute value of number
def absolute(x):
    return abs(x)

# Function to return integer part of a floating point number
def iPartOfNumber(x):
    return int(x)

# Function to round off a number
def roundNumber(x):
    return round(x)

# Function to return fractional part of a number
def fPartOfNumber(x):
    return x - iPartOfNumber(x)

# Function to return 1 - fractional part of number
def rfPartOfNumber(x):
    return 1 - fPartOfNumber(x)

# Function to draw a pixel on screen of given brightness
def drawPixel(screen, x, y, brightness):
    c = int(255 * brightness)
    pygame.draw.line(screen, (c, c, c), (x, y), (x+1, y))

# Function to draw an anti-aliased line
def drawAALine(screen, x0, y0, x1, y1):
    steep = absolute(y1 - y0) > absolute(x1 - x0)

    if steep:
        x0, y0 = swap(x0, y0)
        x1, y1 = swap(x1, y1)

    if x0 > x1:
        x0, x1 = swap(x0, x1)
        y0, y1 = swap(y0, y1)

    dx = x1 - x0
    dy = y1 - y0
    gradient = dy / dx if dx != 0 else 1

    xpxl1 = x0
    xpxl2 = x1
    intersectY = y0

    if steep:
        for x in range(xpxl1, xpxl2 + 1):
            drawPixel(screen, iPartOfNumber(intersectY), x, rfPartOfNumber(intersectY))
            drawPixel(screen, iPartOfNumber(intersectY) - 1, x, fPartOfNumber(intersectY))
            intersectY += gradient
    else:
        for x in range(xpxl1, xpxl2 + 1):
            drawPixel(screen, x, iPartOfNumber(intersectY), rfPartOfNumber(intersectY))
            drawPixel(screen, x, iPartOfNumber(intersectY) - 1, fPartOfNumber(intersectY))
            intersectY += gradient

# Main function
def main():
    pygame.init()

    # Creating a window
    screen = pygame.display.set_mode((640, 480))

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        # Setting background color to white
        screen.fill((255, 255, 255))

        # Drawing a black anti-aliased line
        drawAALine(screen, 80, 200, 550, 150)

        # Updating the window
        pygame.display.flip()

if __name__ == "__main__":
    main()

Output:

outputAALine


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads