How can it save the previous graphics using repaint() in java? - java

I need to update my graphic, In which there are rectangulars. And at each step I need to highlight and modify the status of some of them.
But now in my code, for each repaint(), it draw a totally new picture.
I have read the followint similar quesion:
Keeping draw graphics - removing super.paintComponent ,which cannot solve my problem.
The next time it calls repaint(), those rectangulars disappear. It means it erases the prevous graphic.
Please help!
Here is the code: ("firstPaint = false" is just before the next repaint())
#Override
protected void paintComponent(Graphics g2)
{
super.paintComponent(g2);
final Graphics2D g = (Graphics2D) g2.create();
try{
if(firstPaint){
int status;
g.setColor(Color.BLACK);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
status = current[i][j];
if(status == 1)
{
g.fillRect(j * 20, i * 20, 20, 20);
}
}
}
}
if(executeResult == 2){
g.setColor(Color.BLUE);
for(Cell c:highlightedCells){
g.drawRect(c.j * 20, c.i * 20, 20, 20);
}
}
if(executeResult == 1){
g.setColor(Color.RED);
for(Cell c:highlightedCells){
g.fillRect(c.j * 20, c.i * 20, 5, 5);
}
}
}
finally{
g.dispose();
}
}

Related

Why JLabel is not repainted?

I have a project in which I have several components, . The problem is that when the repaint () command is executed, the JLabels are not painted. I want to make it clear that it is NOT a problem that there are graphics that are being painted on or anything. Apparently what happens is that within repaint (), when I force the JLabel to repaint, they do not paint unless you change the JLabel chain.
Initialization of the JLabel and define its parameters:
puntuacionL.setFont(new Font("Marker Felt", Font.PLAIN, 20));
puntuacionL.setBounds(710, 212, 150, 30);
puntuacionL.setOpaque(true);
puntuacionL.setBackground(Color.CYAN);
puntuacionL.setForeground(Color.white);
puntuacionL.setVisible(true);
comoJugar.setFont(new Font("Marker Felt", Font.PLAIN, 20));
comoJugar.setBounds(710, 245, 150, 30);
comoJugar.setOpaque(true);
comoJugar.setBackground(Color.CYAN);
comoJugar.setForeground(Color.white);
comoJugar.setVisible(true);
I have tried two options to force the redefinition of JLabel:
1. mietiqueta.setText("whatever").
In this option what I do is to redefine in the repaint () label string.
2.
Repaint the component: mietiqueta.paintComponent(g)
I would have uploaded a capture of the result, which would have been very useful, but you need at least 10 reputation points
Repaint() method:
#Override
public void update(Graphics g){
paint(g);
}
#Override
public void paint(Graphics g) {
if (offGraphics == null) {
offImage = createImage(900,900);
offGraphics = (Graphics2D) offImage.getGraphics();
}
puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
comoJugar.setText(" Pulsa H para ayuda");
Graphics2D g2d = (Graphics2D) g;
puntoIncialBala();
Image fondo = new ImageIcon(getClass().getResource("/imagenes/fondo.jpeg")).getImage();
Image img2 = new ImageIcon(getClass().getResource("/imagenes/fondoMapa.png")).getImage();
offGraphics.drawImage(img2, 30, 30, this);
int posVidaX = 710;
offGraphics.setColor(Color.CYAN);
offGraphics.fillRoundRect(710, 30, 150, 100, 10, 10);
for (int k = 0; k < vidas; k++) {
Image vida = new ImageIcon(getClass().getResource("/imagenes/vida.png")).getImage();
offGraphics.drawImage(vida, posVidaX, 50, this);
posVidaX = posVidaX + vida.getWidth(this);
}
timeLabel.paintComponents(g);
for (int k = 0; k < arrayCasilla.size(); k++) {
offGraphics.drawImage(arrayCasilla.get(k).getImg(), (int) arrayCasilla.get(k).getY(), (int) arrayCasilla.get(k).getX(), this);
}
Image img = new ImageIcon(getClass().getResource("/imagenes/BalaCanon.png")).getImage();
if (flagBala == true) {
//puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
if (pintarCasilla == true) {
puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
comoJugar.setText(" Pulsa H para ayuda");
queHayCasilla();
if (casilla[x][y].getTipo().compareTo("cangrejo") == 0) {
puntuacion = puntuacion + 1;
} else if (casilla[x][y].getTipo().compareTo("ron") == 0) {
posVidaX = 710;
//puntuacion = puntuacion + 1;
vidas--;
if (vidas == 0) {
timer.stop();
etiquetaFin.setVisible(true);
finalizar.setVisible(true);
contenedorFinal.setVisible(true);
}
offGraphics.setColor(Color.CYAN);
offGraphics.fillRoundRect(710, 30, 150, 100, 10, 10);
for (int k = 0; k < vidas; k++) {
Image vida = new ImageIcon(getClass().getResource("/imagenes/vida.png")).getImage();
offGraphics.drawImage(vida, posVidaX, 50, this);
posVidaX = posVidaX + vida.getWidth(this);
}
} else if (casilla[x][y].getTipo().compareTo("cofre") == 0) {
puntuacion = puntuacion + 2;
}
if (posY[y] < 441) {
arrayCasilla.add(casilla[x][y]);
}
for (int k = 0; k < arrayCasilla.size(); k++) {
if (posY[y] < 441) {
offGraphics.drawImage(arrayCasilla.get(k).getImg(), (int) arrayCasilla.get(k).getY(), (int) arrayCasilla.get(k).getX(), this);
}
}
animacion.stop();
if (!animacion.isRunning()) {
i = 0;
}
pintarCasilla = false;
flagBala = false;
} else {
offGraphics.drawImage(img, (int) x1, (int) x2, this);
}
}
try {
imgB = ImageIO.read(getClass().getResource("/imagenes/canon.png"));
} catch (IOException ex) {
Logger.getLogger(PanelCanon.class.getName()).log(Level.SEVERE, null, ex);
}
offGraphics.setColor(Color.gray);
offGraphics.fillRect(30, 720, 646, 122);
AffineTransform tx = AffineTransform.getRotateInstance(Math.PI / 2 - anguloRotacion, imgB.getWidth(this) / 2, imgB.getHeight(this) / 2);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
tx.rotate(Math.PI / 2 - anguloRotacion);
System.out.println(tx.toString());
offGraphics.drawImage(op.filter(imgB, null), 360, 740, null);
g2d.drawImage(offImage, 0, 0, this);
puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
comoJugar.setText(" Pulsa H para ayuda");
System.out.println("Posicion bala x" + x1);
System.out.println("Posicion bala y: " + x2);
}
I have a timer that may be interfering in some way, this timer is responsible for controlling the countdown.
Timer code:
timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (seconds == 0 && minutes == 0) {
timer.stop();
etiquetaFin.setVisible(true);
finalizar.setVisible(true);
contenedorFinal.setVisible(true);
} else if (seconds > 0) {
seconds--;
} else if (minutes > 0) {
minutes--;
seconds = 59;
}
revalidate();
//puntuacionL.paintImmediately(710, 212, 150, 30);
//puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
//comoJugar.setText(" Pulsa H para ayuda");
timeLabel.setText(" "+timeFormatter.format(minutes) + ":" + timeFormatter.format(seconds));
}
});
timer.start();
And this is where the magic happens and I do not understand anything anymore. As you can see inside the event I have commented the lines:
puntuacionL.setText(" Puntuación: "+Integer.toString(puntuacion));
comoJugar.setText(" Pulsa H para ayuda");
If within the event of the Timer I redefine the Labels, with a different string from the paint () they are painted, but of course it does not work for me, since I want the string not to be modified.
I have not put the whole class because it is quite large, but if someone still needs the complete context, happy to edit the question and put all the code.
I am really not sure what you are trying to do, but you appear not to understand the basic concepts of using components and of custom painting.
Here are the problems I see:
Don't override update(...). There is no reason to do this.
Don't override paint(). Custom painting is done by overriding the paintComponent(...) method of the component.
Don't read images in the painting method. A component is repainted when you specifically invoke repaint() on the component or when Swing determines the components needs to be painted. Images should be read in the constructor of your class to make the painting code as efficient as possible.
Don't change the properties of your class in the painting method. This would especially apply to the changing the text of an external component like a JLabel.
Don't invoke paintComponents() on any component from within the painting method.
Don't access the Timer in the painting method. Again, the whole purpose of the painting code is to paint the current state of the component, not alter the state. The purpose of the Timer is the change the state of the component every time it fires. So it is the Timer logic that should be changing the text on the JLabel. The JLabel will then automatically repaint itself. See: Program freezes during Thread.sleep() and with Timer for a basic example of undateing a JLabel from withing a Timer.
Read the section from the Swing tutorial on Custom Painting for more information on the basic of painting.

Java's g.drawImage takes significantly longer on Windows over Linux

For a class project I and a few others are working on an isometric game. Everything is being drawn in one JPanel using a buffered image. Each piece of artwork is done in fireworks and saved as a .png.
On Linux, the time it takes to redraw the map every game tick is around 3ms. On Windows (and also OSx) it's around 100ms spiking to 500ms.
This effect has been observed on 4 different computers ranging from typical laptop to an i7-3770K + 660 gaming machine. The CPU usage when this occurs is around 10-20% with RAM usage of the program being around 1GB. The problem has been researched on the internet in many places to no avail and also our section leaders (who are in charge of the project) were stumped. Any ideas?
Here is the paint component in the JPanel and the setEntityImage method. You can see where the time stamps are pulled from at the bottom of the paintComponent. The setEntityImage is being set every 500ms with a new BufferedImage that is pre-drawn on another thread before being passed into this JPanel. I'm including that code as I'm also curious if it's a possible threading issue.
This code is called every 500 ms to consist of a 'game tick', it draws the images (which are all pre-loaded in a static singleton class).
BufferedImage entityImg = ImageUtil.getFromGraphics(map.getWidth() * TILE_WIDTH
+ TILE_WIDTH, map.getHeight() * TILE_HEIGHT + TILE_HEIGHT);
Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i = 0; i < map.getHeight(); i++)
{
for (int j = 0; j < map.getWidth(); j++)
{
try
{
int x, y;
x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
+ map.getWidth() * TILE_WIDTH_HALF;
y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;
Entity e = map.getTile(i, j).getEntity();
if (e != null)
{
g2.drawImage(e.getImage(), x, y, 32, 32, null);
}
}
catch (Exception e)
{
}
}
}
panel.setEntityImage(entityImg);
This code is in our MapPanel which extends a JPanel.
public void setEntityImage(BufferedImage entityImg)
{
this.entityImg = entityImg;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics g2 = (Graphics2D) g;
g2.fillRect(0, 0, tileImg.getWidth(), tileImg.getHeight());
// Draw tiles
g2.drawImage(tileImg, 0, 0, null);
// Draw box over square mouse is hovering over
if (hover)
{
g2.setColor(new Color(255, 255, 255, 128));
int screenX = (this.x * 16 - this.y * 16) + Window.WIDTH * 16;
int screenY = (this.x * 8 + this.y * 8) + 16;
g2.drawLine(screenX, screenY + 8, screenX + 16, screenY);
g2.drawLine(screenX + 16, screenY, screenX + 32, screenY + 8);
g2.drawLine(screenX, screenY + 8, screenX + 16, screenY + 16);
g2.drawLine(screenX + 16, screenY + 16, screenX + 32, screenY + 8);
}
long time = System.currentTimeMillis();
g2.drawImage(entityImg, 0, 0, null);
System.out.println(System.currentTimeMillis() - time);
}
There's some kind of internal optimisation caching going, the particulars I'm not aware of. Essentially, if the BufferedImage is not updated, it paints in 0ms.
I did two optimizations, not sure if either work alone or not but it made a difference in the performance, but not in the time delay (ie, When I dragged the map about, it was significantly better)
The first was in Window#tickUpdate. Rather the creating a new BufferedImage every 500ms, this simply re-uses a single BufferedImage only creating a new instance if there wasn't a previous instance or the size has changed...
public void tickUpdate(Map map) {
int width = map.getWidth() * TILE_WIDTH + TILE_WIDTH;
int height = map.getHeight() * TILE_HEIGHT + TILE_HEIGHT;
if (entityImg == null || entityImg.getWidth() != width || entityImg.getHeight() != height) {
entityImg = ImageUtil.getFromGraphics(width, height);
}
Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
g2.setBackground(new Color(255, 255, 255, 0));
g2.clearRect(0, 0, width, height);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
for (int i = 0; i < map.getHeight(); i++) {
for (int j = 0; j < map.getWidth(); j++) {
Entity e = map.getTile(i, j).getEntity();
if (e != null) {
int x, y;
x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
+ map.getWidth() * TILE_WIDTH_HALF;
y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;
g2.drawImage(e.getImage(), x, y, 32, 32, null);
}
}
}
g2.dispose();
panel.setEntityImage(entityImg);
panel.repaint();
}
And because the BufferedImage could be updated while a paint was occurring, in the MapPanel, I created a another BufferedImage onto which the "entity" image was painted.
public void setEntityImage(BufferedImage img) {
entityLock.lock();
try {
if (entityImg == null || entityImg.getWidth() != img.getWidth() || entityImg.getHeight() != img.getHeight()) {
entityImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g2d = entityImg.createGraphics();
g2d.drawImage(img, 0, 0, null);
g2d.dispose();
} finally {
entityLock.unlock();
}
}
entitytLock is just a ReentrantLock which I used to stop the paintComponent method from trying to paint the BufferedImage while it was been updated.
This doesn't change the timing, but made it, visually, better...
An alternative is to use a VolatileImage...

Draw a string on a shape

I have an oval and I want to label what it is, for my chess game. I want to use ovals with the names of the pieces on them as strings but I can't seem to get it to work.
Is it even possible to drawString on a shape?
My code
public void drawPieces(Graphics2D g2d){
for(int x = 0; x < 8; x++) {
for(int y = 0; y < 8; y++) {
//reds
if(board[x][y]==24){
g2d.setColor(Color.red);
g2d.fillOval(x*80, y*80, 80, 80);
//drawstring goes here
g2d.setColor(Color.blue);
g2d.drawString("test", x*80, y*80);
}
Any suggestions are welcome
Edit, my grid method in case it helps.
public void drawGrid(Graphics2D g2d){
g2d.drawLine(0, 0, 0, 639);
g2d.drawLine(0, 0, 639, 0);
g2d.drawLine(0, 639, 639, 639);
g2d.drawLine(639, 0, 639, 639);
// draw the horizontal lines using a loop from one to 7, coordiates of each line is (0, x*80, 640, x*80) also
// draw vertical lines with coordinates of (x*80, 0, x*80, 640)
for(int i = 1; i < 8; i++) {
g2d.drawLine(0, i*80, 640, i*80);
g2d.drawLine(i*80, 0, i*80, 640);
}
//drawing the black and white squares
for (int row = 0; row < 8; row++)
for (int col = 0; col < 8; col++) {
if ( (row % 2 == 0 && col % 2 == 0) || ( row % 2 == 1 && col % 2 == 1) ){
g2d.setColor(black);
g2d.fillRect(row*80,col*80,80,80);
}
}
}
I can only say that it is possible to draw a string over an oval, and I'm doing just that in my own game. The top drawing code should be fine. You just need to check the parameter you pass to the drawing method and the if-condition. Here is the excerpt from my code where i draw
over the oval using slightly different method, but your should work too:
public void draw(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g2d.fill(new Ellipse2D.Double(center.x, center.y, itemSize, itemSize));
g2d.setColor(Color.white);
g2d.setFont(new Font("Arial", Font.BOLD, 14));
g2d.drawString(itemName, (int)center.x, (int)center.y+18);
}
itemName is some string, and just not to confuse I thing that first two parametres to
g2d.fill(...(-,-,itemSize, itemSize)) are not the center of elipse but the top left corner of its sourounding rectangle.

Algorithm that searches a 2D array

I'm trying to recreate a tetris-like game. I have a 2D array, "_tiles" that stores objects called "ColorShape" .
private ColorShape[][] _tiles = new ColorShape[8][17];;
im trying to make a quickDrop() method for when I hit the down arrow key to find the next available slot in the array to quickly place the shapes. I am having a hard time figuring out the algorithm that will search the rows below the current piece in the column that the piece is in.
This is what I tried to do so far but I think that I am going about this all wrong: (the Pieces are 30x30 pixels thats why they are being divided by 30, so that the array location corresponds to the x and y locations of the shape)
public void quickDrop(){
// j is the column that the piece is currently in
int j = _proxyPiece.getXLocation()/30;
for (int i=0; i<17;i++){
if(_tiles[j][i] == null)
continue;
else if (_tiles[j][i] != null){
_tiles[j][i-2] = _proxyPiece.getFirstPiece();
_tiles[j][i-1] = _proxyPiece.getSecondPiece();
repaint();
_proxyPiece.setPiece(this.newPiece());
repaint();
break;
}
}
}
public void paintComponent(Graphics g) {
if (_pauseState == false){
_pauseText.setVisible(false);
super.paintComponent(g);
// simplify the positioning of things.
g.translate(0, 0);
//Draws the board outline and fills it white
g.setColor(Color.WHITE);
g.drawRect(0, 0, 240, 480);
g.fillRect(0, 0, 240, 480);
//Draws a dark gray grid
g.setColor(Color.DARK_GRAY);
for(int x = 0; x < COL_COUNT + 1; x++) {
for(int y = 0; y < VISIBLE_ROW_COUNT+1; y++) {
g.drawLine(0, y * TILE_SIZE, COL_COUNT * TILE_SIZE, y * TILE_SIZE);
g.drawLine(x * TILE_SIZE, 0, x * TILE_SIZE, VISIBLE_ROW_COUNT * TILE_SIZE);
}
}
Graphics2D aBetterPen = (Graphics2D)g;
_proxyPiece.fill(aBetterPen);
for (int i = 0; i<16; i++){
for(int j=0; j<8;j++){
if(_tiles[j][i] != null)
_tiles[j][i].fill(aBetterPen);
}
}
}
else if (_pauseState == true){
_pauseText.setVisible(true);
super.paintComponent(g);
// simplify the positioning of things.
g.translate(0, 0);
g.setColor(Color.WHITE);
g.drawRect(0, 0, 240, 480);
g.fillRect(0, 0, 240, 480);
}
}
One algorithm to solve this:
Take the current dropping piece and move it over every Y value from its current position downwards.
If it collides with a previously placed piece, or exceeds the bottom of your grid, the last Y position you checked is a valid solution.
If you want all valid solutions instead of just one, repeat this algorithm for all possible rotations of the currently selected piece.
Here's an example of the algorithm. It won't work with your code as is, but it will get you off to a quick start.
ColorShape[][] grid = new ColorShape[width][height];
TetrisShape shape = new TetrisShape(Shape.L_SHAPE);
//position will be bottom left coordinate of bounding rectangle of dropping shape.
Point position = new Point(shape.getPosition());
int resultY = -1;
for(int dy=0;dy<height;dy++){
for(int y=0;y<height;y++){
int shapeY = position.y+y;
if(shapeY>=height || shape.intersectsGrid(grid)){
resultY = shapeY -1;
break;
}
}
}

Drawing a shape but keeping the most previous shape on screen in java

I've created a function to where I can click somewhere in a Jpanel and it draws a shape at the position where the mouse clicked. The problem I am having is when I click in a new position, it moves the shape and redraws it. I would like the previous shape to "Burn" into the screen and stay there. It doesn't have to have any data tied to it, I just want the image of the shape to show where it used to be each time. I have tried many different things, but no success. here is what I mean:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.fillRect(n, m, 32, 32); //I want each one of these shapes to be new, not
//moving, but redrawn
////////////////////////////////////////////////
//This is just drawing a grid and doing other things(irrelevant)
g2.fill(new Ellipse2D.Double(x, y, 32, 32));
for (int i = 0; i < 500; i += 32) {
g2.drawRect(i, j, 32, 32);
for (int j = 0; j < 500; j += 32) {
g2.drawRect(i, j, 32, 32);
}
}
if (paintColBlock){
System.out.println("Drawing Block at " + n +"," + m);
paintColBlock = false;
}
/////////////////////////////////////////////////////////////////////
}
Keep an ArrayList of Points like this:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
for(Point p : points)
g2.fillRect(p.x, p.y, 32, 32);
Adding a new Point to the array at each mouse click, and call repaint():
public void mousePressed(MouseEvent evt){
points.add(new Point(evt.getX(),evt.getY());
repaint();
}

Categories