Friday, January 27, 2012

Gradient trick...

Aloha,
during my work at Quintiq i stumbled upon a problem when drawing lot's of linear gradients in "realtime". In our software we have a gantt chart component that could contain thousands of nodes. Each of these nodes could contain multiple so called compartments which are representing a range of values. So the node itself is visualized using a LinearGradientPaint and also each compartment is visualized with it's own LinearGradientPaint. So far this should not be a problem but now it comes...we can't really cache things because the whole gantt chart is so to say "live". This means if somewhere in the businessmodel a value changed it will affect the ganttchart in the way that the nodes and their compartments will change it's size. Another problem is that the nodes could not only have different widths but also different heights. 
The compartments are separate shapes that will drawn on top of the nodes.
And that's not enough, the chart that is visible on the screen only represents a small part of the whole data which means one could scroll horizontal which let new nodes appear on the screen and others might change their height (this is something special to this kind of graph in our software).



Long story short...we could nail the performance problem to the drawing of the linear gradients and their creation. 
So i was thinking about how to improve the drawing speed of these gradients and came to an interesting approach that i would like to share with you in this post.

First of all i created a 1px wide BufferedImage with the most common height of the nodes. Then i filled this 1px image with a linear gradient (e.g. the green one) and saved the image for later use. Everytime when there was a new color of a compartment i created an 1px gradient image for this color. 
The trick is now to use the TexturePaint to fill all these nodes which has the big advantage that it will scale the gradientimage automaticaly to the needed height. Because we have to create each gradient only once now the repaint speed could be reduced by 30ms on each repaint.

This performance problem only occured when the gantt chart became really huge with thousands of nodes, but i thought it might be interesting for you to know.

Here is a little code snippet that will give you a hint on what i did...

private BufferedImage nodeTexture;

private void init() {
    private final float[] fractions = {
        0.0f,
        0.5f,
        1.0f
    };
    private final Color[] colors = {
        Color.RED,
        Color.GREEN,
        Color.BLUE
    };
    nodeTexture = createNodeTexture(24, fractions, colors);
}

@Override
protected void paintComponent(Graphics g) {
    ...
    Graphics2D g2 = (Graphics2D) g.create();
    for (RoundRectangle2D node : nodes) {
        TexturePaint nodePaint = new TexturePaint(nodeTexture, node.getBounds());
        g2.setPaint(nodePaint);
        g2.fill(node);
    }
    g2.dispose();
    ...
}

// Method that creates a 1px wide gradient image    
private BufferedImage createNodeTexture(final int HEIGHT, float[] fractions, Color[] colors) {
    GraphicsEnvironment gfxEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsConfiguration gfxConf = gfxEnv.getDefaultScreenDevice().getDefaultConfiguration();
    BufferedImage image = gfxConf.createCompatibleImage(1, HEIGHT, Transparency.OPAQUE);
    Graphics2D g2 = image.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Point2D start = new Point2D.Double(0, 0);
    Point2D stop = new Point2D.Double(0, HEIGHT);
    final LinearGradientPaint GRADIENT_NODE = new LinearGradientPaint(
        start, 
        stop, 
        fractions, 
        colors);
    g2.setPaint(GRADIENT_NODE);
    g2.fillRect(0, 0, 1, HEIGHT);
    g2.dispose();
    return image;
} 

Before i forget it...we are hiring, so if you are looking for a job in the Java and/or C++ area please check here


That's it for today, so keep coding...

6 comments:

  1. Gerrit, you have a great site and have done amazing things. At the moment I have to do a scheduling application. Are you able to guide me to an open source library to do the Gantt Chart?
    Thanks and keep up the great work.

    ReplyDelete
    Replies
    1. Hi Manuel,
      Well i think you should definitely take a look at JFreeChart (http://jfree.org/). Because we did the gantt chart by ourselves i have no other idea at hand, sorry.
      Cheers,
      Gerrit

      Delete
  2. Hi, I am using your framework as well and I got some performance problems as well.

    RadialBarGraph rbg = new RadialBarGraph() appears to be very slow (actually not acceptable), even though I am on a hexacore CPU with 6 GB of RAM.

    Are there other ways to construct a radialbargraph ? (or any of the other meters)

    Or perhaps you know how to fasten up the performance

    Hope this can be fixed because I really need that meter into my application :) hehe

    Nice job anyways !

    Greetings

    Sikke

    ReplyDelete
    Replies
    1. Hi Sikke,
      I can imagine that this control might be a bit slow. I have some ideas on how to improve the performance, so please stay tuned. How do you use it to see the performance problem ? You could also send me a mail han dot solo at muenster dot de
      Cheers,
      Gerrit

      Delete
  3. Thougt this blog talk about steel,which is our specialize. by the way,nice article

    ReplyDelete
  4. Very nice job!! Keep going on.
    I am starting to use it and it works well and it is easy to use.
    I just discover a strange behavior of a bargraph and I post a bug report in the Kenai project

    Cratouffe

    ReplyDelete