I've read that JPanel has a double buffering system, but I've also seen people manually create their own back buffer using BufferedImage, which allows the user to add to the image before actually rendering.
How does the JPanel double buffering work, and why would someone create their own back buffer if JPanel already has double buffering? What is an example of a time where BufferedImage as your buffer would be needed/recommended?
From what I've seen it's usually because:
They are already loading an image which serves as a background image and wish to draw on top of that (they probably could just draw the image, leaving it to be hardware accelerated for future renders while drawing everything else using the same Graphics object used to call drawImage)
Because they wish to have a simple way to scale their graphics as their panel resizes with drawImage(Image, 0, 0, panelWidth, panelHeight, ImageObserver) and don't wish to do the extra work on calculating dynamic drawing coordinates for the graphics based on the panel's current dimensions (although this leads to not so good looking graphics, and if the scaled image is not cached per resize there could be performance issues as well).
Because it's old code in the days before Swing was double buffered, or people following code from old books.
The graphics of what you draw in the double buffered painting methods (like paintComponent(Graphics)) will be visible all at once when all of the relevant painting methods are complete. That being the case there's no reason to use a BufferedImage as a way to manually ensure all the Graphics get shown at once.
Related
I'm drawing to a Canvas using Graphics through a BufferStrategy with lines such as
g.drawImage(bufferedImage, x, y, null);
I currently have this running undecorated in a JFrame, 1920x1080p as per the resolution of my laptop. I'm curious as to whether there is any way to alter the resolution of the Graphics rendered, particularly lowering resolution so as to increase efficiency/speed, or fitting to another differently sized screen. There are many objects being rendered with a camera and the game runs fairly well, but any usable alterations to the resolution would be useful as optional in my settings.
I've researched this and found no good answers. Thank you for your time.
(Resolution changes such as for printing.)
Best to use a drawImage with a smaller image, and scaled width and height.
Now, you could even render all in your own BufferedImage using a Graphics2D with BufferedImage.createGraphics and scale afterwards. Not so nice for text or printing.
Or use Graphics2D scaling:
For complex rendering:
g.scale(2.0, 2.0);
... // Draw smaller image
g.scale(0.5, 0.5);
As you might imagine this probably does not help in memory consumption, apart from needing smaller images. At one point all pixels of the image must be given in the devices color size. 256 colors gif, or 10KB jpg will not help.
The other way around, supporting high resolutions with tight memory also exists. There one might use tiled images, see ImageIO.
Important is to prepare the image outside the paintComponent/paint.
You might also go for device compatible bit maps if you make your own BufferedImage, but this seems circumstantial (GraphicsEnvironment).
I have stumbled upon a bottle neck that I would love to fix.
I need to make a BufferedImage grow with time, but the Buffered image should support scrolling as my application requires that the Bufferedimage grow a significant size over time. However the draw calls are not done by the event dispatch thread.
I have a while loop that performs the render calls. What I attempted was to create an instance of a Canvas and add it to a JScrollPane however when I take this approach, the JScrollPane performs its own draw calls and I am not sure how the JScrollPane will detect that the canvas has resized at runtime.
The other issue is that since within the canvas I use a BufferedImage to draw onto, I cannot get it to resize with a temporary BufferedImage.
Here is how I attempted to create a new, larger BufferedImage
if(needsToBeResized)
{
BufferedImage temporaryBufferedImag =
new BufferedImage(originalImage + extraSpace, height, originalImage.getType());
Graphics g2d = temporaryBufferedImag.createGraphics();
g2d.drawImage(originalImage, 0,0,null);
//I presume that this should copy the graphics object with the original img
originalImage = temporaryBufferedImag;
g2d.dispose();
}
If it helps I am developing an oscilloscope type application that needs to be able to keep plotting values real-time but I also need to be able to show the history of values.
Render each interval in its own "mini" BufferedImage. So, for a gross example, say you render every second, and you want to show 20 seconds of history.
So, you render your current second in to a "1 second sized" image. Then you take your cache of the images from the previous 19 seconds, and you stamp them all together, one after the other in to your new total image, and display that.
Next second, you drop off the oldest image, create a new one, stick it on the end, rinse and repeat.
If your overall frame size changes, when you get to render all of the pieces all over again…c'est la vie.
Obviously you will be sampling more than once a second, but you get the gist of it.
All this assumes this is cheaper than simply re-rendering the entire thing from scratch each time.
i've got a question about drawing animations in Java (SWT).
I try to draw an animation of some process.
When i use canvas.redraw() program firstly erases everything that has been drawn and then draws again.
My program draws about 1000 of rectangles per time step (this big quantity is necessary) so animation doesn't looks smooth - it blinks all the time.
Is there a way to make it look smoother, for example to paint new objects over old ones, without erasing them (that would look better anyway)?
The solution for flickering when doing custom painting is to use double buffering. The SWT Canvas object has built-in double-buffering, use it by adding the flag to the styles in the constructor:
Canvas myCanvas = new Canvas (parentComposite, SWT.DOUBLE_BUFFERED);
I'm not quite sure how to phrase this, so bear with me.
I have two JPanels in a container JPanel with an OverlayLayout. Both JPanels in the container override paint(Graphics).
The bottom JPanel is opaque and draws some fairly complicated graphics, so it takes a "long" time (10s or 100s of milliseconds) to render.
The top JPanel is transparent and just draws a rectangle or line or simple shape based on mouse input, so it's really quick.
Is there a way to set things up so when I change the simple shape in the upper panel, it doesn't redraw the bottom panel? (e.g. it somehow caches the bottom panel)
I'm vaguely familiar w/ concepts like bitblt, double-buffering, and XOR-drawing but not really sure what to apply here.
You'd be best off using a single JComponent and creating a BufferedImage to store the bottom image. When the paintComponent operation happens on the JComponent, you just blit the bottom image and use the Graphics object to do any further drawing on top of that (from a stored state). Should be fairly efficient.
You'll want to do the complex drawing operations for the bottom BufferedImage in another thread, as the other poster mentions (omitted this by accident, sorry :)). However, you don't want to cause contention on this image, so you must store an additional BufferedImage for this, and blit it synchronously to the other image at the very moment the drawing operations are complete on it.
Focusing on the complicated panel, the key is factoring everything out of paintComponent() except drawImage(). Put everything else in another thread that continually updates an offscreen buffer. Periodically update the screen at some rate that keeps the simple panel responsive. The only hard part is synchronizing, but SwingWorker is a good choice. There's more here.
What's sure is that if the upper panel is target for a full repaint(), then the lower one will be also.
Maybe you can try to optimize the region to repaint on the upper panel in order to avoid repainting all the lower one. But if the painted rectangle in the upper panel covers the whole area, then you end up with full repaint() once again.
Normally, Swing tries to optimize the regions that need a repaint, but it also aggregates these regions when several repaint are performed in a short time, and if I remember well, the aggregated region is just a rectangle that is the union of all repaint rectangles, which is not always optimized but allows for fast computation of repaint events creation.
Now, I think you should follow the advices given in previous replies; indeed, you should really avoid having a paint() method that can perform computations that can be that long (a few 10s of ms should be the real maximum). Painting should be as fast as possible if you don't want to have a GUI that looks unresponsive to the end user. Hence, favour performing the computation only once (and outside the EDT if possible) store the result in a BufferedImage that you just simply draw later on in the paint() method.
EDIT: added other sources of reflection
If you want to optimize the update of the list of points but still keep it in the paint() method, then you can use the clipping region of the passed Graphics to limit the calls to drawing methods, something like:
Rectangle clip = g.getClipBounds();
for (Point p: allPoints) {
if (clip.contains(p)) {
// draw p with g graphics
}
}
You can even try to optimize the list of points to draw by using a QuadTree instead of a simple List, but you'll have to code it yourself (or find some free implementations, there are probably a few of them out there). With a quadtree, you can optimize the time to find the list of all points that have to be redrawn (based on the Graphics clipping rectangle) and only redraw those points.
Addenda for answer by trashgod and jfpoilpret
1/ OverlayLayout is strange way how to layout JPanels, are you same output with once JPanel (without OverlayLayout and Translucentcy)
2/ (10s or 100s of milliseconds) is maybe small value because there is Native OS Latency (45-75ms for today OS and PC)
3/ synchronizations would be managed by using SwingWorker on BackGround Task and with order, directions and synchronizations for painting processes to the JPanel, maybe your paints are too fast/quickly
4/ you didn't describe more about how, where and which about paint()/paintComponent()
if (SwingUtilities.isEventDispatchThread()) {
paintImmediately(int x, int y, int w, int h) // or Rectangle r
} else {
Runnable doRun = new Runnable() {
#Override
public void run() {
repaint(long tm, int x, int y, int width, int height) // or Rectangle r
}
};
SwingUtilities.invokeLater(doRun);
}
We have an old (more than 10yrs old) Java Swing applicatin which draws lots of circles and connections (lines) between those circles on a JCanvas (a subclass of JComponent) based on lab data.
Because the data becames bigger and bigger, we cannot display the entire drawing now. We have put the JCavans into a JScrollPane but it is not convenience to scroll the drawing.
Can we add zoom in zoom out for it? if yes, how? I know we can zoom image but the drawing on Canvas is an image?
thanks,
EDIT:
we draw those circles and line with Graphics within paintComponent(Graphics g) method.
You could apply a scaling Transform to the Graphics2D object passed to the paintComponent method. You can learn how to use it in the Java 2D programming trail.
Without knowing anything about your application it's hard to provide useful advice (adding a code snippet or better yet a cutdown example app would be helpful to show how things are being drawn), but I'll give it a shot:
Why don't you multiply the x,y and width,height values by a scaling factor before you draw each circle/line? I assume that somewhere your canvas is using a Graphics object to draw each shape?