Reputation: 666
I am having issues with the speed of the drawline
method of the Graphics class. I am using it to draw a line graph to the screen from a linked list. Once the list is large enough (around 150000 values) it takes a lot longer to loop through the entire list and redraw all the lines. I am wondering what I can do to improve the performance of the program either by optimizing the drawline
method, or abandoning it.
@Override
protected void paintComponent(Graphics g) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
Graphics2D scaledG = (Graphics2D) g.create();
g.dispose();
super.paintComponent(scaledG);
AffineTransform scaleTransform = new AffineTransform();
scaleTransform.scale(graphScale, 1);
scaledG.setTransform(scaleTransform);
scaledG.setColor(new Color(242,100,66));
for (Line line : lines) {
scaledG.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2);
}
scaledG.dispose();
}
Upvotes: 1
Views: 1042
Reputation: 1061
As your question is quite broad, it's hard to give a definitive answer, so I'll just give you some general approaches I would check out for improving rendering performance.
Caching static parts of the rendering: One very helpful trick when drawing a large number of shapes is caching the "static" parts of the shapes by rendering them once into a BufferedImage
, then rendering that image together with the "dynamic" shapes onto the Graphics
object.
To apply it, the crucial issue is whether you can know which of your lines will be static and which need to be rendered dynamically. If you can't assume anything on the lines, this approach doesn't apply -- you'll need to render everything. However, if you know that, e.g., the first N lines don't change for most of the renderings, it could be worth checking out.
A minor drawback of this approach is that the dynamic parts are always rendered on top of the static parts. In many applications, for example editing shapes, this does not matter, in others, it does.
"Level of Detail"/smoothing approach: If your line (segments) form a polyline or polygon, consider whether you can interpolate or completely ignore some of the segments. A crude approach would be to check whether the transformed (pixel) endpoints of the following segment differ from the transformed (pixel) endpoint of the current segment; if not, the segment would make no difference to the resulting image, and you can skip it.
However, don't use this algorithm which is just for illutrating the general idea; there are many more and much better algorithms for "reducing" or "smoothing" polylines and polygons, see for example SimpliPoly or David Eberly's polyline reduction for starters.
Of course, this all depends on how "smooth" your lines are, and if they are connected or not. For geographical data (coastlines, rivers, etc.), this optimization can compress your data to fractions of the original size; for completely random and unconnected lines, this won't help.
Provide a quick preview with asynchronous incremental refinement: If both of the above approaches do not apply, or still don't suffice, there may be no effective way to substantially speed up applications (see below). In this case, I would try to provide some kind of quick preview instantaneously, which is then refined asynchronously (SwingWorker
or another computation thread). As soon as one of the lines is changed, you stop the refinement rendering, provide the next preview, and restart the refinement. While this does not really speed up rendering (on the contrary :), it does make your application subjectively more responsive, and this is often much more important than pure clock cycles.
In your example, you could simple start by rendering the first N thousand lines. Then you start your "render remaining" thread, which incrementally draw more lines as long as the lines do not change; however, the user can change parameters of your lines before all lines are rendered, if he realizes that this wasn't what he wanted.
The main downside of this approach is that it adds complexity. Multithreading requires careful synchronization to prevent race issues, and is always prone to nasty bugs. However, it's often amazing how effective this approach is.
Low-level optimizations and profiling: Last but not least, you can always try to strip of all unnecessary computations. Don't set any RenderingHints
. Don't use transformations of the Graphics
object, do it yourself -- in your example, simply multiply the line coordinates, maybe cache the scaled coordinates if the scaling remains constant most of the time (a variant of #1). Maybe even rasterize the lines yourself into some pixel buffer, create an image from it and render that. Profile the rendering and see where you can shave off a few clocks.
However, while this may improve the performance, it is A) hard to maintain and B) won't solve the general problems, just push the frontier a little further.
You can, of course, combine these approaches.
If all of these do not apply or help, your only option is to switch to a programming language that allows more direct hardware (graphics card) communication than Java.
Upvotes: 4
Reputation: 324088
Don't dispose() a Graphics object that is passed into a painting method. Only dispose() Graphics objects that you create.
Try creating a BufferedImage
at normal scale, so the painting is only done once not ever time the paintComponent() method is invoked. Then in the paintComponent() method you can scale the image when you paint it.
Upvotes: 0