Reputation: 335
I'm having problem centering the star shape. The problem is that I hard coded the points for each coordinate. And the problem with this is that Im not getting the right position although I created a perfect 5 star. On more problem is that what if I want to rescale the shape to make it bigger or small by clicking a button? Here's my code.
public void run() {
DifferentShapes panel = new DifferentShapes();
JFrame frame = new JFrame("Different Shapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400,400);
frame.setVisible(true);
frame.add(panel);
frame.setJMenuBar(panel.getMenuBAr());
}
public void star(Graphics shape) {
// int sizeReq = 1;
int [] starX = new int[] {250, 262, 304, 268, 278, 250, 222, 232, 196, 238};
int []starY = new int[] {200, 236, 236, 254, 296, 272, 296, 254, 236, 236};
shape.fillPolygon(starX, starY, starX.length);
}
Upvotes: 1
Views: 2370
Reputation: 347332
You could spend a lot of time manually translating the polygon array, but a better solution might be to translate the polygon to 0x0
, so that the top/left corner will be at 0x0
Basically, I calculated the min/max values for each array and then subtracted the "min" value from all the values...
protected static int[] minMax(int[] values) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int value : values) {
min = Math.min(min, value);
max = Math.max(max, value);
}
return new int[]{min, max};
}
protected static int[] normalise(int[] values, int min) {
for (int index = 0; index < values.length; index++) {
values[index] = values[index] - min;
}
return values;
}
So starting with...
[250, 262, 304, 268, 278, 250, 222, 232, 196, 238]
[200, 236, 236, 254, 296, 272, 296, 254, 236, 236]
translated to...
[54, 66, 108, 72, 82, 54, 26, 36, 0, 42]
[0, 36, 36, 54, 96, 72, 96, 54, 36, 36]
which when painted, looks something like...
You're probably thinking about now, well, that's not much good, I want it in the centre, but to get in the center, we need the width and height of the polygon
To achieve this, I took the translated arrays and passed them through the minMax
method and used the second element of the return value, which gave me a value of 108x96
(width
xheight
), now we have the information we need to center the shape
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics copy = g.create();
int x = (getWidth() - maxWidth) / 2;
int y = (getHeight() - maxHeight) / 2;
copy.translate(x, y);
copy.fillPolygon(starX, starY, starX.length);
copy.dispose();
}
Basically, all this does is uses the maxWidth
and maxHeight
values to calculate the center location within the current component, translate the Graphics
context to the appropriate offset and paints the polygon. The translation moves the origin position (the 0x0
point of the Graphics
context) which allows use to change where the polygon will be painted.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
protected static int[] minMax(int[] values) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int value : values) {
min = Math.min(min, value);
max = Math.max(max, value);
}
return new int[]{min, max};
}
protected static int[] normalise(int[] values, int min) {
for (int index = 0; index < values.length; index++) {
values[index] = values[index] - min;
}
return values;
}
public Test() {
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 {
private int starX[];
private int starY[];
private int maxWidth, maxHeight;
public TestPane() {
starX = new int[]{250, 262, 304, 268, 278, 250, 222, 232, 196, 238};
starY = new int[]{200, 236, 236, 254, 296, 272, 296, 254, 236, 236};
int[] minMaxX = minMax(starX);
int[] minMaxY = minMax(starY);
starX = normalise(starX, minMaxX[0]);
starY = normalise(starY, minMaxY[0]);
minMaxX = minMax(starX);
minMaxY = minMax(starY);
maxWidth = minMaxX[1];
maxHeight = minMaxY[1];
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics copy = g.create();
int x = (getWidth() - maxWidth) / 2;
int y = (getHeight() - maxHeight) / 2;
copy.translate(x, y);
copy.fillPolygon(starX, starY, starX.length);
copy.dispose();
}
}
}
So, about know, you should see the importance of using a 0x0
origin for your shapes.
Have a look at 2D Graphics and Transforming Shapes, Text, and Images for more details
Scaling can be done through Graphics2D#scale
...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D copy = (Graphics2D) g.create();
double scale = slider.getValue() / 100d;
int x = (getWidth() - maxWidth) / 2;
int y = (getHeight() - maxHeight) / 2;
copy.translate(x, y);
copy.scale(scale, scale);
copy.fillPolygon(starX, starY, starX.length);
copy.dispose();
}
but this scales the pixels which might not give the results you want.
Before someone jumps down my throat, if you want to get it centered again, you'll need to scale the maxWidth
and maxHeight
values as well
As I've said in the past, you will get better results with the 2D shapes API, they are self contained, they are easily moved and you will get a better results when they are scaled
For example, using the "translated" values from above, you should create a nice custom, re-usable, class...
public class StarShape extends Path2D.Double {
public StarShape() {
moveTo(54, 0);
lineTo(66, 36);
lineTo(108, 36);
lineTo(75, 54);
lineTo(82, 96);
lineTo(54, 72);
lineTo(26, 96);
lineTo(36, 54);
lineTo(0, 36);
lineTo(42, 36);
closePath();
}
}
Know, I don't know about you, but that is so much easier to read and you could easily plot this on some graph paper.
Now it's easily filled by doing something as simple as...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.fill(new StarShape());
g2d.dispose();
}
But, wait, we want it centered, all to easy...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = starShape.getBounds();
int x = (getWidth() - bounds.width) / 2;
int y = (getHeight() - bounds.height) / 2;
g2d.fill(starShape.createTransformedShape(AffineTransform.getTranslateInstance(x, y)));
g2d.dispose();
}
Well, that was a lot less code than the non-shapes API approach...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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 StarShape extends Path2D.Double {
public StarShape() {
moveTo(54, 0);
lineTo(66, 36);
lineTo(108, 36);
lineTo(75, 54);
lineTo(82, 96);
lineTo(54, 72);
lineTo(26, 96);
lineTo(36, 54);
lineTo(0, 36);
lineTo(42, 36);
closePath();
}
}
public class TestPane extends JPanel {
private StarShape starShape;
public TestPane() {
starShape = new StarShape();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = starShape.getBounds();
int x = (getWidth() - bounds.width) / 2;
int y = (getHeight() - bounds.height) / 2;
g2d.fill(starShape.createTransformedShape(AffineTransform.getTranslateInstance(x, y)));
g2d.dispose();
}
}
}
Generally speaking, scaling uses the same process as translating, BUT, what you get, is the benefit that you can scale a Shape
and then translate it separately, allowing you to get the Rectangle
bounds of the scaled Shape
independently. It also scales the vector (points) and not pixels which will give you a better result...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
double scale = slider.getValue() / 100d;
Shape shape = starShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));
Rectangle bounds = shape.getBounds();
int x = (getWidth() - bounds.width) / 2;
int y = (getHeight() - bounds.height) / 2;
GeneralPath path = new GeneralPath();
path.append(shape.getPathIterator(AffineTransform.getTranslateInstance(x, y)), true);
g2d.fill(path);
g2d.dispose();
}
So, the short answer is, use the Shapes API, the long answer is, use the Shapes API, you won't need to reinvent the wheel for each problem you face
Upvotes: 5
Reputation: 324207
You might want to consider using the Shape Utils helper class to generate the star for you so you don't need to manually calculate all the points on the star.
Basically you just need two lines of code to use the class:
star = ShapeUtils.radiusShape(10, 55, 22);
This will generate a star with 10 points alternating from 55 to 22 pixels from the center.
The first point generated will be on the horizontal line. Since there are 5 large points this means they will be generate every 72 degrees. Since you want the top point to be on the vertical axis you will then need to rotate all the points by -18 degrees:
star = ShapeUtils.rotate(star, -18);
A simple example showing how to paint the star centered in a panel is as follows:
import java.awt.*;
import javax.swing.*;
public class ShapeSSCCE extends JPanel
{
private Shape star;
public ShapeSSCCE()
{
star = ShapeUtils.radiusShape(10, 55, 22);
star = ShapeUtils.rotate(star, -18);
}
@Override
public Dimension getPreferredSize()
{
Rectangle bounds = star.getBounds();
return new Dimension(bounds.width, bounds.height);
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = star.getBounds();
int x = (getWidth() - bounds.width) / 2;
int y = (getHeight() - bounds.height) / 2;
g2d.translate(x, y);
g2d.fill( star );
g2d.dispose();
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("ShapeSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new ShapeSSCCE() );
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Try the code with/without the rotated shape to see the difference.
If you don't want to do custom painting then you can use the ShapeIcon
class also found in the above link. Then you can treat the star like an Icon and add it to a label:
Shape star = ShapeUtils.radiusShape(10, 55, 22);
star = ShapeUtils.rotate(star, -18);
ShapeIcon starIcon = new ShapeIcon(star, Color.RED);
JLabel starLabel = new JLabel( starIcon );
I want to rescale the shape to make it bigger or small by clicking a button?
You could:
ShapeUtils
class with new valuesUpvotes: 2