I'm working on a project to create a screen saver that draws random shapes. I have a few questions, but my main concern right now is how to get the shapes to stay on the screen and not just disappear after they are created. Here is my code. I cannot use any loops and I am not looking to change any of the functionality of what I have yet (other than possibly shapesDrawn).
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ScreenSaver2 extends JPanel implements ActionListener {
private JFrame frame = new JFrame("FullSize");
private Rectangle rectangle;
boolean full;
protected void paintComponent(Graphics g) {
int r = (int)(Math.random() * 255);
int gr = (int)(Math.random() * 255);
int b = (int)(Math.random() * 255);
Color color = new Color(r, gr, b);
int width = 10 + (int)(Math.random() * 40);
int height = 10 + (int)(Math.random() * 40);
int x = (int)(Math.random() * (getWidth() - width));
int y = (int)(Math.random() * (getHeight() - height));
int whichShape = (int)(Math.random() * 3);
int shapesDrawn = 0;
super.paintComponent(g);
if (shapesDrawn >= 30) {
shapesDrawn = 0;
}
switch (whichShape) {
case 0:
g.setColor(color);
g.drawLine(x, y, x + width, y + height);
shapesDrawn++;
break;
case 1:
g.setColor(color);
g.drawRect(x, y, width, height);
shapesDrawn++;
break;
case 2:
g.setColor(color);
g.drawRoundRect(x, y, width, height, 25, 25);
shapesDrawn++;
break;
case 3:
g.setColor(color);
g.drawOval(x, y, width, height);
shapesDrawn++;
break;
}
}
ScreenSaver2() {
// Remove the title bar, min, max, close stuff
frame.setUndecorated(true);
// Add a Key Listener to the frame
frame.addKeyListener(new KeyHandler());
// Add this panel object to the frame
frame.add(this);
// Get the dimensions of the screen
rectangle = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration().getBounds();
// Set the size of the frame to the size of the screen
frame.setSize(rectangle.width, rectangle.height);
frame.setVisible(true);
// Remember that we are currently at full size
full = true;
// set and initialize timer
Timer t = new Timer(500, this);
t.setDelay(500);
t.start();
}
// This method will run when any key is pressed in the window
class KeyHandler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
// Terminate the program.
if (e.getKeyChar() == 'x') {
System.out.println("Exiting");
System.exit(0);
}
// Change background color
else if (e.getKeyChar() == 'r') {
System.out.println("Change background color");
setBackground(new Color((int)(Math.random() * 256), (int)(Math.random() * 256), (int)(Math.random() * 256)));
repaint();
}
// Resize to half-screen
else if (e.getKeyChar() == 'z') {
System.out.println("Resizing");
frame.setSize((int)rectangle.getWidth() / 2, (int)rectangle.getHeight());
}
}
}
public void actionPerformed(ActionEvent e) {
repaint();
}
public static void main(String[] args) {
ScreenSaver2 obj = new ScreenSaver2();
}
}
Rather than painting each new shape within the paintComponent
directly to the Graphics
context, use a backing buffer to render the shapes to first and paint this to the Graphics
context.
This way, you would simply need to maintain a simple counter each time a new shape is rendered.
This is also troublesome, because paintComponent
may be called for any number of reasons, which, you don't control many, meaning that it would be possible for your program to paint more shapes before any timer ticks have actually occurred...
Updated with example
The only choice you have left is to create a backing buffer, onto which you can paint each change as required. This will "store" each change between paint cycles. You could then simply paint this to the screen...
private BufferedImage img;
//...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Create the backing buffer
// This is a little cheat, creating a new image when the number of shapes
// exceeds the requirements, but it saves messing about with clearing
// a alpha image ;)
if (img == null || shapesDrawn >= 30) {
img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
shapesDrawn = 0;
} else if (img.getWidth() != getWidth() || img.getHeight() != img.getHeight()) {
// Update the backing buffer to meet the requirements of the changed screen size...
BufferedImage buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D gbuffer = buffer.createGraphics();
gbuffer.drawImage(img, 0, 0, this);
gbuffer.dispose();
img = buffer;
}
// Get a reference to the backing buffers graphics context...
Graphics2D gbuffer = img.createGraphics();
// Paint the shapes to the backing buffer...
int r = (int) (Math.random() * 255);
int gr = (int) (Math.random() * 255);
int b = (int) (Math.random() * 255);
Color color = new Color(r, gr, b);
int width = 10 + (int) (Math.random() * 40);
int height = 10 + (int) (Math.random() * 40);
int x = (int) (Math.random() * (getWidth() - width));
int y = (int) (Math.random() * (getHeight() - height));
int whichShape = (int) (Math.random() * 3);
int shapesDrawn = 0;
switch (whichShape) {
case 0:
gbuffer.setColor(color);
gbuffer.drawLine(x, y, x + width, y + height);
shapesDrawn++;
break;
case 1:
gbuffer.setColor(color);
gbuffer.drawRect(x, y, width, height);
shapesDrawn++;
break;
case 2:
gbuffer.setColor(color);
gbuffer.drawRoundRect(x, y, width, height, 25, 25);
shapesDrawn++;
break;
case 3:
gbuffer.setColor(color);
gbuffer.drawOval(x, y, width, height);
shapesDrawn++;
break;
}
// Dispose of the buffers graphics context, this frees up memory for us
gbuffer.dispose();
// Paint the image to the screen...
g2d.drawImage(img, 0, 0, this);
g2d.dispose();
}
Possibly the worst advice I've ever had to give
Change...
super.paintComponent(g);
if (shapesDrawn >= 30) {
shapesDrawn = 0;
}
To...
if (shapesDrawn >= 30) {
super.paintComponent(g);
shapesDrawn = 0;
}