algorithmgeometrybresenhamline-drawing

Is there an error in the Bresenham's algorithm from Wikipedia?


Bresenham's algorithm is used to draw a line on a square grid as are pixels for example.

The algorithm is partly based on the subdivision of the plane into 8 parts called octants.

Octants

The trick is to use symmetries to generalize the algorithm regardless of where the second point is located: firstly we "move" it to the first octant, then the calculations are made, and finally the generated points are converted back to their original octant.

Wikipedia provides a basic function to perform the trick.

 function switchToOctantZeroFrom(octant, x, y) 
   switch(octant)  
     case 1: return (x, y)
     case 2: return (y, x)
     case 3: return (y, -x)
     case 4: return (-x, y)
     case 5: return (-x, -y)
     case 6: return (-y, -x)
     case 7: return (-y, x)
     case 8: return (x, -y)

Moreover, it is written that we just have to:

flip the co-ordinate system on the input and output

This is based on the fact that these transpositions are in fact involutions: f(f(x)) = x

Without paying much attention to it, I first thought it would work.

But for cases 3 and 7, it does not work because it is not an involution.

For example:

Case 4: (-5, 1) => (5, 1) => (-5, 1) // Good
Case 3: (-1, 5) => (5, 1) => (1, -5) // Not good

We have to do the trick once again:

Case 3: (-1, 5) => (5, 1) => (1, -5) => (-5, -1) => (-1, 5) // Good

So, did I misunderstand something?

Or is it in fact a lack of precision in the drafting of the article on Wikipedia and should someone improve it?

Is there not a better way to make these transitions without that I need to use two functions switchToOctant_onInput and switchToOctant_onOutput (the obvious solution to this problem that I see now)?


Solution

  • Octants 2, 4, 6, 8 are mapped to octant 1 by reflections which are involutive (self-inverse). Octant 5 is mapped to octant 1 by a 180 degree rotation which is also involutive. However, octants 7 and 3 are mapped to octant 1 by +-90 degree rotations which are not involutive. The mappings simply aren't involutive so there's nothing you can do about it. If you want an inverse function you have to write it.

    The Wikipedia page is misleading because it says the function is a "flip" which suggests an involution.

    There are three approaches I can think of to address the issue: 1) create an inverse function which is very similar except the cases for 3 and 7 are swapped (don't rename the existing function); 2) add cases for negative octants which represent the inverse function so that the inverse of switchOctant(3,x,y) is switchOctant(-3,x,y) which is the same as switchOctant(7,x,y) (however you have to think carefully about octant 0 if you do this); or 3) reduce or eliminate the need for the geometric transformation function by enhancing your line drawing function. In particular, if you enhance the line drawing function to handle any line in the first quadrant (not just first octant!) you can use a geometric transformation mapping any quadrant to the first quadrant which is involutive.

    Update

    I just thought of one more "angle" on this question (so to speak): it is possible to map your 3rd octant to 1st octant by a reflection. A reflection by a line through the origin with inclination theta is given by

    x' = x * cos(2*theta) + y * sin(2*theta)
    y' = x * sin(2*theta) - y * cos(2*theta)
    

    The line of reflection between 1st and 3rd octants has inclination theta = 45 + 45/2.0 degrees, so 2*theta = 135 degrees and we have

    x' = -sqrt(2)/2 * x + sqrt(2)/2 * y
    y' =  sqrt(2)/2 * x + sqrt(2)/2 * y
    

    Similar formulas can be used to map the 7th octant to the 1st. So it is possible to find an involution which maps each octant to the first octant. However, there are two problems with this mapping: 1) it's not continuous whereas the mapping given in the Wikipedia article is continuous (meaning there are no sudden jumps in the image of (x,y) as the point moves around the plane); and 2) it's not clear how to use integer arithmetic to effect the mapping.

    Continuity is not just a theoretical issue. It becomes practical when you consider how you're going to map a point on the boundary between two octants. If you don't do that very carefully with a discontinuous map, you will definitely get incorrect results.

    So this idea is not good, but I just thought I'd mention it for the sake of completeness.