Rotation with Graphics2D - java

I am currently experimenting with the Graphics2D and the KeyListener, and I am currently testing out rotating objects (in this case a pentagon). Now, it's working in the sense that it rotates, except it is supposed to rotate by 1 radian each time VK_RIGHT or VK_LEFT are pressed.
However, currently it does that for the first key press only. From then on, it creates a pattern of rotating it by 1, 2, 3, 4, 5... and so on radians each time (the nth keypress rotates it by nth radians) instead of just 1 radian per keypress.
Creating the JFrame:
import javax.swing.JFrame;
public class Main {
public Main() {
JFrame window = new JFrame("Rotating Hexagons");
window.setSize(800,600);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setContentPane(new RotatingHexagon());
window.pack();
window.setVisible(true);
}
public static void main(String[]args) {
new Main();
}
}
RotatingHexagon Class:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class RotatingHexagon extends JPanel implements KeyListener {
private Polygon poly;
private int[] xpoints = { 0, -10, -7, 7, 10 };
private int[] ypoints = { -10, -2, 10, 10, -2 };
private int rotation = 0;
public RotatingHexagon() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
addKeyListener(this);
}
public void paint(Graphics g) {
init();
Graphics2D g2d = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.WHITE);
g2d.drawString(rotation + " radians", 10, 20);
g2d.translate(width / 2, height / 2);
g2d.scale(20, 20);
g2d.rotate(Math.toRadians(rotation));
g2d.setColor(new Color(255, 100, 100));
g2d.fill(poly);
g2d.setColor(Color.WHITE);
g2d.draw(poly);
}
public void keyPressed(KeyEvent k) {
switch(k.getKeyCode()) {
case KeyEvent.VK_LEFT:
rotation--;
if (rotation < 0) rotation = 359;
repaint();
break;
case KeyEvent.VK_RIGHT:
rotation++;
if (rotation > 360) rotation = 0;
repaint();
break;
}
}
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
}
I really don't have any idea why it isn't just rotating 1 radian each time, so any help is appreciated.
Thanks.

the reason is calling the init() function in the paint() method again and again. So the KeyListener is added multiple times, causing that it is called multiple times, incrementing your counter more every time you press the key.
Move it to the constructor:
public RotatingHexagon() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
addKeyListener(this);
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
}
Andy

You should probally just use a persistant AffineTransform to do the rotation. They are a lot more powerfull.
I also saw several issues in your code, you are calling the init method each frame - this could be 60 times per second. In this you are adding a new keylistener each frame. You are also creating a new polygon which would slow down performance.
I've made some changes to your code and and i've used AffineTransforms as an example. Have a look and see if this helps.
package com.joey.testing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class AffineTransformTest extends JPanel implements KeyListener {
private Polygon poly;
private int[] xpoints = { 0, -10, -7, 7, 10 };
private int[] ypoints = { -10, -2, 10, 10, -2 };
private int rotation = 0;
AffineTransform transform;
AffineTransform rotationTransform;
AffineTransform translateTransform;
public AffineTransformTest() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
//Do Init here - no point in creating new polygon each frame.
//It also adds the key listener each time
init();
updateTransforms();
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
transform = new AffineTransform();
rotationTransform = new AffineTransform();
translateTransform = new AffineTransform();
addKeyListener(this);
}
//Use Paint Compoent
#Override
public void paintComponent(Graphics g) {
//Always call super to clear the screen
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.WHITE);
g2d.drawString(rotation + " radians", 10, 20);
//Store old transform so we can apply it
AffineTransform old = g2d.getTransform();
//Add Transform and move polygon
g2d.setTransform(transform);
g2d.setColor(new Color(255, 100, 100));
g2d.fill(poly);
g2d.setColor(Color.WHITE);
g2d.draw(poly);
g2d.setTransform(old);
}
public void keyPressed(KeyEvent k) {
switch(k.getKeyCode()) {
case KeyEvent.VK_LEFT:
rotation--;
if (rotation < 0) rotation = 359;
repaint();
break;
case KeyEvent.VK_RIGHT:
rotation++;
if (rotation > 360) rotation = 0;
repaint();
break;
}
updateTransforms();
}
public void updateTransforms(){
//Resets transform to rotation
rotationTransform.setToRotation(Math.toRadians(rotation));
translateTransform.setToTranslation(getWidth()/2, getHeight()/2);
//Chain the transforms (Note order matters)
transform.setToIdentity();
transform.concatenate(translateTransform);
transform.concatenate(rotationTransform);
}
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
public static void main(String input[]){
JFrame f= new JFrame("AffineTransform");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(100, 100);
f.setResizable(true);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(new AffineTransformTest());
f.show();
}
}

Related

How do I move from one JFrame window to another on clicking the space bar?

This is the main file where I've created the main method. I've added the JFrames here to make it easier to understand. I created a boolean variable in the second file, and whenever the spacebar is pressed, the variable updates to true. I then use the variable by creating an object in the main class and then add conditional statements to make 1 frame visible or vice versa.
import javax.swing.JFrame;
public class MainCode{
public static void main(String args[]){
JFrame f=new JFrame();
Gameplay GP = new Gameplay();
GameplayLv2 g2 = new GameplayLv2();
JFrame frame2 = new JFrame();
f.setBounds(10,10,1000,700);
f.setTitle("Breakout Ball");
f.setResizable(false);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(GP);
if(GP.flagCheck==true){
f.setVisible(false);
f.remove(GP);
frame2.setBounds(10,10,1000,700);
frame2.setTitle("Breakout Ball");
frame2.setResizable(false);
frame2.setVisible(true);
frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame2.add(g2);
}
}
}
This is the game file, level 1. The issue that I keep facing is at the bottom of this code, the keyEvent block.
import javax.swing.JPanel;
import java.awt.*;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.Timer;
import javax.swing.JFrame;
public class Gameplay extends JPanel implements KeyListener, ActionListener{
public boolean play=false; //prevents game from starting on its own...
public int score=0; //scorebar will be 0 by default...
public int totalBlocks = 30; //no.of tiles/blocks player must knock down...
public Timer speed; //speed of ball...
public int delay=5; //speed of ball....
public int playerX = 300; //starting position of the slider..
public int ballPosX = 210; //starting position of the ball..(x-axis)
public int ballPosY = 350; //starting position of the ball..(y-axis)
public int ballDirX = -1; //direction for ball to start moving in...(x axis)
public int ballDirY = -2; //direction for ball to start moving in...(y axis) //ALSO CONTROLS SPEED OF BALL ALONG Y-AXIS
public boolean flagCheck;
MapGen map;
Color ball_color = new Color(255, 252, 37); //Setting custom colors for every element using RGB values...
Color border_color = new Color(232, 20, 171);
Color border2_color = new Color(20, 232, 213);
Color slider_color = new Color(0, 240, 255);
Color bg_color = new Color(11,10,30);
Color tiles_color = new Color(255, 255, 255);
JFrame frame1 = new JFrame();
public Gameplay(){
map = new MapGen(3,10);
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
speed = new Timer(delay,this);
speed.start();
flagCheck = false;
}
public void paint(Graphics g){
g.setColor(bg_color);
g.fillRect(1, 1, 1000, 700);
//drawing the map(tiles)
map.draw((Graphics2D)g);
// adding borders so that whenever ball hits border, game ends..
g.setColor(border_color);
g.fillRect(0, 0, 10, 1000);
g.setColor(border_color);
g.fillRect(975, 0, 10, 1000);
g.setColor(border2_color);
g.fillRect(0, 0, 1000, 10);
// editing the slider/player...
g.setColor(slider_color);
g.fillRect(playerX, 620, 120, 5);
// editing the ball...
g.setColor(ball_color);
g.fillOval(ballPosX, ballPosY, 20, 20);
//scoring system
g.setColor(Color.white);
g.setFont(new Font("helvetica", Font.BOLD, 20));
g.drawString("SCORE:"+score, 800, 650);
if(ballPosY>670){ //what'll happen when the ball moves out of screen?(anything after y-axis670)? This...
play=false;
ballDirX=0;
ballDirY=0;
g.setColor(bg_color); //this BG will act as a layer on top of the previous screen, hence, covering unnecessarry elements...
g.fillRect(1, 1, 1000, 700);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 25)); //scoreboard display after game ends..
g.drawString("SCORE:"+score, 440, 200);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 40)); //game over text displayed after game ends..
g.drawString("GAME OVER!", 370, 300);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 40)); //restart text displayed after game ends..
g.drawString("Press enter to retry", 260, 405);
}
if(score==30){ //make it 150 //what'll happen when the score is 150, ie win
play=false;
ballDirX=210;
ballDirY=350;
g.setColor(bg_color); //this BG will act as a layer on top of the previous screen, hence, covering unnecessarry elements...
g.fillRect(1, 1, 1000, 700);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 25)); //scoreboard display after game ends..
g.drawString("SCORE:"+score, 420, 200);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 40)); //game over text displayed after game ends..
g.drawString("YOU WIN", 400, 300);
g.setColor(slider_color);
g.setFont(new Font("helvetica", Font.BOLD, 40)); //go to next level text displayed after game ends..
g.drawString("Press space to continue to next level", 155, 405);
}
g.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
speed.start();
if(play==true){
if(new Rectangle(ballPosX, ballPosY, 20, 20).intersects(new Rectangle(playerX, 620, 120, 10)))
//creates an invisible rectangle around the slider and ball which helps with interaction control
{
ballDirY=-ballDirY;
}
A: for(int i=0; i<map.map.length; i++){ //one of the maps are MapGen map; while the other map is from #mapGen class to access the double dimensional array...
for(int j=0; j<map.map[0].length; j++){
if(map.map[i][j]>0){
int tX = j*map.tW+80;
int tY = i*map.tH+50;
int tW = map.tW;
int tH = map.tH;
Rectangle rect = new Rectangle(tX, tY, tW, tH); //creates invisible rects around tiles to detect interaction with ball...
Rectangle ballRect = new Rectangle(ballPosX, ballPosY, 20, 20);
Rectangle tileRect = rect;
if(ballRect.intersects(tileRect)){ //removes one triangle everytime interaction takes place and increases the score by 5...
map.setTileValue(0, i, j);
totalBlocks--;
score+=5;
if(ballPosX+19 <= tileRect.x || ballPosX+1 >= tileRect.x + tileRect.width){ //specifies which direction the ball must head to after interacting with the tiles...
ballDirX=-ballDirX;
}
else{
ballDirY=-ballDirY;
}
break A;
}
}
}
}
ballPosX+=ballDirX;
ballPosY+=ballDirY;
//specifies where the ball will have to shift position. In this case, on hitting either of the borders, position(x-y-coordinates) will change....
if(ballPosX<0)
ballDirX=-ballDirX;
if(ballPosY<0)
ballDirY=-ballDirY;
if(ballPosX>960)
ballDirX=-ballDirX;
}
repaint(); //will re-draw every element(slider etc)...
}
#Override
public void keyTyped(KeyEvent e) {} //unnecessarry methods that when removed produced error...so..yeah...
#Override
public void keyReleased(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_RIGHT)
{
if(playerX>=855) //prevents the paddle/slider from moving out of the screen
playerX=855;
else
moveRight();
}
if(e.getKeyCode()==KeyEvent.VK_LEFT)
{
if(playerX<=10) //prevents the paddle/slider from moving out of the screen
playerX=10;
else
moveLeft();
}
if(e.getKeyCode()==KeyEvent.VK_ENTER){ //What actions must take place when "ENTER" key is pressed. In this context, restarting of game...
if(!play){
play=false;
ballPosX = 210;
ballPosY = 350;
ballDirX = -1;
ballDirY = -2;
playerX = 300;
score=0;
totalBlocks=30;
map=new MapGen(3,10);
repaint();
}
}
if(e.getKeyCode()==KeyEvent.VK_SPACE){ //what actions must take place when "SPACE" key is pressed. In this context, moving to new level-to be implmented...
if(!play && score==30){
flagCheck=true;
System.out.println(flagCheck); //console stuff
}
}
}
public void moveRight(){ //helps with the movement of slider. +values move right on x-axis whereas -values move left on x-axis
play=true;
playerX+=40;
}
public void moveLeft(){
play=true;
playerX-=40;
}
}
You're running in an event driven environment, that is, something happens and then you respond to it.
You've taken a procedural approach to your design. Showing a window and some how hoping that the state of the window "block" the execution workflow and allow you to monitor some other state. This is not how (non modal) windows work.
When you call setVisible, it will return (almost) immediately, the display of the window will occur at some point in the future after that, depending on the OS and other overheads.
This means that GP.flagCheck will also fail.
Instead, stop trying to do this with windows. It's annoying (to the user). Instead, just switch the panels, for example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MasterPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MasterPane extends JPanel {
public MasterPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "spaced");
am.put("spaced", new AbstractAction() {
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
JPanel next = null;
if (counter == 0) {
counter = 1;
next = new LevelTwoPane();
} else if (counter == 1) {
counter = 0;
next = new LevelOnePane();
}
if (next != null) {
removeAll();
add(next);
revalidate();
repaint();
}
}
});
setLayout(new BorderLayout());
add(new LevelOnePane());
}
}
protected abstract class AbstractGamePane extends JPanel {
private Timer timer;
protected abstract void tick();
protected void start() {
if (timer != null) {
timer.stop();
timer = null;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tick();
}
});
timer.start();
}
protected void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
#Override
public void addNotify() {
super.addNotify();
start();
}
#Override
public void removeNotify() {
super.removeNotify();
stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public static class LevelOnePane extends AbstractGamePane {
protected static final String TEXT = "Level one";
private int xPos = 0;
private int xDelta = 1;
#Override
protected void tick() {
xPos += xDelta;
FontMetrics fm = getFontMetrics(getFont());
if (xPos > getWidth() - fm.stringWidth(TEXT)) {
xPos = getWidth() - fm.stringWidth(TEXT);
xDelta *= -1;
} else if (xPos < 0) {
xPos = 0;
xDelta *= -1;
}
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(TEXT, xPos, yPos);
g2d.dispose();
}
}
public class LevelTwoPane extends AbstractGamePane {
protected static final String TEXT = "Level two";
private int xPos = 0;
private int xDelta = 1;
#Override
protected void tick() {
xPos += xDelta;
FontMetrics fm = getFontMetrics(getFont());
if (xPos > getWidth() - fm.stringWidth(TEXT)) {
xPos = getWidth() - fm.stringWidth(TEXT);
xDelta *= -1;
} else if (xPos < 0) {
xPos = 0;
xDelta *= -1;
}
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(TEXT, xPos, yPos);
g2d.dispose();
}
}
}
Even this is unnecessarily heavy handed. An overall better solution would be to use a model/view/controller.
The model would carry all the information about the level that the view needed to render it and the controller would manage the interaction between the view and the model (ie the user taps a button, the view tells the controller and the controller makes decisions about what needs to be done, like updating the model or moving to the next level or what ever).
You really should take a look at How to Use Key Bindings, KeyListeners are just trouble waiting to happen.
You can also have a look at Detecting multiple keypresses in java for a more detailed example
You will note that the MainPane has a binding to Space, but I would also set up key bindings, probably in the AbstractGamePane, assuming all game levels have the same controls, to manage the game play
You should also take a look at How to Use Swing Timers for a way to manage a "game loop" better. Calling repaint within a running paint pass is asking for trouble (it will end up running your CPU hot)

java graphics variables giving wrong value

Hello I wonder if anyone can help me , I have declared 2 values x1, y1 both as 120 and trying to use these in the following method:
private void drawDot(int x, int y, Graphics2D twoD) {
twoD.fillOval(x-50, y-50, 100, 100);
}
However when I use drawDot(120,120,twoD) it paints the filled oval in the wrong place compared to when I manually use
twoD.fillOval(70,70,100,100);
Shouldn't these 2 statements do the exact same thing? Is there something I am missing? I have added an image to show the issue, the oval on the left is the oval drawn by my drawDot method and the oval on the right is the oval in the correct place as it should be. If anyone has any advice it would be greatly appreciated. Thanks
click this link to see how both ovals are drawn
the entire class:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DieFace extends JPanel {
int x1,y1 = 120;
int x2,y2 = 300;
int x3,y3 = 480;
private BufferedImage bufferedImage;
public DieFace() {
this.init();
this.frameInit();
updateVal(1);
}
private void init() {
this.setPreferredSize(new Dimension(600,600));
}
private void frameInit() {
JFrame window = new JFrame("Dice Simulation");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setContentPane(this);
window.pack();
window.setVisible(true);
window.setLocationRelativeTo(null);
}
public void paintComponent(Graphics g) {
Graphics2D twoD = (Graphics2D) g;
twoD.drawImage(bufferedImage,0,0,null);
}
private void drawDot(int x, int y, Graphics2D twoD) {
twoD.fillOval(x-50, y-50, 100, 100);
}
public void updateVal(int dieRoll) {
bufferedImage = new BufferedImage(600,600,BufferedImage.TYPE_INT_ARGB);
Graphics2D twoD = bufferedImage.createGraphics();
twoD.setColor(Color.WHITE);
twoD.fillRect(0, 0, 600, 600);
twoD.setColor(Color.BLACK);
if(dieRoll==1) {
drawDot(x1,y1,twoD);
twoD.fillOval(70, 70, 100, 100);
}
repaint();
}
}
Shouldn't these 2 statements do the exact same thing? Is there something I am missing?
Because you made a mistake in your variable initializations on x1, y1:
int x1,y1 = 120; //This means x1 = 0, y1 = 120
What you actually wanted is:
int x1 = 120, y1 = 120;
Since x1 is not 120, when you invoke
drawDot(x1,y1,twoD);
drawDot(0, 120, twoD); is being invoked
Hence both elipses will appear on the same y-axis, but different on the x-axis.

Trouble translating a line (shape)

For an assignment we were asked to modify existing code to include more than one shape, more than one color, and to have the shapes move. I got a little carried away and started adding in more things but now we have an issue. All was going well until I added the left arm(as viewed on the screen). I am having a problem with a piece of the arm remaining in the previous place when moving the figure to the right. Up, down, and left are all working well, it is only to the right where I hit the problem. I have added a screenshot of what it looks like after I move the figures to the right two times. Thanks for any assistance you may be able to provide.
roboMan output
import javax.swing.JFrame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.Color;
import java.awt.Polygon;
import java.util.Scanner;
import java.awt.geom.Ellipse2D;
public class SwingBot1 {
public static void main(String[] args) {
// contruction of new JFrame object
JFrame frame = new JFrame();
// mutators
frame.setSize(400,400);
frame.setTitle("SwingBot");
// program ends when window closes
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Robot r = new Robot();
frame.add(r);
// voila!
frame.setVisible(true);
// your Scanner-based command loop goes here
Scanner in = new Scanner(System.in);
boolean active = true;
while(active){
System.out.println("Enter \"up\", \"down\", \"left\", or \"right\" "
+ "to move.\nClose the window to quit.");
String direction = in.nextLine();
switch(direction){
case "up":
r.moveBot(0, -20);
break;
case "down":
r.moveBot(0, 20);
break;
case "right":
r.moveBot(20, 0);
break;
case "left":
r.moveBot(-20, 0);
break;
}
}
}
public static class Robot extends JComponent
{
private Rectangle rect = new Rectangle(45, 30, 20, 20);
int x = 30;
int y = 50;
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
// set the color
g2.setColor(Color.RED);
// draw the shape`
g2.fill(rect);
Graphics2D g3 = (Graphics2D) g;
g3.setColor(Color.BLUE);
g3.draw(new Ellipse2D.Double(30, 50, 50, 100));
g3.fillOval(30, 50, 50, 100);
Graphics2D g4 = (Graphics2D) g;
g4.setColor(Color.GREEN);
g4.draw(new Line2D.Double(70, 141, 90, 190));
g4.draw(new Line2D.Double(39, 141, 30, 191));
Graphics2D g5 = (Graphics2D) g;
g5.setColor(Color.RED);
g5.draw(new Line2D.Double(80, 85, 125, 70));
//arm, left as viewed
g5.draw(new Line2D.Double(0, 70, 30, 85));
}
public void moveBot(int x, int y)
{
setX(getX()+x);
setY(getY()+y);
repaint();
}
public int getX()
{
return x;
}
public void setX(int x)
{
this.x = x;
}
public int getY()
{
return y;
}
public void setY(int y)
{
this.y = y;
}
/* public void moveBot(int x, int y)
{
// move the rectangle
rect.translate(x,y);
// redraw the window
poly.translate(x, y);
repaint();
}*/
}
}
It looks like you need to repaint the frame.

Translucent JPanel not clearing background / showing background artifacts in Linux

I am currently in the process of developing an app, that needs the functionality to select screen area. I've come up with creating a transparent, undecorated, fullscreen JFrame, and adding a translucent, non-opaque JPanel inside of it, where a half-translucent dark background, as well as the selection is painted.
And while the idea (and the code) runs fine on Windows, its not the same story on linux, where the background of the JPanel does not seem to be cleared upon calling repaint() (even though i tell it to via various methods) - upon each repaint method, the background and the component get darker and darker, etc.
Here's the MVCE:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ExampleFrame extends JFrame{
private ExamplePanel selectionPane;
public ExampleFrame(){
this.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
ExampleFrame.this.dispatchEvent(new WindowEvent(ExampleFrame.this, WindowEvent.WINDOW_CLOSING));
}
}
});
this.setExtendedState(JFrame.MAXIMIZED_BOTH);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setSize(screenSize);
this.setUndecorated(true);
this.setBackground(new Color(255, 255, 255, 0));
populate();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setType(Window.Type.UTILITY);
this.setVisible(true);
}
private void populate(){
this.selectionPane = new ExamplePanel();
this.setContentPane(selectionPane);
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ExampleFrame();
}
});
}
public static class ExamplePanel extends JPanel{
private static Color bg = new Color(0,0,0,0.5f);
private int sx = -1, sy = -1, ex = -1, ey = -1;
public ExamplePanel(){
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
sx = sy = ex = ey = -1;
sx = e.getX();
sy = e.getY();
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
ex = e.getX();
ey = e.getY();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
ex = e.getX();
ey = e.getY();
repaint();
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
this.setDoubleBuffered(false);
this.setOpaque(false);
this.setBackground(bg);
}
#Override
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
g2.setComposite(AlphaComposite.Clear);
g2.setBackground(new Color(255, 255, 255, 0));
g2.fillRect(0, 0, getWidth(), getHeight());
//g2.clearRect(0, 0, getWidth(), getHeight()); //neither of them work
g2.setComposite(AlphaComposite.Src.derive(.5f));
g2.setPaint(getBackground());
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setComposite(AlphaComposite.Src.derive(1f));
g2.setPaint(Color.WHITE);
g2.drawString("Press Escape to exit", 10, 20);
if(!(sx == -1 || sy == -1 || ex == -1 || ey == -1)){
int asx = Math.min(sx, ex);
int asy = Math.min(sy, ey);
int w = Math.abs(ex - sx);
int h = Math.abs(ey - sy);
g2.setComposite(AlphaComposite.Src);
g2.setPaint(new Color(255, 255, 255, 0));
g2.fillRect(asx, asy, w, h);
g2.setPaint(new Color(0, 0, 0, 1));
g2.fillRect(asx, asy, w, h);
g2.setComposite(AlphaComposite.SrcOver);
g2.setStroke(new BasicStroke(2));
g2.setPaint(new Color(1, 1, 1, 0.15f));
g2.drawRect(asx-1,asy-1, w+2, h+2);
}
}
}
}
Any ideas as to what might cause this? Or maybe this is a bug with Java on linux? I had tested this under Windows 10, and Ubuntu 14.04 LTS as well as unknown version of Arch Linux running with KDE gui (tested by a friend)
EDIT: also tested under OSX (Yosemite & El capitan), both worked fine.
this.setBackground(new Color(255, 255, 255, 0));
If you want a component completely transparent then just use:
component.setOpaque( false );
This tells Swing to look for the parent component and paint it first so you don't get the painting artifacts.
private static Color bg = new Color(0,0,0,0.5f);
If you want semi-transparent backgrounds then need to do custom coding since Swing doesn't support this.
Check out Backgrounds With Transparency for more information on this topic and a couple of solutions.
One is to do your own custom painting with code like:
JPanel panel = new JPanel()
{
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
The other solution is a reusable class that can be used with any component so you don't need to customize every component.
panel.setOpaque(false); // background of parent will be painted first
panel.setBackground( new Color(255, 0, 0, 20) );
frame.add(panel);
It's difficult to know the exact cause of the problem without been able to replicate it. There are a number of areas of concern within the code...
Not honouring the paint call chain by not calling super.paintComponent
The use of setDoubleBuffered(false)
The use of this.setBackground(bg); on a JPanel and passing an alpha based color to it
The extensive use of AlphaComposite and it's scrupulous use to try and clear the Graphics context
The basic course of action would be to simplify the paint process until such a time as you can identify the action or combination of actions which are causing the issues.
Or take another approach. Rather than using a combination of different AlphaComposite settings, you might consider just using a Area and subtract the area you want exposed from it....
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.geom.Area;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ExampleFrame extends JFrame {
private ExamplePanel selectionPane;
public ExampleFrame() {
this.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
ExampleFrame.this.dispatchEvent(new WindowEvent(ExampleFrame.this, WindowEvent.WINDOW_CLOSING));
}
}
});
this.setExtendedState(JFrame.MAXIMIZED_BOTH);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setSize(screenSize);
this.setUndecorated(true);
this.setBackground(new Color(255, 255, 255, 0));
populate();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setType(Window.Type.UTILITY);
this.setVisible(true);
}
private void populate() {
this.selectionPane = new ExamplePanel();
this.setContentPane(selectionPane);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ExampleFrame();
}
});
}
public static class ExamplePanel extends JPanel {
private static Color bg = new Color(0, 0, 0);
private int sx = -1, sy = -1, ex = -1, ey = -1;
public ExamplePanel() {
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
sx = sy = ex = ey = -1;
sx = e.getX();
sy = e.getY();
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
ex = e.getX();
ey = e.getY();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
ex = e.getX();
ey = e.getY();
repaint();
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
this.setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
Area area = new Area(new Rectangle(0, 0, getWidth(), getHeight()));
if (!(sx == -1 || sy == -1 || ex == -1 || ey == -1)) {
int asx = Math.min(sx, ex);
int asy = Math.min(sy, ey);
int w = Math.abs(ex - sx);
int h = Math.abs(ey - sy);
area.subtract(new Area(new Rectangle(asx - 1, asy - 1, w + 2, h + 2)));
}
g2.setComposite(AlphaComposite.Src.derive(.25f));
g2.setPaint(bg);
g2.fill(area);
g2.setComposite(AlphaComposite.Src.derive(1f));
g2.setPaint(Color.WHITE);
g2.drawString("Press Escape to exit", 10, 20);
g2.dispose();
}
}
}
Be sure to call super.paintComponent() if you want to clear the background.
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();

rotate an Ellipse2D object

I'm trying to rotate one whole Ellipse2D object based on user key input. If the user presses the right key, rotate right and left key means rotate left. The rotAngle is set to 25. I made a separate drawRotEllipse because otherwise it would have always drawn the original one. I think my confusion is happening with the Graphics and Shapes Objects. Tried the AffineTransform business but that didn't work out either. I just want it to rotate about the center. Thanks for any help!
class Canvas extends JPanel implements java.io.Serializable{
int x1 = (int) (this.getWidth()/2)+100;
int y1 = (int) (this.getHeight()/2)+20;
int x2 = (int) this.getWidth()+100;
int y2 = (int) this.getHeight()+200;
#Override
public void paintComponent(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.RED);
drawEllipse(g);
}
public void drawEllipse (Graphics g){
Graphics2D g2d = (Graphics2D) g;
myShape = new Ellipse2D.Double(x1,y1,x2,y2);
g2d.draw(myShape);
this.repaint();
}
public void drawRotEllipse (Graphics g){
g2d.draw(myShape);
this.repaint();
}
}
private void jPanel1KeyPressed(java.awt.event.KeyEvent evt) {
if (evt.getKeyCode()==39){
g2d.rotate(Math.toDegrees(rotAngle));
myCanvas.drawRotEllipse(g2d);
}
else if (evt.getKeyCode()==37){
g2d.rotate(Math.toDegrees(-rotAngle));
myCanvas.drawRotEllipse(g2d);
}
}
if (evt.getKeyCode()==39)
Don't use magic numbers. People don't know that means by just looking at the code.
Instead use variable provided by the API:
if (evt.getKeyCode() == KeyEvent.VK_RIGHT)
You KeyEvent code should not do the actual painting. All the code should do is set the "degrees" property of your class. The setDegrees(...) method will then be responsible for invoking repaint(). Now whenever the component is repainted the shape will be painted at its current degrees of rotation.
Here is an example that uses a JSlider to change the rotation degrees of the class.
It rotates an image. You should be able to change the code rotation the image to just draw your shape:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation2 extends JPanel
{
BufferedImage image;
int degrees;
int point = 250;
public Rotation2(BufferedImage image)
{
this.image = image;
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
double radians = Math.toRadians( degrees );
g2.translate(point, point);
g2.rotate(radians);
g2.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2.drawImage(image, 0, 0, null);
g2.dispose();
g.setColor(Color.RED);
g.fillOval(point - 5, point - 5, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
// String path = "mong.jpg";
String path = "dukewavered.gif";
ClassLoader cl = Rotation2.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation2 r = new Rotation2(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}

Categories

Resources