I have Swing Application where I want to display an Icon on a JButton. I had some problems with Windows scaling the Icon and making it very ugly. I was able to solve that problem by using an Icon-Wrapper Class as described here.
Using the wrapper class solves the scaling problem I had but causes a new problem: The icon only shows once I mouse over the button, as can be seen here:
Here is a minimal example:
import javax.swing.*;
import java.awt.*;
import java.net.MalformedURLException;
import java.net.URL;
public class Ui {
private static JFrame frame;
private static JButton button1;
private static JButton button2;
private static JMenuBar jMenuBar;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
createGui();
} catch (MalformedURLException e) {
e.printStackTrace();
}
});
}
private static void createGui() throws MalformedURLException {
frame = new JFrame("Test");
button1 = new JButton();
button2 = new JButton();
jMenuBar = new JMenuBar();
Icon icon = new NoScalingIcon(new ImageIcon(new URL("https://i.stack.imgur.com/wgKeq.png")));
button1.setIcon(icon);
button2.setIcon(icon);
jMenuBar.add(button1);
jMenuBar.add(button2);
frame.setJMenuBar(jMenuBar);
frame.pack();
frame.setVisible(true);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
The icon wrapper class:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class NoScalingIcon implements Icon {
private Icon icon;
public NoScalingIcon(Icon icon) {
this.icon = icon;
}
public int getIconWidth() {
return icon.getIconWidth();
}
public int getIconHeight() {
return icon.getIconHeight();
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = g2d.getTransform();
int scaleX = (int) (x * at.getScaleX());
int scaleY = (int) (y * at.getScaleY());
int offsetX = (int) (icon.getIconWidth() * (at.getScaleX() - 1) / 2);
int offsetY = (int) (icon.getIconHeight() * (at.getScaleY() - 1) / 2);
g2d.setTransform(new AffineTransform());
icon.paintIcon(c, g2d, scaleX + offsetX, scaleY + offsetY);
g2d.dispose();
}
}
What could be the cause for this problem?
I think I found a solution.
Instead of creating a completely new AffineTransform, the code below merges a new scaled transform with the inverse of the existing scale so the scale factor is set back to 1:
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class NoScalingIcon implements Icon
{
private Icon icon;
public NoScalingIcon(Icon icon)
{
this.icon = icon;
}
public int getIconWidth()
{
return icon.getIconWidth();
}
public int getIconHeight()
{
return icon.getIconHeight();
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2d = (Graphics2D)g.create();
AffineTransform at = g2d.getTransform();
int scaleX = (int)(x * at.getScaleX());
int scaleY = (int)(y * at.getScaleY());
int offsetX = (int)(icon.getIconWidth() * (at.getScaleX() - 1) / 2);
int offsetY = (int)(icon.getIconHeight() * (at.getScaleY() - 1) / 2);
int locationX = scaleX + offsetX;
int locationY = scaleY + offsetY;
AffineTransform scaled = AffineTransform.getScaleInstance(1.0 / at.getScaleX(), 1.0 / at.getScaleY());
at.concatenate( scaled );
g2d.setTransform( at );
icon.paintIcon(c, g2d, locationX, locationY);
g2d.dispose();
}
}
Related
I'm currently working on a 2D game in Java for school. We have to use an Abstract Factory design pattern. For the 2D implementation I use a factory as follows:
public class Java2DFact extends AbstractFactory {
public Display display;
private Graphics g;
public Java2DFact() {
display = new Display(2000, 1200);
}
#Override
public PlayerShip getPlayership()
{
return new Java2DPlayership(display.panel);
}
In my display class I create a JFrame and Jpanel
public class Display {
public JFrame frame;
public JPanel panel;
public int width, height;
public Display(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame();
frame.setTitle("SpaceInvaders");
frame.setSize(1200,800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
}
};
panel.setFocusable(true);
frame.add(panel);
}
}
Now from my main gameloop I call the visualize method inside the Java2DPLayership class to visualize my Playership
public class Java2DPlayership extends PlayerShip {
private JPanel panel;
private Graphics2D g2d;
private Image image;
private BufferStrategy bs;
public Java2DPlayership(JPanel panel) {
super();
this.panel = panel;
}
public void visualize() {
try {
image = ImageIO.read(new File("src/Bee.gif"));
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
//g.setColor(new Color(0, 0, 0));
//g.fillRect(10, 10, 12, 8);
g.drawImage(image, (int) super.getMovementComponent().x, (int) super.getMovementComponent().y, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
panel.repaint();
} catch(Exception e){
System.out.println(e.toString());
}
}
}
My goal is to pass around the JPanel to every entity and let it draw its contents onto the panel before showing it. However I can't seem to figure out how to do this. When using this approach by changing the Graphics of the panel I get a lot of flickering.
Here is a fully functional, albeit simple example, I wrote some time ago. It just has a bunch of balls bouncing off the sides of the panel. Notice that the render method of the Ball class accepts the graphics context from paintComponent. If I had more classes that needed to be rendered, I could have created a Renderable interface and have each class implement it. Then I could have a list of Renderable objects and just go thru them and call the method. But as I also said, that would need to happen quickly to avoid tying up the EDT.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Bounce extends JPanel {
private static final int COLOR_BOUND = 256;
private final static double INC = 1;
private final static int DIAMETER = 40;
private final static int NBALLS = 20;
private final static int DELAY = 5;
private final static int PANEL_WIDTH = 800;
private final static int PANEL_HEIGHT = 600;
private final static int LEFT_EDGE = 0;
private final static int TOP_EDGE = 0;
private JFrame frame;
private double rightEdge;
private double bottomEdge;
private List<Ball> balls = new ArrayList<>();
private Random rand = new Random();
private List<Long> times = new ArrayList<>();
private int width;
private int height;
public Bounce(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame("Bounce");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
addComponentListener(new MyComponentListener());
frame.pack();
frame.setLocationRelativeTo(null);
rightEdge = width - DIAMETER;
bottomEdge = height - DIAMETER;
for (int j = 0; j < NBALLS; j++) {
int r = rand.nextInt(COLOR_BOUND);
int g = rand.nextInt(COLOR_BOUND);
int b = rand.nextInt(COLOR_BOUND);
Ball bb = new Ball(new Color(r, g, b), DIAMETER);
balls.add(bb);
}
frame.setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public static void main(String[] args) {
new Bounce(PANEL_WIDTH, PANEL_HEIGHT).start();
}
public void start() {
/**
* Note: Using sleep gives a better response time than
* either the Swing timer or the utility timer. For a DELAY
* of 5 msecs between updates, the sleep "wakes up" every 5
* to 6 msecs while the other two options are about every
* 15 to 16 msecs. Not certain why this is happening though
* since the other timers are run on threads.
*
*/
Timer timer = new Timer(0,(ae)-> {repaint();
for (Ball b : balls) {
b.updateDirection();
}} );
timer.setDelay(5); // 5 ms.
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : balls) {
ball.render(g2d);
}
}
class MyComponentListener extends ComponentAdapter {
public void componentResized(ComponentEvent ce) {
Component comp = ce.getComponent();
rightEdge = comp.getWidth() - DIAMETER;
bottomEdge = comp.getHeight() - DIAMETER;
for (Ball b : balls) {
b.init();
}
}
}
class Ball {
private Color color;
public double x;
private double y;
private double yy;
private int ydir = 1;
private int xdir = 1;
private double slope;
private int diameter;
public Ball(Color color, int diameter) {
this.color = color;
this.diameter = diameter;
init();
}
public void init() {
// Local constants not uses outside of method
// Provides default slope and direction for ball
slope = Math.random() * .25 + .50;
x = (int) (rightEdge * Math.random());
yy = (int) (bottomEdge * Math.random()) + diameter;
xdir = Math.random() > .5 ? -1
: 1;
ydir = Math.random() > .5 ? -1
: 1;
y = yy;
}
public void render(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillOval((int) x, (int) y, diameter, diameter);
}
public void updateDirection() {
x += (xdir * INC);
yy += (ydir * INC);
y = yy * slope;
if (x < LEFT_EDGE || x > rightEdge) {
xdir = -xdir;
}
if (y < TOP_EDGE || y > bottomEdge) {
ydir = -ydir;
}
}
}
}
So I want to make a number line class that I can use to display single points along a single axis, but I want it to respond to the size of the container it's in at the moment and to change its size relative to that. Unfortunately, I'm unable to use getWidth() and getHeight() correctly to get the number line I want. This is the code I have written so far:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
public class NumberLine extends JPanel {
private int value;
private Color green1 = new Color(32, 77, 2);
#Override
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
int maxXValue = getWidth();
int maxYValue = getHeight();
Line2D.Float xline = new Line2D.Float((float) maxXValue/6, (float) maxYValue/2, (float) maxXValue * (5/6), (float) maxYValue/2);
Line2D.Float yline = new Line2D.Float( (float) maxXValue/ 2, (float) maxYValue * (9/20), (float) maxXValue/2, (float) maxYValue *(11/20));
g2.draw(xline);
g2.draw(yline);
Ellipse2D.Float cir = new Ellipse2D.Float((float) (maxXValue/10 + (8 * value/1000) * (maxXValue)), (float) (maxYValue/2), 10F, 10F );
g2.setColor(green1);
g2.fill(cir);
}
public NumberLine(int val0) {
value = val0;
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(150,100);
NumberLine num = new NumberLine(5);
frame.setContentPane(num);
frame.setVisible(true);
}
}
Ideally, I would like something such that if I were to do
NumberLine num = new NumberLine(5);
I would get something that looks like:
Instead, I'm getting:
I think that your problem is one of basic geometry. If you're trying to center the circle within the line, then you need to subtract half its width and height from its location. That's it:
Ellipse2D.Float cir = new Ellipse2D.Float(
(float) (maxXValue / 10 + (8 * value / 1000) * (maxXValue)) - 5,
(float) (maxYValue / 2) - 5, 10F, 10F);
Also you're doing int division and that is returning 0 values where you don't want them. Change
Line2D.Float yline = new Line2D.Float((float) maxXValue / 2, (float) maxYValue * (9 / 20),
(float) maxXValue / 2, (float) maxYValue * (11 / 20));
to
Line2D.Float yline = new Line2D.Float((float) maxXValue / 2, (float) maxYValue * (9f / 20f),
(float) maxXValue / 2f, (float) maxYValue * (11f / 20f));
Unrelated issues:
Don't forget to call the super's paintComponent method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // !! don't forget this!
And avoid "magic" numbers in your program as they make debugging a bear.
Use RenderingHints to smooth out your Graphics2D drawing:
// rendering hints to smooth out your drawing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Start your Swing GUI on the EDT:
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
For example, something like:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import javax.swing.JPanel;
public #SuppressWarnings("serial")
class NumberLine3 extends JPanel {
private static final double X_GAP = 1.0 / 20.0;
private static final double MAJOR_TIC_HT = 0.4;
private static final int PREF_W = 600;
private static final int PREF_H = 50;
private static final Stroke MAIN_STROKE = new BasicStroke(5f);
private static final Stroke MAJOR_TIC_STOKE = new BasicStroke(3f);
private static final int CIRCLE_WIDTH = 20;
private static final Color VALUE_COLOR = new Color(32, 230, 2);
private int maxX;
private int majorTickCount;
private int minorTicksPerMajor;
private double value;
public NumberLine3(int maxX, int majorTickCount, int minorTicksPerMajor, double value) {
this.maxX = maxX;
this.majorTickCount = majorTickCount;
this.minorTicksPerMajor = minorTicksPerMajor;
this.value = value;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// rendering hints to smooth out your drawing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Graphics2D g2b = (Graphics2D) g2.create(); // so we can change stroke without problems
g2b.setStroke(MAIN_STROKE);
int x1 = (int) xValueToScreen(-maxX);
int y1 = getHeight() / 2;
int x2 = (int) xValueToScreen(maxX);
int y2 = y1;
g2b.drawLine(x1, y1, x2, y2);
g2b.setStroke(MAJOR_TIC_STOKE);
for (int i = 0; i <= 2 * majorTickCount; i++) {
double xVal = ((double) i * maxX) / majorTickCount - maxX;
x1 = (int) xValueToScreen(xVal);
x2 = x1;
double dY1 = getHeight() * (1 - MAJOR_TIC_HT) / 2.0;
if (i == majorTickCount) {
dY1 = 0.5 * dY1;
}
double dY2 = getHeight() - dY1;
g2b.drawLine(x1, (int) dY1, x2, (int) dY2);
}
g2b.dispose();
g2.setColor(VALUE_COLOR);
x1 = (int) (xValueToScreen(value) - CIRCLE_WIDTH / 2.0);
y1 = (int) (getHeight() - CIRCLE_WIDTH) / 2;
g2.fillOval(x1, y1, CIRCLE_WIDTH, CIRCLE_WIDTH);
}
private double xValueToScreen(double xValue) {
double gap = getWidth() * X_GAP;
double scale = (double) (getWidth() - 2 * gap) / (2 * maxX);
return (xValue + maxX) * scale + gap;
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
repaint();
}
public int getMaxX() {
return maxX;
}
public int getMajorTickCount() {
return majorTickCount;
}
public int getMinorTicksPerMajor() {
return minorTicksPerMajor;
}
}
Which can be tested with:
import java.awt.BorderLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
#SuppressWarnings("serial")
public class NumberLine3Test extends JPanel {
private static final int MAX_X = 40;
private static final int MAJOR_TICS = 4;
private static final int MINOR_TICS = 5;
private double value = 0.0;
private NumberLine3 numberLine3 = new NumberLine3(MAX_X, MAJOR_TICS, MINOR_TICS, value);
private JSlider slider = new JSlider(-MAX_X, MAX_X, 0);
public NumberLine3Test() {
slider.setPaintTicks(true);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
slider.addChangeListener(ce -> {
value = slider.getValue();
numberLine3.setValue(value);
});
JPanel sliderPanel = new JPanel();
sliderPanel.add(slider);
int ebGap = 40;
sliderPanel.setBorder(BorderFactory.createEmptyBorder(ebGap, ebGap, ebGap, ebGap));
setLayout(new BorderLayout());
add(numberLine3, BorderLayout.PAGE_START);
add(sliderPanel);
}
private static void createAndShowGui() {
NumberLine3Test mainPanel = new NumberLine3Test();
JFrame frame = new JFrame("NumberLine3");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Trying the exercise in lesson2 of the Udacity course. Despite importing the classes (I'm at java.awt.* now, but I also tried java.awt.Color and java.awt.Canvas separately (also need Shape))..
package com.jul.udacity.lesson2;
public class TestRectangle {
public static void main(String[] args) {
// TODO Auto-generated method stub
Rectangle rect1 = new Rectangle(100.0, 100.0, 200.0, 100.0);
rect1.draw();
}
}
And the class is copied from there and java.awt import added. Any help will be great. Thanks!
package com.jul.udacity.lesson2;
//HIDE
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
//import java.awt.Color;
//import java.awt.Shape;
//import java.awt.Canvas;
import java.awt.*;
public class Rectangle implements Shape
{
private Color color = Color.BLACK;
private boolean filled = false;
private double x;
private double y;
private double width;
private double height;
/**
Constructs an empty rectangle.
*/
public Rectangle()
{
x = 0;
y = 0;
width = 0;
height = 0;
}
/**
Constructs a rectangle.
#param x the leftmost x-coordinate
#param y the topmost y-coordinate
#param width the width
#param height the height
*/
public Rectangle(double x, double y, double width, double height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
Gets the leftmost x-position of this rectangle.
#return the leftmost x-position
*/
public int getX()
{
return (int) Math.round(x);
}
/**
Gets the topmost y-position of this rectangle.
#return the topmost y-position
*/
public int getY()
{
return (int) Math.round(y);
}
/**
Gets the width of this rectangle.
#return the width
*/
public int getWidth()
{
return (int) Math.round(width);
}
/**
Gets the height of this rectangle.
#return the height
*/
public int getHeight()
{
return (int) Math.round(height);
}
/**
Moves this rectangle by a given amount.
#param dx the amount by which to move in x-direction
#param dy the amount by which to move in y-direction
*/
public void translate(double dx, double dy)
{
x += dx;
y += dy;
Canvas.getInstance().repaint();
}
/**
Resizes this rectangle both horizontally and vertically.
#param dw the amount by which to resize the width on each side
#param dw the amount by which to resize the height on each side
*/
public void grow(double dw, double dh)
{
width += 2 * dw;
height += 2 * dh;
x -= dw;
y -= dh;
Canvas.getInstance().repaint();
}
/**
Sets the color of this rectangle.
#param newColor the new color
*/
public void setColor(Color newColor)
{
color = newColor;
Canvas.getInstance().repaint();
}
/**
Draws this rectangle.
*/
public void draw()
{
filled = false;
Canvas.getInstance().show(this);
}
/**
Fills this rectangle.
*/
public void fill()
{
filled = true;
Canvas.getInstance().show(this);
}
public String toString()
{
return "Rectangle[x=" + getX() + ",y=" + getY() + ",width=" + getWidth() + ",height=" + getHeight() + "]";
}
public void paintShape(Graphics2D g2)
{
Rectangle2D.Double rect = new Rectangle2D.Double(getX(), getY(),
getWidth(), getHeight());
g2.setColor(new java.awt.Color((int) color.getRed(), (int) color.getGreen(), (int) color.getBlue()));
if (filled)
{
g2.fill(rect);
}
else
{
g2.draw(rect);
}
}
}
You might want to check the lesson directions carefully. java.awt.Canvas has no getInstance() method. You just use new to make a Canvas. So you either didn't read carefully and are using the wrong Canvas, or there's something else going on.
Also the show() methods are deprecated, so I'm leaning towards you are supposed to be using a different Canvas class.
Also, Swing is not thread safe. Read up on how to use Swing objects
markspace is correct: Canvas here is not the class from java.awt - the Udacity instructors are using their own class called Canvas. I suggest grabbing the lesson files and using those. Here's the Canvas class from that Intro To Java Course:
import java.awt.image.BufferedImage;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.RescaleOp;
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Canvas
{
private static Canvas canvas = new Canvas();
private ArrayList<Shape> shapes = new ArrayList<Shape>();
private BufferedImage background;
private JFrame frame;
private CanvasComponent component;
private static final int MIN_SIZE = 100;
private static final int MARGIN = 10;
private static final int LOCATION_OFFSET = 120;
class CanvasComponent extends JComponent
{
public void paintComponent(Graphics g)
{
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(java.awt.Color.BLACK);
if (background != null)
{
g.drawImage(background, 0, 0, null);
}
for (Shape s : new ArrayList<Shape>(shapes))
{
Graphics2D g2 = (Graphics2D) g.create();
s.paintShape(g2);
g2.dispose();
}
}
public Dimension getPreferredSize()
{
int maxx = MIN_SIZE;
int maxy = MIN_SIZE;
if (background != null)
{
maxx = Math.max(maxx, background.getWidth());
maxy = Math.max(maxx, background.getHeight());
}
for (Shape s : shapes)
{
maxx = (int) Math.max(maxx, s.getX() + s.getWidth());
maxy = (int) Math.max(maxy, s.getY() + s.getHeight());
}
return new Dimension(maxx + MARGIN, maxy + MARGIN);
}
}
private Canvas()
{
component = new CanvasComponent();
if (System.getProperty("com.horstmann.codecheck") == null)
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(component);
frame.pack();
frame.setLocation(LOCATION_OFFSET, LOCATION_OFFSET);
frame.setVisible(true);
}
else
{
final String SAVEFILE ="canvas.png";
final Thread currentThread = Thread.currentThread();
Thread watcherThread = new Thread()
{
public void run()
{
try
{
final int DELAY = 10;
while (currentThread.getState() != Thread.State.TERMINATED)
{
Thread.sleep(DELAY);
}
saveToDisk(SAVEFILE);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
};
watcherThread.start();
}
}
public static Canvas getInstance()
{
return canvas;
}
public void show(Shape s)
{
if (!shapes.contains(s))
{
shapes.add(s);
}
repaint();
}
public void repaint()
{
if (frame == null) return;
Dimension dim = component.getPreferredSize();
if (dim.getWidth() > component.getWidth()
|| dim.getHeight() > component.getHeight())
{
frame.pack();
}
else
{
frame.repaint();
}
}
/**
* Pauses so that the user can see the picture before it is transformed.
*/
public void pause()
{
if (frame == null) return;
JOptionPane.showMessageDialog(frame, "Click Ok to continue");
}
/**
* Takes a snapshot of the screen, fades it, and sets it as the background.
*/
public static void snapshot()
{
Dimension dim = getInstance().component.getPreferredSize();
java.awt.Rectangle rect = new java.awt.Rectangle(0, 0, dim.width, dim.height);
BufferedImage image = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, rect.width, rect.height);
g.setColor(java.awt.Color.BLACK);
getInstance().component.paintComponent(g);
float factor = 0.8f;
float base = 255f * (1f - factor);
RescaleOp op = new RescaleOp(factor, base, null);
BufferedImage filteredImage
= new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
op.filter(image, filteredImage);
getInstance().background = filteredImage;
getInstance().component.repaint();
}
public void saveToDisk(String fileName)
{
Dimension dim = component.getPreferredSize();
java.awt.Rectangle rect = new java.awt.Rectangle(0, 0, dim.width, dim.height);
BufferedImage image = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
g.setColor(java.awt.Color.WHITE);
g.fill(rect);
g.setColor(java.awt.Color.BLACK);
component.paintComponent(g);
String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
try
{
ImageIO.write(image, extension, new File(fileName));
}
catch(IOException e)
{
System.err.println("Was unable to save the image to " + fileName);
}
g.dispose();
}
}
Background
Im total Java newbie, today I started learning it (with thenewboston.org). I already know how to make simple windows/forms/gui, how to draw lines etc.
My goal is to create in Java gauge like this:
This is gauge which I created in .NET C# WPF, and now I want to rewrite this to Java.
Main question:
How to create triangle or other shape with some transparency and rotate it?
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Edit:
I already know how to antialias (Hovercraft Full Of Eels already helped me in this - thanks).
Edit2:
My code actually looks like this:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainWindow extends JPanel {
private Point p1 = new Point(100, 100);
private Point p2 = new Point(740, 450);
public MainWindow() {
this.setPreferredSize(new Dimension(800, 600));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawLines(g);
}
private void drawLines(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.DARK_GRAY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private void display() {
JFrame f = new JFrame("Main Window");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
new MainWindow().display();
}
}
You state:
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Suggestions:
Don't hard-code your numbers. Use class fields (variables) instead so that your program can change the position of items drawn easily.
Don't override a component's paint(...) method. Instead override the paintComponent(Graphics g) method of an object that derives from JComponent or one of its children such as JPanel. This will give you the benefit of automatic double-buffering for smoother animation, and also will reduce the likelihood of erroneous drawing of a component's children or borders.
Cast your Graphics object to a Graphics2D object so that you can do more advanced drawing using classes that implement the Shape interface, including Rectangle2D, Ellipse2D, Line2D, Path2D, and many more.
Draw the background image as a BufferedImage using Graphics#drawImage(...) method, and then draw your moving images on top of this, again using the Graphics2D object and again changing the images drawn based on the state of the object (the values held by its fields).
Be careful when doing animations that you obey Swing threading rules, that you don't have any animation or game loops that tie up the Swing thread. A Swing Timer can allow you to create a quick and easy (albeit somewhat primitive) game loop.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DailAnimation extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 350;
private static final Point2D CENTER = new Point2D.Double(PREF_W / 2.0,
PREF_W / 2.0);
private static final double RADIUS = PREF_W / 2.0;
private static final Color LARGE_TICK_COLOR = Color.green;
private static final Color CENTER_HUB_COLOR = Color.LIGHT_GRAY;
private static final Stroke LARGE_TICK_STROKE = new BasicStroke(3f);
private static final int LRG_TICK_COUNT = 9;
private static final double TOTAL_LRG_TICKS = 12;
private static final double LRG_TICK_OUTER_RAD = 0.9;
private static final double LRG_TICK_INNER_RAD = 0.8;
private static final int START_TICK = 10;
private static final double CENTER_HUB_RADIUS = 10;
public static final int MAX_SPEED = 100;
private static final double INIT_SPEED = 0;
private static final double DIAL_INNER_RAD = 0.02;
private static final double DIAL_OUTER_RAD = 0.75;
private static final Color DIAL_COLOR = Color.DARK_GRAY;
private BufferedImage backgroundImg;
private double speed;
private double theta;
private double cosTheta;
private double sinTheta;
public DailAnimation() {
setBackground(Color.white);
backgroundImg = createBackgroundImg();
setSpeed(INIT_SPEED);
}
public void setSpeed(double speed) {
if (speed < 0) {
speed = 0;
} else if (speed > MAX_SPEED) {
speed = MAX_SPEED;
}
this.speed = speed;
this.theta = ((speed / MAX_SPEED) * LRG_TICK_COUNT * 2.0 + START_TICK)
* Math.PI / TOTAL_LRG_TICKS;
cosTheta = Math.cos(theta);
sinTheta = Math.sin(theta);
repaint();
}
private BufferedImage createBackgroundImg() {
BufferedImage img = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(LARGE_TICK_COLOR);
g2.setStroke(LARGE_TICK_STROKE);
for (int i = 0; i < LRG_TICK_COUNT; i++) {
double theta = (i * 2.0 + START_TICK) * Math.PI / TOTAL_LRG_TICKS;
double cosTheta = Math.cos(theta);
double sinTheta = Math.sin(theta);
int x1 = (int) (LRG_TICK_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (LRG_TICK_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g2.drawLine(x1, y1, x2, y2);
}
g2.setColor(CENTER_HUB_COLOR);
int x = (int) (CENTER.getX() - CENTER_HUB_RADIUS);
int y = (int) (CENTER.getY() - CENTER_HUB_RADIUS);
int width = (int) (2 * CENTER_HUB_RADIUS);
int height = width;
g2.fillOval(x, y, width, height);
// g2.draw(ellipse);
g2.dispose();
return img;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(DIAL_COLOR);
int x1 = (int) (DIAL_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (DIAL_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (DIAL_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (DIAL_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g.drawLine(x1, y1, x2, y2);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
final DailAnimation mainPanel = new DailAnimation();
JFrame frame = new JFrame("DailAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
int delay = 100;
new Timer(delay, new ActionListener() {
int speed = 0;
#Override
public void actionPerformed(ActionEvent evt) {
speed ++;
if (speed > DailAnimation.MAX_SPEED) {
((Timer)evt.getSource()).stop();
}
mainPanel.setSpeed(speed);
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Drawing a line via Graphics.drawLine() writes pixels directly to whatever is backing the Graphics instance. If you want to rotate the line, you must calculate what its coordinates should be when rotated. This is the only way to draw things in AWT and Swing.
You could write a needle class that maintained its angle, and then have it handle its rendering every frame.
Hi so I'm writing an simple physics engine to understand object collisions and Java graphics a bit better, and so far I have successfully written the code to add the JPanel to the JFrame and allow them to show up somewhat the correct size, but when I view the actually program, the JPanel seems tobe the right size but it does not start in the upper corner of the window, but rather the upper left of the frame. I seem to have this problem alot where I want something to be at (0, 0) and starts in the upper corner of the frame rather than the panel. Here is my code:
I have an engine class that extends JFrame and contains the main method -->
package io.shparki.PhysicsEngine;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
public class Engine extends JFrame{
public Engine(){
super("Physics Engine and Simulator");
setLayout(new BorderLayout());
add(new EnginePanel(), BorderLayout.CENTER);
pack();
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args){
new Engine();
}
}
and this is my second class, the EnginePanel which extends JPanel and implements Runnable -->
package io.shparki.PhysicsEngine;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import javax.swing.JPanel;
public class EnginePanel extends JPanel implements Runnable{
private static final int WIDTH = 300;
private static final int HEIGHT = WIDTH / 16 * 9;
private static final int SCALE = 4;
public int getWidth() { return WIDTH * SCALE; }
public int getHeight() { return HEIGHT * SCALE; }
#Override
public Dimension getPreferredSize(){ return new Dimension(WIDTH * SCALE, HEIGHT * SCALE); }
private static final int FPS = 85;
private static final int PERIOD = 1000 / FPS;
private int currentFPS = 0;
private Thread animator;
private boolean running = false;
private Graphics dbg;
private Image dbImage = null;
public EnginePanel(){
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setVisible(true);
}
public void addNotify(){
super.addNotify();
startEngine();
}
public void startEngine(){
running = true;
animator = new Thread(this, "Animator");
animator.start();
}
public void stopEngine(){
running = false;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if (dbImage != null){
g.drawImage(dbImage, 0, 0, null);
}
}
public void paintScreen(){
Graphics g;
try{
g = this.getGraphics();
if ( g != null && dbImage != null){
g.drawImage(dbImage, 0, 0, null);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
} catch(Exception ex) { System.out.println("Graphics Context Error : " + ex); }
}
public void run(){
running = true;
init();
Long beforeTime, timeDiff, sleepTime;
while(running){
beforeTime = System.currentTimeMillis();
updateEngine();
renderEngine();
paintScreen();
timeDiff = System.currentTimeMillis() - beforeTime;
sleepTime = PERIOD - timeDiff;
if (sleepTime <= 0){
sleepTime = 5L;
}
currentFPS = (int) (1000 / (sleepTime + timeDiff));
try{
Thread.sleep(sleepTime);
} catch (InterruptedException ex) { ex.printStackTrace(); }
}
}
private TextField FPSTextField;
public void init(){
FPSTextField = new TextField("Currnet FPS: " + currentFPS, 25, 25);
}
public void updateEngine(){
FPSTextField.setText("Currnet FPS: " + currentFPS);
}
public void renderEngine(){
if (dbImage == null){
dbImage = createImage((int)getWidth(), (int)getHeight());
if (dbImage == null){
System.out.println("Graphical Context Error : DBImage is Null");
return;
} else {
dbg = dbImage.getGraphics();
}
}
Graphics2D g2d = (Graphics2D) dbg;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
FPSTextField.render(g2d, Color.MAGENTA);
}
}
I'm not quite sure why this keeps happening and I have searched for help but can not find the answer. Thanks in advance for all who help :)
EDIT: Added code for the TextField object:
package io.shparki.PhysicsEngine;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
public class TextField{
private Point location;
public Point getLocation(){ return location; }
public double getX() { return location.getX(); }
public double getY() { return location.getY(); }
private String text;
public void setText(String text) { this.text = text; }
public String getText() { return this.text; }
public TextField(String text, int x, int y){
this.location = new Point(x, y);
this.text = text;
}
public TextField(String text, Point location){
this.location = location;
this.text = text;
}
public void render(Graphics g){
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d){
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics g, Color color){
g.setColor(color);
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d, Color color){
g2d.setColor(color);
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics g, Color color, Font font){
g.setColor(color);
g.setFont(font);
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d, Color color, Font font){
g2d.setColor(color);
g2d.setFont(font);
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
}
The preferred size of the JPanel EnginePanel restricts the panel from being resized the JFrame is rendered non-resizable. Invoke JFrame#pack after calling setResizable(false). Also move setLocationRelativeTo after pack so that the frame appears centered.
pack();
setLocationRelativeTo(null);
setVisible(true);
If you use a GridBagLayout in the JFrame, then the JPanel will be centered if it is the only thing you add to the JFrame. It will even stay in the center if you resize the window.
Also, the reason the coordinates seem off to you is that you are viewing it as an (x, y) coordinate while it is actually a (row, col) coordinate like in a 2D Array.