OpenGL is great, when it comes to line drawing most people would draw it by:
    glVertex3f( x1,y1,0);
    glVertex3f( x2,y2,0);
It does give you a straight line, but a very ugly one. To improve, most people would enable gl line smoothing:

But this technique has a couple of drawbacks:
-hardware dependent. It does not necessarily look the same on different machines.
-average quality. It does not give perfect quality on most hardware.

This article focuses on 2D rendering in (sub) pixel accuracy. Make sure you view all images in its original size.


The technique introduced in this article gives you:

-premium quality anti-aliased lines
-smaller CPU overhead than any other CPU rasterizing algorithms
-line thickness control
-line color control
-alpha blend (can choose to use alpha blend or not)

Believe that it is rendered in OpenGL.

Using the code

void line(
    double x1, double y1, double x2, double y2, //coordinates of the line
    float w, //thickness of the line in pixel
    float Cr, float Cg, float Cb, //RGB color components
    float Br, float Bg, float Bb, //color of background, ignored if alphablend is true
    bool alphablend); //use alpha blend or not
void hair_line( double x1, double y1, double x2, double y2, bool alphablend=0);
The first function line() gives you all the functionality. You can choose not to use alpha blending by setting alphablend to false, in this case you will get color fading to the background. In no- alpha- blending mode you still get good result when the background is solid and lines are not dense. It is also useful when doing overdraw. The below image should tell you what does alphablend=false mean:

The second function hair_line() draws near-perfectly a black "hair line" of thickness 1px with no color or thickness control. You can optionally use alpha blend otherwise it assumes the background is white. I provide this in case you do not need all the functionalities.
You can just include the header and it should work. If you copy only part of the code, make sure you copy also the function
static inline double GET_ABS(double x) {return x>0?x:-x;}

Make sure you render 2D in this way:
    glOrtho( 0,context_width,context_height,0,0.0f,100.0f);
    line(10,11,100,280, 1.0, 1,0,0.5, 0,0,0, true);
    //draw a straight line from (10,11) to (100,280)
    //, with width 1.0px pink (1,0,0.5) in color and enable alpha blending
    //other 2D drawings,,,
glDisable(GL_BLEND); //and whatever to restore blending options

How does that work?


You just need to know a little bit OpenGL. Look at the hello world OpenGL program. It merely draws a triangle with different colors on each vertex. What do you observe?
//window size is 300x300
glOrtho( 0,300,300,0,0.0f,100.0f);
glClearColor( 1,1,1,0.5f);
glClearDepth( 1.0f);

glColor3f( 1,0,0);
glVertex3f( 150,10,0);
glColor3f( 0,1,0);
glVertex3f( 280,250,0);
glColor3f( 0,0,1);
glVertex3f( 20,250,0);

Yes the edge is jaggy. Then?
Well the interpolation among colors looks perfect.

The technique

The above observation is sufficient to enable us to do what we want.
Now lets draw a paralellogram which changes color from white to red.
glColor3f( 1,1,1);<br/>
glVertex3f( 50,270,0);<br/>
glVertex3f( 100,30,0);<br/>
glColor3f( 1,0,0);<br/>
glVertex3f( 58,270,0);<br/>
glVertex3f( 108,30,0);<br/>

The right side is still jaggy. The left side is,,, smooth. Can you now think of anything?
Now lets draw two paralellograms, which change color from white to red then to white again.
glColor3f( 1,1,1);<br/>
glVertex3f( 50,270,0);<br/>
glVertex3f( 100,30,0);<br/>
glColor3f( 1,0,0);<br/>
glVertex3f( 54,270,0);<br/>
glVertex3f( 104,30,0);<br/>
glColor3f( 1,1,1);<br/>
glVertex3f( 58,270,0);<br/>
glVertex3f( 108,30,0);<br/>

The technique is already explained.
In words: Draw a thin quadrilateral to render the core(inner) part of a line, then draw two more beside the original one that fade in color. The jaggy edge is then eliminated.


This article focuses on 2D line drawing so the meaning of “perfect quality” is with respect to 2D graphics.
In particular, Maxim Shemanarev (responsible for Anti-Grain Geometry) is the boss in fine grained 2D rendering.
Let see a picture from his article.

The above picture shows lines with thickness starting from 0.3 pixels and increasing by 0.3 pixel.
Using triangles to approximate line segments in the correct dimension is not easy. I do it by experiment and hand calibrated the drawing code,

then obtained:

It is not perfect though, the end points are a little bit not sharp enough, so I say “nearly perfect”.
I found fltk-cairo convinent to build so I actually took Cairo, the popular 2D rendering API on Linux, as a benchmark.

Their difference is subtle, so make sure you view them in original resolution and flip them in a slideshow program to observe. I have made one for you here.
It is seen that Cairo draws thin lines a little bit thicker than it should look. The circular fan on the right is drawn as 1px black lines by cairo_set_line_width (cr, 1.0) .

But you see the horizontal line is a 2px grey line. In my code I tried hard to give a 1px #000000 line when you request a 1px #000000 line on exact pixel coordinate, especially at horizontal/ vertical condition. But there is no guarantee in sub- pixel coordinate, other colors and orientations.
Ideal 1px black lines should look very close to aliased raw 1px lines, but just being smoother. Now take a closer look at the fan on the right and flip to compare there.

Hope you agree with my judgment.

A final compare:


Today's graphics card can render millions of triangles per second. Although alphablend is not as fast, it is still faster than any other method.
Some how (by a breif benchmark) it is 100 times faster than OpenGL native line drawing with smoothing turned on (on my machine, maybe that's why it looks so good). And 40 times faster than Cairo when drawing 10000 5px thick lines. Later if I have time I can include a more formal benchmark.
If you want to boost things up further, this technique allows you to separate opaque drawing from semi- transparent drawing (identify it easily by glColor4f( C,C,C, 0);). You can draw the solid part of all lines first then the those which require alphablend. However this ends up an drawing engine which is not easy to incorperate into existing code. Anyhow this technique is already very fast, when putting completely opaque (alpha=1.0) pixels, it takes only one statement if src_alpha!=1.0 to not call the blending function.


I have not tested the code on many machines, so I cannot guarantee.
This technique depends on rasterizing. There is (always) a higher chance that a GL driver implements rasterization correctly than smooth- line drawing.
As far as I know most hardware support sub- pixel accuracy rasterization. In my testings, there are often rounding errors which cause tiny artifact. That is not perfect, but still good.
Again I cannot guarantee, the best way is to test it yourself.

Final words

Download the zip file to play with the code. But I assume you know how to compile fltk 1.3 with cairo and gl enabled.
If you find this useful I just hope you to cite this page. If you used it in a program make sure you email me to let me see how well it would work.

By Chris Tsang, 2011 May
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架