I need to draw a ring, with given thickness, that looks something like this:
The center must be transparent, so that it doesn't cover previously drawn shapes. (or other rings) I've tried something like this:
//g is a Graphics2D object
g.setColor(Color.RED);
g.drawOval(x,y,width,height);
g.setColor(Color.WHITE);
g.drawOval(x+thickness,y+thickness,width-2*thickness,height-2*thickness);
which draws a satisfactory ring, but it covers other shapes; the interior is white, not transparent. How can I modify/rewrite my code so that it doesn't do that?
You can create an Area from an Ellipse2D that describes the outer circle, and subtract the ellipse that describes the inner circle. This way, you will obtain an actual Shape that can either be drawn or filled (and this will only refer to the area that is actually covered by the ring!).
The advantage is that you really have the geometry of the ring available. This allows you, for example, to check whether the ring shape contains a certain point, or to fill it with a Paint that is more than a single color:
Here is an example, the relevant part is the createRingShape method:
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RingPaintTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
RingPaintTestPanel p = new RingPaintTestPanel();
f.getContentPane().add(p);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class RingPaintTestPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.RED);
g.drawString("Text", 100, 100);
g.drawString("Text", 300, 100);
Shape ring = createRingShape(100, 100, 80, 20);
g.setColor(Color.CYAN);
g.fill(ring);
g.setColor(Color.BLACK);
g.draw(ring);
Shape otherRing = createRingShape(300, 100, 80, 20);
g.setPaint(new GradientPaint(
new Point(250, 40), Color.RED,
new Point(350, 200), Color.GREEN));
g.fill(otherRing);
g.setColor(Color.BLACK);
g.draw(otherRing);
}
private static Shape createRingShape(
double centerX, double centerY, double outerRadius, double thickness)
{
Ellipse2D outer = new Ellipse2D.Double(
centerX - outerRadius,
centerY - outerRadius,
outerRadius + outerRadius,
outerRadius + outerRadius);
Ellipse2D inner = new Ellipse2D.Double(
centerX - outerRadius + thickness,
centerY - outerRadius + thickness,
outerRadius + outerRadius - thickness - thickness,
outerRadius + outerRadius - thickness - thickness);
Area area = new Area(outer);
area.subtract(new Area(inner));
return area;
}
}
You can use the Shape and Area classes to create interesting effects:
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.net.*;
public class Subtract extends JPanel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int size = 100;
int thickness = 10;
int innerSize = size - (2 * thickness);
Shape outer = new Ellipse2D.Double(0, 0, size, size);
Shape inner = new Ellipse2D.Double(thickness, thickness, innerSize, innerSize);
Area circle = new Area( outer );
circle.subtract( new Area(inner) );
int x = (getSize().width - size) / 2;
int y = (getSize().height - size) / 2;
g2d.translate(x, y);
g2d.setColor(Color.CYAN);
g2d.fill(circle);
g2d.setColor(Color.BLACK);
g2d.draw(circle);
g2d.dispose();
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Subtract");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Subtract());
frame.setLocationByPlatform( true );
frame.setSize(200, 200);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Using an Area you can also add multiple Shapes together or get the intersection of multiple Shapes.
You could use graphics.setStroke(...) for this. This way the center will be fully transparent and therefore won't cover previously drawn shapes. In my example I had to do some additional calculations because of this method though, to make sure the displayed x and y coordinates are actually the same as the ones of the Ring instance:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example {
public Example() {
ArrayList<Ring> rings = new ArrayList<Ring>();
rings.add(new Ring(10, 10, 100, 20, Color.CYAN));
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gg = (Graphics2D) g.create();
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Ring ring : rings) {
// Previously drawn
gg.setColor(Color.BLACK);
String str = "Hello!";
gg.drawString(str, ring.getX() + (ring.getWidth() - gg.getFontMetrics().stringWidth(str)) / 2,
ring.getY() + ring.getHeight() / 2 + gg.getFontMetrics().getAscent());
// The actual ring
ring.draw(gg);
}
gg.dispose();
}
};
JFrame frame = new JFrame();
frame.setContentPane(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example();
}
});
}
}
class Ring {
private int x, y, width, height, thickness;
private Color color;
public Ring(int x, int y, int width, int height, int thickness, Color color) {
setX(x);
setY(y);
setWidth(width);
setHeight(height);
setThickness(thickness);
setColor(color);
}
public Ring(int x, int y, int radius, int thickness, Color color) {
this(x, y, radius * 2, radius * 2, thickness, color);
}
public void draw(Graphics2D gg) {
Stroke oldStroke = gg.getStroke();
Color oldColor = gg.getColor();
gg.setColor(Color.BLACK);
gg.setStroke(new BasicStroke(getThickness()));
gg.drawOval(getX() + getThickness() / 2, getY() + getThickness() / 2, getWidth() - getThickness(),
getHeight() - getThickness());
gg.setColor(getColor());
gg.setStroke(new BasicStroke(getThickness() - 2));
gg.drawOval(getX() + getThickness() / 2, getY() + getThickness() / 2, getWidth() - getThickness(),
getHeight() - getThickness());
gg.setStroke(oldStroke);
gg.setColor(oldColor);
}
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 int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getThickness() {
return thickness;
}
public void setThickness(int thickness) {
this.thickness = thickness;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
Related
First post in SO, hope I'm doing this right!
I'm trying to make a game in Java where a robot drives around and shines lines at nearby walls. Problem is, these lines stay onscreen, even when the robot moves, so it makes a cloud of lines that won't erase.
In the paintComponent(), I've tried g.fillRect(), but that doesn't erase any lines, just draws a rectangle behind that cloud. Every source I've seen so far seems to suggest things I've already done, unless I'm misunderstanding / overlooking something obvious...
'''
//from https://www.youtube.com/watch?v=p9Y-NBg8eto
/*
Other classes in this folder:
Robot.java Represents a robot, its location and angular orientation.
Obstacles.java Represents lines; the walls the robot should detect.
Sensor.java Represents the beams that shine from the robot to the walls.
//*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.lang.Math;
import java.util.ArrayList;
public class MyPanel extends JPanel implements ActionListener, KeyListener
{
Timer t = new Timer(5, this);
//OBSTACLES:
Obstacles obstacles=new Obstacles(0);
//ROBOT:
Robot robot = new Robot(obstacles);
//`````````````````````````````````````````````````````````
public MyPanel()
{
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setVisible(true);
}
#Override
public void paintComponent(Graphics g)
{
g.dispose();
super.paintComponent(g);
//Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.RED);
g.fillRect(0, 0, 450, 450);
g.setColor(Color.BLACK);
//Graphics2D g2 = (Graphics2D) g;
robot.drawRobot(g);
drawBoundedBeams(g);
obstacles.drawObstacles(g);
drawSun(g);
}
#Override
public void actionPerformed(ActionEvent e)
{
repaint();
}
#Override
public void keyPressed(KeyEvent e)
{
int code = e.getKeyCode();
if (code==KeyEvent.VK_UP)
robot.up();
if (code==KeyEvent.VK_DOWN)
robot.down();
if (code==KeyEvent.VK_LEFT)
robot.left();
if (code==KeyEvent.VK_RIGHT)
robot.right();
robot.updateSensor();
}
#Override public void keyTyped(KeyEvent e) {}
#Override public void keyReleased(KeyEvent e) {}
//```````````````````````````````````````````````````````````
public static void main(String [] args)
{
JFrame f = new JFrame();
MyPanel panel = new MyPanel();
f.add(panel);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
f.setVisible(true);
}
//Dummy lines. For some reason, these delete and move with the robot, no problem.
public void drawSun(Graphics g)
{
int x = (int) robot.x;
int y = (int) robot.y;
double t = robot.angle;
double xang = Math.cos(t);
double yang = Math.sin(t);
double east = (t + Math.PI/2) % (2 * Math.PI);
double eastx = Math.cos(east);
double easty = Math.sin(east);
g.drawLine(x, y, x+100, y);
g.drawLine(x, y, x-100, y);
g.drawLine(x, y, x, y+100);
g.drawLine(x, y, x, y-100);
g.drawLine(x, y, x+100, y+100);
g.drawLine(x, y, x+100, y-100);
g.drawLine(x, y, x-100, y+100);
g.drawLine(x, y, x-100, y-100);
g.drawLine(x, y, x+(int)(xang*100), y+(int)(yang*100));
g.drawLine(x, y, x-(int)(xang*100), y-(int)(yang*100));
g.drawLine(x, y, x+(int)(eastx*100), y+(int)(easty*100));
g.drawLine(x, y, x-(int)(eastx*100), y-(int)(easty*100));
}
//This was in Sensor.java, but I moved it here to try to fix it. (Didn't work.)
//THIS is what isn't getting deleted.
public void drawBoundedBeams(Graphics g) //Graphics2D g2
{
//Graphics2D g2 = (Graphics2D) g;
for (Integer[] beam : robot.sensor.boundedBeams)
{
g.drawLine(beam[0], beam[1], beam[2], beam[3]);
}
}
}
'''
~~~~~~ EDIT: I made a Minimal ~~~~~~
~~~~~~ Reproducible Example. ~~~~~~
MyPanel.java:
'''
/*
Classes in this (reprex) folder:
MyPanel.java displays things ****PROBLEM HERE(?)
Robot.java stores location and orientation
Sensor.java Shines beams outward ****PROBLEM HERE(?)
(I tried to keep the file structure consistent,
in case that was a factor for the issue.)
//*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.lang.Math;
import java.util.ArrayList;
public class MyPanel extends JPanel implements ActionListener, KeyListener
{
Timer t = new Timer(5, this);
Robot robot = new Robot();
//`````````````````````````````````````````````````````````
public MyPanel()
{
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setVisible(true);
}
//********************************************
//********************************************
//********************************************
//**** Problem seems to be HERE. *************
//**** ~~~~~~~~~~~~~~~~~~~~~~~~ *************
//**** (See also: the method in *************
//**** Sensor.java; drawBeams()) *************
//********************************************
//********************************************
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
// And to smooth out the graphics, you can do the following
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.RED);
g2.fillRect(0, 0, 450, 450);
g2.setColor(Color.BLACK);
robot.sensor.drawBeams(g2);
g2.dispose();
}
#Override
public void actionPerformed(ActionEvent e)
{
repaint();
}
#Override
public void keyPressed(KeyEvent e)
{
int code = e.getKeyCode();
if (code==KeyEvent.VK_UP)
robot.up();
if (code==KeyEvent.VK_DOWN)
robot.down();
if (code==KeyEvent.VK_LEFT)
robot.left();
if (code==KeyEvent.VK_RIGHT)
robot.right();
robot.updateSensor();
}
#Override public void keyTyped(KeyEvent e) {}
#Override public void keyReleased(KeyEvent e) {}
//```````````````````````````````````````````````````````````
public static void main(String [] args)
{
JFrame f = new JFrame();
MyPanel panel = new MyPanel();
f.add(panel);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
f.setVisible(true);
}
}
'''
Sensor.java:
'''
import java.lang.Math;
import java.util.ArrayList;
import java.awt.*;
class Sensor
{
private Robot robot;
public int numPieces;
public Integer range = 300;
public ArrayList<Double> offsets = new ArrayList<Double>(); //the angles of each beam
public ArrayList<Integer[]> beams = new ArrayList<Integer[]>(); //the beams that stretch out to the range public ArrayList<Integer[]> boundedBeams = new ArrayList<Integer[]>();
public Sensor(Robot robot)
{
this.robot=robot;
make12Sonars();
}
public void make12Sonars()
{
numPieces=12;
double offset = 0;
while (offset < 2*Math.PI)
{
offsets.add(offset);
offset += Math.PI / 6;
}
}
public void updateSensor()
{
makeBeams();
}
//```````````````````````````````````````````````````````
public void makeBeams() //Makes the maximum beams from the angles.
{
System.out.println("~~~MAKING BEAMS~~~");
for (Integer i=0; i<offsets.size(); i++)
{
System.out.println("\tat index "+i);
Double offset = offsets.get(i);
Double angle = (offset + robot.angle) % (2*Math.PI);
Integer[] beam = getSegment(robot.x, robot.y, angle, Double.valueOf(range));
beams.add(i, beam);
System.out.println("\t\tadded "+beam[0]+", "+beam[1]+", "+beam[2]+", "+beam[3]);
}
}
static public Integer[] getSegment(Double startX, Double startY, Double angle, Double radius)
{
Double x2 = radius * Math.cos(angle) + startX;
Double y2 = radius * Math.sin(angle) + startY;
Integer[] segment = {startX.intValue(), startY.intValue(), x2.intValue(), y2.intValue()};
return segment;
}
//********************************************
//********************************************
//********************************************
//**** Problem seems to be HERE. *************
//**** ~~~~~~~~~~~~~~~~~~~~~~~~ *************
//**** (The lines drawn here *************
//**** do not delete.) *************
//********************************************
//********************************************
public void drawBeams(Graphics g)
{
Integer x = (int) robot.x;
Integer y = (int) robot.y;
for (Integer[] beam : beams)
{
g.drawLine(beam[0], beam[1], beam[2], beam[3]);
}
}
}
'''
Robot.java:
'''
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.lang.Math;
class Robot
{
public double x=0, y=0; //center of the robot
public double velx=0, vely=0; //velocity of the robot
public double angle=0, speed=4; //radians, speed(before_direction)
public Sensor sensor;
public Robot()
{
sensor=new Sensor(this);
}
public void updateSensor() {sensor.updateSensor();}
//`````````````````````````````````````````````````````````````
//*
public void up() {x+=velx; y+=vely;}
public void down() {x-=velx; y-=vely;}
public void left()
{
angle -= 0.1;
angle = angle % (2*Math.PI);
velx = speed * Math.cos(angle);
vely = speed * Math.sin(angle);
}
public void right()
{
angle += 0.1;
angle = angle % (2*Math.PI);
velx = speed * Math.cos(angle);
vely = speed * Math.sin(angle);
} //*/
}
'''
You should not be disposing the default graphics context at the start of paintComponent(). The best thing to do is to make a copy via create and then later, dispose of that right before you return from paintComponent().
Try this as a your `paintComponent()` method.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
// And to smooth out the graphics, you can do the following
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.RED);
g2.fillRect(0, 0, 450, 450);
g2.setColor(Color.BLACK);
robot.drawRobot(g2);
drawBoundedBeams(g2);
obstacles.drawObstacles(g2);
drawSun(g2);
g2.dispose();
}
If this does not help solve your problem, please make a Minimal Reproducible Example that demonstrates the problem. It should not rely on anything that is not within the JDK API (i.e. third party classes, libraries, etc.)
The following example draws 2 Rectangles.
Within the paintComponent() method, the first Rectangle is draw normally and the second Rectangle is rotated.
Rotation is based on the mouse movement. If the mouse is clicked on the rectangle and then moved
in a circular fashion, the 2nd Rectangle rotations as expected, but as the mouse rotations around, the rotation of the Rectangle isn't always in sync with the mouse.
I suspect this is all related to the angle calculation. Any suggestions on how to get the rotation of the Rectangle to be in sync with the mouse movement?
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class SimpleTest extends JComponent
{
static int x = 200,y = 200 , width = 100 , height = 30;
static Vector objectsToDraw = new Vector();
static int mouseClickX, mouseClickY, mouseX, mouseY = 0;
static double angle;
public static void main(String[] args)
{
//Create Frame
JFrame window = new JFrame();
//Attach Mouse Listeners.
window.addMouseMotionListener(new MouseMotionListener()
{
#Override
public void mouseMoved(MouseEvent e) { }
#Override
public void mouseDragged(MouseEvent e)
{
// System.out.println("Dragged");
System.out.println("Dragged at X :" + e.getX() + " Y : " + e.getY());
calculateAngle(e.getX(), e.getY());
mouseX = e.getX();
mouseY = e.getY();
window.repaint();
}
});
window.addMouseListener(new MouseListener()
{
#Override
public void mouseClicked(MouseEvent arg0) { }
#Override
public void mouseEntered(MouseEvent arg0) { }
#Override
public void mouseExited(MouseEvent arg0) { }
#Override
public void mousePressed(MouseEvent e)
{
System.out.println("Pressed at X :" + e.getX() + " Y : " + e.getY());
mouseClickX = e.getX();
mouseClickY = e.getY();
}
#Override
public void mouseReleased(MouseEvent arg0)
{
System.out.println("Released");
mouseClickX = 0;
mouseClickY = 0;
window.repaint();
}
});
//show Window
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(30, 30, 800, 800);
window.getContentPane().add(new SimpleTest());
window.setVisible(true);
}
public static void calculateAngle (int x, int y)
{
int deltaX = x - 250;//Rectangle Center X
int deltaY = y - 200;//Rectangle Center Y
angle = Math.toDegrees(Math.atan2(deltaY, deltaX));
System.out.println("Angle = " + angle);
}
#Override //Works
public void paintComponent(Graphics g)
{
System.out.println("paintComponent() - using angle : " + angle);
Graphics2D g2d = (Graphics2D)g;
AffineTransform old = g2d.getTransform();
g.drawRect(x, y, width, height);
g2d.rotate(Math.toRadians(angle), 250, 215);
Rectangle rect2 = new Rectangle(200, 200, 100, 30);
g.drawRect(x, y, width, height);
g2d.setTransform(old);
}
}
For one, you're adding the MouseListener to the wrong component. Don't add it to the window but rather to the JComponent that does the drawing. Otherwise, your mouse positioning will be off by however large the menu bar is, or any other components that changes the position of the drawing component (the component that uses the mouse positions) relative to the JFrame (your component that currently gets the mouse positions).
e.g.,
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
#SuppressWarnings("serial")
public class SimpleTest2 extends JPanel {
// avoiding "magic" numbers
private static final int PREF_W = 500;
private static final int PREF_H = PREF_W;
public static final int RECT_X = 200;
public static final int RECT_Y = RECT_X;
public static final int RECT_WIDTH = 100;
public static final int RECT_HEIGHT = 30;
private double angle;
private static void createAndShowGui() {
SimpleTest2 mainPanel = new SimpleTest2();
JFrame frame = new JFrame("Simple Test 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack(); // let the GUI size itself
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
public SimpleTest2() {
// using an adapter is a nice clean way of avoiding empty method bodies
MouseAdapter myMouse = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
calculateAngle(e.getX(), e.getY());
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
calculateAngle(e.getX(), e.getY());
repaint();
}
};
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
public void calculateAngle(int x, int y) {
// get rid of "magic" numbers
int deltaX = x - (RECT_X + RECT_WIDTH / 2);// Rectangle Center X
int deltaY = y - (RECT_Y + RECT_HEIGHT / 2);// Rectangle Center Y
angle = Math.toDegrees(Math.atan2(deltaY, deltaX));
}
#Override
public Dimension getPreferredSize() {
// better way to size the drawing component
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // ***don't forget this guy***
Graphics2D g2d = (Graphics2D) g;
// for smoother rendering
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform old = g2d.getTransform();
g.drawRect(RECT_X, RECT_Y, RECT_WIDTH, RECT_HEIGHT);
g2d.rotate(Math.toRadians(angle), 250, 215);
// Rectangle rect2 = new Rectangle(200, 200, 100, 30);
g.drawRect(RECT_X, RECT_Y, RECT_WIDTH, RECT_HEIGHT);
g2d.setTransform(old);
}
}
I need the simplest way to rescale a drawing in java (for example a rectangle...). I found a way to "stretch" them:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.Dimension;
public class Stretch extends JFrame {
int originalHeight = 600;
int originalWidth = 600;
public Stretch() {
super("Stretch");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(originalWidth, originalHeight);
}
public static void main(String[] args) {
Stretch s = new Stretch();
s.setVisible(true);
}
public void paint(Graphics g) {
Dimension size = this.getBounds().getSize();
int rectWidth = 100;
int rectHeight = 130;
g.setColor(Color.white);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
g.drawRect(100, 100, rectWidth + size.width - originalWidth, rectHeight + size.height - originalHeight);
}
}
As you can see the formula is like that:
g.drawRect(100, 100, rectWidth + size.width - originalWidth, rectHeight + size.height - originalHeight);
Now how can I rescale the drawing? Width and height have to maintain the same proportion.
Thank you.
There are bascially two different approaches for this:
You can "scale the whole Graphics"
You can scale the actual shapes
The difference may be subtle in some cases, but can be important: When you scale the whole Graphics, then everything will be scaled. Particularly, when you scale it about 2.0, and then draw a line with a width of 1.0, the line will be drawn 2 pixels wide. Whether or not this is desired depends on the application case.
In order to scale the actual shapes, you can not use the drawRect method. Instead you will have to create Shape instances that represent the geometric shapes. You can then create scaled versions of these shapes with AffineTransform#createTransformedShape.
Here is an example that compares both approaches (and corrects some of the other issues that have been in your code). In both cases, the same rectangle with an original size of (10,13) is painted, scaled by a factor of 5.0.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingDrawnObjects
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ScalingDrawnObjectsPanel p = new ScalingDrawnObjectsPanel();
f.getContentPane().add(p);
f.setSize(600,400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ScalingDrawnObjectsPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
Shape rectangle = new Rectangle2D.Double(2, 2, 10, 13);
g.setColor(Color.RED);
drawWithScaledGraphics(g, rectangle);
g.translate(100, 0);
drawScaledObject(g, rectangle);
}
private static void drawWithScaledGraphics(Graphics2D g, Shape shape)
{
AffineTransform oldAt = g.getTransform();
g.scale(5.0, 5.0);
g.draw(shape);
g.setTransform(oldAt);
}
private static void drawScaledObject(Graphics2D g, Shape shape)
{
AffineTransform at = AffineTransform.getScaleInstance(5.0, 5.0);
g.draw(at.createTransformedShape(shape));
}
}
EDIT In response to the comment
The code that I posted is not "complicated". It is as compilcated as it has to be, but not more. You should not extend JFrame and you should not override paint. You should create the GUI on the EDT. You should ...
However, you should not use code like the following, but maybe this is what you're looking for
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.Dimension;
public class Stretch extends JFrame {
int originalHeight = 600;
int originalWidth = 600;
public Stretch() {
super("Stretch");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(originalWidth, originalHeight);
}
public static void main(String[] args) {
Stretch s = new Stretch();
s.setVisible(true);
}
public void paint(Graphics g) {
Dimension size = this.getBounds().getSize();
int rectWidth = 100;
int rectHeight = 130;
g.setColor(Color.white);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
int w = rectWidth + size.width - originalWidth;
int h = rectHeight + size.height - originalHeight;
double sx = (double)w / rectWidth;
double sy = (double)h / rectHeight;
double s = Math.min(sx, sy);
int fw = (int)(s * rectWidth);
int fh = (int)(s * rectHeight);
g.drawRect(100, 100, fw, fh);
}
}
How about multiplying the width and the height by the scale you want?
So if you want to scale it by 2:
g.drawRect(100, 100, 2 * (rectWidth + size.width - originalWidth), 2 * (rectHeight + size.height - originalHeight));
For a problem I have to draw a circle on the screen with center at coordinates (280,300) with a radius of 50. The hint says: A circle is an oval with the same width and height. The center of this circle is 50 pixels below and 50 pixels to the right of the NW corner of this oval.
There is the TryoutPanel class:
import java.awt.*;
import javax.swing.*;
public class TryoutPanel extends JPanel{
private Color myColor;
public TryoutPanel(Color c){
myColor = c;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
setForeground(myColor);
g.drawString("top",10,50);
g.drawLine(10,60, 200,60);
g.drawString("middle",10,80);
g.drawLine(10,90, 200,90);
g.drawString("bottom",10,110);
g.drawLine(10,120, 200,120);
g.drawRect(200,300,100,50);
g.drawOval(200,300,100,50);
for(int j = 0; j < 9; j++)
g.drawOval(50,200, 10 + 20*j, 210 - 20*j);
}
}
I have to fill in the code in the following:
public void paintComponent(Graphics g){
super.paintComponent(g);
setForeground(myColor);
//INSERT CODE HERE
I tried:
g.drawOval(280,300,50,50);
But it says I used incorrect parameters. What am I doing wrong.
The x/y parameter of drawOval is the top/left corner from where the oval will be drawn
In order to be able to draw the circle around the center point if 230x300, you will need to subtract the radius from each point and then generate a width and height (diameter) of double that...
g.drawOval(230 - radius, 300 - radius, radius * 2, radius * 2);
So, this example basic draws a rectangle around the point of 230x300 with a width/height of 200 (radius = 100) and draws lines through this point to illustrate the center point the oval then drawn about...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TryoutOval {
public static void main(String[] args) {
new TryoutOval();
}
public TryoutOval() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TryoutPanel(Color.RED));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TryoutPanel extends JPanel {
private Color myColor;
public TryoutPanel(Color c) {
myColor = c;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int centerX = 280;
int centerY = 300;
int radius = 50;
int diameter = radius * 2;
int x = centerX - radius;
int y = centerY - radius;
g.setColor(Color.BLACK);
g.drawRect(x, y, diameter, diameter);
g.drawLine(x, y, x + diameter, y + diameter);
g.drawLine(x + diameter, y, x, y + diameter);
g.setColor(myColor);
g.drawOval(x, y, diameter, diameter);
g.fillOval(centerX - 5, centerY - 5, 10, 10);
}
}
}
Oh, and setForeground(myColor); is a horribly bad idea within any paint method as it will cause a paint event to be added to the event queue each time the method is called, which will cause a never ending repaint request which will eventually consume your CPU
This is my code:
package com.Bench3.simplyMining;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Keying extends JPanel {
private static final long serialVersionUID = 1L;
public Rectangle miningRock;
public Rectangle Rocks;
public int mRockW = 150;
public int mRockH = 100;
public long rocks = 0;
public String rockAmount = rocks + " Rocks";
public Keying(Display f, Images i){
miningRock = new Rectangle(0, 658, mRockW, mRockH);
Rocks = new Rectangle(1024, 0, 0, 0);
f.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e){
}
public void keyReleased(KeyEvent e){
/* if(e.getKeyCode() == KeyEvent.VK_A){
left = false;
} -- Used for setting key combinations and stuff */
}
});
}
public void paintComponent(Graphics g){
if(Main.f.i.imagesLoaded){
super.paintComponent(g);
g.drawImage(Main.f.i.miningRock, miningRock.x, miningRock.y, miningRock.width, miningRock.height, null);
g.drawString(rockAmount, Rocks.x, Rocks.y);
this.setBackground(Color.WHITE); // Sets background color
// g.setColor(Color.WHITE); -- Used for setting colors
repaint();
}
}
}
I've been trying to insert the string "rockAmount" into the rectangle "Rocks" using g.drawString(), but when I try it doesn't output the string inside of the rectangle. What am I doing wrong?
EDIT: Solved, thanks to Paul for providing the answer.
The y position of text represents the base line minus the font ascent. This means that it appears that text is rendered above the y position.
When rendering text, you need to take into consideration the FontMetrics of the text and font.
For example, the following renders the text at the rectangles x/y position on the left and then calculates the x/y position so it can centre the text within the give rectangle.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTextRect {
public static void main(String[] args) {
new TestTextRect();
}
public TestTextRect() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int mRockW = 150;
int mRockH = 100;
int x = ((getWidth() / 2) - mRockW) / 2;
int y = (getHeight() - mRockH) / 2;
badRect(g2d, x, y, mRockW, mRockH);
x = (getWidth() / 2) + (((getWidth() / 2) - mRockW) / 2);
goodRect(g2d, x, y, mRockW, mRockH);
g2d.dispose();
}
protected void badRect(Graphics2D g2d, int x, int y, int mRockW, int mRockH) {
g2d.drawRect(x, y, mRockW, mRockH);
String text = "rockAmount";
g2d.drawString(text, x, y);
}
protected void goodRect(Graphics2D g2d, int x, int y, int mRockW, int mRockH) {
g2d.drawRect(x, y, mRockW, mRockH);
FontMetrics fm = g2d.getFontMetrics();
String text = "rockAmount";
x = x + ((mRockW - fm.stringWidth(text)) / 2);
y = (y + ((mRockH - fm.getHeight()) / 2)) + fm.getAscent();
g2d.drawString(text, x, y);
}
}
}
Take a look at Working with text APIs for more details
You should avoid calling repaint or any method that might call repaint from within your paintXxx methods. Because of the way painting is scheduled in Swing, this will set up an infinite loop that will eventually consume you CPU cycles and make your application unresponsive...
You should, also, always call super.paintComponent, regardless of what ever else you want to do...
I would also recommend Key bindings over KeyListener as it provides better control over the focus level
You need to add to x and y position 1/2 of the width and height of the rectangle.
g.drawString(rockAmount,Rocks.x+rectangleWidth/2,Rocks.y+rectangleHeight/2);