May 17, 2011

Drawing nearly perfect 2D line segments in OpenGL

With premium quality anti- aliasing, color, thickness and minimum CPU overhead


Introduction

OpenGL is great, when it comes to line drawing most people would draw it by:
    glBegin(GL_LINES);
    glVertex3f( x1,y1,0);
    glVertex3f( x2,y2,0);
    glEnd();
It does give you a straight line, but a very ugly one. To improve, most people would enable gl line smoothing:
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
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. (surprisingly on my mobility Radeon HD 4200 it looks really good.)
-poor thickness control. Most drivers only support thickness of integer value. And the maximum thickness is 10.0px


Observation

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?


glLoadIdentity();
//window size is 300x300
glOrtho( 0,300,300,0,0.0f,100.0f);
glClearColor( 1,1,1,0.5f);
glClearDepth( 1.0f);
glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);

glBegin(GL_TRIANGLE_STRIP);
glColor3f( 1,0,0);
glVertex3f( 150,10,0);
glColor3f( 0,1,0);
glVertex3f( 280,250,0);
glColor3f( 0,0,1);
glVertex3f( 20,250,0);
glEnd();
Yes the edge is jaggy. Then?
Well the interpolation among colors looks perfect.
The above observation is sufficient to enable us to do what we want.

The fade polygon technique

Now lets draw a paralellogram which changes color from white to red.


glBegin(GL_TRIANGLE_STRIP);
glColor3f( 1,1,1);
glVertex3f( 50,270,0);
glVertex3f( 100,30,0);
glColor3f( 1,0,0);
glVertex3f( 58,270,0);
glVertex3f( 108,30,0);
glEnd();
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.


glBegin(GL_TRIANGLE_STRIP);
glColor3f( 1,1,1);
glVertex3f( 50,270,0);
glVertex3f( 100,30,0);
glColor3f( 1,0,0);
glVertex3f( 54,270,0);
glVertex3f( 104,30,0);
glColor3f( 1,1,1);
glVertex3f( 58,270,0);
glVertex3f( 108,30,0);
glEnd();
Let's call this 'the fade polygon technique': 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 to give effect of anti- aliasing.

Quality

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:

Believe that it is rendered by the above technique in OpenGL. It is not perfect though, so I say “nearly perfect”.
(Update: I refined the rendering such that the end points of a line looks better)
I found fltk-cairo convinent to build so I actually took Cairo, the popular 2D rendering API on Linux, as a benchmark.

Flip between the two picture to compare.

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:

Hope you agree with my judgment.

A final compare:

Functionality

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


Most importantly, source code and usage

source code is at here.
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 useful when doing overdraw. The below image should tell you what alphablend=false means.

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 only need to include the header vase_rend_draft_1.h and it should work. This code use only little features of OpenGL so should be easily incorporated into any existing program. Your base program can be a Nehe hello world sample, glut, SDL, fltk or whatever. 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:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
    glLoadIdentity();
    glOrtho( 0,context_width,context_height,0,0.0f,100.0f);
    line(10,10,100,80, 1.0, 1,0,0.5, 0,0,0, true);
    //other 2D drawings,,,
glPopMatrix();
glDisable(GL_BLEND); //and whatever to restore blending options

Performance

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.

Portability

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. I observe that rasterization in OpenGL ES on iPhone looks good. It would probably work.
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

I can provide the source I used to produce the above images. 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.
Do not miss the second episode Drawing polylines by tessellation.

By Chris Tsang  tyt2y3@gmail.com, 2011 May

May 13, 2011

How to make Windows 7 look at its best

This is the first screen you see after installing Windows 7.

Nice enough? Yes but you can make it even better.
Here are the steps to make Windows 7 to look the best.

Step 1:

Right click on the IE icon and click "Unpin this program from taskbar".

Step 2:

Right click on the task bar and click "Properties".


Go to the "Start menu" tab and click "Customize". Check "don't display this item" for all the items except "Computer" and "Control panel". Un-tick as much items as possible (judge it by yourself).

Step 3:

Go to the "Taskbar" tab, tick "Use small icons". In "Taskbar buttons", choose "Combine when task bar is full"

Step 4 (Final):
Be patient, we are very close now and this is the most important step.


Right click on desktop area and click "Personalize".

In "Basic and high contrast themes", select "Windows classic". You probably need to wait for a while.


Click on the button "Window color".


Click on the "OK" button in the graphic area or select "3D Objects" in item list.
Click the box under "Color 1" and click "other".


In the dialog box, enter 234, 232, 227 in "red", "green", "blue" respectively. Click "OK".
Click "OK" on the "Window color and appearance" dialog.

Done!!

compares:



It now looks cleaner and cause less eye pain. It increases system performance and report writing performance as it distracts you less.

It also reminds the glory days of Windows 2000.

Seriously, why Windows Aero theme is bad?
-It intentionally increase window border to 5 pixels just to let you know the border is semi-transparent so that it is cool. 5 pixels! you can fit a character in it.
Personally, the thick border makes me think I am using children's crayon that there is no accuracy.

-It makes the taskbar taller than it used to be, further wasting screen space. I would like to include a screen shot of Office, showing you how the ribbon on the top and the task bar on the bottom eat up 30% of the screen space. On a 16:9 display, how much space(height) is left for typing? Or does Microsoft want to promote landscape document as the next generation standard?

-The theme is like butter shortcake. On the first bite, it is tasty. When you eat more than 3 pieces, you are drown by butter.
Same is on Windows theme. When you are doing something serious enough, you do not want to be distracted by anything shiny (but useless) or hindered by the "blur and alpha blend" theme.