Java: how to do double-buffering in Swing? - java

EDIT TWO
To prevent snarky comments and one-line answers missing the point: IFF it is as simple as calling setDoubleBuffered(true), then how do I get access to the current offline buffer so that I can start messing with the BufferedImage's underlying pixel databuffer?
I took the time to write a running piece of code (which looks kinda fun too) so I'd really appreciate answers actually answering (what a shock ;) my question and explaining what/how this is working instead of one-liners and snarky comments ;)
Here's a working piece of code that bounces a square across a JFrame. I'd like to know about the various ways that can be used to transform this piece of code so that it uses double-buffering.
Note that the way I clear the screen and redraw the square ain't the most efficient but this is really not what this question is about (in a way, it's better for the sake of this example that it is somewhat slow).
Basically, I need to constantly modify a lot pixels in a BufferedImage (as to have some kind of animation) and I don't want to see the visual artifacts due to single-buffering on screen.
I've got a JLabel whose Icon is an ImageIcon wrapping a BufferedImage. I want to modify that BufferedImage.
What has to be done so that this becomes double-buffered?
I understand that somehow "image 1" will be shown while I'll be drawing on "image 2". But then once I'm done drawing on "image 2", how do I "quickly" replace "image 1" by "image 2"?
Is this something I should be doing manually, like, say, by swapping the JLabel's ImageIcon myself?
Should I be always drawing in the same BufferedImage then do a fast 'blit' of that BufferedImage's pixels in the JLabel's ImageIcon's BufferedImage? (I guess no and I don't see how I could "synch" this with the monitor's "vertical blank line" [or equivalent in flat-screen: I mean, to 'synch' without interfering with the moment the monitor itselfs refreshes its pixels, as to prevent shearing]).
What about the "repaint" orders? Am I suppose to trigger these myself? Which/when exactly should I call repaint() or something else?
The most important requirement is that I should be modifying pixels directly in the images's pixel databuffer.
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class DemosDoubleBuffering extends JFrame {
private static final int WIDTH = 600;
private static final int HEIGHT = 400;
int xs = 3;
int ys = xs;
int x = 0;
int y = 0;
final int r = 80;
final BufferedImage bi1;
public static void main( final String[] args ) {
final DemosDoubleBuffering frame = new DemosDoubleBuffering();
frame.addWindowListener(new WindowAdapter() {
public void windowClosing( WindowEvent e) {
System.exit(0);
}
});
frame.setSize( WIDTH, HEIGHT );
frame.pack();
frame.setVisible( true );
}
public DemosDoubleBuffering() {
super( "Trying to do double buffering" );
final JLabel jl = new JLabel();
bi1 = new BufferedImage( WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB );
final Thread t = new Thread( new Runnable() {
public void run() {
while ( true ) {
move();
drawSquare( bi1 );
jl.repaint();
try {Thread.sleep(10);} catch (InterruptedException e) {}
}
}
});
t.start();
jl.setIcon( new ImageIcon( bi1 ) );
getContentPane().add( jl );
}
private void drawSquare( final BufferedImage bi ) {
final int[] buf = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
for (int i = 0; i < buf.length; i++) {
buf[i] = 0xFFFFFFFF; // clearing all white
}
for (int xx = 0; xx < r; xx++) {
for (int yy = 0; yy < r; yy++) {
buf[WIDTH*(yy+y)+xx+x] = 0xFF000000;
}
}
}
private void move() {
if ( !(x + xs >= 0 && x + xs + r < bi1.getWidth()) ) {
xs = -xs;
}
if ( !(y + ys >= 0 && y + ys + r < bi1.getHeight()) ) {
ys = -ys;
}
x += xs;
y += ys;
}
}
EDIT
This is not for a full-screen Java application, but a regular Java application, running in its own (somewhat small) window.

---- Edited to address per pixel setting ----
The item blow addresses double buffering, but there's also an issue on how to get pixels into a BufferedImage.
If you call
WriteableRaster raster = bi.getRaster()
on the BufferedImage it will return a WriteableRaster. From there you can use
int[] pixels = new int[WIDTH*HEIGHT];
// code to set array elements here
raster.setPixel(0, 0, pixels);
Note that you would probably want to optimize the code to not actually create a new array for each rendering. In addition, you would probably want to optimized the array clearing code to not use a for loop.
Arrays.fill(pixels, 0xFFFFFFFF);
would probably outperform your loop setting the background to white.
---- Edited after response ----
The key is in your original setup of the JFrame and inside the run rendering loop.
First you need to tell SWING to stop Rasterizing whenever it wants to; because, you'll be telling it when you're done drawing to the buffered image you want to swap out in full. Do this with JFrame's
setIgnoreRepaint(true);
Then you'll want to create a buffer strategy. Basically it specifies how many buffers you want to use
createBufferStrategy(2);
Now that you tried to create the buffer strategy, you need to grab the BufferStrategy object as you will need it later to switch buffers.
final BufferStrategy bufferStrategy = getBufferStrategy();
Inside your Thread modify the run() loop to contain:
...
move();
drawSqure(bi1);
Graphics g = bufferStrategy.getDrawGraphics();
g.drawImage(bi1, 0, 0, null);
g.dispose();
bufferStrategy.show();
...
The graphics grabbed from the bufferStrategy will be the off-screen Graphics object, when creating triple buffering, it will be the "next" off-screen Graphics object in a round-robin fashion.
The image and the Graphics context are not related in a containment scenario, and you told Swing you'd do the drawing yourself, so you have to draw the image manually. This is not always a bad thing, as you can specify the buffer flipping when the image is fully drawn (and not before).
Disposing of the graphics object is just a good idea as it helps in garbage collection. Showing the bufferStrategy will flip buffers.
While there might have been a misstep somewhere in the above code, this should get you 90% of the way there. Good luck!
---- Original post follows ----
It might seem silly to refer such a question to a javase tutorial, but have you looked into BufferStrategy and BufferCapatbilites?
The main issue I think you are encountering is that you are fooled by the name of the Image. A BufferedImage has nothing to do with double buffering, it has to do with "buffering the data (typically from disk) in memory." As such, you will need two BufferedImages if you wish to have a "double buffered image"; as it is unwise to alter pixels in image which is being shown (it might cause repainting issues).
In your rendering code, you grab the graphics object. If you set up double buffering according to the tutorial above, this means you will grab (by default) the off-screen Graphics object, and all drawing will be off-screen. Then you draw your image (the right one of course) to the off-screen object. Finally, you tell the strategy to show() the buffer, and it will do the replacement of the Graphics context for you.

Generally we use Canvas class which is suitable for animation in Java. Anyhoo, following is how you achieve double buffering:
class CustomCanvas extends Canvas {
private Image dbImage;
private Graphics dbg;
int x_pos, y_pos;
public CustomCanvas () {
}
public void update (Graphics g) {
// initialize buffer
if (dbImage == null) {
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint (dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
}
public void paint (Graphics g)
{
g.setColor (Color.red);
g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius);
}
}
Now you can update the x_pos and y_pos from a thread, followed by the 'repaint' call on the canvas object. The same technique should work on a JPanel as well.

What you want is basically impossible in windowed mode with Swing. There is no support for raster synchronization for window repaints, this is only available in fullscreen mode (and even then may not be supported by all platforms).
Swing components are double-buffered by default, that is they will do all the rendering to an intermediate buffer and that buffer is then finally copied to the screen, avoiding flicker from background clearing and then painting on top of it.
And thats the only strategy that is reasonable well supported on all underlying platforms. It avoids only repaint flickering, but not visual tearing from moving graphic elements.
A reasonably simple way of having access to the raw pixels of an area fully under you control would be to extend a custom component from JComponent and overwrite its paintComponent()-method to paint the area from a BufferedImage (from memory):
public class PixelBufferComponent extends JComponent {
private BufferedImage bufferImage;
public PixelBufferComponent(int width, int height) {
bufferImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
setPreferredSize(new Dimension(width, height));
}
public void paintComponent(Graphics g) {
g.drawImage(bufferImage, 0, 0, null);
}
}
You can then manipulate you buffered image whichever way you desire. To get your changes made visible on screen, simply call repaint() on it. If you do the pixel manipulation from a thread other than the EDT, you need TWO buffered images to cope with race conditions between the actual repaint and your manipulation thread.
Note that this skeleton will not paint the entire area of the component when used with a layout manager that stretches the component beyond its preferred size.
Note also, the buffered image approach mostly only makes sense if you do real low level pixel manipulation via setRGB(...) on the image or if you directly access the underlying DataBuffer directly. If you can do all the manipulations using Graphics2D's methods, you could do all the stuff in the paintComponent method using the provided graphics (which is actually a Graphics2D and can be simply casted).

Here's a variation in which all drawing takes place on the event dispatch thread.
Addendum:
Basically, I need to constantly modify a lot pixels in a BufferedImage…
This kinetic model illustrates several approaches to pixel animation.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.awt.image.BufferedImage;
/** #see http://stackoverflow.com/questions/4430356 */
public class DemosDoubleBuffering extends JPanel implements ActionListener {
private static final int W = 600;
private static final int H = 400;
private static final int r = 80;
private int xs = 3;
private int ys = xs;
private int x = 0;
private int y = 0;
private final BufferedImage bi;
private final JLabel jl = new JLabel();
private final Timer t = new Timer(10, this);
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DemosDoubleBuffering());
frame.pack();
frame.setVisible(true);
}
});
}
public DemosDoubleBuffering() {
super(true);
this.setLayout(new GridLayout());
this.setPreferredSize(new Dimension(W, H));
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB);
jl.setIcon(new ImageIcon(bi));
this.add(jl);
t.start();
}
#Override
public void actionPerformed(ActionEvent e) {
move();
drawSquare(bi);
jl.repaint();
}
private void drawSquare(final BufferedImage bi) {
Graphics2D g = bi.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, W, H);
g.setColor(Color.blue);
g.fillRect(x, y, r, r);
g.dispose();
}
private void move() {
if (!(x + xs >= 0 && x + xs + r < bi.getWidth())) {
xs = -xs;
}
if (!(y + ys >= 0 && y + ys + r < bi.getHeight())) {
ys = -ys;
}
x += xs;
y += ys;
}
}

Related

Graphics.drawImage() consumes a lot of memory drawing an int[] image

I have an ImageViewComponent that extends JComponent. This component is added to a JPanel that is added to a JFrame. From another class I regularly update the int[] image field of ImageViewComponent from another class.
The problem is that this process eats up a lot of memory. It even consumes so much memory (+/- 130 MB after a few seconds according to JProfiler, and eventually it surpasses 1GB) that the entire program undergoes a 'lag spike' during garbage collection (the lag in the program happens at the same time that the memory is cleared).
This is the code of ImageViewComponent:
public class ImageViewComponent extends JComponent {
private int image_width, image_height, updateInterval, updateCounter;
private int[] imageArray;
private BufferedImage currentDisplayedImage;
private Image scaledDisplayedImage;
/**
* #param width The width of this component
* #param height The height of this component
* #param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.currentDisplayedImage = null;
this.scaledDisplayedImage = null;
}
public void setImage(int[] image, int width, int height) {
this.imageArray = image;
this.image_width = width;
this.image_height = height;
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (image_width == 0 || image_height == 0)
return;
else if (updateCounter != updateInterval && currentDisplayedImage != null) {
g.drawImage(scaledDisplayedImage, 0, 0, this);
updateCounter++;
return;
}
this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB);
this.currentDisplayedImage.setRGB(0, 0, image_width, image_height, this.imageArray, 0, image_width);
this.scaledDisplayedImage = this.currentDisplayedImage.getScaledInstance(this.getPreferredSize().width,
this.getPreferredSize().height, BufferedImage.SCALE_DEFAULT);
g.drawImage(scaledDisplayedImage, 0, 0, this);
// reset update counter
updateCounter = 0;
}
}
JProfiler states that 70% of the program its active memory is allocated in this class, 50% is in Graphics.drawImage while 20% is in BufferedImage initialization.
I have tried fixing it by putting the line this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB) in `setImage' and have it only set it once with a boolean flag but this makes the drawn image turn completely black for short amounts of time once in a while, nor does it fix the memory problem.
I also tried this suggestion, which didn't work either.
How can I fix this memory issue?
There are several issues with the code. Some refer to performance, others to style or best practices, and others (at least potentially) refer to memory consumption.
Performance: The getScaledInstance method is distressingly slow. See https://stackoverflow.com/a/32278737/3182664 and others for better alternatives
Style: It's imageWidth, not image_width
Best practices: For a JComponent, you usually, you override paintComponent and not paint
Memory consumption: That's the main point...:
As MadProgrammer already pointed out: Do things as rarely as possible. The role and purpose of this updateCounter is not entirely clear. I think that the responsibility for updating the image less frequently should be in the class that uses your component - particularly, in the class that calls updateImage (which should simply be done less often). Maintaining this in the paint method is not very reliable.
In your current code, it seems like the currentDisplayedImage is (despite its name) neither displayed nor used in any other way. It may, however, be a good idea to keep it: It will be needed to be filled with the int[] data, and as a source for the scaled image that might have to be created.
One possible implementation of your class might look as follows:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageViewComponent extends JComponent {
private int updateInterval, updateCounter;
private BufferedImage fullImage;
private BufferedImage displayedImage;
/**
* #param width The width of this component
* #param height The height of this component
* #param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.fullImage = null;
this.displayedImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
public void setImage(int[] image, int width, int height) {
// Note: The updateInvervall/updateCounter stuff COULD
// probably also be done here...
if (fullImage == null ||
fullImage.getWidth() != width ||
fullImage.getHeight() != height)
{
fullImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
}
fullImage.setRGB(0, 0, width, height, image, 0, width);
scaleImage(fullImage, displayedImage);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(displayedImage, 0, 0, this);
}
private static BufferedImage scaleImage(
BufferedImage input, BufferedImage output)
{
double scaleX = (double) output.getWidth() / input.getWidth();
double scaleY = (double) output.getHeight() / input.getHeight();
AffineTransform affineTransform =
AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp affineTransformOp =
new AffineTransformOp(affineTransform, null);
return affineTransformOp.filter(input, output);
}
}
but note that this does not do this "updateInterval" handling, for the reason mentioned above.
And a side note: Maybe you don't even have to scale the image. If your intention is to have the image always being displayed at the size of the component, then you can simply do
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the FULL image, which, regardless of its size (!)
// is here painted to just fill this component:
g.drawImage(fullImage, 0, 0, getWidth(), getHeight(), null);
}
Usually, drawing a scaled image like this is pretty fast. But depending on many factors, separating the step of scaling and painting the image, like you did, may also be a reasonable option.

Does Java Graphics (Graphics2D) uses graphics card? [duplicate]

I'm having performance oddities with Java2D. I know of the sun.java2d.opengl VM parameter to enable 3D acceleration for 2D, but even using that has some weird issues.
Here are results of tests I ran:
Drawing a 25x18 map with 32x32 pixel tiles on a JComponent
Image 1 = .bmp format, Image 2 = A .png format
Without -Dsun.java2d.opengl=true
120 FPS using .BMP image 1
13 FPS using .PNG image 2
With -Dsun.java2d.opengl=true
12 FPS using .BMP image 1
700 FPS using .PNG image 2
Without acceleration, I'm assuming some kind of transformation is taking place with every drawImage() I do in software, and is pulling down the FPS considerably in the case of .PNG. Why though, with acceleration, would the results switch (and PNG actually performs incredibly faster)?! Craziness!
.BMP Image 1 is translated to an image type of TYPE_INT_RGB. .PNG Image 2 is translated to an image type of TYPE_CUSTOM. In order to get consistent speed with and without opengl acceleration, I have to create a new BufferedImage with an image type of TYPE_INT_ARGB, and draw Image 1 or Image 2 to this new image.
Here are the results running with that:
Without -Dsun.java2d.opengl=true
120 FPS using .BMP image 1
120 FPS using .PNG image 2
With -Dsun.java2d.opengl=true
700 FPS using .BMP image 1
700 FPS using .PNG image 2
My real question is, can I assume that TYPE_INT_ARGB will be the native image type for all systems and platforms? I'm assuming this value could be different. Is there some way for me to get the native value so that I can always create new BufferedImages for maximum performance?
Thanks in advance...
I think I found a solution by researching and putting bits and pieces together from too many Google searches.
Here it is, comments and all:
private BufferedImage toCompatibleImage(BufferedImage image)
{
// obtain the current system graphical settings
GraphicsConfiguration gfxConfig = GraphicsEnvironment.
getLocalGraphicsEnvironment().getDefaultScreenDevice().
getDefaultConfiguration();
/*
* if image is already compatible and optimized for current system
* settings, simply return it
*/
if (image.getColorModel().equals(gfxConfig.getColorModel()))
return image;
// image is not optimized, so create a new image that is
BufferedImage newImage = gfxConfig.createCompatibleImage(
image.getWidth(), image.getHeight(), image.getTransparency());
// get the graphics context of the new image to draw the old image on
Graphics2D g2d = newImage.createGraphics();
// actually draw the image and dispose of context no longer needed
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
// return the new optimized image
return newImage;
}
In my previous post, GraphicsConfiguration was what held the information needed to create optimized images on a system. It seems to work pretty well, but I would have thought Java would automatically do this for you. Obviously you can't get too comfortable with Java. :) I guess I ended up answering my own question. Oh well, hopefully it'll help some of you I've seen trying to make use of Java for 2D games.
Well, this is old post but I'd like to share my findings about direct drawing with Swing/AWT, without BufferedImage.
Some kind of drawing, as 3D, are better done when painting directly to a int[] buffer. Once done the images, you can use an ImageProducer instance, like MemoryImageSource, to produce images. I'm assuming you know how to perform your drawings directly, without help of Graphics/Graphics2.
/**
* How to use MemoryImageSource to render images on JPanel
* Example by A.Borges (2015)
*/
public class MyCanvas extends JPanel implements Runnable {
public int pixel[];
public int width;
public int height;
private Image imageBuffer;
private MemoryImageSource mImageProducer;
private ColorModel cm;
private Thread thread;
public MyCanvas() {
super(true);
thread = new Thread(this, "MyCanvas Thread");
}
/**
* Call it after been visible and after resizes.
*/
public void init(){
cm = getCompatibleColorModel();
width = getWidth();
height = getHeight();
int screenSize = width * height;
if(pixel == null || pixel.length < screenSize){
pixel = new int[screenSize];
}
mImageProducer = new MemoryImageSource(width, height, cm, pixel,0, width);
mImageProducer.setAnimated(true);
mImageProducer.setFullBufferUpdates(true);
imageBuffer = Toolkit.getDefaultToolkit().createImage(mImageProducer);
if(thread.isInterrupted() || !thread.isAlive()){
thread.start();
}
}
/**
* Do your draws in here !!
* pixel is your canvas!
*/
public /* abstract */ void render(){
// rubisch draw
int[] p = pixel; // this avoid crash when resizing
if(p.length != width * height) return;
for(int x=0; x < width; x++){
for(int y=0; y<height; y++){
int color = (((x + i) % 255) & 0xFF) << 16; //red
color |= (((y + j) % 255) & 0xFF) << 8; //green
color |= (((y/2 + x/2 - j) % 255) & 0xFF) ; //blue
p[ x + y * width] = color;
}
}
i += 1;
j += 1;
}
private int i=1,j=256;
#Override
public void run() {
while (true) {
// request a JPanel re-drawing
repaint();
try {Thread.sleep(5);} catch (InterruptedException e) {}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// perform draws on pixels
render();
// ask ImageProducer to update image
mImageProducer.newPixels();
// draw it on panel
g.drawImage(this.imageBuffer, 0, 0, this);
}
/**
* Overrides ImageObserver.imageUpdate.
* Always return true, assuming that imageBuffer is ready to go when called
*/
#Override
public boolean imageUpdate(Image image, int a, int b, int c, int d, int e) {
return true;
}
}// end class
Note we need unique instance of MemoryImageSource and Image. Do not create new Image or new ImageProducer for each frames, unless you have resized your JPanel. See init() method above.
In a rendering thread, ask a repaint(). On Swing, repaint() will call the overridden paintComponent(), where it call your render() method and then ask your imageProducer to update image.
With Image done, draw it with Graphics.drawImage().
To have a compatible Image, use proper ColorModel when you create your Image. I use GraphicsConfiguration.getColorModel():
/**
* Get Best Color model available for current screen.
* #return color model
*/
protected static ColorModel getCompatibleColorModel(){
GraphicsConfiguration gfx_config = GraphicsEnvironment.
getLocalGraphicsEnvironment().getDefaultScreenDevice().
getDefaultConfiguration();
return gfx_config.getColorModel();
}
From what I remember when I was thinking about doing graphics programming in Java, the built in libraries are slow. I was advised on GameDev.Net that anyone doing anything serious would have to use something like jogl

Double Buffering image in JFrame with other swing controls

I have a JFrame with an JLabel that houses an ImageIcon containing a BufferedImage. The BufferedImage's graphis are drawn on with several different graphics calls, such as drawOval(), drawRectangle(), etc, with many drawn shapes on it. As time passes ,or the user pans/zooms on the panel, the graphics are redrawn, so we could be redrawing several times per second.
Before attempting to add double buffering, the image repaint was slow, but my JComponents would render OK. The image would redraw multiple times as second as the user could drag and resize the label. I decided to attempt double buffering.
With my current double-buffered implementation below, the JLabel/Graphics redraw and show very smooth. However, The JFrame has other controls (JMenuBar, JSlider, JComboBox, etc.) that do not render properly and flicker a lot. I have to manually repaint() them, but then they flicker. What am I doing wrong with Double buffering? How can I get my image to repaint smoothly, but also allow my JComponetns to not flicker?
The view sets himself up to do double buffering. I also tried setting setIgnoreRepaint(true) as a way to fix my issue.
this.createBufferStrategy(2);
...
m_graphicsLabel.setIcon(new ImageIcon(bufferedImage));
This method below is called by the helper class when new graphics are available. I am manually repainting the JComponents, otherwise they wouldn't show up at all. But they flicker as this method can be called multiple times per second.
public void newViewGraphicsAvailable() {
m_xAxisZoomSlider.repaint();
m_yAxisZoomSlider.repaint();
lblZoom.repaint();
lblYZoom.repaint();
m_comboBox.repaint();
m_menuBar.repaint();
m_layersMenu.repaint();
}
This is the helper class that manipulates the graphics object with calls to graphics.drawOval() etc.
private void drawGraphics(){
BufferedImage blankImage = createBlankImage();
Graphics g = null;
try {
g = m_bufferedStrategy.getDrawGraphics();
g.drawImage(blankImage, 0, 0, null);
m_imageDrawer.draw((Graphics2D) g);
} finally {
g.dispose();
}
m_bufferedStrategy.show();
Toolkit.getDefaultToolkit().sync();
view.newGraphicsAvailable();
}
private BufferedImage createBlankImage()
{
short[] backgroundPixels = new short[Width * Height];
for(int index = 0; index < backgroundPixels.length; index++) {
backgroundPixels[index] = 0;
}
DataBuffer dbuf = new DataBufferUShort(backgroundPixels, WaterfallHeight * WaterfallWidth, 0);
int bitMasks[] = new int[]{0xFFFF};
SampleModel sampleModel = new SinglePixelPackedSampleModel(
DataBuffer.TYPE_USHORT, WaterfallWidth, WaterfallHeight, bitMasks);
WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null);
return new BufferedImage(m_indexColorModel, raster, false, null);
}

How to make smoother gradient background colors

Using GradientPaint for gradient background colors is not always satisfactory, especially in certain sizes. For example this code:
public class TestPanel extends JPanel {
protected void paintComponent( Graphics g ) {
Graphics2D g2d = (Graphics2D) g;
int w = getWidth();
int h = getHeight();
Color color1 = Color.BLACK;
Color color2 = Color.GRAY;
GradientPaint gp = new GradientPaint(0, 0, color1, 0, h, color2);
g2d.setPaint(gp);
g2d.fillRect(0, 0, w, h);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
TestPanel panel = new TestPanel();
frame.add(panel);
frame.setSize(200,200);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
produces the following:
The cyclic version looks even worse than that:
How can I make the gradient look smoother (in both cases)?
EDIT:
It seems that it is (at least partially) a monitor problem. The gradient colors look awful on my netbook (1024 x 600, True Color 32-bit) while they look a lot better on my desktop pc (1280 x 1024, True Color 32-bit). But the results are still not so smooth even with the desktop's monitor.
Both are using Java Version 6 Update 33.
Does that mean that an application should only use gradient backgrounds when it is viewed with higher resolutions?
EDIT 2:
Anyway, for those facing simlar problem or are just interested in this, I think that the only solution for a gradient color to look smoother is just higher resolution (assuming that the monitor is already set to true color of course) - which is not really a solution. Like I said in a comment, I thought that a 1024 x 600 resolution would be sufficient for a simple black-to-gray gradient color but it seems that I was wrong. When the same code is run on a computer with a monitor that supports higher resolution the gradient looks better, like through my desktop's monitor, 1280 x 1024. Unfortunately I dont have an option for better resolution but I believe it would look even smoother. I also noticed that the two images that I uploaded (taken from my netbook) when they are viewed through a better monitor these same images look smoother... so it must be just the resolution.
Since there is no solution I think that the only way to use specific gradient steps that would always look smooth (like black-to-gray, which even that seems to look bad in lower resolutions) is to have the gui program test for resolution on start-up and make the choice to show the appropriate gradient but I'm not sure if it is worth it. And using less gradient steps is just a compromise.
Due to lack of more/better responses, I've accepted the use of pre-dithered images as an answer.
I see your images, but cannot reproduce the banding. Do you have your display set to TrueColor? Are you using a recent Java version? Anyway, the following line might help:
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
EDIT: it seems that dithering is not supported in Java for TrueColor gradients, even if you don't have enough shades of gray... Some ideas:
use some colors
use pre-dithered image files
http://en.wikipedia.org/wiki/Colour_banding
I can see it. I think it's because the panel's size and the number of colors don't match up. One way to make it smoother is to make the panel's size an even multiple of the number of colors in the gradient. It's not perfect, but I don't know a better way.
public class TestPanel extends JPanel {
private static final int scale = 2;
private static final Color c1 = Color.BLACK;
private static final Color c2 = Color.GRAY;
private static final int size = (c2.getRed() - c1.getRed()) * scale;
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
#Override
protected void paintComponent( Graphics g ) {
Graphics2D g2d = (Graphics2D) g;
int w = getWidth();
int h = getHeight();
GradientPaint gp = new GradientPaint(0, 0, c1, 0, h, c2);
g2d.setPaint(gp);
g2d.fillRect(0, 0, w, h);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

java applet: is there a simple way to draw and erase in a two-layered 2D scene?

I have an applet which displays some data using circles and lines. As the data continually changes, the display is updated, which means that sometimes the circles and lines must be erased, so I just draw them in white (my background color) to erase them. (There are a lot of them, so erasing everything and then recomputing and redrawing everything except the erased item would be a horribly slow way to erase a single item.)
The logic of the situation is that there are two layers that need to be displayed, and I need to be able to erase an object in one layer without affecting the other layer. I suppose the upper layer would need to have a background color of "transparent", but then how would I erase an object, since drawing in a transparent color has no effect.
What distinguishes this situation from all the transparency-related help on the web is that I want to be able to erase lines and circles one-by-one from the transparent layer, overwriting their pixels with the "fully transparent" color.
Currently my applet draws (using just a single layer) by doing this in start():
screenBuffer = createImage(640, 480);
screenBufferGraphics = screenBuffer.getGraphics();
and this in paint():
g.drawImage(screenBuffer, 0, 0, this);
and objects are rendered (or "erased" by drawing in white) by commands like:
screenBufferGraphics.drawLine(x1,y1,x2,y2);
Is it easy to somehow make a second screen buffer with a transparent background and then be able to draw and erase objects in that buffer and render it over the first buffer?
This seems fairly quick, so long as the rendered image area remains around 640x480, the code can achieve from 125-165 FPS. The code tracks 2000 semi-transparent lines of width 4px, and moves them around in an area 8 times the size of the rendered image.
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.*;
import javax.swing.*;
import java.util.Random;
class LineAnimator {
public static void main(String[] args) {
final int w = 640;
final int h = 480;
final RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
hints.put(
RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY
);
final BufferedImage bi = new BufferedImage(w,h, BufferedImage.TYPE_INT_RGB);
final JLabel l = new JLabel(new ImageIcon(bi));
final BouncingLine[] lines = new BouncingLine[20000];
for (int ii=0; ii<lines.length; ii++) {
lines[ii] = new BouncingLine(w*8,h*8);
}
final Font font = new Font("Arial", Font.BOLD, 30);
ActionListener al = new ActionListener() {
int count = 0;
long lastTime;
String fps = "";
public void actionPerformed(ActionEvent ae) {
count++;
Graphics2D g = bi.createGraphics();
g.setRenderingHints(hints);
g.clearRect(0,0,w,h);
for (int ii=0; ii<lines.length; ii++) {
lines[ii].move();
lines[ii].paint(g);
}
if ( System.currentTimeMillis()-lastTime>1000 ) {
lastTime = System.currentTimeMillis();
fps = count + " FPS";
count = 0;
}
g.setColor(Color.YELLOW);
g.setFont(font);
g.drawString(fps,10,30);
l.repaint();
g.dispose();
}
};
Timer timer = new Timer(1,al);
timer.start();
JOptionPane.showMessageDialog(null, l);
System.exit(0);
}
}
class BouncingLine {
private final Color color;
private static final BasicStroke stroke = new BasicStroke(4);
private static final Random random = new Random();
Line2D line;
int w;
int h;
int x1;
int y1;
int x2;
int y2;
BouncingLine(int w, int h) {
line = new Line2D.Double(random.nextInt(w),random.nextInt(h),random.nextInt(w),random.nextInt(h));
this.w = w;
this.h = h;
this.color = new Color(
128+random.nextInt(127),
128+random.nextInt(127),
128+random.nextInt(127),
85
);
x1 = (random.nextBoolean() ? 1 : -1);
y1 = (random.nextBoolean() ? 1 : -1);
x2 = -x1;
y2 = -y1;
}
public void move() {
int tx1 = 0;
if (line.getX1()+x1>0 && line.getX1()+x1<w) {
tx1 = (int)line.getX1()+x1;
} else {
x1 = -x1;
tx1 = (int)line.getX1()+x1;
}
int ty1 = 0;
if (line.getY1()+y1>0 && line.getY1()+y1<h) {
ty1 = (int)line.getY1()+y1;
} else {
y1 = -y1;
ty1 = (int)line.getY1()+y1;
}
int tx2 = 0;
if (line.getX2()+x2>0 && line.getX2()+x2<w) {
tx2 = (int)line.getX2()+x2;
} else {
x2 = -x2;
tx2 = (int)line.getX2()+x2;
}
int ty2 = 0;
if (line.getY2()+y2>0 && line.getY2()+y2<h) {
ty2 = (int)line.getY2()+y2;
} else {
y2 = -y2;
ty2 = (int)line.getY2()+y2;
}
line.setLine(tx1,ty1,tx2,ty2);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setColor(color);
g2.setStroke(stroke);
//line.set
g2.draw(line);
}
}
Update 1
When I posted that code, I thought you said 100s to 1000s, rather than 1000s to 100,000s! At 20,000 lines the rate drops to around 16-18 FPS.
Update 2
..is this optimized approach, using layers, possible in Java?
Sure. I use that technique in DukeBox - which shows a funky plot of the sound it is playing. It keeps a number of buffered images.
Background. A solid color in a non-transparent image.
Old Traces. The older sound traces as stretched or faded from the original positions. Has transparency, to allow the BG to show.
Latest Trace. Drawn on top of the other two. Has transparency.
After a day of no proposed solutions, I started to think that Java Graphics cannot erase individual items back to a transparent color. But it turns out that the improved Graphics2D, together with BufferedImage and AlphaComposite, provide pretty much exactly the functionality I was looking for, allowing me to both draw shapes and erase shapes (back to full transparency) in various layers.
Now I do the following in start():
screenBuffer = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
screenBufferGraphics = screenBuffer.createGraphics();
overlayBuffer = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
overlayBufferGraphics = overlayBuffer.createGraphics();
I have to use new BufferedImage() instead of createImage() because I need to ask for alpha. (Even for screenBuffer, although it is the background -- go figure!) I use createGraphics() instead of getGraphics() just because my variable screenBufferGraphics is now a Graphics2D object instead of just a Graphics object. (Although casting back and forth works fine too.)
The code in paint() is barely different:
g.drawImage(screenBuffer, 0, 0, null);
g.drawImage(overlayBuffer, 0, 0, null);
And objects are rendered (or erased) like this:
// render to background
screenBufferGraphics.setColor(Color.red);
screenBufferGraphics.fillOval(80,80, 40,40);
// render to overlay
overlayBufferGraphics.setComposite(AlphaComposite.SrcOver);
overlayBufferGraphics.setColor(Color.green);
overlayBufferGraphics.fillOval(90,70, 20,60);
// render invisibility onto overlay
overlayBufferGraphics.setComposite(AlphaComposite.DstOut);
overlayBufferGraphics.setColor(Color.blue);
overlayBufferGraphics.fillOval(70,90, 30,20);
// and flush just this locally changed region
repaint(60,60, 80,80);
The final Color.blue yields transparency, not blueness -- it can be any color that has no transparency.
As a final note, if you are rendering in a different thread from the AWT-EventQueue thread (which you probably are if you spend a lot of time rendering but also need to have a responsive interface), then you will want to synchronize the above code in paint() with your rendering routine; otherwise the display can wind up in a half-drawn state.
If you are rendering in more than one thread, you will need to synchronize the rendering routine anyway so that the Graphics2D state changes do not interfere with each other. (Or maybe each thread could have its own Graphics2D object drawing onto the same BufferedImage -- I didn't try that.)
It looks so simple, it's hard to believe how long it took to figure out how to do this!

Categories