How do you use re-size all Graphic2D - java

In java how can you make a game fully realizable! But so logic and graphics can work with it? I have tried using SCALE methods. But this doesn't allow perfect full-screen for every computer. So I made this:
public void resize(int WIDTH, int HEIGHT, boolean UNDECORATED) {
frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame.setMaximumSize(new Dimension(WIDTH, HEIGHT));
frame.setMinimumSize(new Dimension(WIDTH, HEIGHT));
this.WIDTH = WIDTH;
this.HEIGHT = HEIGHT;
frame.setUndecorated(UNDECORATED);
frame.setSize(WIDTH, HEIGHT);
}
So you can set your screen size to whatever you want! It works but the graphics will not work with it? Is there a way in Graphics2D to stretch all the graphics so it fits? For example if there was a method that existed like:
G2D.resize(WIDTH, HEIGHT, Image.NEAREST_PARENT_RESCALE);
Any idea?
Things I have tried:
Drawing all graphics to a Buffered-image then drawing that Image onto the screen size.
Just using SCALE and doing WIDTH * SCALE etc.
Lots of math
Things I do not mind
If you have a WIDE-SCREEN it stretches graphic2D objects to the size.
If you have a SQUARE-SCREEN it squishes graphics2D objects to the size.
So how can I make a perfectly resealable game using Graphics2D, JFrame.

In the most generic form, one can consider this as a classical problem of graphics programming, namely, as the transformation from world coordinates to screen coordinates. You have an object that has a size of "1.0 x 1.0" in your world coordinate system (regardless of which unit this has). And this object should be painted so that it has a size of, for example, "600 pixels * 600 pixels" on the screen.
Broadly speaking, there are at least three options to achieve this in Swing:
You can draw into an image, and then draw a scaled version of the image
You can draw into a scaled Graphics2D object
You can draw scaled objects
Each of this has possible advantages and disadvantages, and hidden caveats.
Drawing into an image, and drawing a scaled version of the image:
This might look like a simple solution, but has a potential drawback: The image itself has a certain resolution (size). If the image is too small, and you are scaling it up to fill the screen, it may appear blocky. If the image is too large, and you are scaling it down to fit into the screen, pixels of the image may be lost.
In both cases, there are several tuning parameters for the process of scaling the image. In fact, scaling an image is far more tricky than it looks at the first glance. For details, one may refer to the article The Perils of Image.getScaledInstance() by Chris Campbell.
Drawing into a scaled Graphics2D object
The Graphics2D class already offers the full functionality that is necessary to create the transformation between the world coordinate system and the screen coordinate system. This is accomplished by the Graphics2D class by internally storing an AffineTransform, which describes this transformation. This AffineTransform may be modified directly via the Graphics2D object:
void paintSomething(Graphics2D g) {
...
g.draw(someShape);
// Everything that is painted after this line will
// be painted 3 times as large:
g.scale(3.0, 3.0);
g.draw(someShape); // Will be drawn larger
}
Some care has to be taken to properly manage the transform that is stored in the Graphics2D object. In general, one should create a backup of the original AffineTransform before applying additional transformations, and restore this original transform afterwards:
// Create a backup of the original transform
AffineTransform oldAT = g.getTransform();
// Apply some transformations
g.scale(3.0, 4.0);
g.translate(10.0, 20.0);
// Do custom painting the the transformed graphics
paintSomething(g):
// Restore the original transformation
g.setTransform(oldAT);
(Another advice for the last method: The Graphics2D#setTransform method should never be used to apply a new coordinate transform on top of an existing transform. It is solely intended for restoring an "old" transform, as shown in this example (and in the documentation of this method)).
One potential drawback of scaling with the Graphics2D class is that afterwards, everything will be scaled. Particularly, this scaling will also affect line widths (that is, the width of the Stroke). For example, consider a sequence of calls like this one:
// By default, this will paint a line with a width (stroke) of 1.0:
g.draw(someLine);
// Apply some scaling...
g.scale(10.0, 10.0);
// Now, this will paint the same line, but with a width of 10.
g.draw(someLine);
The second call will cause a line to be drawn that is 10 pixels wide. This may not be desired in many cases. This effect can be avoided with the third alternative:
Drawing scaled objects
The transformation between the world coordinate system and the screen coordinate system can also be maintained manually. It is convenient to represent this as an AffineTransform. The AffineTransform class can be used to create transformed versions of Shape object, that can then be drawn directly into an (un-transformed) Graphics2D object. This is accomplished with the AffineTransform#createTransformedShape method:
void paintSomething(Graphics2D g) {
...
// Draw some shape in its normal size
g.draw(someShape);
// Create a scaling transform
AffineTransform at = AffineTransform.getScaleInstance(3.0, 3.0);
// Create a scaled version of the shape
Shape transformedShape = at.createTransformedShape(someShape);
// Draw the scaled shape
g.draw(transformedShape);
}
This is probably the most versatile approach. The only potential drawback is that, when many small, simple shapes are drawn, this will cause many, small temporary transformed shapes to be created, which may cause reduced performance. (There are ways to alleviate this problem, but detailed performance considerations and optimizations are beyond the scope of this answer).
Summary
The follwing image shows the comparison of all approaches. Some example objects (represented as Shape objects) are drawn. Each row compares the three different scaling methods mentioned above. With their "default" size, the objects fill a rectangle in world coordinates that has a size of 100x100. In the first two rows, they are scaled up to fill an area on the screen of 190x190 pixels. In the last two rows, they are scaled down to fill an area on the screen of 60x60 pixels. (These sizes have been chosen in order to have some "odd" scaling factors of 1.9 and 0.6. Certain effects (artifacts) may not appear when the scaling factors are whole numbers, or exactly 0.5, for example).
For the upscaling and the downscaling, there additionally is a comparison between the "standard" way of painting, and "high quality" painting (indicated by the "(HQ)" in the title of each panel). The "high quality" here simply means that the rendering hints
KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY
have been set:
Here is the corresponding program, as an MCVE:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingMethodComparison
{
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);
f.getContentPane().setLayout(new GridLayout(0,1));
Dimension larger = new Dimension(190,190);
Dimension smaller = new Dimension(60,60);
f.getContentPane().add(createPanel(larger, false));
f.getContentPane().add(createPanel(larger, true));
f.getContentPane().add(createPanel(smaller, false));
f.getContentPane().add(createPanel(smaller, true));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createPanel(Dimension d, boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,3));
for (ScalingMethodComparisonPanel.ScalingMethod scalingMethod :
ScalingMethodComparisonPanel.ScalingMethod.values())
{
p.add(createPanel(d, scalingMethod, highQuality));
}
return p;
}
private static JPanel createPanel(
Dimension d, ScalingMethodComparisonPanel.ScalingMethod scalingMethod,
boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,1));
p.setBorder(BorderFactory.createTitledBorder(
scalingMethod.toString()+(highQuality?" (HQ)":"")));
JPanel scalingMethodComparisonPanel =
new ScalingMethodComparisonPanel(
createObjects(), d, scalingMethod, highQuality);
p.add(scalingMethodComparisonPanel);
return p;
}
// Returns a list of objects that should be drawn,
// occupying a rectangle of 100x100 in WORLD COORDINATES
private static List<Shape> createObjects()
{
List<Shape> objects = new ArrayList<Shape>();
objects.add(new Ellipse2D.Double(10,10,80,80));
objects.add(new Rectangle2D.Double(20,20,60,60));
objects.add(new Line2D.Double(30,30,70,70));
return objects;
}
}
class ScalingMethodComparisonPanel extends JPanel
{
private static final Color COLORS[] = {
Color.RED, Color.GREEN, Color.BLUE,
};
enum ScalingMethod
{
SCALING_IMAGE,
SCALING_GRAPHICS,
SCALING_SHAPES,
}
private final List<Shape> objects;
private final ScalingMethod scalingMethod;
private final boolean highQuality;
private final Dimension originalSize = new Dimension(100,100);
private final Dimension scaledSize;
private BufferedImage image;
public ScalingMethodComparisonPanel(
List<Shape> objects,
Dimension scaledSize,
ScalingMethod scalingMethod,
boolean highQuality)
{
this.objects = objects;
this.scaledSize = new Dimension(scaledSize);
this.scalingMethod = scalingMethod;
this.highQuality = highQuality;
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(scaledSize);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(), getHeight());
if (highQuality)
{
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
if (scalingMethod == ScalingMethod.SCALING_IMAGE)
{
paintByScalingImage(g);
}
else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS)
{
paintByScalingGraphics(g);
}
else if (scalingMethod == ScalingMethod.SCALING_SHAPES)
{
paintByScalingShapes(g);
}
}
private void paintByScalingImage(Graphics2D g)
{
if (image == null)
{
image = new BufferedImage(
originalSize.width, originalSize.height,
BufferedImage.TYPE_INT_ARGB);
}
Graphics2D ig = image.createGraphics();
paintObjects(ig, null);
ig.dispose();
g.drawImage(image, 0, 0, scaledSize.width, scaledSize.height, null);
}
private void paintByScalingGraphics(Graphics2D g)
{
AffineTransform oldAT = g.getTransform();
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
g.scale(scaleX, scaleY);
paintObjects(g, null);
g.setTransform(oldAT);
}
private void paintByScalingShapes(Graphics2D g)
{
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
AffineTransform at =
AffineTransform.getScaleInstance(scaleX, scaleY);
paintObjects(g, at);
}
private void paintObjects(Graphics2D g, AffineTransform at)
{
for (int i=0; i<objects.size(); i++)
{
Shape shape = objects.get(i);
g.setColor(COLORS[i%COLORS.length]);
if (at == null)
{
g.draw(shape);
}
else
{
g.draw(at.createTransformedShape(shape));
}
}
}
}

This is actually quite easy in Java. In a Graphics2d environment, the logical coordinate system (the coordinates you use in the drawing routines) and the physical coordinate system (the coordinates as they appear) on the screen are completely unrelated. Every time you draw onto a Graphics2d object, the logical coordinates are first translated to the physical coordinates by an AffineTransform object, and this AffineTransform object can be modified. For this you can use the Graphics2D.scale(double,double), Graphics2D.rotate(double), Graphics2D.translate(double,double) and Graphics2D.shear(double,double) methods.
So if you first call
g2d.scale(2.0,2.0);
then all your graphics that you subsequently draw will be twice as large in both directions.

If I understood you correctly all you want is to draw your graphics in different resolutions without removing or adding any content.
Well one of the "things you have tried" can do that.
Drawing to a fixed size BufferedImage will ensure that all your components are visible within that BufferedImage (assuming you draw them correctly and relative to it's fixed size) then you can just draw the image to your flexible size screen.
Here's a full runnable code example that does that:
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test extends Canvas implements Runnable {
// fixed size for the image
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private BufferedImage image;
private boolean running;
private Thread t;
public Test(Dimension dims) {
super();
setPreferredSize(dims); // actual screen size
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
running = false;
}
public synchronized void start() {
if (running)
return;
t = new Thread(this);
running = true;
t.start();
}
public synchronized void stop() {
if (!running)
return;
running = false;
boolean retry = true;
while (retry) {
try {
t.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void render() {
// draw to your image
Graphics2D g2d = (Graphics2D) image.getGraphics().create();
g2d.fillRect((WIDTH / 2) - 25, (HEIGHT / 2) - 25, 50, 50);
g2d.dispose();
// draw the image to your screen
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
g2d = (Graphics2D) bs.getDrawGraphics().create();
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g2d.dispose();
bs.show();
}
public void run() {
// approximately sync rendering to 60 FPS don't use it as it is.
// there are much better ways to do this.
long startTime = System.currentTimeMillis();
long frameTime = 1000 / 60;
long tick = 0;
while (running) {
while ((System.currentTimeMillis() - startTime) > tick) {
render();
tick += frameTime;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test test = new Test(new Dimension(800, 600));
JFrame frame = new JFrame("Fit to screen");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
test.stop();
frame.dispose();
super.windowClosing(e);
}
});
frame.getContentPane().add(test);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
test.start();
}
});
}
}
This is only a quick implementation there are things that can be done better in that code bu you get the picture. Hope this helps.

Maybe this will help :
Scaling graphics2D that contains basic shapes has a drawback : thickness of lines are doubled if the scale is doubled, that's a problem in an application implementing a zoom feature...
The only way I found is to make the preferred size of the container bigger and then, draw the shapes.
Here's a zoom function using mouse wheel and the pixel of the object pointed by the mouse stays under the mouse pointer.
It took me a long time to figure out how to do that properly, but I finally found out...(the application is an astrolabe and I wanted to zoom in and out)
The graphics2D belongs to a JPanel that is contained in the bottom part of a JSplitPane :
public void mouseWheelMoved(MouseWheelEvent e) {
Dimension dim = new Dimension(), oldDim = this.getPreferredSize();
double newX, newY;
Rectangle rect, oldRect;
if(this.mousewheel >= 0){
this.mousewheel += -e.getWheelRotation() * this.mousewheelSensibility;
}
else {
this.mousewheel = 0;
}
dim.setSize(this.astro.splitBottomDimension.getWidth() + this.mousewheel, this.astro.splitBottomDimension.getHeight() + this.mousewheel);
oldRect = this.getVisibleRect();
this.mouseX = e.getX();
this.mouseY = e.getY();
this.setPreferredSize(dim);
newX = this.mouseX / oldDim.getWidth() * dim.getWidth();
newY = this.mouseY / oldDim.getHeight() * dim.getHeight();
rect = new Rectangle((int)newX - (this.mouseX - oldRect.x), (int)newY - (this.mouseY - oldRect.y), oldRect.width, oldRect.height);
this.scrollRectToVisible(rect);
this.revalidate();

Related

Rotating a graphic using arc2d

i am trying to do a roulette casino game, so for this i made my roulette using the Arc2D package.
My code below
package roulette;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.AffineTransform;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RouletteInterface extends JPanel{
public int spinValue = 0;
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
public void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for(int i = 0; i < 37; i++) {
if(i == 0) {
g2d.setColor(Color.GREEN);
} else {
if(color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
g2d.transform(at);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
}
In short i am not using a generalpath because i want to fill each arc with color red/green or black like the original roulette, and for the rotation i tried using a timer to increase the spinValue (this worked for me but when i use a generalpath) for the AfinneTransformation, but when i run the code, well, nothing happens. It shows me only the roulette without animation. What can i do?
Thanks in advance.
Painting and graphics in general are quite advanced topics, Java/Swing does a good job to "commonalise" the APIs into something which is reasonable easy to use, but still takes time and effort to learn and understand fully.
I would highly recommend having Performing Custom Painting, Painting in AWT and Swing and 2D Graphics and the JavaDocs booked marked, as you will be coming back to them on a regular bases (I still do)
There are lots of issues, which are compounding to make your life difficult.
Starting with...
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
You should favour overriding paintComponent instead of paint, paint is a complicated process and you need to choose your entry point into carefully. Also, you should always call the paint methods super method, unless you are absolutely, positively prepared to take over its core functionality yourself.
In your case, you should also be making a copy of the Graphics context before passing it to paintRoulette, as Graphics is a shared resource and the transformations you are applying will cause issues for anything which is painted after your component.
Transformations...
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
This is somewhat interesting. You're creating translation of 10x10 which will move the origin point of the Graphics context. You then apply a rotation, which is anchored to 10x10.
The reason I mention it is because you then do...
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
This means that the arc is offset by 110x110 from the corner of the component (add in your translation) and you'll be rotating about a point 20x20 from the component's top/left corner (add in your translation) ... this is weird to me because the centre of the of wheel is actually at 250x250 (from the component's top/left corner) which is going to make for one very weird affect.
Finally, you apply the transformation AFTER the painting is done AND then create a Timer inside the paint method...
Painting is done in serial. So one operation will effect the next, this will mean you will need to apply the transformation BEFORE you paint something (that you want transformed)
You also need to understand that you don't control the paint process, this means that your component may be painted for any number of reason at any time without your interaction. This means you could an infinite number of Timers, over a very small period of time.
Instead, your timer should be controlled externally from the paint process.
One other thing that took me some time to work out is...
public int spinValue = 0;
//...
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
You declare spinValue as int, but are adding a floating point value to it, this will have the effect of the decimal component been truncated, so the value will ALWAYS be 0.
Also, AffineTransform#rotate expects angles to be in radians, not degrees. Not sure if it's important, but you should be aware of it.
Runnable example...
Okay, so after applying the above, the code "might" look something like...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 RoulettePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RoulettePane extends JPanel {
private double spinValue = 0;
private Timer timer;
public RoulettePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
spin();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintRoulette(g2d);
g2d.dispose();
}
protected void spin() {
if (timer != null && timer.isRunning()) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
protected void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
int width = getWidth();
int height = getHeight();
int dimeter = Math.min(width, height);
AffineTransform at = AffineTransform.getRotateInstance(spinValue, dimeter / 2, dimeter / 2);
g2d.transform(at);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for (int i = 0; i < 37; i++) {
if (i == 0) {
g2d.setColor(Color.GREEN);
} else {
if (color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(0, 0, dimeter, dimeter, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
}
}
}
nb: I took the translation out for the time been as I wanted to focus on making the output more dynamic based on the actual width/height of the component

Java Rotate Image Towards Point from any X/Y position on screen

I have a ship object that I want to be able to rotate towards any certain point I click on the screen (See pic of ship and sqaures as points). I can get the ship to face the general direction, but it has this strange behavior of as it rotates the ship itself; the image moves around an circular axis, instead of rotating from the center point of the image itself.
Can someone please, give me the formula to get the angle from the center xy point of image to the destination sqaure(mouse click)?
First you have to find the difference on the x axis and the difference on the y axis. Since you seem to say that your ship is centered, you only have to get the (x,y) coordinates of the click. To find the angle, you must know that sin(your angle) = y/sqrt(y^2 + x^2), therefore, your angle = sin^-1(y/sqrt(y^2 + x^2)). It is basic maths, but it's always good to refresh basic knowledge, as it can cause quite the headache when debugging if you made a small mistake there, so I suggest you check out this page:
https://www.mathsisfun.com/algebra/trig-finding-angle-right-triangle.html
but it has this strange behavior of as it rotates the ship itself; the image moves around an circular axis, instead of rotating from the center point of the image itself.
You need to rotate the image about its center point, which means you need to translate the Graphics object to the center of your image before you paint the image.
This example rotates about a fixed point:
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);
}
}
});
}
}

How to draw smooth dot in Java? [duplicate]

We know that there are a class named RadialGradientPaint in Java and we can use it to have a gradient painting for circle.
But I want to have an oval (ellipse) GradientPaint. How to implement oval GradientPaint?
Use an AffineTransform when drawing the RadialGradientPaint. This would require a scale instance of the transform. It might end up looking something like this:
import java.awt.*;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class OvalGradientPaint {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
JPanel gui = new JPanel(new BorderLayout());
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new OvalGradientPaintSurface());
gui.setBackground(Color.WHITE);
JFrame f = new JFrame("Oval Gradient Paint");
f.add(gui);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class OvalGradientPaintSurface extends JPanel {
public int yScale = 150;
public int increment = 1;
RadialGradientPaint paint;
AffineTransform moveToOrigin;
OvalGradientPaintSurface() {
Point2D center = new Point2D.Float(100, 100);
float radius = 90;
float[] dist = {0.05f, .95f};
Color[] colors = {Color.RED, Color.MAGENTA.darker()};
paint = new RadialGradientPaint(center, radius, dist, colors,CycleMethod.REFLECT);
moveToOrigin = AffineTransform.
getTranslateInstance(-100d, -100d);
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
if (increment < 0) {
increment = (yScale < 50 ? -increment : increment);
} else {
increment = (yScale > 150 ? -increment : increment);
}
yScale += increment;
repaint();
}
};
Timer t = new Timer(15, listener);
t.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
AffineTransform moveToCenter = AffineTransform.
getTranslateInstance(getWidth()/2d, getHeight()/2d);
g2.setPaint(paint);
double y = yScale/100d;
double x = 1/y;
AffineTransform at = AffineTransform.getScaleInstance(x, y);
// We need to move it to the origin, scale, and move back.
// Counterintutitively perhaps, we concatentate 'in reverse'.
moveToCenter.concatenate(at);
moveToCenter.concatenate(moveToOrigin);
g2.setTransform(moveToCenter);
// fudge factor of 3 here, to ensure the scaling of the transform
// does not leave edges unpainted.
g2.fillRect(-getWidth(), -getHeight(), getWidth()*3, getHeight()*3);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 200);
}
}
Original image: The original static (boring) 'screen shot' of the app.
RadialGradientPaint provides two ways to paint itself as an ellipse instead of a circle:
Upon construction, you can specify a transform for the gradient. For example, if you provide the following transform: AffineTransform.getScaleInstance(0.5, 1), your gradient will be an upright oval (the x dimension will be half that of the y dimension).
Or, you can use the constructor that requires a Rectangle2D be provided. An appropriate transform will be created to make the gradient ellipse bounds match that of the provided rectangle. I found the class documentation helpful: RadialGradientPaint API. In particular, see the documentation for this constructor.

Is this the correct way of using Java 2D Graphics API?

I'm creating a graphical front-end for a JBox2D simulation. The simulation runs incrementally, and in between the updates, the contents of the simulation are supposed to be drawn. Similar to a game except without input.
I only need geometric primitives to draw a JBox2D simulation. This API seemed like the simplest choice, but its design is a bit confusing.
Currently I have one class called Window extending JFrame, that contains as a member another class called Renderer. The Window class only initializes itself and provides an updateDisplay() method (that is called by the main loop), that calls updateDisplay(objects) method on the Renderer. I made these two methods myself and their only purpose is to call repaint() on the Renderer.
Is the JPanel supposed to be used that way? Or am I supposed to use some more sophisticated method for animation (such that involves events and/or time intervals in some back-end thread)?
If you are wanting to schedule the updates at a set interval, javax.swing.Timer provides a Swing-integrated service for it. Timer runs its task on the EDT periodically, without having an explicit loop. (An explicit loop would block the EDT from processing events, which would freeze the UI. I explained this more in-depth here.)
Ultimately doing any kind of painting in Swing you'll still be doing two things:
Overriding paintComponent to do your drawing.
Calling repaint as-needed to request that your drawing be made visible. (Swing normally only repaints when it's needed, for example when some other program's window passes over top of a Swing component.)
If you're doing those two things you're probably doing it right. Swing doesn't really have a high-level API for animation. It's designed primarily with drawing GUI components in mind. It can certainly do some good stuff, but you will have to write a component mostly from scratch, like you're doing.
Painting in AWT and Swing covers some of the 'behind the scenes' stuff if you do not have it bookmarked.
You might look in to JavaFX. I don't know that much about it personally, but it's supposed to be more geared towards animation.
As somewhat of an optimization, one thing that can be done is to paint on a separate image and then paint the image on to the panel in paintComponent. This is especially useful if the painting is long: repaints can be scheduled by the system so this keeps when it happens more under control.
If you aren't drawing to an image, then you'd need to build a model with objects, and paint all of them every time inside paintComponent.
Here's an example of drawing to an image:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If the routine is long-running and repaints could happen concurrently, double buffering can also be used. Drawing is done to an image which is separate from the one being shown. Then, when the drawing routine is done, the image references are swapped so the update is seamless.
You should typically use double buffering for a game, for example. Double buffering prevents the image from being shown in a partial state. This could happen if, for example, you were using a background thread for the game loop (instead of a Timer) and a repaint happened the game was doing the painting. Without double buffering, this kind of situation would result in flickering or tearing.
Swing components are double buffered by default, so if all of your drawing is happening on the EDT you don't need to write double buffering logic yourself. Swing already does it.
Here is a somewhat more complicated example which shows a long-running task and a buffer swap:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
#Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << ( rand.nextInt(3) + 7 );
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI / 4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
#Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
The painting routine is just intended draw garbage which takes a long time:
For a tightly coupled simulation, javax.swing.Timer is a good choice. Let the timer's listener invoke your implementation of paintComponent(), as shown here and in the example cited here.
For a loosely coupled simulation, let the model evolve in the background thread of a SwingWorker, as shown here. Invoke publish() when apropos to you simulation.
The choice is dictated in part by the nature of the simulation and the duty cycle of the model.
Why not just use stuff from the testbed? It already does everything. Just take the JPanel, controller, and debug draw. It uses Java 2D drawing.
See here for the JPanel that does the buffered rendering:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java
and here for the debug draw:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java
See the TestbedMain.java file to see how the normal testbed is launched, and rip out what you don't need :)
Edits:
Disclaimer: I maintain jbox2d
Here is the package for the testbed framework: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework
TestbedMain.java is in the j2d folder, here:
https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d

How to draw circle on JPanel? Java 2D

I have a JPanel for which I set some image as the background. I need to draw a bunch of circles on top of the image. Now the circles will be positioned based on some coordinate x,y, and the size will be based on some integer size. This is what I have as my class.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
class ImagePanel extends JPanel {
private Image img;
CircleList cList; //added this
public ImagePanel(Image img) {
this.img = img;
Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
setLayout(null);
cList = new CircleList(); //added this
}
public void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, null);
cList.draw(null); //added this
}
}
How can I create some method that can performed this?
Your approach can be something similar to this, in which you use a class CircleList to hold all the circles and the drawing routine too:
class CircleList
{
static class Circle
{
public float x, y, diameter;
}
ArrayList<Circle> circles;
public CirclesList()
{
circles = new ArrayList<Circle>();
}
public void draw(Graphics2D g) // draw must be called by paintComponent of the panel
{
for (Circle c : circles)
g.fillOval(c.x, c.y, c.diameter, c.diameter)
}
}
Easiest thing to do would be to place something along these lines into your paintComponent method.
int x = ...;
int y = ...;
int radius = ...;
g.drawOval(x, y, radius, radius);
Well, you will probably want to create an ArrayList to store the information about the circles to be drawn. Then when the paintComponent() method is invoked you just loop through the ArrayList and draw the circles.
Custom Painting Approaches shows how this might be done for a rectangle. You can modify the code for an oval as well you would probably add methods to update the Array with the location information rather than by doing it dynamically.
Have you looked at JXLayer? It's an awesome library that allows you to layer special painting on top of any GUI element in an obvious way. I believe that will be included in the main java libraries for JDK7

Categories

Resources